diff --git a/OpenRA.Mods.CA/AIUtils.cs b/OpenRA.Mods.CA/AIUtils.cs index 3bc31e9350..4cb11a4a03 100644 --- a/OpenRA.Mods.CA/AIUtils.cs +++ b/OpenRA.Mods.CA/AIUtils.cs @@ -49,6 +49,16 @@ public static ILookup FindQueuesByCategory(Player playe .ToLookup(pq => pq.Info.Type); } + public static ILookup FindQueuesByCategory(IEnumerable players) + { + var player = players.First(); + + return player.World.ActorsWithTrait() + .Where(a => players.Contains(a.Actor.Owner) && a.Trait.Enabled) + .Select(a => a.Trait) + .ToLookup(pq => pq.Info.Type); + } + public static IEnumerable GetActorsWithTrait(World world) { return world.ActorsHavingTrait(); diff --git a/OpenRA.Mods.CA/Traits/BotModules/Squads/States/AirStatesCA.cs b/OpenRA.Mods.CA/Traits/BotModules/Squads/States/AirStatesCA.cs index 1e632ef6f1..31bdaa4fa4 100644 --- a/OpenRA.Mods.CA/Traits/BotModules/Squads/States/AirStatesCA.cs +++ b/OpenRA.Mods.CA/Traits/BotModules/Squads/States/AirStatesCA.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common; @@ -229,23 +230,6 @@ public void Tick(SquadCA owner) return; } - var firstUnit = owner.Units.FirstOrDefault(); - var buildableInfo = firstUnit.Info.TraitInfoOrDefault(); - var limitOne = buildableInfo != null && buildableInfo.BuildLimit == 1; - var canBuildMoreOfAircraft = firstUnit != null && !limitOne && owner.SquadManager.CanBuildMoreOfAircraft(firstUnit.Info); - var waitingCount = owner.WaitingUnits.Count; - - var waitingPatience = 99; - if (waitingCount > 7) - waitingPatience = 65; - else if (waitingCount > 3) - waitingPatience = 85; - else if (waitingCount > 1) - waitingPatience = 95; - - var impatience = owner.World.LocalRandom.Next(100); - var noPatience = impatience > waitingPatience; - foreach (var a in owner.Units) { var currentActivity = a.CurrentActivity; @@ -300,7 +284,25 @@ public void Tick(SquadCA owner) owner.WaitingUnits.Add(a); } - if ((!canBuildMoreOfAircraft || noPatience) && owner.WaitingUnits.Count > 0 && owner.WaitingUnits.Count >= owner.RearmingUnits.Count) + var firstUnit = owner.Units.FirstOrDefault(); + var buildableInfo = firstUnit.Info.TraitInfoOrDefault(); + var limitOne = buildableInfo != null && buildableInfo.BuildLimit == 1; + + var waitingCount = owner.WaitingUnits.Count; + + var waitingPatience = 99; + if (waitingCount > 7) + waitingPatience = 65; + else if (waitingCount > 3) + waitingPatience = 85; + else if (waitingCount > 1) + waitingPatience = 95; + + var impatience = owner.World.LocalRandom.Next(100); + var noPatience = impatience > waitingPatience; + Func canBuildMoreOfAircraft = () => firstUnit != null && !limitOne && owner.SquadManager.CanBuildMoreOfAircraft(firstUnit.Info); + + if (owner.WaitingUnits.Count > 0 && owner.WaitingUnits.Count >= owner.RearmingUnits.Count && (noPatience || !canBuildMoreOfAircraft())) { foreach (var a in owner.WaitingUnits) if (CanAttackTarget(a, owner.TargetActor)) diff --git a/OpenRA.Mods.CA/Traits/BotModules/UnitBuilderBotModuleCA.cs b/OpenRA.Mods.CA/Traits/BotModules/UnitBuilderBotModuleCA.cs index 6e958298e1..b42b99e712 100644 --- a/OpenRA.Mods.CA/Traits/BotModules/UnitBuilderBotModuleCA.cs +++ b/OpenRA.Mods.CA/Traits/BotModules/UnitBuilderBotModuleCA.cs @@ -302,28 +302,68 @@ bool CanBuildMoreOfAircraft(ActorInfo actorInfo) if (attackAircraftInfo == null) return true; + int currentCount; var limit = Info.MaxAircraft; - var currentCount = 0; + var isAirToAir = Info.AirToAirUnits.Contains(actorInfo.Name); - if (Info.MaintainAirSuperiority) + if (Info.MaintainAirSuperiority && isAirToAir) { - var numAirToAirUnits = AIUtils.GetActorsWithTrait(player.World).Count(a => a.Owner == player && Info.AirToAirUnits.Contains(a.Info.Name)); + // Get all production queues to count queued aircraft (for allies as well) + var queues = AIUtils.FindQueuesByCategory(player.World.Players.Where(p => p.IsAlliedWith(player))); - if (Info.AirToAirUnits.Contains(actorInfo.Name)) + // Count queued air-to-air units across all queues + var queuedAirToAirCount = queues.SelectMany(g => g).SelectMany(q => q.AllQueued()) + .Count(item => Info.AirToAirUnits.Contains(item.Item)); + + var friendlyAirToAirCount = player.World.Actors.Count(a => a.Owner.RelationshipWith(player) == PlayerRelationship.Ally + && Info.AirToAirUnits.Contains(a.Info.Name)); + + var enemyAirThreatCount = player.World.Actors.Count(a => a.Owner.RelationshipWith(player) == PlayerRelationship.Enemy + && Info.AirThreatUnits.Contains(a.Info.Name)); + + currentCount = friendlyAirToAirCount + queuedAirToAirCount; + limit = Math.Max(enemyAirThreatCount + 1, limit); + + if (Info.MaxAirSuperiority > 0) + limit = Math.Min(Info.MaxAirSuperiority, limit); + } + else + { + // Get all production queues to count queued aircraft + var queues = AIUtils.FindQueuesByCategory(player); + + // Non air-to-air aircraft uses the standard aircraft limit + if (Info.MaintainAirSuperiority) { - currentCount = numAirToAirUnits; - var numFriendlyAirToAirUnits = player.World.Actors.Count(a => a.Owner.RelationshipWith(player) == PlayerRelationship.Ally && Info.AirToAirUnits.Contains(a.Info.Name)); - var numEnemyAirThreatUnits = player.World.Actors.Count(a => a.Owner.RelationshipWith(player) == PlayerRelationship.Enemy && Info.AirThreatUnits.Contains(a.Info.Name)); - limit = Math.Max(numEnemyAirThreatUnits - numFriendlyAirToAirUnits + 1, limit); + var existingNonAirToAirCount = player.World.Actors.Count(a => + a.Owner == player && + a.Info.HasTraitInfo() && + a.Info.HasTraitInfo() && + !Info.AirToAirUnits.Contains(a.Info.Name)); - if (Info.MaxAirSuperiority > 0) - limit = Math.Min(Info.MaxAirSuperiority, limit); + var queuedNonAirToAirCount = queues.SelectMany(g => g).SelectMany(q => q.AllQueued()) + .Count(item => !Info.AirToAirUnits.Contains(item.Item) && + world.Map.Rules.Actors[item.Item].HasTraitInfo()); + + currentCount = existingNonAirToAirCount + queuedNonAirToAirCount; } else - currentCount = AIUtils.GetActorsWithTrait(player.World).Count(a => a.Owner == player && a.Info.HasTraitInfo()) - numAirToAirUnits; + { + var existingAircraftCount = player.World.Actors.Count(a => + a.Owner == player && + a.Info.HasTraitInfo() && + a.Info.HasTraitInfo()); + + // Count all queued aircraft + var queuedAircraft = queues.SelectMany(g => g).SelectMany(q => q.AllQueued()) + .Count(item => world.Map.Rules.Actors[item.Item].HasTraitInfo()); + + currentCount = existingAircraftCount + queuedAircraft; + } } - else - currentCount = AIUtils.GetActorsWithTrait(player.World).Count(a => a.Owner == player && a.Info.HasTraitInfo()); + + // bot debug + TextNotificationsManager.Debug("AI: {0} aircraft count for {1}: {2}/{3}", player, actorInfo.Name, currentCount, limit); return currentCount < limit; } diff --git a/OpenRA.Mods.CA/Traits/DelayedWeaponAttachable.cs b/OpenRA.Mods.CA/Traits/DelayedWeaponAttachable.cs index 4b2a0adc84..cb872bac34 100644 --- a/OpenRA.Mods.CA/Traits/DelayedWeaponAttachable.cs +++ b/OpenRA.Mods.CA/Traits/DelayedWeaponAttachable.cs @@ -40,7 +40,7 @@ public class DelayedWeaponAttachableInfo : ConditionalTraitInfo public class DelayedWeaponAttachable : ConditionalTrait, ITick, INotifyKilled, ISelectionBar, INotifyTransform, INotifyRemovedFromWorld { - public HashSet Container { get; private set; } + public List Container { get; private set; } readonly Actor self; readonly HashSet detectors = new HashSet(); @@ -52,7 +52,7 @@ public DelayedWeaponAttachable(Actor self, DelayedWeaponAttachableInfo info) : base(info) { this.self = self; - Container = new HashSet(); + Container = new List(); } void ITick.Tick(Actor self) @@ -62,7 +62,7 @@ void ITick.Tick(Actor self) foreach (var trigger in Container) trigger.Tick(self); - Container.RemoveWhere(p => !p.IsValid); + Container.RemoveAll(p => !p.IsValid); while (tokens.Count > Container.Count) self.RevokeCondition(tokens.Pop()); @@ -81,7 +81,7 @@ void INotifyKilled.Killed(Actor self, AttackInfo e) trigger.Activate(self); } - Container.RemoveWhere(p => !p.IsValid); + Container.RemoveAll(p => !p.IsValid); } } @@ -150,7 +150,7 @@ void INotifyTransform.BeforeTransform(Actor self) foreach (var trigger in Container) trigger.Activate(self); - Container.RemoveWhere(p => !p.IsValid); + Container.RemoveAll(p => !p.IsValid); while (tokens.Count > 0 && Container.Count == 0) self.RevokeCondition(tokens.Pop()); diff --git a/OpenRA.Mods.CA/Traits/EncyclopediaExtras.cs b/OpenRA.Mods.CA/Traits/EncyclopediaExtras.cs index 8556c11b27..e537b9a7d4 100644 --- a/OpenRA.Mods.CA/Traits/EncyclopediaExtras.cs +++ b/OpenRA.Mods.CA/Traits/EncyclopediaExtras.cs @@ -15,6 +15,10 @@ namespace OpenRA.Mods.CA.Traits [Desc("To override encyclopedia preview.")] public class EncyclopediaExtrasInfo : TraitInfo { + [FluentReference] + [Desc("If set, will be used instead of the tooltip name in encyclopedia.")] + public readonly string Name = null; + [Desc("If set will override the preview with this actor.")] public readonly string RenderPreviewActor; @@ -31,6 +35,12 @@ public class EncyclopediaExtrasInfo : TraitInfo [Desc("If no Buildable Description exists, this will be shown instead.")] public readonly string Description = ""; + [Desc("Actor name this entry is a variant of (e.g., 'IFV'). Hides entry from main list.")] + public readonly string VariantOf = null; + + [Desc("Group name for variant dropdown (e.g., 'Allies Infantry').")] + public readonly string VariantGroup = null; + public override object Create(ActorInitializer init) { return new EncyclopediaExtras(init, this); } } diff --git a/OpenRA.Mods.CA/Traits/Modifiers/WithColoredOverlayCA.cs b/OpenRA.Mods.CA/Traits/Modifiers/WithColoredOverlayCA.cs index f4c3046dab..7c8b6479d9 100644 --- a/OpenRA.Mods.CA/Traits/Modifiers/WithColoredOverlayCA.cs +++ b/OpenRA.Mods.CA/Traits/Modifiers/WithColoredOverlayCA.cs @@ -9,6 +9,7 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; @@ -94,19 +95,18 @@ public WithColoredOverlayPreviewModifier(WithColoredOverlayCAInfo info) public IEnumerable ModifyPreviewRender(WorldRenderer wr, IEnumerable renderables, Rectangle bounds) { - var result = new List(); - - foreach (var r in renderables) + foreach (var r in renderables.ToList()) { - result.Add(r); + yield return r; - // For preview rendering, we apply tint to all modifyable renderables - // (unlike in-game where we skip decorations) if (r is IModifyableRenderable mr) - result.Add(mr.WithTint(tint, mr.TintModifiers | TintModifiers.ReplaceColor).WithAlpha(alpha)); + { + var currentTintModifiers = mr.TintModifiers; + yield return mr.WithTint(tint, currentTintModifiers | TintModifiers.ReplaceColor) + .WithAlpha(alpha) + .AsDecoration(); + } } - - return result; } public void Tick() { } diff --git a/OpenRA.Mods.CA/Traits/Modifiers/WithPalettedOverlay.cs b/OpenRA.Mods.CA/Traits/Modifiers/WithPalettedOverlay.cs index eafed33c46..430970d4c1 100644 --- a/OpenRA.Mods.CA/Traits/Modifiers/WithPalettedOverlay.cs +++ b/OpenRA.Mods.CA/Traits/Modifiers/WithPalettedOverlay.cs @@ -117,7 +117,7 @@ public IEnumerable ModifyPreviewRender(WorldRenderer wr, IEnumerabl yield return r; // For preview rendering, apply palette swap to all paletted renderables - // (unlike in-game where we skip decorations) + // (unlike in-game where we skip decorations, because the colored overlay used for customising the actor color is a decoration) if (palette != null && r is IPalettedRenderable pr) yield return pr.WithPalette(palette) .WithZOffset(r.ZOffset + 1) diff --git a/OpenRA.Mods.CA/Traits/Player/CampaignProgressTracker.cs b/OpenRA.Mods.CA/Traits/Player/CampaignProgressTracker.cs index 7e6ecdbe6d..87e14379e2 100644 --- a/OpenRA.Mods.CA/Traits/Player/CampaignProgressTracker.cs +++ b/OpenRA.Mods.CA/Traits/Player/CampaignProgressTracker.cs @@ -48,7 +48,7 @@ void INotifyWinStateChanged.OnPlayerWon(Player player) if (!player.World.Map.Categories.Contains("Campaign")) return; - var missionTitle = GetMapTileWithoutNumber(player.World.Map.Title); + var missionTitle = GetMapTitleWithoutNumber(player.World.Map.Title); var worldActor = player.World.WorldActor; var difficulty = worldActor.TraitsImplementing() .FirstOrDefault(sld => sld.Info.ID == "difficulty"); @@ -124,16 +124,23 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) developerCommandUsed = true; } - public static string GetMapTileWithoutNumber(string mapTitle) + public static string GetMapTitleWithoutNumber(string mapTitle) { var firstColonIndex = mapTitle.IndexOf(": "); var firstPeriodIndex = mapTitle.IndexOf(". "); var splitIndex = firstColonIndex >= 0 ? firstColonIndex : firstPeriodIndex; + string result; if (splitIndex >= 0) - return mapTitle.Substring(splitIndex + 2); + result = mapTitle.Substring(splitIndex + 2); + else + result = mapTitle; - return mapTitle; + var bracketIndex = result.IndexOf('('); + if (bracketIndex >= 0) + result = result.Substring(0, bracketIndex); + + return result.TrimEnd(); } public static Dictionary GetCampaignProgress() diff --git a/OpenRA.Mods.CA/Traits/Render/WithPreviewDecoration.cs b/OpenRA.Mods.CA/Traits/Render/WithPreviewDecoration.cs index 6fad1be597..f915acd4b4 100644 --- a/OpenRA.Mods.CA/Traits/Render/WithPreviewDecoration.cs +++ b/OpenRA.Mods.CA/Traits/Render/WithPreviewDecoration.cs @@ -13,7 +13,6 @@ using OpenRA.Graphics; using OpenRA.Mods.Common.Traits.Render; using OpenRA.Primitives; -using OpenRA.Traits; namespace OpenRA.Mods.CA.Traits.Render { diff --git a/OpenRA.Mods.CA/Widgets/ActorPreviewCAWidget.cs b/OpenRA.Mods.CA/Widgets/ActorPreviewCAWidget.cs index b4def9ff4f..e4c31a8c0a 100644 --- a/OpenRA.Mods.CA/Widgets/ActorPreviewCAWidget.cs +++ b/OpenRA.Mods.CA/Widgets/ActorPreviewCAWidget.cs @@ -16,7 +16,6 @@ using OpenRA.Mods.Common.Graphics; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; -using OpenRA.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.CA.Widgets @@ -26,6 +25,7 @@ namespace OpenRA.Mods.CA.Widgets /// - Arbitrary player colors (not tied to map players) /// - Preview rendering of overlay traits like WithColoredOverlay, WithPalettedOverlay /// - Automatic palette swapping for player-colored sprites to use encyclopedia palette + /// Uses Render() instead of RenderUI() to get SpriteRenderables which already support IModifyableRenderable /// public class ActorPreviewCAWidget : Widget { @@ -110,14 +110,53 @@ public void SetPreview(ActorInfo actor, TypeDictionary td, Color? color = null) public override void PrepareRenderables() { - var scale = GetScale() * viewportSizes.DefaultScale; var origin = RenderOrigin + PreviewOffset + new int2(RenderBounds.Size.Width / 2, RenderBounds.Size.Height / 2); - IEnumerable baseRenderables = preview - .SelectMany(p => p.RenderUI(worldRenderer, origin, scale)); + // Calculate where WPos.Zero would render on screen in viewport coordinates + var zeroScreenPos = worldRenderer.ScreenPxPosition(WPos.Zero); + var viewportZeroPos = worldRenderer.Viewport.WorldToViewPx(zeroScreenPos); + + // Calculate offset from that position to our desired screen position + // This offset in screen pixels needs to be converted to world units for OffsetBy + var screenOffset = origin - viewportZeroPos; + + // Convert screen pixel offset to world vector + // The tile size and scale determine the conversion factor + var worldOffsetX = screenOffset.X * worldRenderer.TileScale / worldRenderer.TileSize.Width; + var worldOffsetY = screenOffset.Y * worldRenderer.TileScale / worldRenderer.TileSize.Height; + var worldOffset = new WVec(worldOffsetX, worldOffsetY, 0); + + // Use Render() at WPos.Zero to get SpriteRenderables which support IModifyableRenderable + // This allows WithColoredOverlayCA and alpha modifications to work automatically + var baseRenderables = preview + .SelectMany(p => p.Render(worldRenderer, WPos.Zero)) + .OrderBy(WorldRenderer.RenderableZPositionComparisonKey) + .Select(r => + { + // Offset each renderable to the correct screen position + r = r.OffsetBy(worldOffset); + + // Apply encyclopedia scale to SpriteRenderables + if (r is SpriteRenderable sr && GetScale() != 1f) + { + var scaleFactor = GetScale(); + return new SpriteRenderable( + sr.GetType().GetField("sprite", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(sr) as Sprite, + sr.Pos, + sr.Offset, + sr.ZOffset, + sr.Palette, + (float)sr.GetType().GetField("scale", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(sr) * scaleFactor, + sr.Alpha, + sr.Tint, + sr.TintModifiers, + sr.IsDecoration); + } + return r; + }) + .ToList(); // Calculate scaled bounds for modifiers - // The bounds are centered around origin, scaled appropriately var scaledBounds = new Rectangle( origin.X - (int)(IdealPreviewSize.X * GetScale() / 2), origin.Y - (int)(IdealPreviewSize.Y * GetScale() / 2), @@ -126,13 +165,11 @@ public override void PrepareRenderables() // Apply preview modifiers foreach (var modifier in previewModifiers) - baseRenderables = modifier.ModifyPreviewRender(worldRenderer, baseRenderables, scaledBounds); + baseRenderables = modifier.ModifyPreviewRender(worldRenderer, baseRenderables, scaledBounds).ToList(); // Swap player palettes to encyclopedia palettes for proper coloring - baseRenderables = baseRenderables.Select(r => SwapPlayerPalette(r)); - renderables = baseRenderables - .OrderBy(WorldRenderer.RenderableZPositionComparisonKey) + .Select(r => SwapPlayerPalette(r)) .Select(r => r.PrepareRender(worldRenderer)) .ToArray(); } @@ -165,7 +202,12 @@ IRenderable SwapPlayerPalette(IRenderable r) paletteCache[newPaletteName] = newPalette; } - return pr.WithPalette(newPalette); + var ret = (IRenderable)pr.WithPalette(newPalette); + + if (r is IModifyableRenderable mr && ret is IModifyableRenderable retMr) + ret = retMr.WithAlpha(mr.Alpha).WithTint(mr.Tint, mr.TintModifiers); + + return ret; } } diff --git a/OpenRA.Mods.CA/Widgets/Logic/EncyclopediaLogicCA.cs b/OpenRA.Mods.CA/Widgets/Logic/EncyclopediaLogicCA.cs index 4aafd84c12..f43d95b1d2 100644 --- a/OpenRA.Mods.CA/Widgets/Logic/EncyclopediaLogicCA.cs +++ b/OpenRA.Mods.CA/Widgets/Logic/EncyclopediaLogicCA.cs @@ -80,6 +80,18 @@ public class EncyclopediaLogicCA : ChromeLogic // Build icon widget readonly SpriteWidget buildIconWidget; + // Variant dropdown widget + readonly DropDownButtonWidget variantDropdown; + + // Variant lookup - maps parent actor name to list of variant actors (case-insensitive) + readonly Dictionary> variantsByParent = new(StringComparer.OrdinalIgnoreCase); + + // Variant group order - tracks order groups were first encountered during file scan + readonly Dictionary variantGroupOrder = new(); + + // Currently selected variant (if any) + ActorInfo selectedVariant; + // Folder structure tracking readonly Dictionary folderNodes = new(); readonly Dictionary folderExpanded = new(); @@ -173,6 +185,8 @@ public EncyclopediaLogicCA(Widget widget, World world, ModData modData, Action o buildIconWidget = widget.GetOrNull("BUILD_ICON"); + variantDropdown = widget.GetOrNull("VARIANT_DROPDOWN"); + foreach (var actor in modData.DefaultRules.Actors.Values) { var statistics = actor.TraitInfoOrDefault(); @@ -189,6 +203,9 @@ public EncyclopediaLogicCA(Widget widget, World world, ModData modData, Action o foreach (var faction in world.Map.Rules.Actors[SystemActors.World].TraitInfos().Where(f => f.Selectable)) factions.Add(faction.InternalName, faction); + // Build variant lookup - find all actors that are variants of other actors + BuildVariantLookup(); + // Build folder hierarchy BuildFolderHierarchy(); @@ -260,6 +277,27 @@ void SelectFirstItem() return (null, null); } + void BuildVariantLookup() + { + foreach (var actorInfo in info) + { + var actor = actorInfo.Key; + var extras = actor.TraitInfoOrDefault(); + + if (extras?.VariantOf != null) + { + if (!variantsByParent.ContainsKey(extras.VariantOf)) + variantsByParent[extras.VariantOf] = new List(); + + variantsByParent[extras.VariantOf].Add(actor); + + // Track group order based on first encounter during file scan (only for non-null groups) + if (extras.VariantGroup != null && !variantGroupOrder.ContainsKey(extras.VariantGroup)) + variantGroupOrder[extras.VariantGroup] = variantGroupOrder.Count; + } + } + } + void BuildFolderHierarchy() { // Group actors by their category paths (actors can have multiple categories) @@ -271,6 +309,11 @@ void BuildFolderHierarchy() var encyclopedia = actorInfo.Value; var categories = encyclopedia.Category ?? ""; + // Skip variants - they are accessed via dropdown only + var extras = actor.TraitInfoOrDefault(); + if (extras?.VariantOf != null) + continue; + // Split by semicolon to allow multiple categories per actor var categoryPaths = ParseCategoryPaths(categories); @@ -508,6 +551,7 @@ void SelectActor(ActorInfo actor, string categoryPath = null) LoadExtras(actor); var selectedInfo = info[actor]; selectedActor = actor; + selectedVariant = null; currentCategoryPath = categoryPath; // Remember this actor for the current top-level category @@ -516,6 +560,9 @@ void SelectActor(ActorInfo actor, string categoryPath = null) lastSelectedActorByCategory[selectedTopLevelCategory] = actor; } + // Setup variant dropdown + SetupVariantDropdown(actor); + // Update the encyclopedia color palette with the faction color var previewColor = GetPreviewColorFromCategory(categoryPath); EncyclopediaColorPalette.SetPreviewColor(previewColor); @@ -592,128 +639,7 @@ void SelectActor(ActorInfo actor, string categoryPath = null) } } - var currentY = 0; - - if (productionContainer != null) - { - var currentX = 0; - var productionContainerHeight = 0; - const int IconWidth = 16; - const int LabelSpacing = 4; - const int GroupSpacing = 20; - - var costIcon = productionContainer.GetOrNull("COST_ICON"); - var timeIcon = productionContainer.GetOrNull("TIME_ICON"); - var notProducibleIcon = productionContainer.GetOrNull("NOT_PRODUCIBLE_ICON"); - var notProducibleLabel = productionContainer.GetOrNull("NOT_PRODUCIBLE"); - - if (costIcon != null) costIcon.Visible = false; - if (timeIcon != null) timeIcon.Visible = false; - if (productionCost != null) productionCost.Visible = false; - if (productionTime != null) productionTime.Visible = false; - if (armorTypeIcon != null) armorTypeIcon.Visible = false; - if (armorTypeLabel != null) armorTypeLabel.Visible = false; - if (productionPowerIcon != null) productionPowerIcon.Visible = false; - if (productionPower != null) productionPower.Visible = false; - if (notProducibleIcon != null) notProducibleIcon.Visible = false; - if (notProducibleLabel != null) notProducibleLabel.Visible = false; - - if (bi != null && !selectedInfo.HideBuildable) - { - var cost = actor.TraitInfoOrDefault()?.Cost ?? 0; - if (cost > 0 && productionCost != null && costIcon != null) - { - var costText = cost.ToString(NumberFormatInfo.CurrentInfo); - productionCost.Text = costText; - costIcon.Bounds.X = currentX; - productionCost.Bounds.X = currentX + IconWidth + LabelSpacing; - var costWidth = Game.Renderer.Fonts[productionCost.Font].Measure(costText).X; - currentX += IconWidth + LabelSpacing + costWidth + GroupSpacing; - - costIcon.Visible = true; - productionCost.Visible = true; - productionContainerHeight = descriptionFont.Measure(costText).Y; - } - - var time = BuildTime(selectedActor, selectedInfo.BuildableQueue); - if (time > 0 && productionTime != null && timeIcon != null) - { - var timeText = WidgetUtils.FormatTime(time, world.Timestep); - productionTime.Text = timeText; - timeIcon.Bounds.X = currentX; - productionTime.Bounds.X = currentX + IconWidth + LabelSpacing; - var timeWidth = Game.Renderer.Fonts[productionTime.Font].Measure(timeText).X; - currentX += IconWidth + LabelSpacing + timeWidth + GroupSpacing; - - timeIcon.Visible = true; - productionTime.Visible = true; - productionContainerHeight = Math.Max(productionContainerHeight, descriptionFont.Measure(timeText).Y); - } - } - else - { - if (encyclopediaExtrasInfo != null && encyclopediaExtrasInfo.HideNotProducible) - { - productionContainer.Visible = false; - } - else - { - notProducibleIcon.Visible = true; - notProducibleIcon.Bounds.X = currentX; - notProducibleLabel.Visible = true; - notProducibleLabel.Bounds.X = currentX + IconWidth + LabelSpacing; - var notProducibleLabelWidth = Game.Renderer.Fonts[notProducibleLabel.Font].Measure(notProducibleLabel.Text).X; - currentX += IconWidth + LabelSpacing + notProducibleLabelWidth + GroupSpacing; - productionContainerHeight = descriptionFont.Measure(notProducibleLabel.Text).Y; - } - } - - if (armorTypeLabel != null && armorTypeIcon != null) - { - var armor = actor.TraitInfos().FirstOrDefault(); - if (armor != null && !string.IsNullOrEmpty(armor.Type)) - { - SelectionTooltipLogic.GetArmorTypeLabel(armorTypeLabel, actor); - var hasArmorType = !string.IsNullOrEmpty(armorTypeLabel.Text); - if (hasArmorType) - { - armorTypeIcon.Bounds.X = currentX; - armorTypeLabel.Bounds.X = currentX + IconWidth + LabelSpacing; - var armorWidth = Game.Renderer.Fonts[armorTypeLabel.Font].Measure(armorTypeLabel.Text).X; - currentX += IconWidth + LabelSpacing + armorWidth + GroupSpacing; - - armorTypeIcon.Visible = true; - armorTypeLabel.Visible = true; - productionContainerHeight = Math.Max(productionContainerHeight, descriptionFont.Measure(armorTypeLabel.Text).Y); - } - } - } - - var power = actor.TraitInfos().Where(i => i.EnabledByDefault).Sum(i => i.Amount); - if (power != 0 && productionPower != null && productionPowerIcon != null) - { - var powerText = power.ToString(NumberFormatInfo.CurrentInfo); - productionPower.Text = powerText; - productionPowerIcon.Bounds.X = currentX; - productionPower.Bounds.X = currentX + IconWidth + LabelSpacing; - var powerWidth = Game.Renderer.Fonts[productionPower.Font].Measure(powerText).X; - currentX += IconWidth + LabelSpacing + powerWidth + GroupSpacing; - - productionPowerIcon.Visible = true; - productionPower.Visible = true; - productionContainerHeight = Math.Max(productionContainerHeight, descriptionFont.Measure(powerText).Y); - } - - // Only show the production container if it has any visible content - var hasVisibleContent = (costIcon?.Visible == true) || - (timeIcon?.Visible == true) || - (armorTypeIcon?.Visible == true) || - (productionPowerIcon?.Visible == true) || - (notProducibleIcon?.Visible == true); - - productionContainer.Visible = hasVisibleContent; - currentY = productionContainerHeight + 10; - } + var currentY = SetupProductionContainer(actor); FactionInfo subfaction = null; var subfactionText = ""; @@ -794,6 +720,302 @@ void SelectActor(ActorInfo actor, string categoryPath = null) currentY += additionalInfoHeight + 8; } + currentY = SetupDescriptionSection(actor, currentY, showEncyclopediaDescription: true); + + actorDetailsContainer.Bounds.Height = currentY; + + descriptionPanel.Layout.AdjustChildren(); + + descriptionPanel.ScrollToTop(); + } + + void SetupVariantDropdown(ActorInfo actor) + { + if (variantDropdown == null) + return; + + // Check if this actor has variants + if (!variantsByParent.TryGetValue(actor.Name, out var variants) || variants.Count == 0) + { + variantDropdown.IsDisabled = () => true; + variantDropdown.GetText = () => ""; + return; + } + + variantDropdown.IsDisabled = () => false; + variantDropdown.GetText = () => selectedVariant != null + ? GetActorDisplayName(selectedVariant) + : "Select variant..."; + + variantDropdown.OnMouseDown = _ => + { + // Include the base actor along with variants + var allVariants = new List { actor }; + allVariants.AddRange(variants); + + // Separate variants into grouped and ungrouped + var variantsWithGroups = allVariants + .Select(v => new + { + Actor = v, + Group = v.TraitInfoOrDefault()?.VariantGroup + }) + .ToList(); + + var hasAnyGroups = variantsWithGroups.Any(v => v.Group != null); + + ScrollItemWidget SetupItem(ActorInfo variantActor, ScrollItemWidget template) + { + bool IsSelected() => selectedVariant == variantActor; + void OnClick() => SelectVariant(variantActor); + + var scrollItem = ScrollItemWidget.Setup(template, IsSelected, OnClick); + var label = scrollItem.Get("LABEL"); + label.GetText = () => GetActorDisplayName(variantActor); + return scrollItem; + } + + if (!hasAnyGroups) + { + // No groups - use simple flat dropdown without headers (preserve YAML order) + var itemHeight = 25; + var totalHeight = Math.Min(allVariants.Count * itemHeight, 300) + 5; + + variantDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", totalHeight, allVariants, SetupItem); + } + else + { + // Has groups - use grouped dropdown with headers + var groupedVariants = variantsWithGroups + .Where(v => v.Group != null) + .GroupBy(v => v.Group) + .OrderBy(g => GetVariantGroupSortOrder(g.Key)) + .ToDictionary( + g => g.Key, + g => g.Select(v => v.Actor).AsEnumerable() + ); + + // Add ungrouped variants first (with empty key, handled specially) + var ungrouped = variantsWithGroups.Where(v => v.Group == null).Select(v => v.Actor).ToList(); + if (ungrouped.Any()) + { + var orderedGrouped = new Dictionary> + { + { "", ungrouped } + }; + foreach (var kvp in groupedVariants) + orderedGrouped[kvp.Key] = kvp.Value; + groupedVariants = orderedGrouped; + } + + // Calculate dropdown height + var itemHeight = 25; + var headerHeight = 13; + var totalHeight = groupedVariants.Sum(g => (string.IsNullOrEmpty(g.Key) ? 0 : headerHeight) + g.Value.Count() * itemHeight) + 5; + totalHeight = Math.Min(totalHeight, 300); // Cap at 300px + + variantDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", totalHeight, groupedVariants, SetupItem); + } + }; + } + + void SelectVariant(ActorInfo variant) + { + if (variant == null || selectedActor == null) + return; + + selectedVariant = variant; + + // Update the preview to show the variant + LoadExtras(variant); + var selectedInfo = info.ContainsKey(variant) ? info[variant] : info[selectedActor]; + + var previewColor = GetPreviewColorFromCategory(currentCategoryPath); + EncyclopediaColorPalette.SetPreviewColor(previewColor); + + var previewOwner = GetPreviewOwner(selectedInfo); + var typeDictionary = CreatePreviewTypeDictionary(previewOwner); + + if (previewBackground.IsVisible()) + { + previewWidget.SetPreview(renderActor, typeDictionary, previewColor); + previewWidget.GetScale = () => selectedInfo.Scale; + } + + // Update title to show variant name + if (titleLabel != null) + titleLabel.Text = GetActorDisplayName(variant); + + // Update description from variant's traits + UpdateVariantDescription(variant); + } + + void UpdateVariantDescription(ActorInfo variant) + { + var currentY = SetupProductionContainer(variant); + + // Hide subfaction info for variants + if (subfactionLabel != null) + subfactionLabel.Visible = false; + if (subfactionFlagImage != null) + subfactionFlagImage.Visible = false; + if (additionalInfoLabel != null) + additionalInfoLabel.Visible = false; + + currentY = SetupDescriptionSection(variant, currentY, showEncyclopediaDescription: false); + + actorDetailsContainer.Bounds.Height = currentY; + descriptionPanel.Layout.AdjustChildren(); + descriptionPanel.ScrollToTop(); + } + + int SetupProductionContainer(ActorInfo actor) + { + if (productionContainer == null) + return 0; + + var currentX = 0; + var productionContainerHeight = 0; + const int IconWidth = 16; + const int LabelSpacing = 4; + const int GroupSpacing = 20; + + var costIcon = productionContainer.GetOrNull("COST_ICON"); + var timeIcon = productionContainer.GetOrNull("TIME_ICON"); + var notProducibleIcon = productionContainer.GetOrNull("NOT_PRODUCIBLE_ICON"); + var notProducibleLabel = productionContainer.GetOrNull("NOT_PRODUCIBLE"); + + if (costIcon != null) costIcon.Visible = false; + if (timeIcon != null) timeIcon.Visible = false; + if (productionCost != null) productionCost.Visible = false; + if (productionTime != null) productionTime.Visible = false; + if (armorTypeIcon != null) armorTypeIcon.Visible = false; + if (armorTypeLabel != null) armorTypeLabel.Visible = false; + if (productionPowerIcon != null) productionPowerIcon.Visible = false; + if (productionPower != null) productionPower.Visible = false; + if (notProducibleIcon != null) notProducibleIcon.Visible = false; + if (notProducibleLabel != null) notProducibleLabel.Visible = false; + + // For variants without BuildableInfo/ValuedInfo, fall back to base actor + var bi = actor.TraitInfoOrDefault(); + var valued = actor.TraitInfoOrDefault(); + var actorForProduction = actor; + + if ((bi == null || valued == null) && actor != selectedActor && selectedActor != null) + { + // Variant doesn't have production info, use base actor + if (bi == null) + bi = selectedActor.TraitInfoOrDefault(); + if (valued == null) + { + valued = selectedActor.TraitInfoOrDefault(); + actorForProduction = selectedActor; + } + } + + var selectedInfo = info.ContainsKey(actor) ? info[actor] : info[selectedActor]; + + if (bi != null && !selectedInfo.HideBuildable) + { + var cost = valued?.Cost ?? 0; + if (cost > 0 && productionCost != null && costIcon != null) + { + var costText = cost.ToString(NumberFormatInfo.CurrentInfo); + productionCost.Text = costText; + costIcon.Bounds.X = currentX; + productionCost.Bounds.X = currentX + IconWidth + LabelSpacing; + var costWidth = Game.Renderer.Fonts[productionCost.Font].Measure(costText).X; + currentX += IconWidth + LabelSpacing + costWidth + GroupSpacing; + + costIcon.Visible = true; + productionCost.Visible = true; + productionContainerHeight = descriptionFont.Measure(costText).Y; + } + + var time = BuildTime(actorForProduction, selectedInfo.BuildableQueue); + if (time > 0 && productionTime != null && timeIcon != null) + { + var timeText = WidgetUtils.FormatTime(time, world.Timestep); + productionTime.Text = timeText; + timeIcon.Bounds.X = currentX; + productionTime.Bounds.X = currentX + IconWidth + LabelSpacing; + var timeWidth = Game.Renderer.Fonts[productionTime.Font].Measure(timeText).X; + currentX += IconWidth + LabelSpacing + timeWidth + GroupSpacing; + + timeIcon.Visible = true; + productionTime.Visible = true; + productionContainerHeight = Math.Max(productionContainerHeight, descriptionFont.Measure(timeText).Y); + } + } + else + { + if (encyclopediaExtrasInfo != null && encyclopediaExtrasInfo.HideNotProducible) + { + productionContainer.Visible = false; + } + else + { + notProducibleIcon.Visible = true; + notProducibleIcon.Bounds.X = currentX; + notProducibleLabel.Visible = true; + notProducibleLabel.Bounds.X = currentX + IconWidth + LabelSpacing; + var notProducibleLabelWidth = Game.Renderer.Fonts[notProducibleLabel.Font].Measure(notProducibleLabel.Text).X; + currentX += IconWidth + LabelSpacing + notProducibleLabelWidth + GroupSpacing; + productionContainerHeight = descriptionFont.Measure(notProducibleLabel.Text).Y; + } + } + + if (armorTypeLabel != null && armorTypeIcon != null) + { + var armor = actor.TraitInfos().FirstOrDefault(); + if (armor != null && !string.IsNullOrEmpty(armor.Type)) + { + SelectionTooltipLogic.GetArmorTypeLabel(armorTypeLabel, actor); + var hasArmorType = !string.IsNullOrEmpty(armorTypeLabel.Text); + if (hasArmorType) + { + armorTypeIcon.Bounds.X = currentX; + armorTypeLabel.Bounds.X = currentX + IconWidth + LabelSpacing; + var armorWidth = Game.Renderer.Fonts[armorTypeLabel.Font].Measure(armorTypeLabel.Text).X; + currentX += IconWidth + LabelSpacing + armorWidth + GroupSpacing; + + armorTypeIcon.Visible = true; + armorTypeLabel.Visible = true; + productionContainerHeight = Math.Max(productionContainerHeight, descriptionFont.Measure(armorTypeLabel.Text).Y); + } + } + } + + var power = actor.TraitInfos().Where(i => i.EnabledByDefault).Sum(i => i.Amount); + if (power != 0 && productionPower != null && productionPowerIcon != null) + { + var powerText = power.ToString(NumberFormatInfo.CurrentInfo); + productionPower.Text = powerText; + productionPowerIcon.Bounds.X = currentX; + productionPower.Bounds.X = currentX + IconWidth + LabelSpacing; + var powerWidth = Game.Renderer.Fonts[productionPower.Font].Measure(powerText).X; + currentX += IconWidth + LabelSpacing + powerWidth + GroupSpacing; + + productionPowerIcon.Visible = true; + productionPower.Visible = true; + productionContainerHeight = Math.Max(productionContainerHeight, descriptionFont.Measure(powerText).Y); + } + + // Only show the production container if it has any visible content + var hasVisibleContent = (costIcon?.Visible == true) || + (timeIcon?.Visible == true) || + (armorTypeIcon?.Visible == true) || + (productionPowerIcon?.Visible == true) || + (notProducibleIcon?.Visible == true); + + productionContainer.Visible = hasVisibleContent; + return productionContainerHeight + 10; + } + + int SetupDescriptionSection(ActorInfo actor, int currentY, bool showEncyclopediaDescription) + { + // Get prerequisites and description + var bi = actor.TraitInfoOrDefault(); var prerequisitesText = ""; var descriptionText = ""; @@ -863,6 +1085,7 @@ void SelectActor(ActorInfo actor, string categoryPath = null) currentY += descriptionHeight + 8; } + // Get strengths/weaknesses/attributes var strengthsText = ""; var weaknessesText = ""; var attributesText = ""; @@ -901,24 +1124,34 @@ void SelectActor(ActorInfo actor, string categoryPath = null) attributesLabel.Visible = false; } - var encyclopediaText = ""; - if (selectedInfo != null && !string.IsNullOrEmpty(selectedInfo.Description)) - encyclopediaText = WidgetUtils.WrapText(FluentProvider.GetMessage(selectedInfo.Description), descriptionLabel.Bounds.Width, descriptionFont); - - if (!string.IsNullOrEmpty(encyclopediaText) && encyclopediaDescriptionLabel != null) + // Show encyclopedia description only for base actors + if (showEncyclopediaDescription) { - SetupTextLabel(encyclopediaDescriptionLabel, encyclopediaText, ref currentY, 0); + var selectedInfo = info.ContainsKey(actor) ? info[actor] : null; + var encyclopediaText = ""; + if (selectedInfo != null && !string.IsNullOrEmpty(selectedInfo.Description)) + encyclopediaText = WidgetUtils.WrapText(FluentProvider.GetMessage(selectedInfo.Description), descriptionLabel.Bounds.Width, descriptionFont); + + if (!string.IsNullOrEmpty(encyclopediaText) && encyclopediaDescriptionLabel != null) + { + SetupTextLabel(encyclopediaDescriptionLabel, encyclopediaText, ref currentY, 0); + } + else if (encyclopediaDescriptionLabel != null) + { + encyclopediaDescriptionLabel.Visible = false; + } } else if (encyclopediaDescriptionLabel != null) { encyclopediaDescriptionLabel.Visible = false; } - actorDetailsContainer.Bounds.Height = currentY; - - descriptionPanel.Layout.AdjustChildren(); + return currentY; + } - descriptionPanel.ScrollToTop(); + int GetVariantGroupSortOrder(string groupName) + { + return variantGroupOrder.TryGetValue(groupName, out var order) ? order : int.MaxValue; } void RotatePreview() @@ -1033,6 +1266,10 @@ string InferPreviewOwnerFromCategory(string categoryPath) return "Nod"; } + if (selectedActor.Name == "sbag" || selectedActor.Name == "fenc") { + return "GDI"; + } + return topLevelCategory switch { "Allies" => "Greece", @@ -1066,7 +1303,11 @@ Color GetPreviewColorFromCategory(string categoryPath) return Color.FromArgb(230, 230, 255); // E6E6FF } - return Color.FromArgb(254, 17, 0); + return Color.FromArgb(254, 17, 0); // FE1100 + } + + if (selectedActor.Name == "sbag" || selectedActor.Name == "fenc") { + return Color.FromArgb(242, 207, 116); // F2CF74 } return topLevelCategory switch @@ -1075,7 +1316,7 @@ Color GetPreviewColorFromCategory(string categoryPath) "Soviets" => Color.FromArgb(254, 17, 0), // FE1100 "GDI" => Color.FromArgb(242, 207, 116), // F2CF74 "Scrin" => Color.FromArgb(128, 0, 200), // 7700FF - _ => Color.FromArgb(158, 166, 179) // 9ea6b3 + _ => Color.FromArgb(158, 166, 179) // 9EA6B3 }; } @@ -1194,6 +1435,14 @@ void LoadExtras(ActorInfo actor) static string GetActorDisplayName(ActorInfo actor) { + // Check for EncyclopediaExtrasInfo.Name first + var extras = actor.TraitInfoOrDefault(); + if (extras != null && !string.IsNullOrEmpty(extras.Name)) + { + return FluentProvider.GetMessage(extras.Name); + } + + // Fall back to TooltipInfo var name = actor.TraitInfos().FirstOrDefault(info => info.EnabledByDefault)?.Name; if (!string.IsNullOrEmpty(name)) { diff --git a/OpenRA.Mods.CA/Widgets/Logic/MissionBrowserLogicCA.cs b/OpenRA.Mods.CA/Widgets/Logic/MissionBrowserLogicCA.cs index 9a2f043117..c6b1c5895a 100644 --- a/OpenRA.Mods.CA/Widgets/Logic/MissionBrowserLogicCA.cs +++ b/OpenRA.Mods.CA/Widgets/Logic/MissionBrowserLogicCA.cs @@ -256,7 +256,7 @@ void CreateMissionGroup(string title, IEnumerable previews, Action o () => StartMissionClicked(onExit)); var label = item.Get("TITLE"); - var missionTitle = CampaignProgressTracker.GetMapTileWithoutNumber(preview.Title); + var missionTitle = CampaignProgressTracker.GetMapTitleWithoutNumber(preview.Title); if (campaignProgress.ContainsKey(missionTitle)) { @@ -352,7 +352,14 @@ void SelectMap(MapPreview preview) infoVideo = missionData.BackgroundVideo; infoVideoVisible = infoVideo != null; - var briefingText = missionData.Briefing?.Replace("\\n", "\n"); + // Try to translate the briefing as a Fluent key, fall back to raw text + var rawBriefing = missionData.Briefing; + string briefingText; + if (!string.IsNullOrEmpty(rawBriefing) && preview.TryGetMessage(rawBriefing, out var translated)) + briefingText = translated; + else + briefingText = rawBriefing?.Replace("\\n", "\n"); + var briefing = WidgetUtils.WrapText(briefingText, description.Bounds.Width, descriptionFont); var height = descriptionFont.Measure(briefing).Y; Game.RunAfterTick(() => @@ -401,7 +408,7 @@ void RebuildOptions() var yOffset = 0; foreach (var option in allOptions.Where(o => o is LobbyBooleanOption)) { - if (!missionOptions.ContainsKey(option.Id) || !( new[] { "True", "False" }.Contains(missionOptions[option.Id]))) + if (!missionOptions.ContainsKey(option.Id) || !(new[] { "True", "False" }.Contains(missionOptions[option.Id]))) missionOptions[option.Id] = option.DefaultValue; if (checkboxColumns.Count == 0) @@ -603,7 +610,7 @@ void LoadSavedOptions() var savedOptionsFileContents = File.ReadAllText(savedOptionsFilePath); var savedOptions = JsonConvert.DeserializeObject>(savedOptionsFileContents); - foreach(KeyValuePair option in savedOptions) + foreach (KeyValuePair option in savedOptions) missionOptions[option.Key] = option.Value; } catch diff --git a/mods/ca/chrome/encyclopedia.yaml b/mods/ca/chrome/encyclopedia.yaml index c8dcf19a4e..536546d7f1 100644 --- a/mods/ca/chrome/encyclopedia.yaml +++ b/mods/ca/chrome/encyclopedia.yaml @@ -79,9 +79,17 @@ Background@ENCYCLOPEDIA_PANEL: Width: PARENT_WIDTH - 250 - 10 Height: PARENT_HEIGHT - 65 Children: + DropDownButton@VARIANT_DROPDOWN: + X: 0 + Y: 0 + Width: PARENT_WIDTH - 148 - 10 + Height: 25 + Font: Regular + Text: Select Variant... ScrollPanel@ACTOR_DESCRIPTION_PANEL: + Y: 30 Width: PARENT_WIDTH - 148 - 10 - Height: PARENT_HEIGHT + Height: PARENT_HEIGHT - 30 TopBottomSpacing: 8 Background: observer-scrollpanel-button-pressed Children: diff --git a/mods/ca/fluent/coop.ftl b/mods/ca/fluent/coop.ftl new file mode 100644 index 0000000000..30a0cd9321 --- /dev/null +++ b/mods/ca/fluent/coop.ftl @@ -0,0 +1,3 @@ +## coop.lua shared strings +high-command = High Command +resource-spawners-deleted = All resource spawners are deleted now. Good luck! diff --git a/mods/ca/fluent/encyclopedia.ftl b/mods/ca/fluent/encyclopedia.ftl index e8857f1895..8e1db5d173 100644 --- a/mods/ca/fluent/encyclopedia.ftl +++ b/mods/ca/fluent/encyclopedia.ftl @@ -37,3 +37,5 @@ encyclopedia-tips-general-description = • Most infantry prone when they are fi • Service Depot repairs are completely free. Vehicles can be sold while being repaired. • All production times are proportional to cost. The cost to time ratio is lower for Refineries, MCVs and higher for upgrades. + + • In Single Queue the maximum production speed is 2x, which requires 5 infantry/aircraft production structures, or 4 vehicle/building production structures. diff --git a/mods/ca/fluent/lua.ftl b/mods/ca/fluent/lua.ftl new file mode 100644 index 0000000000..fccd86d79c --- /dev/null +++ b/mods/ca/fluent/lua.ftl @@ -0,0 +1,18 @@ +## shared strings +objective-failed = Objective failed +objective-completed = Objective completed + +primary = Primary +secondary = Secondary + +new-primary-objective = New primary objective +new-secondary-objective = New secondary objective + +## Common message categories +notification = Notification +tip = Tip +warning = Warning + +## Common notifications +reinforcements-arrived = Reinforcements have arrived. +signal-flare-detected = Signal flare detected. Press [{ $hotkey }] to view location. diff --git a/mods/ca/maps/frozen_rift_ca.oramap b/mods/ca/maps/frozen_rift_ca.oramap index 6f2ba1c5d2..e8af655d15 100644 Binary files a/mods/ca/maps/frozen_rift_ca.oramap and b/mods/ca/maps/frozen_rift_ca.oramap differ diff --git a/mods/ca/missions/coop-campaign/ca-prologue-04-coop/juncture-coop.lua b/mods/ca/missions/coop-campaign/ca-prologue-04-coop/juncture-coop.lua index 49b20fc6b0..ebabfce80d 100644 --- a/mods/ca/missions/coop-campaign/ca-prologue-04-coop/juncture-coop.lua +++ b/mods/ca/missions/coop-campaign/ca-prologue-04-coop/juncture-coop.lua @@ -23,3 +23,23 @@ end AfterTick = function() end + +WarpInBanshees = function() + local firstActivePlayer = GetFirstActivePlayer() + if firstActivePlayer == nil then + return + end + + local hpad1 = Actor.Create("hpad.td", true, { Owner = firstActivePlayer, Location = HpadSpawn1.Location }) + local hpad2 = Actor.Create("hpad.td", true, { Owner = firstActivePlayer, Location = HpadSpawn2.Location }) + + Trigger.AfterDelay(10, function() + local useFirstHpad = true + for _, player in ipairs(MissionPlayers) do + local hpad = useFirstHpad and hpad1 or hpad2 + local banshee = Actor.Create("scrn", true, { Owner = player, Location = hpad.Location, CenterPosition = hpad.CenterPosition, Facing = Angle.NorthEast }) + banshee.Move(hpad.Location) + useFirstHpad = not useFirstHpad + end + end) +end diff --git a/mods/ca/missions/coop-campaign/ca-prologue-04-coop/map.yaml b/mods/ca/missions/coop-campaign/ca-prologue-04-coop/map.yaml index c4570d6b68..794513aeff 100644 --- a/mods/ca/missions/coop-campaign/ca-prologue-04-coop/map.yaml +++ b/mods/ca/missions/coop-campaign/ca-prologue-04-coop/map.yaml @@ -40,7 +40,7 @@ Players: Bot: campaign Faction: gdi Color: F2CF74 - Enemies: Nod, Creeps + Enemies: Nod, Multi0, Multi1, Multi2, Multi3, Multi4, Multi5, Creeps PlayerReference@Multi0: Name: Multi0 Playable: True diff --git a/mods/ca/missions/coop-campaign/ca03-deliverance-coop/deliverance-coop.lua b/mods/ca/missions/coop-campaign/ca03-deliverance-coop/deliverance-coop.lua index 4e6585aa70..e827806531 100644 --- a/mods/ca/missions/coop-campaign/ca03-deliverance-coop/deliverance-coop.lua +++ b/mods/ca/missions/coop-campaign/ca03-deliverance-coop/deliverance-coop.lua @@ -26,6 +26,13 @@ AfterTick = function() end TransferGDIUnits = function() + Utils.Do(MissionPlayers, function(p) + p.PlayLowPowerNotification = false + Trigger.AfterDelay(DateTime.Seconds(10), function() + p.PlayLowPowerNotification = true + end) + end) + local gdiForces = GDI.GetActors() Utils.Do(gdiForces, function(a) if a.Type ~= "player" then diff --git a/mods/ca/missions/coop-campaign/ca14-treachery-coop/treachery-coop.lua b/mods/ca/missions/coop-campaign/ca14-treachery-coop/treachery-coop.lua index 8dcf9fab27..b466f58796 100644 --- a/mods/ca/missions/coop-campaign/ca14-treachery-coop/treachery-coop.lua +++ b/mods/ca/missions/coop-campaign/ca14-treachery-coop/treachery-coop.lua @@ -42,3 +42,16 @@ end AfterTick = function() end + +TransferAbandonedBase = function() + local baseBuildings = Map.ActorsInBox(AbandonedBaseTopLeft.CenterPosition, AbandonedBaseBottomRight.CenterPosition, function(a) + return a.Owner == USSRAbandoned + end) + + Utils.Do(baseBuildings, function(a) + a.Owner = GetFirstActivePlayer() + end) + + CACoopQueueSyncer() + StopSpread = false +end diff --git a/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop-rules.yaml b/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop-rules.yaml index c8c1ad8fb0..2377c490a2 100644 --- a/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop-rules.yaml +++ b/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop-rules.yaml @@ -4,3 +4,7 @@ World: LobbyMissionInfo@InitialObjectives: Prefix: Initial Objectives Text: • Liberate the five enslaved GDI bases by killing Masterminds.\n• Eliminate all Scrin forces.\n• Avoid killing enslaved GDI units. + +MDRN: + Attachable: + OnDetachBehavior: None diff --git a/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop.lua b/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop.lua index d1f1f67210..8176b1ae95 100644 --- a/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop.lua +++ b/mods/ca/missions/coop-campaign/ca27-emancipation-coop/emancipation-coop.lua @@ -31,7 +31,7 @@ AfterTick = function() end FreeSlaves = function(slaves) - local baseSlaves = Utils.Where(slaves, function(s) return IsBaseTransferActor(a) end) + local baseSlaves = Utils.Where(slaves, function(s) return IsBaseTransferActor(s) end) local otherSlaves = Utils.Where(slaves, function(s) return s.HasProperty("Move") and not IsHarvester(s) end) local firstActivePlayer = GetFirstActivePlayer() @@ -63,4 +63,6 @@ FreeSlaves = function(slaves) end) end end) + + CACoopQueueSyncer() end diff --git a/mods/ca/missions/coop-campaign/ca36-reckoning-coop/reckoning-coop.lua b/mods/ca/missions/coop-campaign/ca36-reckoning-coop/reckoning-coop.lua index 7973e2198a..9d503d8a58 100644 --- a/mods/ca/missions/coop-campaign/ca36-reckoning-coop/reckoning-coop.lua +++ b/mods/ca/missions/coop-campaign/ca36-reckoning-coop/reckoning-coop.lua @@ -31,6 +31,18 @@ AfterWorldLoaded = function() Utils.Do(Utils.Where({ Multi2, Multi5 }, function(p) return p ~= nil end), function(p) Actor.Create("rebel.allegiance", true, { Owner = p }) end) + + if (Multi1 ~= nil and Multi1.IsLocalPlayer) or (Multi4 ~= nil and Multi4.IsLocalPlayer) then + Camera.Position = GDIBase.CenterPosition + end + + if Multi2 ~= nil and Multi2.IsLocalPlayer then + Camera.Position = Exterminator3Patrol1.CenterPosition + end + + if Multi5 ~= nil and Multi5.IsLocalPlayer then + Camera.Position = R4.CenterPosition + end end AfterTick = function() @@ -60,6 +72,9 @@ DistributeUnitsAndBases = function() if Multi1 ~= nil or Multi4 ~= nil then GDIActive = true + local excess = GDIHostile.GetActorsByTypes({ "gtek", "hq", "afld.gdi", "titn", "htnk.ion", "htnk.drone", "atwr" }) + Utils.Do(excess, function(a) a.Destroy() end) + local firstGdiPlayer = Multi1 ~= nil and Multi1 or Multi4 TransferBaseToPlayer(GDIHostile, firstGdiPlayer) @@ -79,10 +94,13 @@ DistributeUnitsAndBases = function() InitAttackSquad(Squads.ScrinGDIKiller, Scrin, activeGdiPlayers) end - Trigger.AfterDelay(1, function() - -- transfer top Scrin base to first Scrin player (y = 0 -> 50) - if Multi2 ~= nil or Multi5 ~= nil then - ScrinRebelsActive = true + -- transfer top Scrin base to first Scrin player (y = 0 -> 50) + if Multi2 ~= nil or Multi5 ~= nil then + ScrinRebelsActive = true + + Trigger.AfterDelay(1, function() + local excess = ScrinRebels.GetActorsByTypes({ "scrt", "nerv", "grav", "sign", "tpod" }) + Utils.Do(excess, function(a) a.Destroy() end) local firstScrinPlayer = Multi2 ~= nil and Multi2 or Multi5 @@ -113,8 +131,9 @@ DistributeUnitsAndBases = function() local activeRebelPlayers = Utils.Where({ Multi1, Multi4 }, function(p) return p ~= nil end) Squads.ScrinRebelKiller.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 40, Max = 80 }) InitAttackSquad(Squads.ScrinRebelKiller, Scrin, activeRebelPlayers) - end - end) + CACoopQueueSyncer() + end) + end CACoopQueueSyncer() end diff --git a/mods/ca/missions/coop-campaign/ca48-banishment-coop/banishment-coop.lua b/mods/ca/missions/coop-campaign/ca48-banishment-coop/banishment-coop.lua index cdf4540fb2..67e84cffd0 100644 --- a/mods/ca/missions/coop-campaign/ca48-banishment-coop/banishment-coop.lua +++ b/mods/ca/missions/coop-campaign/ca48-banishment-coop/banishment-coop.lua @@ -55,6 +55,8 @@ TransferBaseActors = function(base) Utils.Do(baseActors, function(a) a.Owner = recipientPlayer end) + + CACoopQueueSyncer() end DoMcvArrival = function() diff --git a/mods/ca/missions/main-campaign/ca-prologue-01/en.ftl b/mods/ca/missions/main-campaign/ca-prologue-01/en.ftl new file mode 100644 index 0000000000..819b280c94 --- /dev/null +++ b/mods/ca/missions/main-campaign/ca-prologue-01/en.ftl @@ -0,0 +1,31 @@ +## Briefing +briefing = The Soviets have captured one of our most important scientists, Professor Einstein. + + He is currently being held inside a Soviet base not far from the border. It is crucial that he be rescued before he can be transported deeper into Soviet territory. + + Using a small task force led by our special commando Tanya Adams, rescue Einstein and bring him to the extraction point. + + The Soviet base is protected by Tesla Coils. Cutting their power supply will render them inoperative. + +## Objectives +find-einstein = Find Einstein. +tanya-survive = Tanya must survive. +einstein-survive = Einstein must survive. +destroy-sub-pen = Destroy the Soviet Sub Pen. +extract-einstein = Bring Einstein to the extraction point and board + the transport helicopter. + +## Notifications +allied-cruisers-arrived = Allied cruisers have arrived. +unidentified-allied-units = Unidentified Allied units detected. + +## Dialogue +cruiser-captain = Cruiser Captain +encountering-soviets = Encountering Soviet naval presence! We're under heavy fire! +waters-cleared = This is impossible! These waters were cleared! + +unknown-speaker = Unknown +temporal-disturbance = Another temporal disturbance.. Well, we can work this out later. For now, we are at your disposal commander. + +## Tips +tooltip-tip = Information is displayed in the bottom right of the screen if any single unit or structure is selected, listing its strengths and weaknesses (as long as Selected Unit Tooltip is enabled in settings). diff --git a/mods/ca/missions/main-campaign/ca-prologue-01/map.yaml b/mods/ca/missions/main-campaign/ca-prologue-01/map.yaml index 279958ef8d..f05290ece8 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-01/map.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-01/map.yaml @@ -522,3 +522,5 @@ Actors: Rules: ca|rules/custom/campaign-rules.yaml, ca|rules/custom/campaign-tooltips.yaml, ca|rules/custom/two-tone-nod.yaml, ca|rules/custom/disable-coalitions.yaml, relativity-rules.yaml Weapons: ca|weapons/custom/campaign.yaml, relativity-weapons.yaml + +FluentMessages: ca|fluent/lua.ftl, en.ftl diff --git a/mods/ca/missions/main-campaign/ca-prologue-01/relativity-rules.yaml b/mods/ca/missions/main-campaign/ca-prologue-01/relativity-rules.yaml index 7b0b022c80..97da9b7ee5 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-01/relativity-rules.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-01/relativity-rules.yaml @@ -6,7 +6,7 @@ World: LuaScript: Scripts: campaign.lua, relativity.lua MissionData: - Briefing: The Soviets have captured one of our most important scientists, Professor Einstein.\n\nHe is currently being held inside a Soviet base not far from the border. It is crucial that he be rescued before he can be transported deeper into Soviet territory.\n\nUsing a small task force led by our special commando Tanya Adams, rescue Einstein and bring him to the extraction point.\n\nThe Soviet base is protected by Tesla Coils. Cutting their power supply will render them inoperative. + Briefing: briefing -ScriptLobbyDropdown@DIFFICULTY: ScriptLobbyDropdown@PROLOGUEDIFFICULTY: ID: prologuedifficulty diff --git a/mods/ca/missions/main-campaign/ca-prologue-01/relativity.lua b/mods/ca/missions/main-campaign/ca-prologue-01/relativity.lua index 187fe3b592..466ed27c34 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-01/relativity.lua +++ b/mods/ca/missions/main-campaign/ca-prologue-01/relativity.lua @@ -27,9 +27,9 @@ WorldLoaded = function() SetupPlayers() InitObjectives(Greece) - FindEinsteinObjective = Greece.AddObjective("Find Einstein.") - TanyaSurviveObjective = Greece.AddObjective("Tanya must survive.") - EinsteinSurviveObjective = Greece.AddObjective("Einstein must survive.") + FindEinsteinObjective = Greece.AddObjective(UserInterface.GetFluentMessage("find-einstein")) + TanyaSurviveObjective = Greece.AddObjective(UserInterface.GetFluentMessage("tanya-survive")) + EinsteinSurviveObjective = Greece.AddObjective(UserInterface.GetFluentMessage("einstein-survive")) RunInitialActivities() @@ -39,7 +39,8 @@ WorldLoaded = function() Trigger.OnAllKilled(LabGuardsTeam, LabGuardsKilled) - Trigger.AfterDelay(DateTime.Seconds(5), function() Actor.Create("camera", true, { Owner = Greece, Location = BaseCameraPoint.Location }) end) + Trigger.AfterDelay(DateTime.Seconds(5), + function() Actor.Create("camera", true, { Owner = Greece, Location = BaseCameraPoint.Location }) end) Camera.Position = InsertionLZ.CenterPosition @@ -52,7 +53,7 @@ WorldLoaded = function() Trigger.OnKilled(SubPen, function(self, killer) if ObjectiveDestroySubPen == nil then - ObjectiveDestroySubPen = Greece.AddObjective("Destroy the Soviet Sub Pen.") + ObjectiveDestroySubPen = Greece.AddObjective(UserInterface.GetFluentMessage("destroy-sub-pen")) end if not Greece.IsObjectiveCompleted(ObjectiveDestroySubPen) then @@ -64,7 +65,7 @@ WorldLoaded = function() end) Trigger.AfterDelay(DateTime.Seconds(30), function() - Tip("Information is displayed in the bottom right of the screen if any single unit or structure is selected, listing its strengths and weaknesses (as long as Selected Unit Tooltip is enabled in settings).") + Tip("tooltip-tip") end) AfterWorldLoaded() @@ -164,7 +165,7 @@ end SendCruisers = function() CruisersArrived = true - Notification("Allied cruisers have arrived.") + Notification("allied-cruisers-arrived") MediaCA.PlaySound(MissionDir .. "/r_alliedcruisers.aud", 2); Actor.Create("camera", true, { Owner = Greece, Location = CruiserCameraPoint.Location }) Beacon.New(Greece, CruiserBeacon.CenterPosition) @@ -177,15 +178,18 @@ SendCruisers = function() i = i + 1 end) + local cruiserCaptain = UserInterface.GetFluentMessage("cruiser-captain") + Trigger.AfterDelay(DateTime.Seconds(4), function() - Media.DisplayMessage("Encountering Soviet naval presence! We're under heavy fire!", "Cruiser Captain", HSLColor.FromHex("99ACF2")) + Media.DisplayMessage(UserInterface.GetFluentMessage("encountering-soviets"), cruiserCaptain, + HSLColor.FromHex("99ACF2")) MediaCA.PlaySound(MissionDir .. "/encountering.aud", 2) Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(4)), function() - Media.DisplayMessage("This is impossible! These waters were cleared!", "Cruiser Captain", HSLColor.FromHex("99ACF2")) + Media.DisplayMessage(UserInterface.GetFluentMessage("waters-cleared"), cruiserCaptain, HSLColor.FromHex("99ACF2")) MediaCA.PlaySound(MissionDir .. "/impossible.aud", 2) Trigger.AfterDelay(DateTime.Seconds(2), function() if not SubPen.IsDead and ObjectiveDestroySubPen == nil then - ObjectiveDestroySubPen = Greece.AddObjective("Destroy the Soviet Sub Pen.") + ObjectiveDestroySubPen = Greece.AddObjective(UserInterface.GetFluentMessage("destroy-sub-pen")) Beacon.New(Greece, SubPen.CenterPosition) Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(4)), function() PrismsArrived = true @@ -198,11 +202,12 @@ SendCruisers = function() Actor.Create("ptnk", true, { Owner = England, Location = PrismSpawn2.Location, Facing = Angle.East }) Actor.Create("ptnk", true, { Owner = England, Location = PrismSpawn3.Location, Facing = Angle.East }) Trigger.AfterDelay(DateTime.Seconds(2), function() - Notification("Unidentified Allied units detected.") + Notification("unidentified-allied-units") MediaCA.PlaySound(MissionDir .. "/r_unidentified.aud", 2) Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(3)), function() - Media.DisplayMessage("Another temporal disturbance.. Well, we can work this out later. For now, we are at your disposal commander.", "Unknown", HSLColor.FromHex("99ACF2")) + Media.DisplayMessage(UserInterface.GetFluentMessage("temporal-disturbance"), + UserInterface.GetFluentMessage("unknown-speaker"), HSLColor.FromHex("99ACF2")) MediaCA.PlaySound(MissionDir .. "/disturbance.aud", 2) Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(5)), function() local prismTanks = England.GetActorsByType("ptnk") @@ -243,7 +248,7 @@ CreateEinstein = function() Einstein = Actor.Create(EinsteinType, true, { Location = EinsteinSpawnPoint.Location, Owner = Greece }) Einstein.Scatter() Trigger.OnKilled(Einstein, RescueFailed) - ExtractObjective = Greece.AddObjective("Bring Einstein to the extraction point and board\nthe transport helicopter.") + ExtractObjective = Greece.AddObjective(UserInterface.GetFluentMessage("extract-einstein")) Trigger.AfterDelay(DateTime.Seconds(1), function() PlaySpeechNotificationToMissionPlayers("TargetFreed") end) end diff --git a/mods/ca/missions/main-campaign/ca-prologue-02/en.ftl b/mods/ca/missions/main-campaign/ca-prologue-02/en.ftl new file mode 100644 index 0000000000..89f58c03e9 --- /dev/null +++ b/mods/ca/missions/main-campaign/ca-prologue-02/en.ftl @@ -0,0 +1,18 @@ +## Briefing +briefing = While we extracted all the information we needed from Einstein, Comrade Stalin was most displeased to learn of his escape. + + The traitor who informed the Allies of the scientist's whereabouts has been identified, and Stalin demands retribution. + + Go to the village the traitor calls home. Wipe it out. Leave no survivors. + + The Allies have set up a base nearby, to protect the village while they prepare to evacuate it. Once the village is destroyed, destroy the Allied base. + +## Objectives +wipe-out-village = Wipe out the village. +destroy-allied-base = Destroy the Allied base. + +## Dialogue +tesla-tank = Tesla Tank +greetings = Greetings Comrades! The Soviet Empire truly knows no boundaries! +unknown-speaker = Unknown +doubts = We understand that Comrade Stalin has his doubts about our agreement. We hope these gifts will put his mind at ease. diff --git a/mods/ca/missions/main-campaign/ca-prologue-02/map.yaml b/mods/ca/missions/main-campaign/ca-prologue-02/map.yaml index e28ac5ed09..ef83acae49 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-02/map.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-02/map.yaml @@ -998,3 +998,5 @@ Actors: Rules: ca|rules/custom/campaign-rules.yaml, ca|rules/custom/campaign-tooltips.yaml, ca|rules/custom/two-tone-nod.yaml, ca|rules/custom/disable-doctrines.yaml, reprisal-rules.yaml Weapons: ca|weapons/custom/campaign.yaml + +FluentMessages: ca|fluent/lua.ftl, en.ftl diff --git a/mods/ca/missions/main-campaign/ca-prologue-02/reprisal-rules.yaml b/mods/ca/missions/main-campaign/ca-prologue-02/reprisal-rules.yaml index dc19922065..f88f3f80c1 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-02/reprisal-rules.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-02/reprisal-rules.yaml @@ -6,7 +6,7 @@ World: LuaScript: Scripts: campaign.lua, reprisal.lua MissionData: - Briefing: While we extracted all the information we needed from Einstein, Comrade Stalin was most displeased to learn of his escape.\n\nThe traitor who informed the Allies of the scientist's whereabouts has been identified, and Stalin demands retribution.\n\nGo to the village the traitor calls home. Wipe it out. Leave no survivors.\n\nThe Allies have set up a base nearby, to protect the village while they prepare to evacuate it. Once the village is destroyed, destroy the Allied base. + Briefing: briefing -ScriptLobbyDropdown@DIFFICULTY: ScriptLobbyDropdown@PROLOGUEDIFFICULTY: ID: prologuedifficulty diff --git a/mods/ca/missions/main-campaign/ca-prologue-02/reprisal.lua b/mods/ca/missions/main-campaign/ca-prologue-02/reprisal.lua index 50843ba897..56ff281fa8 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-02/reprisal.lua +++ b/mods/ca/missions/main-campaign/ca-prologue-02/reprisal.lua @@ -7,9 +7,12 @@ AlliedAttackPaths = { { EastAttackRally.Location, SovietBase.Location } } -TeslaTrigger = { TeslaTrigger1.Location, TeslaTrigger2.Location, TeslaTrigger3.Location, TeslaTrigger4.Location, TeslaTrigger5.Location, TeslaTrigger6.Location, TeslaTrigger7.Location } -TeslaTriggerWest = { TeslaTriggerWest1.Location, TeslaTriggerWest2.Location, TeslaTriggerWest3.Location, TeslaTriggerWest4.Location, TeslaTriggerWest5.Location, TeslaTriggerWest6.Location, TeslaTriggerWest7.Location, - TeslaTriggerWest8.Location, TeslaTriggerWest9.Location, TeslaTriggerWest10.Location, TeslaTriggerWest11.Location, TeslaTriggerWest12.Location, TeslaTriggerWest13.Location, TeslaTriggerWest14.Location, +TeslaTrigger = { TeslaTrigger1.Location, TeslaTrigger2.Location, TeslaTrigger3.Location, TeslaTrigger4.Location, + TeslaTrigger5.Location, TeslaTrigger6.Location, TeslaTrigger7.Location } +TeslaTriggerWest = { TeslaTriggerWest1.Location, TeslaTriggerWest2.Location, TeslaTriggerWest3.Location, + TeslaTriggerWest4.Location, TeslaTriggerWest5.Location, TeslaTriggerWest6.Location, TeslaTriggerWest7.Location, + TeslaTriggerWest8.Location, TeslaTriggerWest9.Location, TeslaTriggerWest10.Location, TeslaTriggerWest11.Location, + TeslaTriggerWest12.Location, TeslaTriggerWest13.Location, TeslaTriggerWest14.Location, TeslaTriggerWest15.Location, TeslaTriggerWest16.Location } Squads = { @@ -47,19 +50,21 @@ WorldLoaded = function() InitObjectives(USSR) InitGreece() - ObjectiveWipeOutVillage = USSR.AddObjective("Wipe out the village.") - ObjectiveDestroyBase = USSR.AddObjective("Destroy the Allied base.") + ObjectiveWipeOutVillage = USSR.AddObjective(UserInterface.GetFluentMessage("wipe-out-village")) + ObjectiveDestroyBase = USSR.AddObjective(UserInterface.GetFluentMessage("destroy-allied-base")) Trigger.AfterDelay(DateTime.Seconds(2), function() PlaySpeechNotificationToMissionPlayers("ReinforcementsArrived") - Notification("Reinforcements have arrived.") + Notification("reinforcements-arrived") DoMcvArrival() end) Trigger.AfterDelay(DateTime.Minutes(1), function() local villageFlare = Actor.Create("flare", true, { Owner = USSR, Location = VillageCenter.Location }) PlaySpeechNotificationToMissionPlayers("SignalFlare") - Notification("Signal flare detected. Press [" .. UtilsCA.Hotkey("ToLastEvent") .. "] to view location.") + Media.DisplayMessage( + UserInterface.GetFluentMessage("signal-flare-detected", { ["hotkey"] = UtilsCA.Hotkey("ToLastEvent") }), + UserInterface.GetFluentMessage("notification"), HSLColor.FromHex("1E90FF")) Beacon.New(USSR, VillageCenter.CenterPosition) Trigger.AfterDelay(DateTime.Minutes(5), villageFlare.Destroy) end) @@ -157,11 +162,13 @@ WarpInTeslaTanks = function(TankLocation1, TankLocation2, EffectLocation) Actor.Create("ttnk", true, { Owner = USSR, Location = TankLocation1, Facing = Angle.South }) Actor.Create("ttnk", true, { Owner = USSR, Location = TankLocation2, Facing = Angle.South }) Trigger.AfterDelay(DateTime.Seconds(2), function() - Media.DisplayMessage("Greetings Comrades! The Soviet Empire truly knows no boundaries!", "Tesla Tank", HSLColor.FromHex("FF0000")) + Media.DisplayMessage(UserInterface.GetFluentMessage("greetings"), UserInterface.GetFluentMessage("tesla-tank"), + HSLColor.FromHex("FF0000")) MediaCA.PlaySound(MissionDir .. "/greetings.aud", 2) Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(6)), function() - Media.DisplayMessage("We understand that Comrade Stalin has his doubts about our agreement. We hope these gifts will put his mind at ease.", "Unknown", HSLColor.FromHex("999999")) + Media.DisplayMessage(UserInterface.GetFluentMessage("doubts"), UserInterface.GetFluentMessage("unknown-speaker"), + HSLColor.FromHex("999999")) MediaCA.PlaySound(MissionDir .. "/doubts.aud", 2) end) end) diff --git a/mods/ca/missions/main-campaign/ca-prologue-03/detachment-rules.yaml b/mods/ca/missions/main-campaign/ca-prologue-03/detachment-rules.yaml index fdaa17d87d..2556eaceb8 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-03/detachment-rules.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-03/detachment-rules.yaml @@ -6,7 +6,7 @@ World: LuaScript: Scripts: campaign.lua, detachment.lua MissionData: - Briefing: GDI forces have found themselves scattered and confused in unfamiliar surroundings, confronted by hostile soldiers.\n\nOther GDI units have been in radio contact after finding themselves in the same situation.\n\nGunfire can be heard echoing through the trees, and reports of casualties have already been heard over the radio. There is no time to lose.\n\nTake command, locate these other GDI troops, and find a route to safety. + Briefing: briefing -ScriptLobbyDropdown@DIFFICULTY: ScriptLobbyDropdown@PROLOGUEDIFFICULTY: ID: prologuedifficulty diff --git a/mods/ca/missions/main-campaign/ca-prologue-03/detachment.lua b/mods/ca/missions/main-campaign/ca-prologue-03/detachment.lua index 5bd935f20f..5b36025bbd 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-03/detachment.lua +++ b/mods/ca/missions/main-campaign/ca-prologue-03/detachment.lua @@ -25,8 +25,8 @@ WorldLoaded = function() InitObjectives(GDI) InitUSSR() - ObjectiveLocateForces = GDI.AddObjective("Locate all GDI forces.") - ObjectiveExit = GDI.AddObjective("Find a safe exit route.") + ObjectiveLocateForces = GDI.AddObjective(UserInterface.GetFluentMessage("locate-forces")) + ObjectiveExit = GDI.AddObjective(UserInterface.GetFluentMessage("find-exit")) SetupReveals({ Reveal1, Reveal3, Reveal4 }) @@ -44,9 +44,9 @@ WorldLoaded = function() FirstRevealComplete = true local camera = Actor.Create("smallcamera", true, { Owner = GDI, Location = Reveal2.Location }) - if UtilsCA.FogEnabled() then - Tip("When an enemy structure is destroyed under the fog of war, it won't disappear until its location is revealed again. The explosion sound and screen shake can be used to verify its destruction.") - end + if UtilsCA.FogEnabled() then + Tip("fog-tip") + end Trigger.AfterDelay(DateTime.Seconds(4), function() camera.Destroy() @@ -59,15 +59,15 @@ WorldLoaded = function() if IsMissionPlayer(a.Owner) and not GroupsFound[g.Id] then Trigger.RemoveProximityTrigger(id) GroupsFound[g.Id] = true - Notification("GDI forces found.") - MediaCA.PlaySound(MissionDir .. "/gdifound.aud", 2) + Notification("gdi-found") + MediaCA.PlaySound(MissionDir .. "/gdifound.aud", 2) - if g.Id == 2 then - Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(2)), function() - Media.DisplayMessage("Thank god! You found us!.", "GDI Soldier", HSLColor.FromHex("F2CF74")) - MediaCA.PlaySound(MissionDir .. "/thankgod.aud", 1.5) - end) - end + if g.Id == 2 then + Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(2)), function() + Media.DisplayMessage(UserInterface.GetFluentMessage("thank-god"), UserInterface.GetFluentMessage("gdi-soldier"), HSLColor.FromHex("F2CF74")) + MediaCA.PlaySound(MissionDir .. "/thankgod.aud", 1.5) + end) + end local groupActors = Map.ActorsInCircle(g.Waypoint.CenterPosition, WDist.New(8 * 1024)); Utils.Do(groupActors, function(a) @@ -87,7 +87,7 @@ WorldLoaded = function() Trigger.AfterDelay(DateTime.Seconds(4), function() Actor.Create("flare", true, { Owner = GDI, Location = SignalFlare.Location }) PlaySpeechNotificationToMissionPlayers("SignalFlare") - Notification("Signal flare detected. Press [" .. UtilsCA.Hotkey("ToLastEvent") .. "] to view location.") + Media.DisplayMessage(UserInterface.GetFluentMessage("signal-flare-detected", { ["hotkey"] = UtilsCA.Hotkey("ToLastEvent") }), UserInterface.GetFluentMessage("notification"), HSLColor.FromHex("1E90FF")) Beacon.New(GDI, SignalFlare.CenterPosition) end) end @@ -96,11 +96,11 @@ WorldLoaded = function() end) Trigger.AfterDelay(DateTime.Seconds(4), function() - Media.DisplayMessage("Commander what's going on, where the hell are we?!", "GDI Soldier", HSLColor.FromHex("F2CF74")) + Media.DisplayMessage(UserInterface.GetFluentMessage("where-are-we"), UserInterface.GetFluentMessage("gdi-soldier"), HSLColor.FromHex("F2CF74")) Media.PlaySound(MissionDir .. "/wherearewe.aud") Trigger.AfterDelay(DateTime.Seconds(20), function() - Media.DisplayMessage("Come in, any GDI units, hostile troops have us pinned down.", "Radio", HSLColor.FromHex("F2CF74")) + Media.DisplayMessage(UserInterface.GetFluentMessage("pinned-down"), UserInterface.GetFluentMessage("radio"), HSLColor.FromHex("F2CF74")) MediaCA.PlaySoundAtPos(MissionDir .. "/pinned.aud", 2, Camera.Position + WVec.New(2560, 0, 0)) end) end) @@ -148,9 +148,9 @@ OncePerSecondChecks = function() Trigger.AfterDelay(DateTime.Seconds(2), function() Reinforcements.Reinforce(GDI, { "n1", "n2", "n1", "n2", "n1", "medi", "mtnk", "mtnk" }, { RescueSpawn.Location, RescueRally1.Location, RescueRally2.Location }) - Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(2)), function() - Media.DisplayMessage("Hold your fire, we're GDI! Damn, we thought we'd lost the whole company! We've got a base not far from here, we'll take you there.", "GDI Soldier", HSLColor.FromHex("F2CF74")) - MediaCA.PlaySound(MissionDir .. "/holdfire.aud", 2) + Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(2)), function() + Media.DisplayMessage(UserInterface.GetFluentMessage("hold-fire"), UserInterface.GetFluentMessage("gdi-soldier"), HSLColor.FromHex("F2CF74")) + MediaCA.PlaySound(MissionDir .. "/holdfire.aud", 2) Trigger.AfterDelay(DateTime.Seconds(12), function() GDI.MarkCompletedObjective(ObjectiveExit) diff --git a/mods/ca/missions/main-campaign/ca-prologue-03/en.ftl b/mods/ca/missions/main-campaign/ca-prologue-03/en.ftl new file mode 100644 index 0000000000..15cece9ccc --- /dev/null +++ b/mods/ca/missions/main-campaign/ca-prologue-03/en.ftl @@ -0,0 +1,27 @@ +## Briefing +briefing = GDI forces have found themselves scattered and confused in unfamiliar surroundings, confronted by hostile soldiers. + + Other GDI units have been in radio contact after finding themselves in the same situation. + + Gunfire can be heard echoing through the trees, and reports of casualties have already been heard over the radio. There is no time to lose. + + Take command, locate these other GDI troops, and find a route to safety. + +## Objectives +locate-forces = Locate all GDI forces. +find-exit = Find a safe exit route. + +## Notifications +gdi-found = GDI forces found. + +## Tips +fog-tip = When an enemy structure is destroyed under the fog of war, it won't disappear until its location is revealed again. The explosion sound and screen shake can be used to verify its destruction. + +## Dialogue +gdi-soldier = GDI Soldier +radio = Radio + +where-are-we = Commander what's going on, where the hell are we?! +pinned-down = Come in, any GDI units, hostile troops have us pinned down. +thank-god = Thank god! You found us! +hold-fire = Hold your fire, we're GDI! Damn, we thought we'd lost the whole company! We've got a base not far from here, we'll take you there. diff --git a/mods/ca/missions/main-campaign/ca-prologue-03/map.yaml b/mods/ca/missions/main-campaign/ca-prologue-03/map.yaml index 85745d79bc..b5b2beb63c 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-03/map.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-03/map.yaml @@ -1181,3 +1181,5 @@ Actors: Rules: ca|rules/custom/campaign-rules.yaml, ca|rules/custom/campaign-tooltips.yaml, ca|rules/custom/two-tone-nod.yaml, detachment-rules.yaml Weapons: ca|weapons/custom/campaign.yaml + +FluentMessages: ca|fluent/lua.ftl, en.ftl diff --git a/mods/ca/missions/main-campaign/ca-prologue-04/en.ftl b/mods/ca/missions/main-campaign/ca-prologue-04/en.ftl new file mode 100644 index 0000000000..9d3dbb0df2 --- /dev/null +++ b/mods/ca/missions/main-campaign/ca-prologue-04/en.ftl @@ -0,0 +1,16 @@ +## Briefing +briefing = Critical components and supplies are being transported to a secret staging area. Few in the Brotherhood know the full details of this operation, but all will be revealed in time. + + GDI continue to threaten vital transportation routes, and you have been chosen to secure one such route in Egypt. + + GDI is blockading the Nile river with their fleet, and this is unacceptably delaying our progress. + + Establish a foothold, then your objective is to destroy the GDI anti-aircraft systems that line the river bank. Once those are out of the way, Kane has a surprise in store for GDI's navy. + +## Objectives +destroy-aa = Destroy GDI anti-aircraft defenses. +destroy-frigates = Destroy GDI naval blockade. + +## Dialogue +kane = Kane +things-to-come = You have done well commander! Now behold; a taste of things to come. Use them wisely. diff --git a/mods/ca/missions/main-campaign/ca-prologue-04/juncture-rules.yaml b/mods/ca/missions/main-campaign/ca-prologue-04/juncture-rules.yaml index 1ca9d2f687..8aa7c5c6d3 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-04/juncture-rules.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-04/juncture-rules.yaml @@ -6,7 +6,7 @@ World: LuaScript: Scripts: campaign.lua, juncture.lua MissionData: - Briefing: Critical components and supplies are being transported to a secret staging area. Few in the Brotherhood know the full details of this operation, but all will be revealed in time.\n\nGDI continue to threaten vital transportation routes, and you have been chosen to secure one such route in Egypt.\n\nGDI is blockading the Nile river with their fleet, and this is unacceptably delaying our progress.\n\nEstablish a foothold, then your objective is to destroy the GDI anti-aircraft systems that line the river bank. Once those are out of the way, Kane has a surprise in store for GDI's navy. + Briefing: briefing -ScriptLobbyDropdown@DIFFICULTY: ScriptLobbyDropdown@PROLOGUEDIFFICULTY: ID: prologuedifficulty @@ -42,6 +42,9 @@ JJET: MECH: Inherits@CAMPAIGNDISABLED: ^Disabled +SAB: + Inherits@CAMPAIGNDISABLED: ^Disabled + STNK.Nod: Inherits@CAMPAIGNDISABLED: ^Disabled diff --git a/mods/ca/missions/main-campaign/ca-prologue-04/juncture.lua b/mods/ca/missions/main-campaign/ca-prologue-04/juncture.lua index ebb85db58e..8879145c41 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-04/juncture.lua +++ b/mods/ca/missions/main-campaign/ca-prologue-04/juncture.lua @@ -40,15 +40,15 @@ WorldLoaded = function() InitObjectives(Nod) InitGDI() - ObjectiveDestroyAA = Nod.AddObjective("Destroy GDI anti-aircraft defenses.") + ObjectiveDestroyAA = Nod.AddObjective(UserInterface.GetFluentMessage("destroy-aa")) local aaGuns = GDI.GetActorsByType("cram") Trigger.OnAllKilled(aaGuns, function() local frigates = GDI.GetActorsByType("dd2") - ObjectiveDestroyFrigates = Nod.AddObjective("Destroy GDI naval blockade.") + ObjectiveDestroyFrigates = Nod.AddObjective(UserInterface.GetFluentMessage("destroy-frigates")) Trigger.AfterDelay(DateTime.Seconds(6), function() - WarpInBanshees() + InitReinforcements() end) Trigger.OnAllKilled(frigates, function() @@ -105,7 +105,7 @@ InitGDI = function() end) end -WarpInBanshees = function() +InitReinforcements = function() if BansheesWarped then return end @@ -119,20 +119,14 @@ WarpInBanshees = function() RocksToRemove1.Destroy() RocksToRemove2.Destroy() - local hpad1 = Actor.Create("hpad.td", true, { Owner = Nod, Location = HpadSpawn1.Location }) - local hpad2 = Actor.Create("hpad.td", true, { Owner = Nod, Location = HpadSpawn2.Location }) - - Trigger.AfterDelay(10, function() - Actor.Create("scrn", true, { Owner = Nod, Location = hpad1.Location, CenterPosition = hpad1.CenterPosition, Facing = Angle.NorthEast }) - Actor.Create("scrn", true, { Owner = Nod, Location = hpad1.Location, CenterPosition = hpad2.CenterPosition, Facing = Angle.NorthEast }) - end) + WarpInBanshees() Trigger.AfterDelay(DateTime.Seconds(2), function() PlaySpeechNotificationToMissionPlayers("ReinforcementsArrived") - Notification("Reinforcements have arrived.") + Notification("reinforcements-arrived") Trigger.AfterDelay(AdjustTimeForGameSpeed(DateTime.Seconds(3)), function() - Media.DisplayMessage("You have done well commander! Now behold; a taste of things to come. Use them wisely.", "Kane", HSLColor.FromHex("FF0000")) + Media.DisplayMessage(UserInterface.GetFluentMessage("things-to-come"), UserInterface.GetFluentMessage("kane"), HSLColor.FromHex("FF0000")) MediaCA.PlaySound(MissionDir .. "/thingstocome.aud", 2) end) end) @@ -151,3 +145,15 @@ PanToBanshees = function() PanToBansheesComplete = true end end + +-- overridden in co-op version +WarpInBanshees = function() + local hpad1 = Actor.Create("hpad.td", true, { Owner = Nod, Location = HpadSpawn1.Location }) + local hpad2 = Actor.Create("hpad.td", true, { Owner = Nod, Location = HpadSpawn2.Location }) + Trigger.AfterDelay(10, function() + local banshee1 = Actor.Create("scrn", true, { Owner = Nod, Location = hpad1.Location, CenterPosition = hpad1.CenterPosition, Facing = Angle.NorthEast }) + local banshee2 = Actor.Create("scrn", true, { Owner = Nod, Location = hpad1.Location, CenterPosition = hpad2.CenterPosition, Facing = Angle.NorthEast }) + banshee1.Move(hpad1.Location) + banshee2.Move(hpad2.Location) + end) +end diff --git a/mods/ca/missions/main-campaign/ca-prologue-04/map.yaml b/mods/ca/missions/main-campaign/ca-prologue-04/map.yaml index 7295671976..a8643db111 100644 --- a/mods/ca/missions/main-campaign/ca-prologue-04/map.yaml +++ b/mods/ca/missions/main-campaign/ca-prologue-04/map.yaml @@ -778,4 +778,6 @@ Rules: ca|rules/custom/campaign-rules.yaml, ca|rules/custom/campaign-tooltips.ya Weapons: ca|weapons/custom/campaign.yaml +FluentMessages: ca|fluent/lua.ftl, en.ftl + Notifications: juncture-notifications.yaml diff --git a/mods/ca/missions/main-campaign/ca03-deliverance/deliverance.lua b/mods/ca/missions/main-campaign/ca03-deliverance/deliverance.lua index 43cca14a89..15ab11b4b9 100644 --- a/mods/ca/missions/main-campaign/ca03-deliverance/deliverance.lua +++ b/mods/ca/missions/main-campaign/ca03-deliverance/deliverance.lua @@ -349,15 +349,9 @@ GDIBaseFound = function() IsGDIBaseFound = true MediaCA.PlaySound(MissionDir .. "/r_gdibasediscovered.aud", 2) - Greece.PlayLowPowerNotification = false - TransferGDIUnits() - - Trigger.AfterDelay(DateTime.Seconds(5), function() - Greece.PlayLowPowerNotification = true - end) - InitUSSRAttacks() + TimerTicks = HoldOutTime[Difficulty] Trigger.AfterDelay(DateTime.Seconds(1), function() @@ -396,6 +390,11 @@ end -- overridden in co-op version TransferGDIUnits = function() + Greece.PlayLowPowerNotification = false + Trigger.AfterDelay(DateTime.Seconds(10), function() + Greece.PlayLowPowerNotification = true + end) + local gdiForces = GDI.GetActors() Utils.Do(gdiForces, function(a) if a.Type ~= "player" then diff --git a/mods/ca/missions/main-campaign/ca07-conspiracy/conspiracy.lua b/mods/ca/missions/main-campaign/ca07-conspiracy/conspiracy.lua index e0872b28dd..496523c467 100644 --- a/mods/ca/missions/main-campaign/ca07-conspiracy/conspiracy.lua +++ b/mods/ca/missions/main-campaign/ca07-conspiracy/conspiracy.lua @@ -355,10 +355,6 @@ AwakenSleeperCell = function() GDIHarvester.Owner = Nod end - Trigger.AfterDelay(1, function() - Actor.Create("QueueUpdaterDummy", true, { Owner = Nod }) - end) - if ObjectiveRescueResearchers == nil then ObjectiveRescueResearchers = Nod.AddObjective("Locate and rescue Nod researchers.") end @@ -389,6 +385,10 @@ TransferLegionForces = function() self.Owner = Nod end end) + + Trigger.AfterDelay(1, function() + Actor.Create("QueueUpdaterDummy", true, { Owner = Nod }) + end) end InitEvacSite = function() diff --git a/mods/ca/missions/main-campaign/ca14-treachery/treachery.lua b/mods/ca/missions/main-campaign/ca14-treachery/treachery.lua index 97e0f87182..5a05840dd6 100644 --- a/mods/ca/missions/main-campaign/ca14-treachery/treachery.lua +++ b/mods/ca/missions/main-campaign/ca14-treachery/treachery.lua @@ -327,10 +327,6 @@ AbandonedBaseDiscovered = function() InitAlliedAttacks() - Trigger.AfterDelay(1, function() - Actor.Create("QueueUpdaterDummy", true, { Owner = USSR }) - end) - Trigger.AfterDelay(ReinforcementsDelay[Difficulty], function() PlaySpeechNotificationToMissionPlayers("ReinforcementsArrived") Beacon.New(USSR, ReinforcementsDestination.CenterPosition) @@ -355,6 +351,10 @@ TransferAbandonedBase = function() Utils.Do(baseBuildings, function(a) a.Owner = USSR end) + + Trigger.AfterDelay(1, function() + Actor.Create("QueueUpdaterDummy", true, { Owner = USSR }) + end) end TraitorHQKilledOrCaptured = function() diff --git a/mods/ca/missions/main-campaign/ca21-incapacitation/incapacitation.lua b/mods/ca/missions/main-campaign/ca21-incapacitation/incapacitation.lua index a2e5b4f461..7d6248866d 100644 --- a/mods/ca/missions/main-campaign/ca21-incapacitation/incapacitation.lua +++ b/mods/ca/missions/main-campaign/ca21-incapacitation/incapacitation.lua @@ -159,7 +159,7 @@ WorldLoaded = function() end) Trigger.AfterDelay(DateTime.Seconds(5), function() - SpawnLeechers() + SpawnInitialLeechers() Trigger.AfterDelay(DateTime.Seconds(5), function() Tip("Leechers can be deployed using [" .. UtilsCA.Hotkey("Deploy") .. "] to temporarily transform into balls of bio-matter which heal nearby allies.") @@ -274,6 +274,7 @@ end OncePerFiveSecondChecks = function() if DateTime.GameTime > 1 and DateTime.GameTime % 125 == 0 then UpdatePlayerBaseLocations() + LeecherRespawnCheck() end end @@ -395,7 +396,7 @@ UpdateObjective = function() UserInterface.SetMissionText(#activeAA .. " active anti-aircraft defenses remaining. " .. #aircraftStructuresRemaining .. " aircraft structures remaining.", HSLColor.Yellow) end -SpawnLeechers = function() +SpawnInitialLeechers = function() local wormhole = Actor.Create("wormhole", true, { Owner = Scrin, Location = LeecherSpawn.Location }) Beacon.New(Scrin, LeecherSpawn.CenterPosition, DateTime.Seconds(20)) @@ -411,51 +412,71 @@ SpawnLeechers = function() Utils.Do(leechers, function(leecher) leecher.GrantCondition("difficulty-" .. Difficulty) leecher.Scatter() - LeecherDeathTrigger(leecher) end) + SetupLeecherRespawning() + Trigger.AfterDelay(DateTime.Seconds(5), function() wormhole.Kill() end) end) end -LeecherDeathTrigger = function(a) - if RespawnEnabled then - Trigger.OnKilled(a, function(self, killer) - Trigger.AfterDelay(1, function() - local orbs = Utils.Where(self.Owner.GetActorsByType("lchr.orb"), function(a) - return not OrbsRespawning[tostring(a)] - end) +SetupLeecherRespawning = function() + if not RespawnEnabled then + return + end - Utils.Do(orbs, function(orb) - OrbsRespawning[tostring(orb)] = true - - Trigger.OnKilled(orb, function(self, killer) - local spawnCell = CPos.New(LeecherSpawn.Location.X + Utils.RandomInteger(-1, 1), LeecherSpawn.Location.Y + Utils.RandomInteger(-1, 1)) - Notification("Leecher arriving in 20 seconds.") - - Trigger.AfterDelay(DateTime.Seconds(20), function() - local wormhole = Actor.Create("wormhole", true, { Owner = Scrin, Location = spawnCell }) - - Trigger.AfterDelay(DateTime.Seconds(1), function() - local leecher = Reinforcements.Reinforce(self.Owner, { "lchr" }, { spawnCell }, 1)[1] - leecher.Scatter() - Beacon.New(self.Owner, leecher.CenterPosition) - Media.PlaySpeechNotification(self.Owner, "ReinforcementsArrived") - leecher.GrantCondition("difficulty-" .. Difficulty) - LeecherDeathTrigger(leecher) - end) - - Trigger.AfterDelay(DateTime.Seconds(5), function() - wormhole.Kill() - end) - end) - end) - end) - end) + LeecherStatuses = {} + + Trigger.AfterDelay(10, function() + local leechers = GetMissionPlayersActorsByType("lchr") + Utils.Do(leechers, function(leecher) + if not LeecherStatuses[leecher.Owner.InternalName] then + LeecherStatuses[leecher.Owner.InternalName] = { Owner = leecher.Owner, IsRespawning = false, Count = 0 } + end + LeecherStatuses[leecher.Owner.InternalName].Count = LeecherStatuses[leecher.Owner.InternalName].Count + 1 end) + end) +end + +LeecherRespawnCheck = function() + if not RespawnEnabled or not LeecherStatuses then + return end + Utils.Do(LeecherStatuses, function(status) + if not status.IsRespawning then + local leecherCount = #status.Owner.GetActorsByTypes({ "lchr", "lchr.orb" }) + if leecherCount < status.Count then + status.IsRespawning = true + RespawnLeecher(status) + end + end + end) +end + +RespawnLeecher = function(status) + Notification("Leecher arriving in 20 seconds.") + + local player = status.Owner + local spawnCell = CPos.New(LeecherSpawn.Location.X + Utils.RandomInteger(-1, 1), LeecherSpawn.Location.Y + Utils.RandomInteger(-1, 1)) + + Trigger.AfterDelay(DateTime.Seconds(20), function() + local wormhole = Actor.Create("wormhole", true, { Owner = player, Location = spawnCell }) + + Trigger.AfterDelay(DateTime.Seconds(1), function() + local leecher = Reinforcements.Reinforce(player, { "lchr" }, { spawnCell }, 1)[1] + leecher.Scatter() + Beacon.New(player, leecher.CenterPosition) + Media.PlaySpeechNotification(player, "ReinforcementsArrived") + leecher.GrantCondition("difficulty-" .. Difficulty) + status.IsRespawning = false + end) + + Trigger.AfterDelay(DateTime.Seconds(5), function() + wormhole.Kill() + end) + end) end IntruderDeathTrigger = function(a) diff --git a/mods/ca/missions/main-campaign/ca27-emancipation/emancipation.lua b/mods/ca/missions/main-campaign/ca27-emancipation/emancipation.lua index f36c305686..b604c3956f 100644 --- a/mods/ca/missions/main-campaign/ca27-emancipation/emancipation.lua +++ b/mods/ca/missions/main-campaign/ca27-emancipation/emancipation.lua @@ -202,10 +202,6 @@ WorldLoaded = function() UpdateObjectiveText() FreeSlaves(slaves) - Trigger.AfterDelay(1, function() - Actor.Create("QueueUpdaterDummy", true, { Owner = GDI }) - end) - if m == Mastermind4 then Utils.Do(MissionPlayers, function(p) Actor.Create("amcv.enabled", true, { Owner = p }) @@ -374,4 +370,8 @@ FreeSlaves = function(slaves) end) end end) + + Trigger.AfterDelay(1, function() + Actor.Create("QueueUpdaterDummy", true, { Owner = GDI }) + end) end diff --git a/mods/ca/missions/main-campaign/ca28-duality/duality-rules.yaml b/mods/ca/missions/main-campaign/ca28-duality/duality-rules.yaml index d102606dcc..68d8c843d4 100644 --- a/mods/ca/missions/main-campaign/ca28-duality/duality-rules.yaml +++ b/mods/ca/missions/main-campaign/ca28-duality/duality-rules.yaml @@ -100,6 +100,7 @@ PDGY: Condition: activated Armament@PRIMARY: Weapon: ProdigyZap + -MindController: WORMHOLE: -Targetable: diff --git a/mods/ca/missions/main-campaign/ca36-reckoning/reckoning-rules.yaml b/mods/ca/missions/main-campaign/ca36-reckoning/reckoning-rules.yaml index 8ecc5bb335..5b1339614a 100644 --- a/mods/ca/missions/main-campaign/ca36-reckoning/reckoning-rules.yaml +++ b/mods/ca/missions/main-campaign/ca36-reckoning/reckoning-rules.yaml @@ -119,6 +119,126 @@ hstk.upgrade: Buildable: Prerequisites: ~player.nod, tmpl +# Enable GDI subfaction specific tech + +STWR: + Buildable: + Prerequisites: vehicles.any, ~structures.gdi + +TITN: + Buildable: + Prerequisites: gtek, ~!railgun.upgrade, ~vehicles.gdi + +TITN.RAIL: + Buildable: + Prerequisites: gtek, ~railgun.upgrade, ~vehicles.gdi + +JUGG: + Buildable: + Prerequisites: upgc, ~vehicles.gdi + +HSAM: + Buildable: + Prerequisites: anyradar, ~vehicles.gdi + +MDRN: + Buildable: + Prerequisites: anyradar, ~vehicles.gdi + +GDRN: + Buildable: + Prerequisites: ~vehicles.gdi, ~!tow.upgrade + +GDRN.TOW: + Buildable: + Prerequisites: ~vehicles.gdi, ~tow.upgrade + +WOLV: + Buildable: + Prerequisites: gtek, ~vehicles.gdi + +XO: + Buildable: + Prerequisites: gtek, ~vehicles.gdi + +PBUL: + Buildable: + Prerequisites: gtek, ~vehicles.gdi + +AURO: + Buildable: + Prerequisites: afld.gdi, gtek, ~aircraft.gdi + +gyro.upgrade: + Buildable: + Prerequisites: gtek, ~player.gdi + +abur.upgrade: + Buildable: + Prerequisites: gtek, ~player.gdi + +bdrone.upgrade: + Buildable: + Prerequisites: gtek, ~player.gdi + +railgun.upgrade: + Buildable: + Prerequisites: upgc, ~player.gdi + +ionmam.upgrade: + Buildable: + Prerequisites: upgc, !mdrone.upgrade, !hovermam.upgrade, ~player.gdi + TooltipExtras: + Attributes: \n(!) Only ONE Mammoth Tank upgrade can be chosen + +hovermam.upgrade: + Buildable: + Prerequisites: upgc, !ionmam.upgrade, !mdrone.upgrade, ~player.gdi + TooltipExtras: + Attributes: \n(!) Only ONE Mammoth Tank upgrade can be chosen + +mdrone.upgrade: + Buildable: + Prerequisites: upgc, !ionmam.upgrade, !hovermam.upgrade, ~player.gdi + TooltipExtras: + Attributes: \n(!) Only ONE Mammoth Tank upgrade can be chosen + +# Enable Scrin subfaction specific tech + +STCR: + Buildable: + Prerequisites: anyradar, ~vehicles.scrin + +LCHR: + Buildable: + Prerequisites: anyradar, ~vehicles.scrin + +RUIN: + Buildable: + Prerequisites: scrt, ~vehicles.scrin + +ATMZ: + Buildable: + Prerequisites: scrt, ~vehicles.scrin + +stellar.upgrade: + Buildable: + Prerequisites: scrt, ~player.scrin + +coalescence.upgrade: + Buildable: + Prerequisites: scrt, ~player.scrin + +RTPD: + Buildable: + Prerequisites: scrt, ~vehicles.scrin + +ENRV: + Buildable: + Prerequisites: scrt, ~aircraft.scrin + +# Misc + NERV: DetonateWeaponPower@BUZZERSWARMAI: Prerequisites: nerv diff --git a/mods/ca/missions/main-campaign/ca36-reckoning/reckoning.lua b/mods/ca/missions/main-campaign/ca36-reckoning/reckoning.lua index 7a0c80de66..7bc5dfd6ff 100644 --- a/mods/ca/missions/main-campaign/ca36-reckoning/reckoning.lua +++ b/mods/ca/missions/main-campaign/ca36-reckoning/reckoning.lua @@ -384,15 +384,15 @@ end SendNextExterminator = function() if NextExterminatorIndex <= ExterminatorAttackCount[Difficulty] and not Victory then - local exterminator + local exterminatorLocations if Exterminators[NextExterminatorIndex] ~= nil then - exterminator = Exterminators[NextExterminatorIndex] + exterminatorLocations = Exterminators[NextExterminatorIndex] else - exterminator = { SpawnLocation = Utils.Random({ ExterminatorSpawnWest.Location, ExterminatorSpawnEast.Location }) } + exterminatorLocations = { SpawnLocation = Utils.Random({ ExterminatorSpawnWest.Location, ExterminatorSpawnEast.Location }) } end - local wormhole = Actor.Create("wormholelg", true, { Owner = Scrin, Location = exterminator.SpawnLocation }) + local wormhole = Actor.Create("wormholelg", true, { Owner = Scrin, Location = exterminatorLocations.SpawnLocation }) Trigger.AfterDelay(DateTime.Seconds(2), function() MediaCA.PlaySound("etpd-aggro.aud", 2) @@ -406,9 +406,9 @@ SendNextExterminator = function() Notification("Exterminator Tripod detected.") end - local reinforcements = Reinforcements.Reinforce(Scrin, { "etpd" }, { exterminator.SpawnLocation }, 10, function(a) - if exterminator.Path ~= nil then - local path = exterminator.Path + local exterminator = Reinforcements.Reinforce(Scrin, { "etpd" }, { exterminatorLocations.SpawnLocation }, 10, function(a) + if exterminatorLocations.Path ~= nil then + local path = exterminatorLocations.Path a.Patrol(path) Trigger.OnIdle(a, function(self) @@ -433,7 +433,33 @@ SendNextExterminator = function() Trigger.AfterDelay(DateTime.Seconds(10), function() wormhole.Kill() end) - end) + end)[1] + + if IsVeryHardOrAbove() then + local startGuardCount + local maxGuardCount + + if Difficulty == "brutal" then + startGuardCount = 4 + maxGuardCount = 10 + else + startGuardCount = 2 + maxGuardCount = 8 + end + + local guardsList = {} + + -- starting with startGuardCount, add a guard every 10 minutes + local numGuards = math.max(startGuardCount, math.min(maxGuardCount, startGuardCount + math.floor((DateTime.GameTime - DateTime.Minutes(10)) / DateTime.Minutes(10)))) + for i = 1, numGuards do + table.insert(guardsList, "gunw") + end + + local guards = Reinforcements.Reinforce(Scrin, guardsList, { exterminatorLocations.SpawnLocation }, 15, function(g) + FollowActor(g, exterminator) + IdleHunt(g) + end) + end NextExterminatorIndex = NextExterminatorIndex + 1 diff --git a/mods/ca/missions/main-campaign/ca45-multipolarity/multipolarity.lua b/mods/ca/missions/main-campaign/ca45-multipolarity/multipolarity.lua index a7a15bde0a..abbfee094c 100644 --- a/mods/ca/missions/main-campaign/ca45-multipolarity/multipolarity.lua +++ b/mods/ca/missions/main-campaign/ca45-multipolarity/multipolarity.lua @@ -274,6 +274,22 @@ WorldLoaded = function() DoCommandoDrop() end + Trigger.OnAllKilledOrCaptured({ SovietFactory, SovietBarracks }, function() + SovietProductionDestroyed = true + if HawthorneClaimedSovietBase then + Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) + Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) + end + end) + + Trigger.OnAllKilledOrCaptured({ NodAirstrip, NodHand }, function() + NodProductionDestroyed = true + if HawthorneClaimedNodBase then + Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) + Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) + end + end) + AfterWorldLoaded() end @@ -407,7 +423,7 @@ FlipAlliedBase = function() end FlipNodBase = function() - if NodBaseFlipped or SovietBaseFlipped then + if NodBaseFlipped or SovietBaseFlipped or HawthorneClaimedNodBase then return end @@ -433,7 +449,7 @@ FlipNodBase = function() end FlipSovietBase = function() - if SovietBaseFlipped or NodBaseFlipped then + if SovietBaseFlipped or NodBaseFlipped or HawthorneClaimedSovietBase then return end @@ -459,12 +475,16 @@ FlipSovietBase = function() end HawthorneClaimRandomBase = function() - if HawthorneClaimedSovietBase and HawthorneClaimedNodBase then + -- no bases left to claim + if (HawthorneClaimedSovietBase or SovietBaseFlipped) and (HawthorneClaimedNodBase or NodBaseFlipped) then return - elseif HawthorneClaimedSovietBase then + -- soviet base claimed, claim nod base + elseif HawthorneClaimedSovietBase or SovietBaseFlipped then HawthorneClaimNodBase() - elseif HawthorneClaimedNodBase then + -- nod base claimed, claim soviet base + elseif HawthorneClaimedNodBase or NodBaseFlipped then HawthorneClaimSovietBase() + -- both available, randomly choose one else local choice = Utils.Random({ "Soviet", "Nod" }) if choice == "Soviet" then @@ -476,7 +496,7 @@ HawthorneClaimRandomBase = function() end HawthorneClaimSovietBase = function() - if HawthorneClaimedSovietBase then + if HawthorneClaimedSovietBase or SovietBaseFlipped then return end @@ -524,17 +544,14 @@ HawthorneClaimSovietBase = function() AutoRepairBuilding(sam, GDI) AutoRebuildBuilding(sam, GDI, 10) - Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) - Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) - - Trigger.OnAllKilledOrCaptured({ SovietFactory, SovietBarracks }, function() - Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) - Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) - end) + if not SovietProductionDestroyed then + Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) + Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) + end end HawthorneClaimNodBase = function() - if HawthorneClaimedNodBase then + if HawthorneClaimedNodBase or NodBaseFlipped then return end @@ -548,8 +565,6 @@ HawthorneClaimNodBase = function() MediaCA.PlaySound(MissionDir .. "/hth_nodequipauto.aud", 2) end - InitAttackSquad(Squads.Nod, GDI) - local nodBaseActors = Utils.Where(Nod.GetActors(), function(a) return not a.IsDead and a.Type ~= "player" end) @@ -584,13 +599,10 @@ HawthorneClaimNodBase = function() AutoRepairBuilding(nsam, GDI) AutoRebuildBuilding(nsam, GDI, 10) - Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) - Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) - - Trigger.OnAllKilledOrCaptured({ NodAirstrip, NodHand }, function() - Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) - Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 20, Max = 40 }) - end) + if not NodProductionDestroyed then + Squads.Main.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) + Squads.Secondary.AttackValuePerSecond = AdjustAttackValuesForDifficulty({ Min = 15, Max = 30 }) + end end DoDisruptorDrop = function() diff --git a/mods/ca/missions/main-campaign/ca47-ad-nihilum/ad-nihilum.lua b/mods/ca/missions/main-campaign/ca47-ad-nihilum/ad-nihilum.lua index 68c60ad415..de2a438fa6 100644 --- a/mods/ca/missions/main-campaign/ca47-ad-nihilum/ad-nihilum.lua +++ b/mods/ca/missions/main-campaign/ca47-ad-nihilum/ad-nihilum.lua @@ -354,17 +354,28 @@ SendNextVoidEngine = function() end) end)[1] - if Difficulty == "brutal" and DateTime.GameTime > DateTime.Minutes(30) then + if IsVeryHardOrAbove() then + local startGuardCount + local maxGuardCount + + if Difficulty == "brutal" then + startGuardCount = 4 + maxGuardCount = 10 + else + startGuardCount = 2 + maxGuardCount = 8 + end + local guardsList = {} - -- starting with 4, add a guard for every 10 minutes past 30 minutes, up to a max of 10 guards - local numGuards = math.min(10, 4 + math.floor((DateTime.GameTime - DateTime.Minutes(30)) / DateTime.Minutes(10))) + -- starting with startGuardCount, add a guard every 10 minutes + local numGuards = math.max(startGuardCount, math.min(maxGuardCount, startGuardCount + math.floor((DateTime.GameTime - DateTime.Minutes(10)) / DateTime.Minutes(10)))) for i = 1, numGuards do table.insert(guardsList, "gunw") end local guards = Reinforcements.Reinforce(MaleficScrin, guardsList, { spawnLoc }, 250, function(g) - g.Guard(voidEngine) + FollowActor(g, voidEngine) IdleHunt(g) end) end diff --git a/mods/ca/missions/main-campaign/ca48-banishment/banishment.lua b/mods/ca/missions/main-campaign/ca48-banishment/banishment.lua index 95a3a67b44..cac308ba60 100644 --- a/mods/ca/missions/main-campaign/ca48-banishment/banishment.lua +++ b/mods/ca/missions/main-campaign/ca48-banishment/banishment.lua @@ -129,7 +129,7 @@ WorldLoaded = function() end) local nerveCenters = MaleficScrin.GetActorsByType("nerv") - Trigger.OnAllKilled(nerveCenters, function() + Trigger.OnAllKilledOrCaptured(nerveCenters, function() Greece.MarkCompletedObjective(ObjectiveDestroyScrinBases) end) @@ -488,6 +488,10 @@ TransferBaseActors = function(base) Utils.Do(baseActors, function(a) a.Owner = Greece end) + + Trigger.AfterDelay(1, function() + Actor.Create("QueueUpdaterDummy", true, { Owner = Greece }) + end) end -- overridden in co-op version diff --git a/mods/ca/mod.yaml b/mods/ca/mod.yaml index a2011489f2..3db1e68617 100644 --- a/mods/ca/mod.yaml +++ b/mods/ca/mod.yaml @@ -100,9 +100,11 @@ FluentMessages: common|fluent/rules.ftl ca|fluent/ca.ftl ca|fluent/chrome.ftl + ca|fluent/coop.ftl ca|fluent/encyclopedia.ftl ca|fluent/factions.ftl ca|fluent/hotkeys.ftl + ca|fluent/lua.ftl ca|fluent/options.ftl ca|fluent/powers.ftl ca|fluent/rules.ftl diff --git a/mods/ca/rules/aircraft.yaml b/mods/ca/rules/aircraft.yaml index 03ea3608fa..0727984284 100644 --- a/mods/ca/rules/aircraft.yaml +++ b/mods/ca/rules/aircraft.yaml @@ -482,7 +482,9 @@ SUK.UPG: Selectable: Class: suk -ReplacedInQueue: - -Encyclopedia: + EncyclopediaExtras: + Name: Seismic Sukhoi + VariantOf: SUK YAK: Inherits: ^Plane @@ -1706,7 +1708,9 @@ A10.SW: RequiresCondition: !ammo && ammo2 ValidTargets: Air, AirSmall InvalidTargets: NoAutoTarget - -Encyclopedia: + EncyclopediaExtras: + Name: Sidewinder Warthog + VariantOf: A10 A10.GAU: Inherits: A10 @@ -1783,7 +1787,9 @@ A10.GAU: AmmoPools: secondary FullSequence: pip-red WithMuzzleOverlay: - -Encyclopedia: + EncyclopediaExtras: + Name: Avenger Warthog + VariantOf: A10 A10.bomber: Inherits: ^NeutralPlane @@ -4321,7 +4327,7 @@ PHAN: EjectOnDeath: ChuteSound: gejecta.aud Encyclopedia: - Category: Nod/Aircraft + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Allied Helipad. @@ -4444,7 +4450,7 @@ KAMV: EjectOnDeath: ChuteSound: gejecta.aud Encyclopedia: - Category: Nod/Aircraft + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Soviet Airfield. @@ -4575,7 +4581,7 @@ SHDE: EjectOnDeath: ChuteSound: gejecta.aud Encyclopedia: - Category: Nod/Aircraft + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from GDI Helipad. @@ -4728,7 +4734,7 @@ VERT: EjectOnDeath: ChuteSound: gejecta.aud Encyclopedia: - Category: Nod/Aircraft + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Nod Helipad. @@ -4810,7 +4816,7 @@ MCOR: EjectOnDeath: ChuteSound: gejecta.aud Encyclopedia: - Category: Nod/Aircraft + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Scrin Gravity Stabilizer. diff --git a/mods/ca/rules/custom/campaign-rules.yaml b/mods/ca/rules/custom/campaign-rules.yaml index 416f43eb1f..4e7dd1a755 100644 --- a/mods/ca/rules/custom/campaign-rules.yaml +++ b/mods/ca/rules/custom/campaign-rules.yaml @@ -673,6 +673,8 @@ ETPD: FireDelay: 22 MuzzleSequence: muzzle MuzzlePalette: scrin + AttackTurreted: + PauseOnCondition: empdisable || being-warped Health: HP: 900000 Shielded: @@ -707,8 +709,14 @@ ETPD: RequiresCondition: shields-up Targetable@DriverKillImmune: TargetTypes: DriverKillImmune - Targetable@BlindImmune: - TargetTypes: BlindImmune + RevealsShroudMultiplier@Blinded: + Modifier: 80 + RangeMultiplier@Blinded: + Modifier: 80 + RequiresCondition: blinded + FirepowerMultiplier@Blinded: + Modifier: 90 + RequiresCondition: blinded SpeedMultiplier@TEMPORAL: Modifier: 75 ReloadDelayMultiplier@TEMPORAL: diff --git a/mods/ca/rules/custom/disable-coalitions.yaml b/mods/ca/rules/custom/disable-coalitions.yaml index a62ab5c846..a3d56366c2 100644 --- a/mods/ca/rules/custom/disable-coalitions.yaml +++ b/mods/ca/rules/custom/disable-coalitions.yaml @@ -37,3 +37,7 @@ PMAK: ENFO: Buildable: Prerequisites: ~tent, atek, ~techlevel.high + +OREP: + Buildable: + Prerequisites: ~structures.allies, atek diff --git a/mods/ca/rules/encyclopedia.yaml b/mods/ca/rules/encyclopedia.yaml index 40aaec3766..183885aebf 100644 --- a/mods/ca/rules/encyclopedia.yaml +++ b/mods/ca/rules/encyclopedia.yaml @@ -38,6 +38,2249 @@ encyclopedia.tips.general: QuantizeFacingsFromSequence: Sequence: stand +^SupportPowerPreview: + Inherits: ^InvisibleDummy + Buildable: + BuildDurationModifier: 100 + RenderSprites: + Image: icon + Encyclopedia: + EncyclopediaExtras: + HideNotProducible: true + +# Allied Support Powers + +encyclopedia.power.veilofwar: + Inherits: ^SupportPowerPreview + Buildable: + Icon: veilofwar + IconPalette: chrometd + Prerequisites: anyradar + BuildDuration: 7500 + Tooltip: + Name: Veil of War + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Creates an expanding area of shroud, reducing the vision and weapon range of enemy units and defenses. + Subfaction: england + +encyclopedia.power.clustermines: + Inherits: ^SupportPowerPreview + Buildable: + Icon: cmines + Prerequisites: anyradar + BuildDuration: 6750 + Tooltip: + Name: Cluster Mines + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Sends a cargo plane to drop a minefield at the target location. + Subfaction: france + +encyclopedia.power.strafe: + Inherits: ^SupportPowerPreview + Buildable: + Icon: strafe + Prerequisites: hpad + BuildDuration: 7500 + Tooltip: + Name: Strafing Run + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Calls in P51 ground attack planes to perform strafing runs on the target. + Subfaction: usa + +encyclopedia.power.cryostorm: + Inherits: ^SupportPowerPreview + Buildable: + Icon: cryostorm + Prerequisites: alhq + BuildDuration: 8250 + Tooltip: + Name: Cryostorm + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Creates a turbulent area of extreme cold, reducing movement speed and increasing damage taken. + AdditionalInfo: Requires Sweden Coalition. + +encyclopedia.power.heliosbomb: + Inherits: ^SupportPowerPreview + Buildable: + Icon: heliosbomb + IconPalette: caneon + Prerequisites: alhq + BuildDuration: 10500 + Tooltip: + Name: Helios Bomb + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Calls in a bomber which drops a Helios bomb, blinding units in a large area. + AdditionalInfo: Requires Greece Coalition. + +encyclopedia.power.bsky: + Inherits: ^SupportPowerPreview + Buildable: + Icon: bsky + Prerequisites: alhq + BuildDuration: 9000 + Tooltip: + Name: Black Sky Strike + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Fires long range guided missiles at multiple ground targets prioritized by value. Tracking can be lost if targets move far enough from their initial location. + AdditionalInfo: Requires Korea Coalition. + +encyclopedia.power.spysatellite: + Inherits: ^SupportPowerPreview + Buildable: + Icon: gps + Prerequisites: atek + BuildDuration: 5250 + Tooltip: + Name: Spy Satellite + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Periodically reveals the entire map for a short time (activated automatically). + +encyclopedia.power.tempinc: + Inherits: ^SupportPowerPreview + Buildable: + Icon: tempinc + Prerequisites: pdox + BuildDuration: 7500 + Tooltip: + Name: Temporal Incursion + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Summons reinforcements from the future. Units return to their origin time after a short time. + Subfaction: germany + +encyclopedia.power.timewarp: + Inherits: ^SupportPowerPreview + Buildable: + Icon: timewarp + Prerequisites: pdox + BuildDuration: 4500 + Tooltip: + Name: Time Warp + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Disrupts time and space at the target location. Affected units & structures are frozen in time, unable to act, but immune to damage. + Subfaction: germany + +encyclopedia.power.chronoshift: + Inherits: ^SupportPowerPreview + Buildable: + Icon: chrono + Prerequisites: pdox + BuildDuration: 4500 + Tooltip: + Name: Chronoshift + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Teleports up to 9 selected vehicles to a targeted location, returning them to their original location after a short time.\n\n• If killed, units are returned with 20% health\n• Units with larger health pools take additional damage\n• Passengers cannot be unloaded\n• Enemy units are teleported for reduced duration + +encyclopedia.power.lightningstorm: + Inherits: ^SupportPowerPreview + Buildable: + Icon: storm + Prerequisites: weat + BuildDuration: 13500 + Tooltip: + Name: Lightning Storm + Encyclopedia: + Category: Allies/Support Powers + EncyclopediaExtras: + Description: Initiate a Lightning Storm which deals heavy damage over a large area. + +# Soviet Support Powers + +encyclopedia.power.spyplane: + Inherits: ^SupportPowerPreview + Buildable: + Icon: spyplane + Prerequisites: anyradar + BuildDuration: 3750 + Tooltip: + Name: Spy Plane + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Dispatches a spy plane that reveals the target location for a limited time. + +encyclopedia.power.paratroopers: + Inherits: ^SupportPowerPreview + Buildable: + Icon: paratroopers + Prerequisites: anyradar + BuildDuration: 7500 + Tooltip: + Name: Paratroopers + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Dispatches a Halo transport to drop a squad of infantry anywhere on the map. + +encyclopedia.power.stormtroopers: + Inherits: ^SupportPowerPreview + Buildable: + Icon: stormtroopers + Prerequisites: anyradar + BuildDuration: 9000 + Tooltip: + Name: Storm-troopers + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Dispatches a Halo transport to drop a squad of Shock Troopers anywhere on the map. + Subfaction: russia + +encyclopedia.power.parabombs: + Inherits: ^SupportPowerPreview + Buildable: + Icon: parabombs + Prerequisites: afld + BuildDuration: 7500 + Tooltip: + Name: Parabombs + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Calls in a Badger bomber which drops parachuted bombs on your target. + +encyclopedia.power.carpetbomb: + Inherits: ^SupportPowerPreview + Buildable: + Icon: carpetbomb + Prerequisites: afld + BuildDuration: 10500 + Tooltip: + Name: Carpet Bomb + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Calls in a squad of Badgers which drop bombs on your target. + Subfaction: ukraine + +encyclopedia.power.atomicbombair: + Inherits: ^SupportPowerPreview + Buildable: + Icon: abombair + Prerequisites: afld + BuildDuration: 10500 + Tooltip: + Name: Atomic Bomb (Air) + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Calls in a Badger bomber which drops an atom bomb on your target. + Subfaction: iraq + +encyclopedia.power.mutabomb: + Inherits: ^SupportPowerPreview + Buildable: + Icon: mutabomb + IconPalette: chromes + Prerequisites: anyradar + BuildDuration: 6750 + Tooltip: + Name: Muta Bomb + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Calls in a Badger bomber which drops a genetic mutation bomb on your target, transforming infantry into Brutes under your control. + Subfaction: yuri + +encyclopedia.power.chaosbombs: + Inherits: ^SupportPowerPreview + Buildable: + Icon: chaosbombs + Prerequisites: afld + BuildDuration: 8250 + Tooltip: + Name: Chaos Bombs + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Calls in a Badger bomber which drops parachuted chaos bombs on your target. + Subfaction: yuri + +encyclopedia.power.atomicammo: + Inherits: ^SupportPowerPreview + Buildable: + Icon: atomicammo + Prerequisites: npwr + BuildDuration: 4500 + Tooltip: + Name: Atomic Shells + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Grants tanks a limited supply of atomic shells (also for a limited duration).\n\nAffects Soviet tanks only (Heavy/Rhino Tank, Lasher/Thrasher Tank, Siege Tank, Mammoth Tank, Overlord Tank, Apocalypse Tank, Nuke Cannon) + +encyclopedia.power.heroes: + Inherits: ^SupportPowerPreview + Buildable: + Icon: heroes + BuildDuration: 6000 + Tooltip: + Name: Heroes of the Union + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Targeted basic infantry units become Heroes of the Union, significantly increasing their damage, speed, range and resilience.\n\nCan affect Rifle Infantry, Rocket Soldiers, Grenadiers and Flamethrowers. + AdditionalInfo: Requires Infantry Doctrine. + +encyclopedia.power.tankdrop: + Inherits: ^SupportPowerPreview + Buildable: + Icon: tankdrop + BuildDuration: 13500 + Tooltip: + Name: Tank Drop + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Dispatches cargo planes to air drop tanks at the target location. + AdditionalInfo: Requires Armor Doctrine. + +encyclopedia.power.killzone: + Inherits: ^SupportPowerPreview + Buildable: + Icon: killzone + BuildDuration: 4500 + Tooltip: + Name: Kill Zone + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Calls in a Spy Plane which marks a target area. Any enemy units within it take increased damage. + AdditionalInfo: Requires Artillery Doctrine. + +encyclopedia.power.ironcurtain: + Inherits: ^SupportPowerPreview + Buildable: + Icon: invuln + Prerequisites: iron + BuildDuration: 4500 + Tooltip: + Name: Iron Curtain + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Makes selected friendly vehicles temporarily invulnerable. + +encyclopedia.power.atombomb: + Inherits: ^SupportPowerPreview + Buildable: + Icon: abomb + Prerequisites: mslo + BuildDuration: 13500 + Tooltip: + Name: Atom Bomb + Encyclopedia: + Category: Soviets/Support Powers + EncyclopediaExtras: + Description: Launches a devastating atomic bomb at the target location, dealing heavy damage over a large area. + +# GDI Support Powers + +encyclopedia.power.recondrone: + Inherits: ^SupportPowerPreview + Buildable: + Icon: uavicon + Prerequisites: anyradar + BuildDuration: 3750 + Tooltip: + Name: Recon Drone + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: A drone flies across the map, revealing the area as it passes.\n\nDetects cloaked units. + +encyclopedia.power.xodrop: + Inherits: ^SupportPowerPreview + Buildable: + Icon: xodrop + Prerequisites: anyradar + BuildDuration: 9750 + Tooltip: + Name: X-O Drop + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: An Orca transport drops a squad of X-O Powersuits at the target location. + Subfaction: talon + +encyclopedia.power.droppods: + Inherits: ^SupportPowerPreview + Buildable: + Icon: droppods + IconPalette: chrometd + Prerequisites: anyradar + BuildDuration: 4500 + Tooltip: + Name: Drop Pods + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Instantly deploys a small team of elite soldiers at the target location via orbital drop pods. + Subfaction: zocom + +encyclopedia.power.reinforcements: + Inherits: ^SupportPowerPreview + Buildable: + Icon: orcaca + IconPalette: chrometd + Prerequisites: anyradar + BuildDuration: 6000 + Tooltip: + Name: Reinforcements + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: An Orca Carryall drops an APC containing an infantry squad at the target location. + Subfaction: eagle + +encyclopedia.power.interceptors: + Inherits: ^SupportPowerPreview + Buildable: + Icon: airsupport + Prerequisites: afld.gdi + BuildDuration: 10500 + Tooltip: + Name: Interceptors + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: A squadron of Interceptors provides air cover over the target area, engaging any enemy aircraft within range. + +encyclopedia.power.naniterepair: + Inherits: ^SupportPowerPreview + Buildable: + Icon: nrepair + Prerequisites: gtek + BuildDuration: 6750 + Tooltip: + Name: Nanite Repair + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Repairs selected damaged vehicles over time. + Subfaction: arc + +encyclopedia.power.surgicalstrike: + Inherits: ^SupportPowerPreview + Buildable: + Icon: surgicalstrike + IconPalette: chrometd + Prerequisites: eye + BuildDuration: 6000 + Tooltip: + Name: Surgical Strike + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Initiate an precision Ion Cannon strike which deals instant damage to a small area. + Subfaction: zocom + +encyclopedia.power.firestorm: + Inherits: ^SupportPowerPreview + Buildable: + Icon: fstorm + IconPalette: chrometd + Prerequisites: upgc + BuildDuration: 7500 + Tooltip: + Name: Firestorm + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Fires a barrage of rockets at the target area. + AdditionalInfo: Requires Bombardment Strategy. + +encyclopedia.power.advancedradar: + Inherits: ^SupportPowerPreview + Buildable: + Icon: arscan + IconPalette: chrometd + Prerequisites: upgc + BuildDuration: 4500 + Tooltip: + Name: Advanced Radar + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Reveals the location of enemy structures & units through the fog of war. + AdditionalInfo: Requires Seek & Destroy Strategy. + +encyclopedia.power.naniteshield: + Inherits: ^SupportPowerPreview + Buildable: + Icon: nshield + Prerequisites: upgc + BuildDuration: 6000 + Tooltip: + Name: Nanite Shield + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Reduces the damage taken by all vehicles in the target area. + AdditionalInfo: Requires Hold the Line Strategy. + +encyclopedia.power.empmissile: + Inherits: ^SupportPowerPreview + Buildable: + Icon: empmissile + Prerequisites: patr + BuildDuration: 4500 + Tooltip: + Name: EMP Missile + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Launches an EMP missile that disables vehicles and structures in the target area. + +encyclopedia.power.ioncannon: + Inherits: ^SupportPowerPreview + Buildable: + Icon: ioncannon + Prerequisites: eye + BuildDuration: 13500 + Tooltip: + Name: Ion Cannon + Encyclopedia: + Category: GDI/Support Powers + EncyclopediaExtras: + Description: Fires a devastating ion cannon beam at the target location. + +# Nod Support Powers + +encyclopedia.power.hacksat: + Inherits: ^SupportPowerPreview + Buildable: + Icon: hacksat + Prerequisites: anyradar + BuildDuration: 4500 + Tooltip: + Name: Hack Satellite + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Reveals the targeted area for a short time. + +encyclopedia.power.airdrop: + Inherits: ^SupportPowerPreview + Buildable: + Icon: airdropicon + Prerequisites: airs + BuildDuration: 13500 + Tooltip: + Name: Air Drop + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Dispatches cargo planes to air drop tanks at the target location. Units dependent on subfaction. + +encyclopedia.power.substrike: + Inherits: ^SupportPowerPreview + Buildable: + Icon: substrike + Prerequisites: weap.td + BuildDuration: 12000 + Tooltip: + Name: Subterranean Strike + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Deploys a squad of infantry via Subterranean APC. + Subfaction: marked + +encyclopedia.power.infbomb: + Inherits: ^SupportPowerPreview + Buildable: + Icon: infbomb + IconPalette: chrometd + Prerequisites: anyradar + BuildDuration: 9750 + Tooltip: + Name: Inferno Bomb + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: A B2 Stealth Bomber drops inferno bombs on your target. + Subfaction: blackh + +encyclopedia.power.cashhack: + Inherits: ^SupportPowerPreview + Buildable: + Icon: chack + Prerequisites: anyradar + BuildDuration: 6000 + Tooltip: + Name: Cash Hack + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Steal up to $2000 credits from a targeted enemy Refinery. + Subfaction: legion + +encyclopedia.power.shadowteam: + Inherits: ^SupportPowerPreview + Buildable: + Icon: shadteam + Prerequisites: anyradar + BuildDuration: 6750 + Tooltip: + Name: Shadow Team + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Calls for a Shadow Team; three stealth infantry that arrive via glider armed with grenades and a machine pistol.\n\nOperatives can be ordered to land via the deploy command. + Subfaction: shadow + +encyclopedia.power.frenzy: + Inherits: ^SupportPowerPreview + Buildable: + Icon: frenzy + Prerequisites: tmpl + BuildDuration: 6750 + Tooltip: + Name: Frenzy + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Increases the movement speed and rate of fire of targeted units for a limited time.\n\nWarning: Units are weakened for a short time after frenzy wears off. + Subfaction: marked + +encyclopedia.power.techhack: + Inherits: ^SupportPowerPreview + Buildable: + Icon: techhack + Prerequisites: tmpl + BuildDuration: 18000 + Tooltip: + Name: Tech Hack + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Hack the targeted enemy production structure, providing access to a unit developed using enemy technology.\n\nFor infantry, vehicle and air production respectively: \n\n• Allies: Cryo Mortar, Reckoner, Phantom\n• Soviets: Cyberdog, Cyclops, Kamov\n• GDI: Sonic Mortar, Basilisk, Shade\n• Nod: Chem Mortar, Mantis, Vertigo\n• Scrin: Cyberscrin, Viper, Manticore + Subfaction: legion + +encyclopedia.power.assassinsquad: + Inherits: ^SupportPowerPreview + Buildable: + Icon: assassinsquad + IconPalette: chrometd + BuildDuration: 6000 + Tooltip: + Name: Assassin Squad + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Deploy a squad of Assassins that can snipe infantry from long range and destroy buildings with C4.\n\nTarget infantry production structure to deploy. + AdditionalInfo: Requires Wrath Covenant. + +encyclopedia.power.hackercell: + Inherits: ^SupportPowerPreview + Buildable: + Icon: hackercell + BuildDuration: 6000 + Tooltip: + Name: Hacker Cell + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Deploy a squad of Hackers that can remotely capture buildings and take control of defenses.\n\nTarget infantry production structure to deploy. + AdditionalInfo: Requires Unity Covenant. + +encyclopedia.power.confessorcabal: + Inherits: ^SupportPowerPreview + Buildable: + Icon: confessorcabal + IconPalette: chrometd + BuildDuration: 6000 + Tooltip: + Name: Confessor Cabal + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Deploy a Confessor Cabal which is able to construct Idols of Kane that buffs allies and debuffs enemies.\n\nTarget infantry production structure to deploy. + AdditionalInfo: Requires Zeal Covenant. + +encyclopedia.power.clustermissile: + Inherits: ^SupportPowerPreview + Buildable: + Icon: clustermissile + IconPalette: chrometd + Prerequisites: tmpl + BuildDuration: 10500 + Tooltip: + Name: Cluster Missile + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Launches a Cluster Missile which deals heavy damage at the target location. + +encyclopedia.power.tibstealth: + Inherits: ^SupportPowerPreview + Buildable: + Icon: invis + IconPalette: chrometd + Prerequisites: sgen + BuildDuration: 4500 + Tooltip: + Name: Tiberium Stealth + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Makes selected vehicles and structures temporarily invisible.\n\nWarning: Harmful to non-cyborg infantry.\n\nHazmat Suits upgrade makes allies immune to damage and enemies take 50% damage. + +encyclopedia.power.chemmissile: + Inherits: ^SupportPowerPreview + Buildable: + Icon: chemmissile + IconPalette: chrometd + Prerequisites: mslo.nod + BuildDuration: 13500 + Tooltip: + Name: Chemical Missile + Encyclopedia: + Category: Nod/Support Powers + EncyclopediaExtras: + Description: Launches an deadly Chemical Missile. Deals heavy damage and creates toxic clouds which are extremely harmful to infantry.\n + +# Scrin Support Powers + + +encyclopedia.power.resourcescan: + Inherits: ^SupportPowerPreview + Buildable: + Icon: rescanpower + Prerequisites: anyradar + BuildDuration: 7500 + Tooltip: + Name: Resource Scan + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Reveals the area surrounding all resources on the map. + +encyclopedia.power.ichorspike: + Inherits: ^SupportPowerPreview + Buildable: + Icon: ichorspike + IconPalette: chromes + BuildDuration: 6000 + Tooltip: + Name: Ichor Spike + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Creates an Ichor Spike at the target location. Resources within its area of influence grow and spread faster.\n\nAlso empowers units with Resource Conversion upgrade. + AdditionalInfo: Requires Loyalist Allegiance. + +encyclopedia.power.colonyspike: + Inherits: ^SupportPowerPreview + Buildable: + Icon: colonyspike + IconPalette: chromes + BuildDuration: 7500 + Tooltip: + Name: Colony Spike + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Creates a Colony Spike at the target location. Enables production of non-defensive structures.\n\nWhen fully charged allows non-defensive structures to be built nearby.\n\nMust be built within range of a Colony Platform. + AdditionalInfo: Requires Rebel Allegiance. + +encyclopedia.power.voidspike: + Inherits: ^SupportPowerPreview + Buildable: + Icon: voidspike + IconPalette: chromes + BuildDuration: 6000 + Tooltip: + Name: Voidspike + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Creates a Voidspike at the target location. Gradually transforms nearby resources into Black Tiberium which is devoid of most of its useful properties.\n\nDamages nearby enemy units. + AdditionalInfo: Requires Malefic Allegiance. + +encyclopedia.power.ionsurge: + Inherits: ^SupportPowerPreview + Buildable: + Icon: ionsurgepower + IconPalette: chromes + Prerequisites: anyradar + BuildDuration: 6750 + Tooltip: + Name: Ion Surge + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Increases the movement speed of all friendly units passing through the target area. + Subfaction: traveler + +encyclopedia.power.stormspikepower: + Inherits: ^SupportPowerPreview + Buildable: + Icon: stormspikepower + IconPalette: chromes + Prerequisites: anyradar + BuildDuration: 7500 + Tooltip: + Name: Storm Spike + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Creates a temporary Storm Column defensive structure at the target location. + Subfaction: reaper + +encyclopedia.power.buzzerswarm: + Inherits: ^SupportPowerPreview + Buildable: + Icon: buzzpower + IconPalette: chromes + Prerequisites: anyradar + BuildDuration: 7500 + Tooltip: + Name: Buzzer Swarm + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Spawns a controllable swarm which blinds and damages anything nearby; particularly harmful to infantry. + Subfaction: harbinger + +encyclopedia.power.ichorseed: + Inherits: ^SupportPowerPreview + Buildable: + Icon: ichorpower + Prerequisites: scrt + BuildDuration: 6000 + Tooltip: + Name: Ichor Seed + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Grows Tiberium at selected location. + +encyclopedia.power.greatercoalescence: + Inherits: ^SupportPowerPreview + Buildable: + Icon: grclpower + Prerequisites: scrt + BuildDuration: 6000 + Tooltip: + Name: Greater Coalescence + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Spawns a controllable biomass which heals nearby allies and sustains itself by feeding off enemies.\n\nFeeding from power plants will shut them down temporarily. + Subfaction: collector + +encyclopedia.power.gateway: + Inherits: ^SupportPowerPreview + Buildable: + Icon: gateway + IconPalette: chromes + Prerequisites: scrt + BuildDuration: 6000 + Tooltip: + Name: Gateway + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Creates a Gateway at the target location. Acts as the exit for any\n targeted infantry or vehicle production structure.\n\nRequires target location to be within vision. + AdditionalInfo: Requires Rebel Allegiance. + +encyclopedia.power.anathemapower: + Inherits: ^SupportPowerPreview + Buildable: + Icon: anathema + IconPalette: chromes + Prerequisites: scrt + BuildDuration: 3000 + Tooltip: + Name: Anathema + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Targeted vehicle greatly increases in power over time. After 30 seconds the unit explodes. + AdditionalInfo: Requires Malefic Allegiance. + +encyclopedia.power.owrath: + Inherits: ^SupportPowerPreview + Buildable: + Icon: owrath + IconPalette: chromes + Prerequisites: scrt + BuildDuration: 10500 + Tooltip: + Name: Overlord's Wrath + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: A Tiberium meteor strikes the target location dealing heavy damage and creating a patch of Tiberium. + AdditionalInfo: Requires Loyalist Allegiance. + +encyclopedia.power.fleetrecall: + Inherits: ^SupportPowerPreview + Buildable: + Icon: fleetrecall + IconPalette: chromes + Prerequisites: sign + BuildDuration: 5250 + Tooltip: + Name: Fleet Recall + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Recalls all selected Scrin fleet vessels back to the Signal Transmitter.\n\nApplies to: Mothership, Planetary Assault Carrier, Devastator + +encyclopedia.power.suppression: + Inherits: ^SupportPowerPreview + Buildable: + Icon: spresspower + Prerequisites: mani + BuildDuration: 4500 + Tooltip: + Name: Suppression + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Applies a suppression field to the target area, slowing unit movement and rate of fire. + +encyclopedia.power.rift: + Inherits: ^SupportPowerPreview + Buildable: + Icon: riftpower + Prerequisites: rfgn + BuildDuration: 13500 + Tooltip: + Name: Rift + Encyclopedia: + Category: Scrin/Support Powers + EncyclopediaExtras: + Description: Initiate a Rift which deals heavy damage over time to a large area. + +encyclopedia.power.forceshield: + Inherits: ^SupportPowerPreview + Buildable: + Icon: forceshield + Prerequisites: techcenter.any + BuildDuration: 7500 + Tooltip: + Name: Force Shield + Encyclopedia: + Category: Other/Shared Support Powers + EncyclopediaExtras: + Description: Makes selected friendly structures temporarily invulnerable.\n\nWarning: Causes power failure. + +encyclopedia.power.sendcash: + Inherits: ^SupportPowerPreview + Buildable: + Icon: sendcash + Prerequisites: anyrefinery + BuildDuration: 0 + Tooltip: + Name: Send Cash + Encyclopedia: + Category: Other/Shared Support Powers + EncyclopediaExtras: + Description: Available in team games and co-op. Sends $1000 credits to targeted ally. + +# IFV Variants +^IFVVariantPreview: + Inherits: ^EffectPreview + RenderSprites: + Image: ifv + WithFacingSpriteBody: + WithSpriteTurret: + Sequence: turret + Turreted: + Encyclopedia: + Scale: 2 + +encyclopedia.ifv.missile: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-rocket + Tooltip: + Name: Missile IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Rocket Soldier. Fires anti-air/anti-armor missiles. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Rocket Soldier + +encyclopedia.ifv.mg: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-mg + Tooltip: + Name: Machine Gun IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Rifle Infantry. Fires a rapid machine gun. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Rifle Infantry + +encyclopedia.ifv.engi: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-engi + Tooltip: + Name: Engineer IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with an Engineer. Can capture enemy structures. + TooltipExtras: + Weaknesses: • Unarmed + Attributes: • Captures structures\n• Passenger: Engineer + +encyclopedia.ifv.med: + Inherits: ^IFVVariantPreview + Tooltip: + Name: Medical IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Medic. Heals nearby friendly infantry. + TooltipExtras: + Weaknesses: • Unarmed + Attributes: • Heals friendly infantry\n• Passenger: Medic + -WithSpriteTurret: + WithIdleOverlay@MEDIC: + Sequence: medic + Offset: 0,0,40 + IsDecoration: True + +encyclopedia.ifv.mech: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-mech + Tooltip: + Name: Repair IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Mechanic. Repairs nearby friendly vehicles. + TooltipExtras: + Weaknesses: • Unarmed + Attributes: • Repairs friendly vehicles\n• Passenger: Mechanic + +encyclopedia.ifv.spy: + Inherits: ^IFVVariantPreview + Tooltip: + Name: Spy IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Spy. Can sabotage enemy structures and detect cloaked units. + TooltipExtras: + Weaknesses: • Cannot deal damage + Attributes: • Sabotages enemy structures:\n • Power Plants: Power outage\n • Barracks/Factory: Infantry/vehicles produced as veteran\n • Superweapons: Reset timer\n • Radar: Reset shroud\n • Helipad: Single-use paratroopers\n • Airfield/Gravity Stabilizer: Single-use airstrike\n• Passenger: Spy + -WithSpriteTurret: + WithIdleOverlay@SPINNER: + Sequence: spinner + Offset: 0,0,200 + +encyclopedia.ifv.snip: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-snip + Tooltip: + Name: Sniper IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Sniper. Fires long-range sniper rounds. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Heavy Armor + Weaknesses: • Weak vs Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Sniper + +encyclopedia.ifv.enfo: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-enfo + Tooltip: + Name: Enforcer IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with an Enforcer. Fires double shotgun blasts. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Enforcer + +encyclopedia.ifv.ggi: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-ggi + Tooltip: + Name: GGI IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Guardian GI. Fires powerful anti-armor rounds. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Guardian GI + +encyclopedia.ifv.seal: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-cmdo + Tooltip: + Name: SEAL IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Navy SEAL. Fires heavy anti-infantry rounds. + TooltipExtras: + Strengths: • Strong vs Infantry + Weaknesses: • Cannot attack Aircraft, Vehicles, Buildings + Attributes: • Passenger: Navy SEAL + +encyclopedia.ifv.cryo: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-cryo + Tooltip: + Name: Cryo IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Cryo Trooper. Fires freezing blasts. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Buildings, Defenses\n• Cannot attack Aircraft + Attributes: • Slows enemy units and makes them take increased damage\n• Passenger: Cryo Trooper + +encyclopedia.ifv.tigr: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-tigr + Tooltip: + Name: Tiger Guard IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Tiger Guard. Fires long range missiles. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Tiger Guard + +encyclopedia.ifv.hopl: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-hopl + Tooltip: + Name: Hoplite IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Hoplite. Fires a prism beam. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Hoplite + +encyclopedia.ifv.cmdo: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-cmdo + Tooltip: + Name: Commando IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Allies + Description: IFV loaded with a Commando (Tanya/Boris/Commando). Fires rapid and powerful sniper bursts with high damage per shot. + TooltipExtras: + Strengths: • Strong vs Infantry + Weaknesses: • Cannot attack Aircraft, Vehicles, Buildings + Attributes: • Passenger: Tanya, Boris, Commando + +encyclopedia.ifv.grenade: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-frag + Tooltip: + Name: Grenade IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Grenadier. Launches fragmentation grenades. + TooltipExtras: + Strengths: • Strong vs Buildings, Defenses, Light Armor + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Grenadier + +encyclopedia.ifv.flame: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-flame + Tooltip: + Name: Flamethrower IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Flamethrower. Projects devastating flames. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Flamethrower + +encyclopedia.ifv.chem: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-chem + Tooltip: + Name: Chemical IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Toxin Soldier. Sprays toxic chemicals. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Chem Warrior + +encyclopedia.ifv.rad: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-rad + Tooltip: + Name: Desolator IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Desolator. Fires a radiation beam. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Desolator, Rad Trooper + +encyclopedia.ifv.ivan: + Inherits: ^IFVVariantPreview + Tooltip: + Name: Explosive IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Crazy Ivan. Self-destructs on contact with the target, dealing massive damage. + TooltipExtras: + Strengths: • Strong vs Everything + Weaknesses: • Self-destructs + Attributes: • Passengers: Crazy Ivan, Burster, Terror Dog + -WithSpriteTurret: + WithIdleOverlay@IVAN: + Sequence: nuke + Offset: 0,0,40 + +encyclopedia.ifv.tes: + Inherits: ^IFVVariantPreview + Tooltip: + Name: Tesla IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Shock Trooper or Tesla Trooper. Fires a powerful tesla bolt. + TooltipExtras: + Strengths: • Strong vs Infantry, Heavy Armor, Light Armor + Weaknesses: • Cannot attack Aircraft + Attributes: • Passenger: Shock Trooper, Tesla Trooper + -WithSpriteTurret: + WithIdleOverlay@TESLA: + Sequence: tesla + +encyclopedia.ifv.psy: + Inherits: ^IFVVariantPreview + Tooltip: + Name: Psychic IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with Yuri or a Mastermind. Can mind control enemy units. Control is lost if passenger exits. + TooltipExtras: + Strengths: • Strong vs Infantry, Vehicles + Weaknesses: • Cannot deal damage\n• Cannot attack Aircraft + Attributes: • Can mind control enemy units\n• Control lost if passenger exits\n• Passenger: Yuri, Mastermind + -WithSpriteTurret: + WithIdleOverlay@PSYCHIC: + Sequence: psy + Palette: scrin + +encyclopedia.ifv.cmsr: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-mg + Tooltip: + Name: Commissar IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Commissar. Inspires nearby friendly infantry, boosting their combat effectiveness. + TooltipExtras: + Strengths: Inspires nearby allies + Weaknesses: Unarmed + Attributes: • Passenger: Commissar + WithPreviewDecoration@CMSR: + Image: pips + Sequence: pip-cmsr + Position: TopLeft + +encyclopedia.ifv.laser: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-laser + Tooltip: + Name: Laser IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with an Acolyte or Templar. Fires a powerful laser beam. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Acolyte, Templar + +encyclopedia.ifv.enli: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-enli + Tooltip: + Name: Enlightened IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with an Enlightened. Fires a powerful particle beam. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor + Weaknesses: • Weak vs Infantry\n• Cannot attack Aircraft + Attributes: • Passenger: Enlightened + +encyclopedia.ifv.rmbc: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-rmbc + Tooltip: + Name: Cyborg Elite IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Cyborg Elite. Fires a plasma cannon. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor, Infantry + Weaknesses: • Cannot attack Aircraft + +encyclopedia.ifv.bh: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-bh + Tooltip: + Name: Black Hand IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Black Hand. Projects devastating anti-vehicle flames. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor + Weaknesses: • Weak vs Buildings, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Black Hand + +encyclopedia.ifv.conf: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-conf + Tooltip: + Name: Confessor IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Confessor. Fires hallucinogenic rounds. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Explosion causes units to attack indiscriminately\n• Passenger: Confessor + +encyclopedia.ifv.reap: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-reap + Tooltip: + Name: Reaper IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Cyborg Reaper. Fires anti-personnel missiles. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor + Attributes: • Passenger: Reaper + +encyclopedia.ifv.shad: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-mg + Tooltip: + Name: Shadow IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Shadow Operative. Armed with a heavy machinegun and able to cloak. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Shadow Operative + +encyclopedia.ifv.hack: + Inherits: ^IFVVariantPreview + Tooltip: + Name: Hacker IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Hacker. Can hack enemy defenses and buildings. Control is lost if passenger exits. + TooltipExtras: + Strengths: • Strong vs Defenses, Buildings + Weaknesses: • Cannot deal damage\n• Control lost if passenger exits + Attributes: • Passenger: Hacker + -WithSpriteTurret: + WithIdleOverlay@HACK: + Sequence: hack + +encyclopedia.ifv.ztrp: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-ztrp + Tooltip: + Name: Zone Trooper IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Zone Trooper. Fires powerful railgun shots. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor, Defenses + Weaknesses: • Weak vs Infantry, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Zone Trooper + +encyclopedia.ifv.zdef: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-zdef + Tooltip: + Name: Zone Defender IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Zone Defender. Fires ion blasts. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor, Defenses + Weaknesses: • Weak vs Infantry, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Zone Defender + +encyclopedia.ifv.zrai: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-zrai + Tooltip: + Name: Zone Raider IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Zone Raider. Fires sonic grenades. + TooltipExtras: + Strengths: • Strong vs Light Armor, Buildings + Weaknesses: • Cannot attack Aircraft + Attributes: • Passenger: Zone Raider + +encyclopedia.ifv.disin: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-disin + Tooltip: + Name: Disintegrator IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Disintegrator. Fires disintegration beams. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Disintegrator (Scrin) + +encyclopedia.ifv.shard: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-shard + Tooltip: + Name: Shard IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Ravager or Eviscerator. Fires Tiberium shards. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Ravager, Eviscerator + +encyclopedia.ifv.pdisc: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-pdisc + Tooltip: + Name: Plasma Disc IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: Fires plasma discs. + TooltipExtras: + Strengths: • Strong vs Buildings, Defenses, Heavy Armor, Light Armor + Weaknesses: • Weak vs Infantry\n• Cannot attack Aircraft + Attributes: • Passenger: Intruder, Marauder + +encyclopedia.ifv.impl: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-impl + Tooltip: + Name: Impaler IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with an Impaler. Fires long range impaling projectiles. + TooltipExtras: + Strengths: • Strong vs Light Armor, Infantry + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Impaler + +encyclopedia.ifv.stlk: + Inherits: ^IFVVariantPreview + RenderSprites: + Image: ifv + WithSpriteTurret: + Sequence: turret-stlk + Palette: playerscrin + Tooltip: + Name: Stalker IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Stalker. Fires anti-infantry plasma darts and can cloak. + TooltipExtras: + Strengths: • Strong vs Light Armor, Infantry + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Stalker + +encyclopedia.ifv.mortchem: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-mort + Tooltip: + Name: Chemical Mortar IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Chemical Mortar Infantry. Fires chemical mortar rounds that leave toxic residue. + TooltipExtras: + Strengths: • Strong vs Infantry, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft\n• Has difficulty hitting moving targets + Attributes: • Passenger: Chemical Mortar + +encyclopedia.ifv.mortcryo: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-mort + Tooltip: + Name: Cryo Mortar IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Cryo Mortar Infantry. Fires cryo mortar rounds that slow and freeze enemies. + TooltipExtras: + Strengths: • Strong vs Infantry, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft\n• Has difficulty hitting moving targets + Attributes: • Passenger: Cryo Mortar + +encyclopedia.ifv.mortsonic: + Inherits: ^IFVVariantPreview + WithSpriteTurret: + Sequence: turret-mort + Tooltip: + Name: Sonic Mortar IFV + EncyclopediaExtras: + VariantOf: IFV + VariantGroup: Other Factions + Description: IFV loaded with a Sonic Mortar Infantry. Fires sonic mortar rounds that cause concussion. + TooltipExtras: + Strengths: • Strong vs Infantry, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft\n• Has difficulty hitting moving targets + Attributes: • Passenger: Sonic Mortar + +# Reckoner Variants +^ReckonerVariantPreview: + Inherits: ^EffectPreview + RenderSprites: + Image: reck + WithFacingSpriteBody: + WithSpriteTurret: + Sequence: turret + Turreted: + Encyclopedia: + Scale: 2 + +encyclopedia.reck.missile: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-rocket + Tooltip: + Name: Missile Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Rocket Soldier. Fires anti-air/anti-armor missiles. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Rocket Soldier + +encyclopedia.reck.mg: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-mg + Tooltip: + Name: Machine Gun Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Rifle Infantry. Fires a rapid machine gun. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Rifle Infantry + +encyclopedia.reck.engi: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-engi + Tooltip: + Name: Engineer Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with an Engineer. Can capture enemy structures. + TooltipExtras: + Weaknesses: • Unarmed + Attributes: • Captures structures\n• Passenger: Engineer + +encyclopedia.reck.med: + Inherits: ^ReckonerVariantPreview + Tooltip: + Name: Medical Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Medic. Heals nearby friendly infantry. + TooltipExtras: + Weaknesses: • Unarmed + Attributes: • Heals friendly infantry\n• Passenger: Medic + -WithSpriteTurret: + WithIdleOverlay@MEDIC: + Sequence: medic + Offset: 0,0,40 + IsDecoration: True + +encyclopedia.reck.mech: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-mech + Tooltip: + Name: Repair Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Mechanic. Repairs nearby friendly vehicles. + TooltipExtras: + Weaknesses: • Unarmed + Attributes: • Repairs friendly vehicles\n• Passenger: Mechanic + +encyclopedia.reck.spy: + Inherits: ^ReckonerVariantPreview + Tooltip: + Name: Spy Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Spy. Can sabotage enemy structures and detect cloaked units. + TooltipExtras: + Weaknesses: • Cannot deal damage + Attributes: • Sabotages enemy structures:\n • Power Plants: Power outage\n • Barracks/Factory: Infantry/vehicles produced as veteran\n • Superweapons: Reset timer\n • Radar: Reset shroud\n • Helipad: Single-use paratroopers\n • Airfield/Gravity Stabilizer: Single-use airstrike\n• Passenger: Spy + -WithSpriteTurret: + WithIdleOverlay@SPINNER: + Sequence: spinner + Offset: 0,0,200 + +encyclopedia.reck.snip: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-snip + Tooltip: + Name: Sniper Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Sniper. Fires long-range sniper rounds. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Heavy Armor + Weaknesses: • Weak vs Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Sniper + +encyclopedia.reck.enfo: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-enfo + Tooltip: + Name: Enforcer Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with an Enforcer. Fires double shotgun blasts. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Enforcer + +encyclopedia.reck.ggi: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-ggi + Tooltip: + Name: GGI Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Guardian GI. Fires powerful anti-armor rounds. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Guardian GI + +encyclopedia.reck.seal: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-cmdo + Tooltip: + Name: SEAL Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Navy SEAL. Fires heavy anti-infantry rounds. + TooltipExtras: + Strengths: • Strong vs Infantry + Weaknesses: • Cannot attack Aircraft, Vehicles, Buildings + Attributes: • Passenger: Navy SEAL + +encyclopedia.reck.cryo: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-cryo + Tooltip: + Name: Cryo Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Cryo Trooper. Fires freezing blasts. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Buildings, Defenses\n• Cannot attack Aircraft + Attributes: • Slows enemy units and makes them take increased damage\n• Passenger: Cryo Trooper + +encyclopedia.reck.tigr: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-tigr + Tooltip: + Name: Tiger Guard Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Tiger Guard. Fires long range missiles. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Tiger Guard + +encyclopedia.reck.hopl: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-hopl + Tooltip: + Name: Hoplite Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Hoplite. Fires a prism beam. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Hoplite + +encyclopedia.reck.cmdo: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-cmdo + Tooltip: + Name: Commando Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Allies + Description: Reckoner loaded with a Commando (Tanya/Boris/Commando). Fires rapid and powerful sniper bursts with high damage per shot. + TooltipExtras: + Strengths: • Strong vs Infantry + Weaknesses: • Cannot attack Aircraft, Vehicles, Buildings + Attributes: • Passenger: Tanya, Boris, Commando + +encyclopedia.reck.grenade: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-frag + Tooltip: + Name: Grenade Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Grenadier. Launches fragmentation grenades. + TooltipExtras: + Strengths: • Strong vs Buildings, Defenses, Light Armor + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Grenadier + +encyclopedia.reck.flame: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-flame + Tooltip: + Name: Flamethrower Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Flamethrower. Projects devastating flames. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Flamethrower + +encyclopedia.reck.chem: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-chem + Tooltip: + Name: Chemical Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Toxin Soldier. Sprays toxic chemicals. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft + Attributes: • Passenger: Chem Warrior + +encyclopedia.reck.rad: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-rad + Tooltip: + Name: Desolator Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Desolator. Fires a radiation beam. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Desolator, Rad Trooper + +encyclopedia.reck.ivan: + Inherits: ^ReckonerVariantPreview + Tooltip: + Name: Explosive Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Crazy Ivan. Self-destructs on contact with the target, dealing massive damage. + TooltipExtras: + Strengths: • Strong vs Everything + Weaknesses: • Self-destructs + Attributes: • Passengers: Crazy Ivan, Burster, Terror Dog + -WithSpriteTurret: + WithIdleOverlay@IVAN: + Sequence: nuke + Offset: 0,0,40 + +encyclopedia.reck.tes: + Inherits: ^ReckonerVariantPreview + Tooltip: + Name: Tesla Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Shock Trooper or Tesla Trooper. Fires a powerful tesla bolt. + TooltipExtras: + Strengths: • Strong vs Infantry, Heavy Armor, Light Armor + Weaknesses: • Cannot attack Aircraft + Attributes: • Passenger: Shock Trooper, Tesla Trooper + -WithSpriteTurret: + WithIdleOverlay@TESLA: + Sequence: tesla + +encyclopedia.reck.psy: + Inherits: ^ReckonerVariantPreview + Tooltip: + Name: Psychic Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with Yuri or a Mastermind. Can mind control enemy units. Control is lost if passenger exits. + TooltipExtras: + Strengths: • Strong vs Infantry, Vehicles + Weaknesses: • Cannot deal damage\n• Cannot attack Aircraft + Attributes: • Can mind control enemy units\n• Control lost if passenger exits\n• Passenger: Yuri, Mastermind + -WithSpriteTurret: + WithIdleOverlay@PSYCHIC: + Sequence: psy + Palette: scrin + +encyclopedia.reck.cmsr: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-mg + Tooltip: + Name: Commissar Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Commissar. Inspires nearby friendly infantry, boosting their combat effectiveness. + TooltipExtras: + Strengths: Inspires nearby allies + Weaknesses: Unarmed + Attributes: • Passenger: Commissar + WithPreviewDecoration@CMSR: + Image: pips + Sequence: pip-cmsr + Position: TopLeft + +encyclopedia.reck.laser: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-laser + Tooltip: + Name: Laser Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with an Acolyte or Templar. Fires a powerful laser beam. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Acolyte, Templar + +encyclopedia.reck.enli: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-enli + Tooltip: + Name: Enlightened Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with an Enlightened. Fires a powerful particle beam. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor + Weaknesses: • Weak vs Infantry\n• Cannot attack Aircraft + Attributes: • Passenger: Enlightened + +encyclopedia.reck.rmbc: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-rmbc + Tooltip: + Name: Cyborg Elite Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Cyborg Elite. Fires a plasma cannon. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor, Infantry + Weaknesses: • Cannot attack Aircraft + +encyclopedia.reck.bh: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-bh + Tooltip: + Name: Black Hand Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Black Hand. Projects devastating anti-vehicle flames. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor + Weaknesses: • Weak vs Buildings, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Black Hand + +encyclopedia.reck.conf: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-conf + Tooltip: + Name: Confessor Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Confessor. Fires hallucinogenic rounds. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Explosion causes units to attack indiscriminately\n• Passenger: Confessor + +encyclopedia.reck.reap: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-reap + Tooltip: + Name: Reaper Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Cyborg Reaper. Fires anti-personnel missiles. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor, Buildings + Weaknesses: • Weak vs Heavy Armor + Attributes: • Passenger: Reaper + +encyclopedia.reck.shad: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-mg + Tooltip: + Name: Shadow Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Shadow Operative. Armed with a heavy machinegun and able to cloak. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Shadow Operative + +encyclopedia.reck.hack: + Inherits: ^ReckonerVariantPreview + Tooltip: + Name: Hacker Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Hacker. Can hack enemy defenses and buildings. Control is lost if passenger exits. + TooltipExtras: + Strengths: • Strong vs Defenses, Buildings + Weaknesses: • Cannot deal damage\n• Control lost if passenger exits + Attributes: • Passenger: Hacker + -WithSpriteTurret: + WithIdleOverlay@HACK: + Sequence: hack + +encyclopedia.reck.ztrp: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-ztrp + Tooltip: + Name: Zone Trooper Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Zone Trooper. Fires powerful railgun shots. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor, Defenses + Weaknesses: • Weak vs Infantry, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Zone Trooper + +encyclopedia.reck.zdef: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-zdef + Tooltip: + Name: Zone Defender Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Zone Defender. Fires ion blasts. + TooltipExtras: + Strengths: • Strong vs Heavy Armor, Light Armor, Defenses + Weaknesses: • Weak vs Infantry, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Zone Defender + +encyclopedia.reck.zrai: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-zrai + Tooltip: + Name: Zone Raider Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Zone Raider. Fires sonic grenades. + TooltipExtras: + Strengths: • Strong vs Light Armor, Buildings + Weaknesses: • Cannot attack Aircraft + Attributes: • Passenger: Zone Raider + +encyclopedia.reck.disin: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-disin + Tooltip: + Name: Disintegrator Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Disintegrator. Fires disintegration beams. + TooltipExtras: + Strengths: • Strong vs Aircraft, Heavy Armor + Weaknesses: • Weak vs Infantry + Attributes: • Passenger: Disintegrator (Scrin) + +encyclopedia.reck.shard: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-shard + Tooltip: + Name: Shard Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Ravager or Eviscerator. Fires Tiberium shards. + TooltipExtras: + Strengths: • Strong vs Infantry, Light Armor + Weaknesses: • Weak vs Heavy Armor, Defenses, Buildings\n• Cannot attack Aircraft + Attributes: • Passenger: Ravager, Eviscerator + +encyclopedia.reck.pdisc: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-pdisc + Tooltip: + Name: Plasma Disc Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Fires plasma discs. + TooltipExtras: + Strengths: • Strong vs Buildings, Defenses, Heavy Armor, Light Armor + Weaknesses: • Weak vs Infantry\n• Cannot attack Aircraft + Attributes: • Passenger: Intruder, Marauder + +encyclopedia.reck.impl: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-impl + Tooltip: + Name: Impaler Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with an Impaler. Fires long range impaling projectiles. + TooltipExtras: + Strengths: • Strong vs Light Armor, Infantry + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Impaler + +encyclopedia.reck.stlk: + Inherits: ^ReckonerVariantPreview + RenderSprites: + Image: ifv + WithSpriteTurret: + Sequence: turret-stlk + Palette: playerscrin + Tooltip: + Name: Stalker Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Stalker. Fires anti-infantry plasma darts and can cloak. + TooltipExtras: + Strengths: • Strong vs Light Armor, Infantry + Weaknesses: • Weak vs Heavy Armor, Defenses\n• Cannot attack Aircraft + Attributes: • Passenger: Stalker + +encyclopedia.reck.mortchem: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-mort + Tooltip: + Name: Chemical Mortar Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Chemical Mortar Infantry. Fires chemical mortar rounds that leave toxic residue. + TooltipExtras: + Strengths: • Strong vs Infantry, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft\n• Has difficulty hitting moving targets + Attributes: • Passenger: Chemical Mortar + +encyclopedia.reck.mortcryo: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-mort + Tooltip: + Name: Cryo Mortar Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Cryo Mortar Infantry. Fires cryo mortar rounds that slow and freeze enemies. + TooltipExtras: + Strengths: • Strong vs Infantry, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft\n• Has difficulty hitting moving targets + Attributes: • Passenger: Cryo Mortar + +encyclopedia.reck.mortsonic: + Inherits: ^ReckonerVariantPreview + WithSpriteTurret: + Sequence: turret-mort + Tooltip: + Name: Sonic Mortar Reckoner + EncyclopediaExtras: + VariantOf: RECK + VariantGroup: Other Factions + Description: Reckoner loaded with a Sonic Mortar Infantry. Fires sonic mortar rounds that cause concussion. + TooltipExtras: + Strengths: • Strong vs Infantry, Buildings + Weaknesses: • Weak vs Heavy Armor\n• Cannot attack Aircraft\n• Has difficulty hitting moving targets + Attributes: • Passenger: Sonic Mortar + encyclopedia.buffs.anathema: Inherits: ^VehicleEffectPreview Tooltip: diff --git a/mods/ca/rules/infantry.yaml b/mods/ca/rules/infantry.yaml index d7d0ffa3b1..f7fec0ffc5 100644 --- a/mods/ca/rules/infantry.yaml +++ b/mods/ca/rules/infantry.yaml @@ -1381,7 +1381,7 @@ THF: Description: Steals enemy credits and hijacks enemy vehicles. TooltipExtras: Weaknesses: • Unarmed - Attributes: • Cloaked when not moving\n• Gains vision range while cloaked. + Attributes: • Cloaked when not moving\n• Gains vision range while cloaked Valued: Cost: 500 Tooltip: @@ -4459,7 +4459,7 @@ ENLI: Types: Veterancy Prerequisites: barracks.upgraded Encyclopedia: - Category: Nod/Infantry + Category: Nod/Stolen Technology MORT.Cryo: Inherits: ^MortarInfantryBase @@ -5091,6 +5091,7 @@ TIGR: FacingTolerance: 32 ChargeLevel: 0,2 PauseOnCondition: being-warped || blinded || reapersnare + TargetFrozenActors: true AttackMove: Voice: Move AutoTargetPriority@HeavyPrio: diff --git a/mods/ca/rules/scrin.yaml b/mods/ca/rules/scrin.yaml index 656f68d416..5969a2bcd4 100644 --- a/mods/ca/rules/scrin.yaml +++ b/mods/ca/rules/scrin.yaml @@ -5505,6 +5505,7 @@ MANI: Amount: -200 MustBeDestroyed: RequiredForShortGame: false + ProvidesPrerequisite@buildingname: InfiltrateForSupportPowerReset: Types: ResetSupportPowerInfiltrate InfiltrationNotification: BuildingInfiltrated @@ -5889,6 +5890,8 @@ SSPK: Inherits: SCOL Tooltip: Name: Storm Spike + Encyclopedia: + Category: Scrin/Support Powers -Sellable: -Buildable: Health: diff --git a/mods/ca/rules/structures.yaml b/mods/ca/rules/structures.yaml index 88870438ac..57a00927ad 100644 --- a/mods/ca/rules/structures.yaml +++ b/mods/ca/rules/structures.yaml @@ -375,6 +375,7 @@ IRON: Amount: -200 MustBeDestroyed: RequiredForShortGame: false + ProvidesPrerequisite@buildingname: InfiltrateForSupportPowerReset: Types: ResetSupportPowerInfiltrate InfiltrationNotification: BuildingInfiltrated @@ -2424,7 +2425,7 @@ SBAG: WithWallSpriteBody: Type: sandbag Encyclopedia: - Category: Allies/Defenses + Category: Allies/Defenses; GDI/Defenses FENC: Inherits: ^Wall @@ -5219,6 +5220,7 @@ SGEN: Type: Rectangle TopLeft: -1536, -1024 BottomRight: 1536, 1024 + ProvidesPrerequisite@buildingname: InfiltrateForSupportPowerReset: Types: ResetSupportPowerInfiltrate InfiltrationNotification: BuildingInfiltrated @@ -5515,6 +5517,7 @@ PATR: RejectsOrders@LOADED: Except: Sell, PowerDown RequiresCondition: loaded-rocket + ProvidesPrerequisite@buildingname: InfiltrateForSupportPowerReset: Types: ResetSupportPowerInfiltrate InfiltrationNotification: BuildingInfiltrated diff --git a/mods/ca/rules/vehicles.yaml b/mods/ca/rules/vehicles.yaml index 6dc8c50aec..9271621140 100644 --- a/mods/ca/rules/vehicles.yaml +++ b/mods/ca/rules/vehicles.yaml @@ -216,7 +216,7 @@ NUKC: Voice: Move ImmovableCondition: deployed RequireForceMoveCondition: !undeployed - PauseOnCondition: being-captured || empdisable || being-warped || driver-dead || notmobile || deploying || undeploying + PauseOnCondition: being-captured || empdisable || being-warped || driver-dead || notmobile || deploying || undeploying || deployed RevealsShroud: MinRange: 4c0 Range: 5c0 @@ -280,11 +280,11 @@ NUKC: Sequence: undeploy Body: body WithFacingSpriteBody: - RequiresCondition: undeployed + RequiresCondition: undeployed || deploying WithFacingSpriteBody@DeployedChassis: Name: deployed Sequence: deployed - RequiresCondition: !undeployed + RequiresCondition: !undeployed && !deploying WithFacingSpriteBody@Overlay: Name: overlay Sequence: overlay @@ -936,6 +936,8 @@ GTNK.squad: Upgradeable@RHINO: Actor: 3tnk.rhino.atomic -Upgradeable@ATOMIC: + EncyclopediaExtras: + VariantOf: 3TNK 3TNK.YURI: Inherits: 3TNK @@ -1068,6 +1070,7 @@ GTNK.squad: -Upgradeable@LASHER: EncyclopediaExtras: Subfaction: yuri + VariantOf: 3TNK.YURI 3TNK.RHINO: Inherits: ^Tank @@ -1177,6 +1180,7 @@ GTNK.squad: Encyclopedia: Category: Soviets/Vehicles EncyclopediaExtras: + Name: Rhino Tank AdditionalInfo: Requires Armor Doctrine. 3TNK.RHINO.ATOMIC: @@ -1207,6 +1211,9 @@ GTNK.squad: Upgradeable@LASHER: Actor: 3tnk.rhino.atomicyuri -Upgradeable@ATOMIC: + EncyclopediaExtras: + Name: Atomic Rhino Tank + VariantOf: 3TNK.RHINO 3TNK.RHINO.YURI: Inherits: 3TNK.RHINO @@ -1245,6 +1252,7 @@ GTNK.squad: -SpeedMultiplier@CRUSHSLOW: -Upgradeable@LASHER: EncyclopediaExtras: + Name: Thrasher Tank Subfaction: yuri 3TNK.RHINO.ATOMICYURI: @@ -1282,7 +1290,9 @@ GTNK.squad: -Upgradeable@LASHER: -WithDecoration@UpgradeOverlay: EncyclopediaExtras: + Name: Atomic Thrasher Tank Subfaction: yuri + VariantOf: 3TNK.RHINO.YURI 4TNK: Inherits: ^Tank @@ -1450,6 +1460,8 @@ GTNK.squad: Upgradeable@APOC: Actor: apoc.atomic -Upgradeable@ATOMIC: + EncyclopediaExtras: + VariantOf: 4TNK 4TNK.ERAD: Inherits: 4TNK @@ -1538,6 +1550,8 @@ GTNK.squad: Upgradeable@OVLD: Actor: ovld.erad.atomic -Upgradeable@ERAD: + EncyclopediaExtras: + VariantOf: 4TNK.ERAD ARTY: Inherits: ^Vehicle @@ -2374,6 +2388,8 @@ APC: RequiresCondition: !mindcontrolled Encyclopedia: Category: Allies/Vehicles + EncyclopediaExtras: + Name: APC AAPC: Inherits: APC @@ -2408,6 +2424,8 @@ AAPC: -ReplacedInQueue: -Upgradeable@Archer: -WithDecoration@UpgradeOverlay: + EncyclopediaExtras: + Name: Archer APC APC.AI: Inherits: APC @@ -3785,7 +3803,20 @@ APC2: UpgradeAtActors: fix, rep, srep RequiresCondition: !mindcontrolled Encyclopedia: - Category: GDI/Vehicles; Nod/Vehicles + Category: GDI/Vehicles + EncyclopediaExtras: + Name: APC + +APC2.Nod.Preview: + Inherits: APC2 + RenderSprites: + Image: apc2.nod + Buildable: + Queue: Encyclopedia + Encyclopedia: + Category: Nod/Vehicles + EncyclopediaExtras: + Subfaction: legion APC2.NODAI: Inherits: APC2 @@ -3997,6 +4028,8 @@ VULC: RequiresCondition: loaded-cmdo Encyclopedia: Category: GDI/Vehicles + EncyclopediaExtras: + Name: Vulcan VULC.AI: Inherits: VULC @@ -4148,6 +4181,8 @@ LTNK.Laser: Selectable: Class: ltnk -ReplacedInQueue: + EncyclopediaExtras: + VariantOf: LTNK MTNK: Inherits: ^TankTD @@ -4294,7 +4329,7 @@ MTNK: BuildDuration: 100 RequiresCondition: !mindcontrolled Encyclopedia: - Category: GDI/Vehicles; Nod/Vehicles + Category: GDI/Vehicles MTNK.Drone: Inherits: MTNK @@ -4383,6 +4418,18 @@ MTNK.Drone: EncyclopediaExtras: Subfaction: arc +MTNK.Nod.Preview: + Inherits: MTNK + RenderSprites: + Image: mtnk.nod + Buildable: + Queue: Encyclopedia + BuildPaletteOrder: 101 + Encyclopedia: + Category: Nod/Vehicles + EncyclopediaExtras: + Subfaction: legion + MTNK.Laser: Inherits: MTNK RenderSprites: @@ -4420,6 +4467,7 @@ MTNK.Laser: Category: Nod/Vehicles EncyclopediaExtras: Subfaction: legion + VariantOf: MTNK.Nod.Preview MDRN: Inherits: ^Vehicle @@ -5804,6 +5852,8 @@ BTR: VoiceSet: BtrVoice Encyclopedia: Category: Soviets/Vehicles + EncyclopediaExtras: + Name: BTR BTR.AI: Inherits: BTR @@ -5946,6 +5996,7 @@ BTR.YURI: -Upgradeable@GATTLING: -WithDecoration@UpgradeOverlay: EncyclopediaExtras: + Name: Gattling BTR Subfaction: yuri BTR.YURI.AI: @@ -7113,6 +7164,8 @@ IFV: CasingTargetOffset: 0, -600, 0 Encyclopedia: Category: Allies/Vehicles + EncyclopediaExtras: + Name: IFV IFV.AI: Inherits: ^Tank @@ -7359,9 +7412,10 @@ RECK: SpeedMultiplier@TigerGuard: Modifier: 90 Encyclopedia: - Category: Nod/Vehicles + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Allied War Factory. + -Name: TITN: Inherits: ^TankTD @@ -8615,6 +8669,7 @@ BATF: s3: loaded s4: loaded s6: loaded-repair + mrdr: loaded evis: loaded impl: loaded stlk: loaded @@ -9274,7 +9329,7 @@ CYCP: Actor: CYCP.Husk RequiresCondition: !being-warped Encyclopedia: - Category: Nod/Vehicles + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Soviet War Factory. @@ -9356,7 +9411,7 @@ BASI: Condition: inherited-cloak RequiresCondition: hidden || invisibility Encyclopedia: - Category: Nod/Vehicles + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from GDI Weapons Factory. @@ -9420,7 +9475,7 @@ MANT: GuardsSelection: ValidTargets: Infantry, Vehicle Encyclopedia: - Category: Nod/Vehicles + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Nod Airstrip. @@ -9491,7 +9546,7 @@ VIPR: TargetTypes: Water, Ship RequiresCondition: onwater Encyclopedia: - Category: Nod/Vehicles + Category: Nod/Stolen Technology EncyclopediaExtras: AdditionalInfo: Stolen from Scrin Warp Sphere. @@ -9850,6 +9905,8 @@ APOC.ATOMIC: Upgradeable@ERAD: Actor: apoc.erad.atomic -Upgradeable@ATOMIC: + EncyclopediaExtras: + VariantOf: APOC APOC.ERAD: Inherits: APOC @@ -9933,6 +9990,7 @@ APOC.ERAD.ATOMIC: VoiceSet: EradVoice EncyclopediaExtras: Subfaction: iraq + VariantOf: APOC.ERAD OVLD: Inherits: ^Tank @@ -10089,6 +10147,8 @@ OVLD.ATOMIC: SpeedMultiplier: Modifier: 125 -SpawnActorOnDeath: + EncyclopediaExtras: + VariantOf: OVLD OVLD.ERAD: Inherits: OVLD @@ -10165,6 +10225,7 @@ OVLD.ERAD.ATOMIC: VoiceSet: EradVoice EncyclopediaExtras: Subfaction: iraq + VariantOf: OVLD.ERAD THWK: Inherits: ^TankTD diff --git a/mods/ca/scripts/campaign.lua b/mods/ca/scripts/campaign.lua index 92cb275dd5..5fe01a59e8 100644 --- a/mods/ca/scripts/campaign.lua +++ b/mods/ca/scripts/campaign.lua @@ -18,7 +18,7 @@ StructureBuildTimeMultipliers = { } McvRebuildDelay = { - easy = DateTime.Minutes(25), -- not used by default + easy = DateTime.Minutes(25), -- not used by default normal = DateTime.Minutes(15), -- not used by default hard = DateTime.Minutes(8), vhard = DateTime.Minutes(5), @@ -26,11 +26,11 @@ McvRebuildDelay = { } UnitBuildTimeMultipliers = { - easy = 1.25, -- 2000 value/min per queue (~33/s) + easy = 1.25, -- 2000 value/min per queue (~33/s) normal = 0.81, -- 3086 value/min per queue (~51/s) - hard = 0.6, -- 4166 value/min per queue (~69/s) - vhard = 0.4, -- 6250 value/min per queue (~104/s) - brutal = 0.3 -- 8333 value/min per queue (~138/s) + hard = 0.6, -- 4166 value/min per queue (~69/s) + vhard = 0.4, -- 6250 value/min per queue (~104/s) + brutal = 0.3 -- 8333 value/min per queue (~138/s) -- 1.0 -- 2500 value/min per queue (~41.6/s) -- 0.52 -- 4807 value/min per queue (~80/s) -- 0.34 -- 7352 value/min per queue (~122/s) @@ -42,7 +42,7 @@ AttackValueMultipliers = { easy = 0.5, -- standard min 20/s, max 40/s normal = 1, -- standard min 40/s, max 80/s hard = 1.5, -- standard min 60/s, max 120/s - vhard = 2, -- standard min 80/s, max 160/s + vhard = 2, -- standard min 80/s, max 160/s brutal = 2.5 -- standard min 100/s, max 200/s } @@ -50,7 +50,7 @@ NormalRampDuration = DateTime.Minutes(17) RampDurationMultipliers = { easy = 1.06, -- 18 min - normal = 1, -- 17 min + normal = 1, -- 17 min hard = 0.94, -- 16 min vhard = 0.88, -- 15 min brutal = 0.82 -- 14 min @@ -136,7 +136,8 @@ CashRewardOnCaptureTypes = { "proc", "proc.td", "proc.scrin", "silo", "silo.td", WallTypes = { "sbag", "fenc", "brik", "cycl", "barb" } -KeyStructures = { "fact", "afac", "sfac", "proc", "proc.td", "proc.scrin", "weap", "weap.td", "airs", "wsph", "dome", "hq", "nerv", "atek", "stek", "gtek", "tmpl", "scrt", "mcv", "amcv", "smcv" } +KeyStructures = { "fact", "afac", "sfac", "proc", "proc.td", "proc.scrin", "weap", "weap.td", "airs", "wsph", "dome", + "hq", "nerv", "atek", "stek", "gtek", "tmpl", "scrt", "mcv", "amcv", "smcv" } DefaultQueueProducers = { Infantry = BarracksTypes, @@ -161,56 +162,56 @@ RebuildFunctions = { } -- should be populated with the human players in the mission -MissionPlayers = { } +MissionPlayers = {} -- should be populated with squads definitions needed by the mission -Squads = { } +Squads = {} -- -- begin automatically populated vars (do not assign values to these) -- -- stores the player base locations (recalculated at intervals) -PlayerBaseLocations = { } +PlayerBaseLocations = {} -- queued structures for AI -BuildingQueues = { } +BuildingQueues = {} -- stores active squad leaders -SquadLeaders = { } +SquadLeaders = {} -- stores actors which have called for help so they don't do so repeatedly -AlertedUnits = { } +AlertedUnits = {} -- per production structure, stores which squad to assign produced units to next -SquadAssignmentQueue = { } +SquadAssignmentQueue = {} -- stores which AI production structures have triggers assigned to prevent them being added multiple times -OnProductionTriggers = { } +OnProductionTriggers = {} -- when player kills an AI harvester it delays the production of the next attack wave -HarvesterDeathStacks = { } +HarvesterDeathStacks = {} -- minimum time until next special composition for each AI player -SpecialCompositionMinTimes = { } +SpecialCompositionMinTimes = {} -- caches unit costs for adjusting composition difficulty -UnitCosts = { } +UnitCosts = {} -- player characteristics used to enable AI behaviours -PlayerCharacteristics = { } +PlayerCharacteristics = {} -- stores original AI conyards for rebuilding purposes -AiConyards = { } +AiConyards = {} -- stores queue of conyards that need rebuilding -AiConyardRebuildQueue = { } +AiConyardRebuildQueue = {} -- stores actors that have a trigger assigned for rebuilding AI conyards -McvProductionTriggers = { } +McvProductionTriggers = {} -- stores buildings that should be sold on capture attempts -BuildingsToSellOnCaptureAttempt = { } +BuildingsToSellOnCaptureAttempt = {} -- -- end automatically populated vars @@ -225,10 +226,12 @@ InitObjectives = function(player) if p.IsLocalPlayer then Trigger.AfterDelay(1, function() local colour = HSLColor.Yellow + local messageKey = "new-primary-objective" if p.GetObjectiveType(id) ~= "Primary" then colour = HSLColor.Gray + messageKey = "new-secondary-objective" end - Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective", colour) + Media.DisplayMessage(p.GetObjectiveDescription(id), UserInterface.GetFluentMessage(messageKey), colour) end) end end) @@ -236,13 +239,15 @@ InitObjectives = function(player) Trigger.OnObjectiveCompleted(player, function(p, id) if p.IsLocalPlayer then Media.PlaySoundNotification(player, "AlertBleep") - Media.DisplayMessage(p.GetObjectiveDescription(id), "Objective completed", HSLColor.LimeGreen) + Media.DisplayMessage(p.GetObjectiveDescription(id), UserInterface.GetFluentMessage("objective-completed"), + HSLColor.LimeGreen) end end) Trigger.OnObjectiveFailed(player, function(p, id) if p.IsLocalPlayer then - Media.DisplayMessage(p.GetObjectiveDescription(id), "Objective failed", HSLColor.Red) + Media.DisplayMessage(p.GetObjectiveDescription(id), UserInterface.GetFluentMessage("objective-failed"), + HSLColor.Red) end end) @@ -264,11 +269,13 @@ InitObjectives = function(player) end Notification = function(text) - Media.DisplayMessage(text, "Notification", HSLColor.FromHex("1E90FF")) + Media.DisplayMessage(UserInterface.GetFluentMessage(text), UserInterface.GetFluentMessage("notification"), + HSLColor.FromHex("1E90FF")) end Tip = function(text) - Media.DisplayMessage(text, "Tip", HSLColor.FromHex("29F3CF")) + Media.DisplayMessage(UserInterface.GetFluentMessage(text), UserInterface.GetFluentMessage("tip"), + HSLColor.FromHex("29F3CF")) end IsNormalOrAbove = function() @@ -295,7 +302,7 @@ IsVeryHardOrBelow = function() return IsHardOrBelow() or Difficulty == "vhard" end -AttackAircraftTargets = { } +AttackAircraftTargets = {} InitAttackAircraft = function(aircraft, targetPlayers, targetList, targetType) if not aircraft.IsDead then local typeKey = string.gsub(aircraft.Type, "%.", "_") @@ -353,7 +360,8 @@ end ChooseRandomTarget = function(unit, targetPlayer) local target = nil local enemies = Utils.Where(targetPlayer.GetActors(), function(self) - return self.HasProperty("Health") and unit.CanTarget(self) and not Utils.Any(WallTypes, function(type) return self.Type == type end) + return self.HasProperty("Health") and unit.CanTarget(self) and + not Utils.Any(WallTypes, function(type) return self.Type == type end) end) if #enemies > 0 then target = Utils.Random(enemies) @@ -375,7 +383,8 @@ ChooseRandomTargetOfTypes = function(unit, targetPlayer, targetList, targetType) end local enemies = Utils.Where(enemies, function(e) - return e.HasProperty("Health") and (e.HasProperty("Move") or e.HasProperty("StartBuildingRepairs")) and unit.CanTarget(e) + return e.HasProperty("Health") and (e.HasProperty("Move") or e.HasProperty("StartBuildingRepairs")) and + unit.CanTarget(e) end) if #enemies > 0 then @@ -389,7 +398,8 @@ end ChooseRandomBuildingTarget = function(unit, targetPlayer) local target = nil local enemies = Utils.Where(targetPlayer.GetActors(), function(e) - return e.HasProperty("Health") and e.HasProperty("StartBuildingRepairs") and unit.CanTarget(e) and not Utils.Any(WallTypes, function(t) return e.Type == t end) + return e.HasProperty("Health") and e.HasProperty("StartBuildingRepairs") and unit.CanTarget(e) and + not Utils.Any(WallTypes, function(t) return e.Type == t end) end) if #enemies > 0 then target = Utils.Random(enemies) @@ -415,7 +425,6 @@ IdleHunt = function(actor) end AssaultPlayerBaseOrHunt = function(actor, targetPlayers, waypoints, fromIdle) - if actor.IsDead or not actor.HasProperty("AttackMove") or IsMissionPlayer(actor.Owner) then return end @@ -492,7 +501,8 @@ end PlayerBuildingsCount = function(player) local buildings = Utils.Where(player.GetActors(), function(a) - return a.HasProperty("StartBuildingRepairs") and not a.HasProperty("Attack") and a.Type ~= "silo" and a.Type ~= "silo.td" and a.Type ~= "silo.scrin" + return a.HasProperty("StartBuildingRepairs") and not a.HasProperty("Attack") and a.Type ~= "silo" and + a.Type ~= "silo.td" and a.Type ~= "silo.scrin" end) local mcvs = player.GetActorsByTypes(McvTypes) return #buildings + #mcvs @@ -505,7 +515,8 @@ end MissionPlayersHaveNavalPresence = function() local count = 0 for _, p in pairs(MissionPlayers) do - local navalUnits = p.GetActorsByTypes({ "isub", "msub", "ca", "cv", "dd", "pt", "ss", "seas", "ss2", "sb", "dd2", "pt2", "lst" }) + local navalUnits = p.GetActorsByTypes({ "isub", "msub", "ca", "cv", "dd", "pt", "ss", "seas", "ss2", "sb", "dd2", + "pt2", "lst" }) count = count + #navalUnits end return count > 4 @@ -550,7 +561,8 @@ RemoveActorsBasedOnDifficultyTags = function() local brutalOnlyActors = Map.ActorsWithTag("BrutalOnly") local actorsToRemoveAbove = { - easy = Utils.Concat(normalAndAboveActors, Utils.Concat(hardAndAboveActors, Utils.Concat(veryHardAndAboveActors, brutalOnlyActors))), + easy = Utils.Concat(normalAndAboveActors, + Utils.Concat(hardAndAboveActors, Utils.Concat(veryHardAndAboveActors, brutalOnlyActors))), normal = Utils.Concat(hardAndAboveActors, Utils.Concat(veryHardAndAboveActors, brutalOnlyActors)), hard = Utils.Concat(veryHardAndAboveActors, brutalOnlyActors), vhard = brutalOnlyActors, @@ -569,7 +581,8 @@ RemoveActorsBasedOnDifficultyTags = function() local veryHardAndBelowActors = Map.ActorsWithTag("VeryHardAndBelow") local actorsToRemoveBelow = { - brutal = Utils.Concat(veryHardAndBelowActors, Utils.Concat(hardAndBelowActors, Utils.Concat(normalAndBelowActors, easyOnlyActors))), + brutal = Utils.Concat(veryHardAndBelowActors, + Utils.Concat(hardAndBelowActors, Utils.Concat(normalAndBelowActors, easyOnlyActors))), vhard = Utils.Concat(hardAndBelowActors, Utils.Concat(normalAndBelowActors, easyOnlyActors)), hard = Utils.Concat(normalAndBelowActors, easyOnlyActors), normal = easyOnlyActors, @@ -584,14 +597,16 @@ RemoveActorsBasedOnDifficultyTags = function() end AutoRepairBuildings = function(player) - local buildings = Utils.Where(Map.ActorsInWorld, function(self) return self.Owner == player and self.HasProperty("StartBuildingRepairs") end) + local buildings = Utils.Where(Map.ActorsInWorld, + function(self) return self.Owner == player and self.HasProperty("StartBuildingRepairs") end) Utils.Do(buildings, function(a) AutoRepairBuilding(a, player) end) end AutoRepairAndRebuildBuildings = function(player, maxAttempts) - local buildings = Utils.Where(Map.ActorsInWorld, function(self) return self.Owner == player and self.HasProperty("StartBuildingRepairs") end) + local buildings = Utils.Where(Map.ActorsInWorld, + function(self) return self.Owner == player and self.HasProperty("StartBuildingRepairs") end) Utils.Do(buildings, function(a) local excludeFromRebuilding = false @@ -641,7 +656,7 @@ end AutoRebuildBuilding = function(building, player, maxAttempts) if BuildingQueues[player.InternalName] == nil then - BuildingQueues[player.InternalName] = { } + BuildingQueues[player.InternalName] = {} end if maxAttempts == nil then maxAttempts = 15 @@ -708,7 +723,7 @@ RebuildBuilding = function(queueItem) RebuildFunctions[queueItem.Player.InternalName](b) end - -- otherwise add to back of queue (if attempts remaining) + -- otherwise add to back of queue (if attempts remaining) elseif queueItem.AttemptsRemaining > 1 then queueItem.AttemptsRemaining = queueItem.AttemptsRemaining - 1 BuildingQueues[queueItem.Player.InternalName][#BuildingQueues[queueItem.Player.InternalName] + 1] = queueItem @@ -754,7 +769,8 @@ HasOwnedBuildingsNearby = function(player, pos, noEnemyBuildings) local bottomRight = WPos.New(pos.X + 8192, pos.Y + 8192, 0) local nearbyBuildings = Map.ActorsInBox(topLeft, bottomRight, function(a) - return not a.IsDead and a.Owner == player and a.HasProperty("StartBuildingRepairs") and not a.HasProperty("Attack") and a.Type ~= "silo" and a.Type ~= "silo.td" and a.Type ~= "silo.scrin" + return not a.IsDead and a.Owner == player and a.HasProperty("StartBuildingRepairs") and not a.HasProperty("Attack") and + a.Type ~= "silo" and a.Type ~= "silo.td" and a.Type ~= "silo.scrin" end) -- require an owned building nearby @@ -859,11 +875,10 @@ end QueueMcv = function(player, conyardEntry) if AiConyardRebuildQueue[player.InternalName] == nil then - AiConyardRebuildQueue[player.InternalName] = { } + AiConyardRebuildQueue[player.InternalName] = {} end Trigger.AfterDelay(McvRebuildDelay[Difficulty], function() - -- get a conyard without an assigned mcv that has friendly buildings nearby, where a producer can path to it local possibleConyards = GetRebuildableConyardEntries(player) @@ -896,7 +911,8 @@ QueueMcv = function(player, conyardEntry) Trigger.OnIdle(produced, function(self) -- if deploy failed, wait 10 seconds, move to a random location within 5 cells then return and try again produced.Wait(DateTime.Seconds(10)) - local randomCell = CPos.New(targetConyardEntry.DeployLocation.X + Utils.RandomInteger(-5, 5), targetConyardEntry.DeployLocation.Y + Utils.RandomInteger(-5, 5)) + local randomCell = CPos.New(targetConyardEntry.DeployLocation.X + Utils.RandomInteger(-5, 5), + targetConyardEntry.DeployLocation.Y + Utils.RandomInteger(-5, 5)) produced.Move(randomCell) produced.Move(targetConyardEntry.DeployLocation) produced.Deploy() @@ -906,7 +922,8 @@ QueueMcv = function(player, conyardEntry) Trigger.OnRemovedFromWorld(produced, function(self) if not self.IsDead then Trigger.AfterDelay(DateTime.Seconds(1), function() - local nearbyConyards = Map.ActorsInCircle(Map.CenterOfCell(targetConyardEntry.DeployLocation), WDist.FromCells(2), function(a) + local nearbyConyards = Map.ActorsInCircle(Map.CenterOfCell(targetConyardEntry.DeployLocation), + WDist.FromCells(2), function(a) return a.Owner == player and IsConyard(a) end) if #nearbyConyards > 0 then @@ -936,10 +953,10 @@ end GetRebuildableConyardEntries = function(player) return Utils.Where(AiConyards, function(c) return c.Actor.IsDead - and c.AssignedMcv == nil - and c.Player == player - and HasOwnedBuildingsNearby(player, Map.CenterOfCell(c.DeployLocation), true) - and Utils.Any(c.PossibleProducers, function(p) return not p.IsDead end) + and c.AssignedMcv == nil + and c.Player == player + and HasOwnedBuildingsNearby(player, Map.CenterOfCell(c.DeployLocation), true) + and Utils.Any(c.PossibleProducers, function(p) return not p.IsDead end) end) end @@ -948,9 +965,9 @@ RestoreSquadProduction = function(oldBuilding, newBuilding) return end - for squadName,squad in pairs(Squads) do + for squadName, squad in pairs(Squads) do if squad.ProducerActors ~= nil then - for queue,actors in pairs(squad.ProducerActors) do + for queue, actors in pairs(squad.ProducerActors) do if actors ~= nil then for idx, a in pairs(actors) do if a == oldBuilding then @@ -972,7 +989,7 @@ TargetSwapChance = function(unit, chance, isMissionPlayerFunc) if isMissionPlayerFunc(self.Owner) or not isMissionPlayerFunc(attacker.Owner) then return end - local rand = Utils.RandomInteger(1,100) + local rand = Utils.RandomInteger(1, 100) if rand > 100 - chance then if not unit.IsDead and not attacker.IsDead and unit.HasProperty("Attack") then unit.Stop() @@ -1068,11 +1085,11 @@ InitAttackSquad = function(squad, player, targetPlayers) end if squad.QueueProductionStatuses == nil then - squad.QueueProductionStatuses = { } + squad.QueueProductionStatuses = {} end if squad.IdleUnits == nil then - squad.IdleUnits = { } + squad.IdleUnits = {} end if squad.Delay ~= nil then @@ -1103,7 +1120,6 @@ InitNavalAttackSquad = function(squad, player, targetPlayers) end InitAttackWave = function(squad) - if IsSquadInProduction(squad) then return end @@ -1135,12 +1151,16 @@ InitAttackWave = function(squad) -- filter possible compositions based on game time and other requirements local validCompositions = Utils.Where(allCompositions, function(composition) return (composition.MinTime == nil or DateTime.GameTime >= composition.MinTime + squad.InitTime) -- after min time - and (composition.MaxTime == nil or DateTime.GameTime < composition.MaxTime + squad.InitTime) -- before max time - and (composition.RequiredTargetCharacteristics == nil or Utils.All(composition.RequiredTargetCharacteristics, function(characteristic) - return PlayerOrMissionPlayersHaveCharacteristic(squad.TargetPlayer, characteristic) - end)) -- target player has all required characteristics - and (composition.Prerequisites == nil or squad.Player.HasPrerequisites(composition.Prerequisites)) -- player has prerequisites - and (composition.EnabledFunc == nil or composition.EnabledFunc()) -- custom function for whether the composition is enabled + and (composition.MaxTime == nil or DateTime.GameTime < composition.MaxTime + squad.InitTime) -- before max time + and + (composition.RequiredTargetCharacteristics == nil or Utils.All(composition.RequiredTargetCharacteristics, function( + characteristic) + return PlayerOrMissionPlayersHaveCharacteristic(squad.TargetPlayer, characteristic) + end)) -- target player has all required characteristics + and + (composition.Prerequisites == nil or squad.Player.HasPrerequisites(composition.Prerequisites)) -- player has prerequisites + and + (composition.EnabledFunc == nil or composition.EnabledFunc()) -- custom function for whether the composition is enabled end) -- determine whether to choose a special composition (33% chance if enough time has elapsed since last used) @@ -1198,9 +1218,9 @@ InitAttackWave = function(squad) end GetQueuesForComposition = function(composition) - local queues = { } + local queues = {} - for k,v in pairs(composition) do + for k, v in pairs(composition) do if IsQueue(k) then table.insert(queues, k) end @@ -1233,11 +1253,15 @@ IsQueue = function(key) end PlayerHasCharacteristic = function(player, characteristic) - return PlayerCharacteristics[player.InternalName] ~= nil and PlayerCharacteristics[player.InternalName][characteristic] ~= nil and PlayerCharacteristics[player.InternalName][characteristic] + return PlayerCharacteristics[player.InternalName] ~= nil and + PlayerCharacteristics[player.InternalName][characteristic] ~= nil and + PlayerCharacteristics[player.InternalName][characteristic] end MissionPlayersHaveCharacteristic = function(characteristic) - return PlayerCharacteristics["MissionPlayers"] ~= nil and PlayerCharacteristics["MissionPlayers"][characteristic] ~= nil and PlayerCharacteristics["MissionPlayers"][characteristic] + return PlayerCharacteristics["MissionPlayers"] ~= nil and + PlayerCharacteristics["MissionPlayers"][characteristic] ~= nil and + PlayerCharacteristics["MissionPlayers"][characteristic] end PlayerOrMissionPlayersHaveCharacteristic = function(player, characteristic) @@ -1248,7 +1272,7 @@ ProduceNextAttackSquadUnit = function(squad, queue, unitIndex) local units = squad.QueuedUnits[queue] if units == nil then - units = { } + units = {} end -- if there are no more units to build for this queue, check if any other queues are producing -- if none are, send the attack and produce next attack squad after interval @@ -1293,7 +1317,7 @@ ProduceNextAttackSquadUnit = function(squad, queue, unitIndex) InitAttackSquad(squad, squad.Player, squad.TargetPlayers) end) end - -- if more units to build, set them to produce after delay equal to their build time (with difficulty multiplier applied) + -- if more units to build, set them to produce after delay equal to their build time (with difficulty multiplier applied) else squad.QueueProductionStatuses[queue] = true local nextUnit = units[unitIndex] @@ -1340,13 +1364,13 @@ ProduceNextAttackSquadUnit = function(squad, queue, unitIndex) local producerId = tostring(producer) local engineersNearby = Map.ActorsInCircle(producer.CenterPosition, WDist.New(2730), function(a) - return not a.IsDead and (a.Type == "e6" or a.Type == "n6" or a.Type == "s6" or a.Type == "mast") and a.Owner ~= producer.Owner + return not a.IsDead and (a.Type == "e6" or a.Type == "n6" or a.Type == "s6" or a.Type == "mast") and + a.Owner ~= producer.Owner end) -- prevents capturing player getting a free unit -- the capture blocks the production until the capture completes if #engineersNearby == 0 then - -- set that the next unit produced from this producer should be assigned to the specified squad AddToSquadAssignmentQueue(producerId, squad) @@ -1377,7 +1401,6 @@ ProduceNextAttackSquadUnit = function(squad, queue, unitIndex) end HandleProducedSquadUnit = function(produced, producerId, squad) - -- if the produced unit is not owned by the squad player, ignore it if produced.Owner ~= squad.Player then return @@ -1405,7 +1428,6 @@ HandleProducedSquadUnit = function(produced, producerId, squad) if assignedSquad.OnProducedAction ~= nil then assignedSquad.OnProducedAction(produced) end - elseif produced.HasProperty("Hunt") then produced.Hunt() end @@ -1417,15 +1439,16 @@ end -- also split by player to prevent these getting jumbled if producer owner changes AddToSquadAssignmentQueue = function(producerId, squad) InitSquadAssignmentQueueForProducer(producerId, squad.Player) - SquadAssignmentQueue[squad.Player.InternalName][producerId][#SquadAssignmentQueue[squad.Player.InternalName][producerId] + 1] = squad + SquadAssignmentQueue[squad.Player.InternalName][producerId][#SquadAssignmentQueue[squad.Player.InternalName][producerId] + 1] = + squad end InitSquadAssignmentQueueForProducer = function(producerId, player) if SquadAssignmentQueue[player.InternalName] == nil then - SquadAssignmentQueue[player.InternalName] = { } + SquadAssignmentQueue[player.InternalName] = {} end if SquadAssignmentQueue[player.InternalName][producerId] == nil then - SquadAssignmentQueue[player.InternalName][producerId] = { } + SquadAssignmentQueue[player.InternalName][producerId] = {} end end @@ -1478,10 +1501,10 @@ function CalculateValuePerSecond(currentTick, attackValues) rampDuration = NormalRampDuration * RampDurationMultipliers[Difficulty] end local growthFactor = 2.06 - local progress = currentTick / rampDuration - local scaledProgress = progress ^ growthFactor - local value = minValue + (maxValue - minValue) * scaledProgress - return math.min(math.floor(value), maxValue) + local progress = currentTick / rampDuration + local scaledProgress = progress ^ growthFactor + local value = minValue + (maxValue - minValue) * scaledProgress + return math.min(math.floor(value), maxValue) end IsSquadInProduction = function(squad) @@ -1556,7 +1579,7 @@ SendAttackSquad = function(squad) ClearSquadLeader(squadLeader) end) - -- if not squad leader, follow the leader + -- if not squad leader, follow the leader else SquadLeaders[actorId] = squadLeader FollowSquadLeader(a, squad) @@ -1569,11 +1592,11 @@ SendAttackSquad = function(squad) end end) end - squad.IdleUnits = { } + squad.IdleUnits = {} end ClearSquadLeader = function(squadLeader) - for k,v in pairs(SquadLeaders) do + for k, v in pairs(SquadLeaders) do if v == squadLeader then SquadLeaders[k] = nil end @@ -1590,7 +1613,7 @@ FollowSquadLeader = function(actor, squad) actor.Stop() actor.AttackMove(cell, 1) - Trigger.AfterDelay(Utils.RandomInteger(35,65), function() + Trigger.AfterDelay(Utils.RandomInteger(35, 65), function() FollowSquadLeader(actor, squad) end) else @@ -1612,6 +1635,24 @@ FollowSquadLeader = function(actor, squad) end end +FollowActor = function(actor, actorToFollow) + if not actor.IsDead and actor.IsInWorld then + if not actorToFollow.IsDead and actorToFollow.IsInWorld then + local possibleCells = Utils.ExpandFootprint(Utils.ExpandFootprint({ actorToFollow.Location }, true)) + local cell = Utils.Random(possibleCells) + actor.Stop() + actor.AttackMove(cell, 1) + + Trigger.AfterDelay(Utils.RandomInteger(35, 65), function() + FollowActor(actor, actorToFollow) + end) + else + actor.Stop() + Trigger.ClearAll(actor) + end + end +end + SetupRefAndSilosCaptureCredits = function(player) local silosAndRefineries = player.GetActorsByTypes(CashRewardOnCaptureTypes) Utils.Do(silosAndRefineries, function(a) @@ -1701,7 +1742,8 @@ SellOnCaptureAttempt = function(buildings, sellAsGroup) local captureCells = Utils.ExpandFootprint(footprint, true) Trigger.OnEnteredFootprint(captureCells, function(a, id) - if IsMissionPlayer(a.Owner) and (a.Type == "e6" or a.Type == "n6" or a.Type == "s6" or a.Type == "mast" or (a.Type == "ifv" and a.HasPassengers and Utils.Any(a.Passengers, function(p) return p.Type == "e6" or p.Type == "n6" or p.Type == "s6" end))) then + if IsMissionPlayer(a.Owner) and (a.Type == "e6" or a.Type == "n6" or a.Type == "s6" or a.Type == "mast" or (a.Type == "ifv" and a.HasPassengers and Utils.Any(a.Passengers, function( + p) return p.Type == "e6" or p.Type == "n6" or p.Type == "s6" end))) then Trigger.RemoveFootprintTrigger(id) if sellAsGroup then Utils.Do(buildings, function(b2) @@ -1884,15 +1926,18 @@ IsGroundHunterUnit = function(actor) end IsGreeceGroundHunterUnit = function(actor) - return IsGroundHunterUnit(actor) and actor.Type ~= "arty" and actor.Type ~= "cryo" and actor.Type ~= "mgg" and actor.Type ~= "mrj" + return IsGroundHunterUnit(actor) and actor.Type ~= "arty" and actor.Type ~= "cryo" and actor.Type ~= "mgg" and + actor.Type ~= "mrj" end IsUSSRGroundHunterUnit = function(actor) - return IsGroundHunterUnit(actor) and actor.Type ~= "v2rl" and actor.Type ~= "v3rl" and actor.Type ~= "katy" and actor.Type ~= "grad" and actor.Type ~= "nukc" + return IsGroundHunterUnit(actor) and actor.Type ~= "v2rl" and actor.Type ~= "v3rl" and actor.Type ~= "katy" and + actor.Type ~= "grad" and actor.Type ~= "nukc" end IsGDIGroundHunterUnit = function(actor) - return (IsGroundHunterUnit(actor) or actor.Type == "jjet") and actor.Type ~= "msam" and actor.Type ~= "memp" and actor.Type ~= "thwk" + return (IsGroundHunterUnit(actor) or actor.Type == "jjet") and actor.Type ~= "msam" and actor.Type ~= "memp" and + actor.Type ~= "thwk" end IsNodGroundHunterUnit = function(actor) @@ -1927,13 +1972,10 @@ InitAiUpgrades = function(player, advancedDelay) end if (player.Faction == "allies") then - if IsHardOrAbove() then Actor.Create("cryw.upgrade", true, { Owner = Greece }) end - elseif (player.Faction == "soviet") then - if IsHardOrAbove() then Trigger.AfterDelay(advancedDelay, function() Actor.Create("tarc.upgrade", true, { Owner = player }) @@ -1943,9 +1985,7 @@ InitAiUpgrades = function(player, advancedDelay) Actor.Create(selectedDoctrineUpgrade, true, { Owner = player }) end) end - elseif (player.Faction == "nod") then - if IsHardOrAbove() then Trigger.AfterDelay(advancedDelay, function() Actor.Create("blacknapalm.upgrade", true, { Owner = player }) @@ -1955,9 +1995,7 @@ InitAiUpgrades = function(player, advancedDelay) Actor.Create("cyborgarmor.upgrade", true, { Owner = player }) end) end - elseif (player.Faction == "gdi") then - if IsHardOrAbove() then Actor.Create("sonic.upgrade", true, { Owner = player, }) Actor.Create("empgren.upgrade", true, { Owner = player, }) @@ -1965,8 +2003,8 @@ InitAiUpgrades = function(player, advancedDelay) Trigger.AfterDelay(advancedDelay, function() local strategyUpgrades = { { "bombard.strat", "bombard2.strat", "hailstorm.upgrade" }, - { "seek.strat", "seek2.strat", "hypersonic.upgrade" }, - { "hold.strat", "hold2.strat", "hammerhead.upgrade" }, + { "seek.strat", "seek2.strat", "hypersonic.upgrade" }, + { "hold.strat", "hold2.strat", "hammerhead.upgrade" }, } local selectedStrategyUpgrades = Utils.Random(strategyUpgrades) @@ -1975,9 +2013,7 @@ InitAiUpgrades = function(player, advancedDelay) end) end) end - elseif (player.Faction == "scrin") then - if IsHardOrAbove() then Actor.Create("ioncon.upgrade", true, { Owner = player }) @@ -1986,7 +2022,6 @@ InitAiUpgrades = function(player, advancedDelay) Actor.Create("shields.upgrade", true, { Owner = player }) end) end - end end @@ -2025,7 +2060,7 @@ AdjustAttackValuesForDifficulty = function(attackValues, difficulty) end AdjustCompositionsForDifficulty = function(compositions, difficulty) - local updatedCompositions = { } + local updatedCompositions = {} Utils.Do(compositions, function(comp) local updatedComposition = AdjustCompositionForDifficulty(comp, difficulty) @@ -2045,16 +2080,15 @@ AdjustCompositionForDifficulty = function(composition, difficulty) end -- total unadjusted cost for all units in each queue - local queueTotalUnitCost = { } + local queueTotalUnitCost = {} -- total adjusted cost for each queue - local queueAllocatedTotalUnitCost = { } + local queueAllocatedTotalUnitCost = {} -- units added to the adjusted composition - local updatedComposition = { } - - for k,v in pairs(composition) do + local updatedComposition = {} + for k, v in pairs(composition) do if IsQueue(k) then local queueName = k local queueUnits = v @@ -2062,7 +2096,7 @@ AdjustCompositionForDifficulty = function(composition, difficulty) queueAllocatedTotalUnitCost[queueName] = 0 -- for each unit in the queue - for i,unit in pairs(queueUnits) do + for i, unit in pairs(queueUnits) do local chosenUnit -- if the unit is a table of possible units, use the one with the highest cost for calculation purposes @@ -2079,11 +2113,12 @@ AdjustCompositionForDifficulty = function(composition, difficulty) queueTotalUnitCost[queueName] = queueTotalUnitCost[queueName] + UnitCosts[chosenUnit] end - local adjustedDesiredTotalUnitCostForQueue = queueTotalUnitCost[queueName] * CompositionValueMultipliers[difficulty] + local adjustedDesiredTotalUnitCostForQueue = queueTotalUnitCost[queueName] * + CompositionValueMultipliers[difficulty] -- allocate units until the adjusted cost is reached while queueAllocatedTotalUnitCost[queueName] < adjustedDesiredTotalUnitCostForQueue do - for i,unit in pairs(queueUnits) do + for i, unit in pairs(queueUnits) do if queueAllocatedTotalUnitCost[queueName] >= adjustedDesiredTotalUnitCostForQueue then break end @@ -2097,7 +2132,7 @@ AdjustCompositionForDifficulty = function(composition, difficulty) end if updatedComposition[queueName] == nil then - updatedComposition[queueName] = { } + updatedComposition[queueName] = {} end table.insert(updatedComposition[queueName], unit) @@ -2111,7 +2146,6 @@ AdjustCompositionForDifficulty = function(composition, difficulty) end else if k == "MinTime" or k == "MaxTime" then - if difficulty == "easy" then updatedComposition[k] = v * 1.4 elseif difficulty == "normal" then @@ -2119,7 +2153,6 @@ AdjustCompositionForDifficulty = function(composition, difficulty) elseif difficulty == "brutal" then updatedComposition[k] = v * 0.9 end - else updatedComposition[k] = v end @@ -2265,9 +2298,12 @@ CalculatePlayerCharacteristics = function() end) Utils.Do(MissionPlayers, function(p) - PlayerCharacteristics["MissionPlayers"].InfantryValue = PlayerCharacteristics["MissionPlayers"].InfantryValue + PlayerCharacteristics[p.InternalName].InfantryValue - PlayerCharacteristics["MissionPlayers"].HeavyValue = PlayerCharacteristics["MissionPlayers"].HeavyValue + PlayerCharacteristics[p.InternalName].HeavyValue - PlayerCharacteristics["MissionPlayers"].AirValue = PlayerCharacteristics["MissionPlayers"].AirValue + PlayerCharacteristics[p.InternalName].AirValue + PlayerCharacteristics["MissionPlayers"].InfantryValue = PlayerCharacteristics["MissionPlayers"].InfantryValue + + PlayerCharacteristics[p.InternalName].InfantryValue + PlayerCharacteristics["MissionPlayers"].HeavyValue = PlayerCharacteristics["MissionPlayers"].HeavyValue + + PlayerCharacteristics[p.InternalName].HeavyValue + PlayerCharacteristics["MissionPlayers"].AirValue = PlayerCharacteristics["MissionPlayers"].AirValue + + PlayerCharacteristics[p.InternalName].AirValue end) if PlayerCharacteristics["MissionPlayers"].InfantryValue > 20000 then @@ -2352,145 +2388,145 @@ PacOrDevastator = { "pac", "deva" } UnitCompositions = { Allied = { -- 0 to 10 minutes - { Infantry = {}, Vehicles = { "jeep", "jeep", "jeep", "jeep", "jeep" }, MaxTime = DateTime.Minutes(10) }, - { Infantry = {}, Vehicles = { "1tnk", "1tnk", "1tnk", "1tnk" }, MaxTime = DateTime.Minutes(10) }, - { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1" }, Vehicles = { "2tnk", "apc.ai", "2tnk", "arty" }, MaxTime = DateTime.Minutes(10) }, - { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e1", "e1" }, Vehicles = { "2tnk", "ifv.ai", "2tnk", "ifv.ai" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = {}, Vehicles = { "jeep", "jeep", "jeep", "jeep", "jeep" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = {}, Vehicles = { "1tnk", "1tnk", "1tnk", "1tnk" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1" }, Vehicles = { "2tnk", "apc.ai", "2tnk", "arty" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e1", "e1" }, Vehicles = { "2tnk", "ifv.ai", "2tnk", "ifv.ai" }, MaxTime = DateTime.Minutes(10) }, -- 10 minutes onwards - { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "ifv.ai", "aapc.ai", "apc.ai", "arty" }, MinTime = DateTime.Minutes(10) }, - { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "2tnk", "2tnk", "ifv.ai", "ptnk", "ptnk" }, MinTime = DateTime.Minutes(10) }, - { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "ifv.ai", "ptnk", "2tnk", "2tnk", AlliedT3SupportVehicle }, MinTime = DateTime.Minutes(10) }, - { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1" }, Vehicles = { "batf.ai", "ifv.ai", "batf.ai", "ifv.ai" }, MinTime = DateTime.Minutes(10) }, - { Infantry = {}, Vehicles = { "apc.ai", "jeep", "ifv.ai", "aapc.ai", "ifv.ai", "apc.ai" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "ifv.ai", "aapc.ai", "apc.ai", "arty" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "2tnk", "2tnk", "ifv.ai", "ptnk", "ptnk" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "ifv.ai", "ptnk", "2tnk", "2tnk", AlliedT3SupportVehicle }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1" }, Vehicles = { "batf.ai", "ifv.ai", "batf.ai", "ifv.ai" }, MinTime = DateTime.Minutes(10) }, + { Infantry = {}, Vehicles = { "apc.ai", "jeep", "ifv.ai", "aapc.ai", "ifv.ai", "apc.ai" }, MinTime = DateTime.Minutes(10) }, -- 16 minutes onwards - { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "2tnk", "ifv.ai", AlliedT3SupportVehicle, "2tnk", PrismCannonOrZeus }, MinTime = DateTime.Minutes(16) }, - { Infantry = { "e3", "enfo", "enfo", "enfo", "enfo", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "enfo", "enfo" }, Vehicles = { "2tnk", "ptnk", "2tnk", "2tnk", "2tnk", "ptnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", AlliedAdvancedInfantry, "e1", "e3", "e1", "e1", "e3", "e1", "e1" }, Vehicles = { "2tnk", "2tnk", "ifv.ai", AlliedT3SupportVehicle, "2tnk", PrismCannonOrZeus }, MinTime = DateTime.Minutes(16) }, + { Infantry = { "e3", "enfo", "enfo", "enfo", "enfo", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "e1", "e1", "e1", "e3", "enfo", "enfo" }, Vehicles = { "2tnk", "ptnk", "2tnk", "2tnk", "2tnk", "ptnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, ------ Anti-tank - { Infantry = { "e3", "e1", "e3", "e3", "e1", "e3", "e1", "e3", "e1", "e3", "e3", "e1", "e3", "e3", "e3", "e3" }, Vehicles = { "tnkd", "tnkd", "tnkd", "tnkd", "tnkd", "tnkd" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, + { Infantry = { "e3", "e1", "e3", "e3", "e1", "e3", "e1", "e3", "e1", "e3", "e3", "e1", "e3", "e3", "e3", "e3" }, Vehicles = { "tnkd", "tnkd", "tnkd", "tnkd", "tnkd", "tnkd" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, ------ Anti-infantry - { Infantry = { "e3", "enfo", "e1", "e1", "e1", "enfo", "e1", "e1", "enfo", "e3", "e1", "e1", "enfo", "e1", "e1", "e1", "e1", "e1", "e1", "enfo", "enfo" }, Vehicles = { "ptnk", "ptnk", "ptnk", "cryo", "ptnk", "ptnk" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, + { Infantry = { "e3", "enfo", "e1", "e1", "e1", "enfo", "e1", "e1", "enfo", "e3", "e1", "e1", "enfo", "e1", "e1", "e1", "e1", "e1", "e1", "enfo", "enfo" }, Vehicles = { "ptnk", "ptnk", "ptnk", "cryo", "ptnk", "ptnk" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, -- Specials - { Infantry = {}, Vehicles = { "ctnk", "ctnk", "ctnk", "ctnk", "ctnk", "ctnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "seal", "seal", "seal", "seal", "seal", "seal", "e7" }, Vehicles = { }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "snip", "snip", "snip", "snip", "snip", "snip", "snip", "snip" }, Vehicles = { "rtnk", "rtnk", "rtnk", "rtnk", "rtnk", "rtnk", "rtnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "e3", "cryt", "cryt", "cryt", "e3", "e1", "e1", "e1", "e1", "e1", "e1", "cryt", "cryt", "cryt", }, Vehicles = { "cryo", "2tnk", "2tnk", "cryo", "ifv", "cryo", "2tnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true } + { Infantry = {}, Vehicles = { "ctnk", "ctnk", "ctnk", "ctnk", "ctnk", "ctnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "seal", "seal", "seal", "seal", "seal", "seal", "e7" }, Vehicles = {}, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "snip", "snip", "snip", "snip", "snip", "snip", "snip", "snip" }, Vehicles = { "rtnk", "rtnk", "rtnk", "rtnk", "rtnk", "rtnk", "rtnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "e3", "cryt", "cryt", "cryt", "e3", "e1", "e1", "e1", "e1", "e1", "e1", "cryt", "cryt", "cryt", }, Vehicles = { "cryo", "2tnk", "2tnk", "cryo", "ifv", "cryo", "2tnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true } }, Soviet = { -- 0 to 10 minutes - { Infantry = { "e3", "e1", "e1", "e1", "e1", "e1", "e2", "e3", "e4" }, Vehicles = { "3tnk", "btr.ai", "3tnk" }, MaxTime = DateTime.Minutes(10), }, + { Infantry = { "e3", "e1", "e1", "e1", "e1", "e1", "e2", "e3", "e4" }, Vehicles = { "3tnk", "btr.ai", "3tnk" }, MaxTime = DateTime.Minutes(10), }, -- 10 to 16 minutes - { Infantry = { "e3", "e1", "e1", "e3", "shok", "e1", "shok", "e1", "e2", "e3", "e4", "e1" }, Vehicles = { "3tnk", SovietMammothVariant, "btr.ai", TeslaVariant, SovietBasicArty }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16), }, - { Infantry = { "e3", "e1", "shok", "e3", "shok", "e1", "shok", "e1", "shok", "e3", "e4", "e1" }, Vehicles = { TeslaVariant, SovietMammothVariant, "btr.ai", TeslaVariant, SovietBasicArty }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16), }, + { Infantry = { "e3", "e1", "e1", "e3", "shok", "e1", "shok", "e1", "e2", "e3", "e4", "e1" }, Vehicles = { "3tnk", SovietMammothVariant, "btr.ai", TeslaVariant, SovietBasicArty }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16), }, + { Infantry = { "e3", "e1", "shok", "e3", "shok", "e1", "shok", "e1", "shok", "e3", "e4", "e1" }, Vehicles = { TeslaVariant, SovietMammothVariant, "btr.ai", TeslaVariant, SovietBasicArty }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16), }, -- 16 minutes onwards { Infantry = { "e3", "e1", "e1", "e3", "shok", "e1", "e1", "cmsr", "e1", "e2", "e3", "e4", "e1", "e1", "e1", "e1", "shok" }, Vehicles = { "3tnk", SovietMammothVariant, "btr.ai", TeslaVariant, SovietBasicArty, SovietAdvancedArty }, MinTime = DateTime.Minutes(16), }, - { Infantry = { "e3", "e1", "e1", "e3", "ttrp", "e1", "ttrp", "e1", "cmsr", "e2", "e3", "e4", "e1", "e1", "e1", "e1" }, Vehicles = { "3tnk", SovietMammothVariant, "btr.ai", TeslaVariant, SovietBasicArty, SovietAdvancedArty }, MinTime = DateTime.Minutes(16), }, - { Infantry = { "e3", "e1", "e1", "e3", "e8", "e1", "e8", "e1", "deso", "deso", "e2", "e3", "e4", "e1", "e1", "e1", "e1" }, Vehicles = { SovietMammothVariant, "3tnk.atomic", "btr.ai", "3tnk.atomic", "apoc", "v3rl" }, MinTime = DateTime.Minutes(16), }, + { Infantry = { "e3", "e1", "e1", "e3", "ttrp", "e1", "ttrp", "e1", "cmsr", "e2", "e3", "e4", "e1", "e1", "e1", "e1" }, Vehicles = { "3tnk", SovietMammothVariant, "btr.ai", TeslaVariant, SovietBasicArty, SovietAdvancedArty }, MinTime = DateTime.Minutes(16), }, + { Infantry = { "e3", "e1", "e1", "e3", "e8", "e1", "e8", "e1", "deso", "deso", "e2", "e3", "e4", "e1", "e1", "e1", "e1" }, Vehicles = { SovietMammothVariant, "3tnk.atomic", "btr.ai", "3tnk.atomic", "apoc", "v3rl" }, MinTime = DateTime.Minutes(16), }, ------ Anti-tank - { Infantry = { "e3", "e1", "e3", "e3", "e1", "e3", "e1", "e3", "e1", "e3", "e3", "e1", "e3", "e3", "e3", "e3" }, Vehicles = { "ttra", "ttra", "ttra", "ttra", "ttra", "ttra" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, + { Infantry = { "e3", "e1", "e3", "e3", "e1", "e3", "e1", "e3", "e1", "e3", "e3", "e1", "e3", "e3", "e3", "e3" }, Vehicles = { "ttra", "ttra", "ttra", "ttra", "ttra", "ttra" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, ------ Anti-infantry - { Infantry = { "e3", "e1", "e1", "e1", "e1", "shok", "shok", "ttrp", "e1", "e1", "e1", "e1", "e1", "e1", "e1", "e1", "e1" }, Vehicles = { "btr", "btr", "ttnk", "v2rl", "ttnk", "btr", "v2rl", "v2rl" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, + { Infantry = { "e3", "e1", "e1", "e1", "e1", "shok", "shok", "ttrp", "e1", "e1", "e1", "e1", "e1", "e1", "e1", "e1", "e1" }, Vehicles = { "btr", "btr", "ttnk", "v2rl", "ttnk", "btr", "v2rl", "v2rl" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, -- Specials - { Infantry = { "ttrp", "ttrp", "ttrp", "ttrp", "ttrp", "ttrp", "ttrp", "ttrp" }, Vehicles = { "ttnk", "ttra", "ttnk", "ttra", "ttnk", "ttnk", "ttra" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "deso", "deso", "deso", "deso", "deso", "deso", "deso", "deso" }, Vehicles = { "4tnk.erad", "4tnk.erad", "4tnk.erad", "4tnk.erad", "4tnk.erad" }, MinTime = DateTime.Minutes(18), IsSpecial = true } + { Infantry = { "ttrp", "ttrp", "ttrp", "ttrp", "ttrp", "ttrp", "ttrp", "ttrp" }, Vehicles = { "ttnk", "ttra", "ttnk", "ttra", "ttnk", "ttnk", "ttra" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "deso", "deso", "deso", "deso", "deso", "deso", "deso", "deso" }, Vehicles = { "4tnk.erad", "4tnk.erad", "4tnk.erad", "4tnk.erad", "4tnk.erad" }, MinTime = DateTime.Minutes(18), IsSpecial = true } }, GDI = { -- 0 to 10 minutes - { Infantry = {}, Vehicles = { HumveeOrGuardianDrone, HumveeOrGuardianDrone, HumveeOrGuardianDrone, HumveeOrGuardianDrone, HumveeOrGuardianDrone }, MaxTime = DateTime.Minutes(10) }, - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n2", "n2" }, Vehicles = { "mtnk", "mtnk", "msam", "vulc", "vulc.ai" }, MaxTime = DateTime.Minutes(10) }, - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n2", "n2", "n1", "n1", "n1", "n3" }, Vehicles = { "mtnk", "mtnk", HumveeOrGuardianDrone, HumveeOrGuardianDrone }, MaxTime = DateTime.Minutes(10) }, + { Infantry = {}, Vehicles = { HumveeOrGuardianDrone, HumveeOrGuardianDrone, HumveeOrGuardianDrone, HumveeOrGuardianDrone, HumveeOrGuardianDrone }, MaxTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n2", "n2" }, Vehicles = { "mtnk", "mtnk", "msam", "vulc", "vulc.ai" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n2", "n2", "n1", "n1", "n1", "n3" }, Vehicles = { "mtnk", "mtnk", HumveeOrGuardianDrone, HumveeOrGuardianDrone }, MaxTime = DateTime.Minutes(10) }, -- 10 minutes onwards - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1" }, Vehicles = { "mtnk", "mtnk", "vulc", "vulc.ai", "jugg" }, MinTime = DateTime.Minutes(10) }, - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "jjet", "n1", "n3", "n1", "n1", "n1" }, Vehicles = { "mtnk", "vulc", "vulc.ai", "msam", "vulc.ai" }, MinTime = DateTime.Minutes(10) }, - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "jjet", "n1", "n3", "n1", "n1", "n1" }, Vehicles = { "titn", "mtnk", "msam", "vulc", GDIMammothVariant }, MinTime = DateTime.Minutes(10) }, - { Infantry = { "jjet", "bjet", "jjet", "jjet", "bjet", "jjet" }, Vehicles = { TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone }, MinTime = DateTime.Minutes(10) }, - { Infantry = {}, Vehicles = { "hsam", "hsam", "hsam", "hsam", "hsam", "hsam", "hsam" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1" }, Vehicles = { "mtnk", "mtnk", "vulc", "vulc.ai", "jugg" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "jjet", "n1", "n3", "n1", "n1", "n1" }, Vehicles = { "mtnk", "vulc", "vulc.ai", "msam", "vulc.ai" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "jjet", "n1", "n3", "n1", "n1", "n1" }, Vehicles = { "titn", "mtnk", "msam", "vulc", GDIMammothVariant }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "jjet", "bjet", "jjet", "jjet", "bjet", "jjet" }, Vehicles = { TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone, TOWHumveeOrGuardianDrone }, MinTime = DateTime.Minutes(10) }, + { Infantry = {}, Vehicles = { "hsam", "hsam", "hsam", "hsam", "hsam", "hsam", "hsam" }, MinTime = DateTime.Minutes(10) }, -- 10 to 16 minutes - { Infantry = { "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "htnk", WolverineOrXO, WolverineOrXO, "hsam", "vulc", GDIMammothVariant, WolverineOrXO }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16) }, + { Infantry = { "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "htnk", WolverineOrXO, WolverineOrXO, "hsam", "vulc", GDIMammothVariant, WolverineOrXO }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16) }, -- 16 minutes onwards - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", ZoneTrooperVariant, "n1", "n1", ZoneTrooperVariant, ZoneTrooperVariant }, Vehicles = { "mtnk", GDIMammothVariant, "vulc", "mtnk", "msam", GDIMammothVariant }, MinTime = DateTime.Minutes(16) }, - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n3" }, Vehicles = { "vulc.ai", "disr", "vulc", "disr", "disr" }, MinTime = DateTime.Minutes(16) }, - { Infantry = { "n3", "rmbo", "n3", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { GDIMammothVariant, "msam", "vulc", "msam", GDIMammothVariant }, MinTime = DateTime.Minutes(16) }, - { Infantry = { "n3", "n1", "n1", ZoneTrooperVariant, ZoneTrooperVariant, ZoneTrooperVariant, ZoneTrooperVariant, "n1", "n1" }, Vehicles = { GDIMammothVariant, "mtnk", WolverineOrXO, WolverineOrXO, GDIMammothVariant, GDIMammothVariant }, MinTime = DateTime.Minutes(16) }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", ZoneTrooperVariant, "n1", "n1", ZoneTrooperVariant, ZoneTrooperVariant }, Vehicles = { "mtnk", GDIMammothVariant, "vulc", "mtnk", "msam", GDIMammothVariant }, MinTime = DateTime.Minutes(16) }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n3" }, Vehicles = { "vulc.ai", "disr", "vulc", "disr", "disr" }, MinTime = DateTime.Minutes(16) }, + { Infantry = { "n3", "rmbo", "n3", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { GDIMammothVariant, "msam", "vulc", "msam", GDIMammothVariant }, MinTime = DateTime.Minutes(16) }, + { Infantry = { "n3", "n1", "n1", ZoneTrooperVariant, ZoneTrooperVariant, ZoneTrooperVariant, ZoneTrooperVariant, "n1", "n1" }, Vehicles = { GDIMammothVariant, "mtnk", WolverineOrXO, WolverineOrXO, GDIMammothVariant, GDIMammothVariant }, MinTime = DateTime.Minutes(16) }, ------ Anti-tank - { Infantry = { "n3", "n3", "n3", "ztrp", "n3", "n3", "ztrp", "ztrp", "ztrp", "ztrp" }, Vehicles = { GDIMammothVariant, "xo", GDIMammothVariant, "xo", GDIMammothVariant, "xo" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, + { Infantry = { "n3", "n3", "n3", "ztrp", "n3", "n3", "ztrp", "ztrp", "ztrp", "ztrp" }, Vehicles = { GDIMammothVariant, "xo", GDIMammothVariant, "xo", GDIMammothVariant, "xo" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, ------ Anti-infantry - { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n1", "n1", "n1" }, Vehicles = { "wolv", "wolv", "vulc", "vulc.ai", "wolv", "disr", "jugg", "wolv" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, + { Infantry = { "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n1", "n1", "n1" }, Vehicles = { "wolv", "wolv", "vulc", "vulc.ai", "wolv", "disr", "jugg", "wolv" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, -- Specials - { Infantry = { "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2" }, Vehicles = { "htnk.ion", "htnk.ion", "htnk.ion" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = {}, Vehicles = { "memp", "memp", "memp", "memp", "memp" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = {}, Vehicles = { "mtnk", "vulc", "thwk", "mtnk", "vulc", "thwk", "thwk", "thwk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "ztrp", "ztrp", "ztrp", "ztrp", "ztrp", "ztrp", "ztrp", "ztrp" }, Vehicles = { "titn.rail", "titn.rail", "titn.rail", "titn.rail", "titn.rail" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2", "n2" }, Vehicles = { "htnk.ion", "htnk.ion", "htnk.ion" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = {}, Vehicles = { "memp", "memp", "memp", "memp", "memp" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = {}, Vehicles = { "mtnk", "vulc", "thwk", "mtnk", "vulc", "thwk", "thwk", "thwk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "ztrp", "ztrp", "ztrp", "ztrp", "ztrp", "ztrp", "ztrp", "ztrp" }, Vehicles = { "titn.rail", "titn.rail", "titn.rail", "titn.rail", "titn.rail" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, }, Nod = { -- 0 to 10 minutes - { Infantry = {}, Vehicles = { "bike", "bike", "bike", "bike" }, MaxTime = DateTime.Minutes(10) }, - { Infantry = { "n3", "n1", "n1", "n1", "n4", "n1", "n1", "n1" }, Vehicles = { "bggy", "bggy", "bike", "bike" }, MaxTime = DateTime.Minutes(10) }, - { Infantry = { "n3", "n1", "n1", "n4", "n1", "n1", "n1" }, Vehicles = { "ltnk", "bggy", "bike" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = {}, Vehicles = { "bike", "bike", "bike", "bike" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n4", "n1", "n1", "n1" }, Vehicles = { "bggy", "bggy", "bike", "bike" }, MaxTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n4", "n1", "n1", "n1" }, Vehicles = { "ltnk", "bggy", "bike" }, MaxTime = DateTime.Minutes(10) }, -- 10 minutes onwards - { Infantry = { "n3", "n1", "n1", "n1", "n1", "n4", "n3", "bh", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "ltnk", "ltnk", FlameTankHeavyFlameTankOrHowitzer, "arty.nod" }, MinTime = DateTime.Minutes(10) }, - { Infantry = { "n3", "n1", "n1", "n1", "n4", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "ltnk", "arty.nod", FlameTankHeavyFlameTankOrHowitzer, "mlrs" }, MinTime = DateTime.Minutes(10) }, - { Infantry = { BasicCyborg, BasicCyborg, BasicCyborg, BasicCyborg, "tplr", AdvancedCyborg, "n1c", "n1c", BasicCyborg, AdvancedCyborg }, Vehicles = { "ltnk", FlameTankHeavyFlameTankOrHowitzer, "ltnk" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n1", "n4", "n3", "bh", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "ltnk", "ltnk", FlameTankHeavyFlameTankOrHowitzer, "arty.nod" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { "n3", "n1", "n1", "n1", "n4", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "ltnk", "arty.nod", FlameTankHeavyFlameTankOrHowitzer, "mlrs" }, MinTime = DateTime.Minutes(10) }, + { Infantry = { BasicCyborg, BasicCyborg, BasicCyborg, BasicCyborg, "tplr", AdvancedCyborg, "n1c", "n1c", BasicCyborg, AdvancedCyborg }, Vehicles = { "ltnk", FlameTankHeavyFlameTankOrHowitzer, "ltnk" }, MinTime = DateTime.Minutes(10) }, -- 10 to 16 minutes - { Infantry = {}, Vehicles = { "stnk.nod", "sapc.ai", "stnk.nod", "stnk.nod", "sapc.ai" }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16) }, + { Infantry = {}, Vehicles = { "stnk.nod", "sapc.ai", "stnk.nod", "stnk.nod", "sapc.ai" }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(16) }, -- 16 minutes onwards - { Infantry = {}, Vehicles = { "stnk.nod", "stnk.nod", "sapc.ai", "stnk.nod", "stnk.nod", "sapc.ai", "stnk.nod", "stnk.nod" }, MinTime = DateTime.Minutes(16) }, - { Infantry = { BasicCyborg, BasicCyborg, BasicCyborg, BasicCyborg, BasicCyborg, AdvancedCyborg, "n1c", "n1c", BasicCyborg, BasicCyborg, "rmbc", AdvancedCyborg, AdvancedCyborg }, Vehicles = { "ltnk", "ltnk", FlameTankHeavyFlameTankOrHowitzer, "mlrs" }, MinTime = DateTime.Minutes(16) }, + { Infantry = {}, Vehicles = { "stnk.nod", "stnk.nod", "sapc.ai", "stnk.nod", "stnk.nod", "sapc.ai", "stnk.nod", "stnk.nod" }, MinTime = DateTime.Minutes(16) }, + { Infantry = { BasicCyborg, BasicCyborg, BasicCyborg, BasicCyborg, BasicCyborg, AdvancedCyborg, "n1c", "n1c", BasicCyborg, BasicCyborg, "rmbc", AdvancedCyborg, AdvancedCyborg }, Vehicles = { "ltnk", "ltnk", FlameTankHeavyFlameTankOrHowitzer, "mlrs" }, MinTime = DateTime.Minutes(16) }, ------ Anti-tank - { Infantry = { "n3c", "n3c", "n1", "enli", "n4", "n1", "n3c", "enli", "n3c", "n3c", "enli", "n1" }, Vehicles = { "ltnk", "ltnk", "wtnk", "wtnk", "stnk.nod", "ltnk" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, + { Infantry = { "n3c", "n3c", "n1", "enli", "n4", "n1", "n3c", "enli", "n3c", "n3c", "enli", "n1" }, Vehicles = { "ltnk", "ltnk", "wtnk", "wtnk", "stnk.nod", "ltnk" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, ------ Anti-infantry - { Infantry = { "n4", "n4", "n1", "n1", "n1", "n1", "n1", "n1", "n4", "n4", "n4", "n4", "n1", "n1", "n1", "n1", "n4", "n4" }, Vehicles = { "ltnk.laser", "ltnk.laser", "mlrs", FlameTankHeavyFlameTankOrHowitzer, FlameTankHeavyFlameTankOrHowitzer, "mlrs" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, + { Infantry = { "n4", "n4", "n1", "n1", "n1", "n1", "n1", "n1", "n4", "n4", "n4", "n4", "n1", "n1", "n1", "n1", "n4", "n4" }, Vehicles = { "ltnk.laser", "ltnk.laser", "mlrs", FlameTankHeavyFlameTankOrHowitzer, FlameTankHeavyFlameTankOrHowitzer, "mlrs" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, -- Specials - { Infantry = { "bh", "bh", "bh", "bh", "bh", "bh", "bh", "bh", "bh" }, Vehicles = { "hftk", "hftk", "hftk", "hftk", "hftk", "hftk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "n3", "n1", "n1", "n1", "n4", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "wtnk", "wtnk", "wtnk", "wtnk", "wtnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { }, Vehicles = { "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "rmbc", "rmbc", "rmbc", "rmbc", "rmbc", "enli", "rmbc", "rmbc", "enli" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "reap", "reap", "reap", "reap", "reap", "reap", "reap", "reap", "reap" }, Vehicles = { "ltnk", "bike", "bike", "ltnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "bh", "bh", "bh", "bh", "bh", "bh", "bh", "bh", "bh" }, Vehicles = { "hftk", "hftk", "hftk", "hftk", "hftk", "hftk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "n3", "n1", "n1", "n1", "n4", "n1", "n3", "n1", "n1", "n1", "n1", "n1", "n1", "n3", "n1", "n1" }, Vehicles = { "wtnk", "wtnk", "wtnk", "wtnk", "wtnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = {}, Vehicles = { "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike", "bike" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "rmbc", "rmbc", "rmbc", "rmbc", "rmbc", "enli", "rmbc", "rmbc", "enli" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "reap", "reap", "reap", "reap", "reap", "reap", "reap", "reap", "reap" }, Vehicles = { "ltnk", "bike", "bike", "ltnk" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, }, Scrin = { -- 0 to 10 minutes - { Infantry = { "s3", "s1", "s1", "s1", "s3", "s3", "s4" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", GunWalkerSeekerOrLacerator }, MaxTime = DateTime.Minutes(10), }, + { Infantry = { "s3", "s1", "s1", "s1", "s3", "s3", "s4" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", GunWalkerSeekerOrLacerator }, MaxTime = DateTime.Minutes(10), }, -- 10 to 13 minutes - { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", CorrupterOrDevourer, GunWalkerSeekerOrLacerator, "tpod", GunWalkerSeekerOrLacerator, CorrupterOrDevourer }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(13), }, + { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", CorrupterOrDevourer, GunWalkerSeekerOrLacerator, "tpod", GunWalkerSeekerOrLacerator, CorrupterOrDevourer }, MinTime = DateTime.Minutes(10), MaxTime = DateTime.Minutes(13), }, -- 13 to 19 minutes - { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3", "s3", "s4", "s4" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", CorrupterOrDevourer, GunWalkerSeekerOrLacerator, TripodVariant, CorrupterOrDevourer }, Aircraft = { PacOrDevastator }, MinTime = DateTime.Minutes(13), MaxTime = DateTime.Minutes(19), }, + { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3", "s3", "s4", "s4" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", CorrupterOrDevourer, GunWalkerSeekerOrLacerator, TripodVariant, CorrupterOrDevourer }, Aircraft = { PacOrDevastator }, MinTime = DateTime.Minutes(13), MaxTime = DateTime.Minutes(19), }, ------ Anti-infantry - { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3", "s1", "s1", "s1", "s2" }, Vehicles = { "shrw", "corr", "corr", "shrw", "corr", "shrw", "corr", "corr" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, + { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3", "s1", "s1", "s1", "s2" }, Vehicles = { "shrw", "corr", "corr", "shrw", "corr", "shrw", "corr", "corr" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassInfantry" } }, ------ Anti-tank - { Infantry = { "s3", "s4", "s1", "s4", "s4", "s1", "s4", "s4", "s3", "s1", "s4", "s1", "s4", "s4", "s4" }, Vehicles = { "gunw", "devo", "devo", "gunw", "devo", "atmz", "devo", "tpod", "atmz", "devo" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, + { Infantry = { "s3", "s4", "s1", "s4", "s4", "s1", "s4", "s4", "s3", "s1", "s4", "s1", "s4", "s4", "s4" }, Vehicles = { "gunw", "devo", "devo", "gunw", "devo", "atmz", "devo", "tpod", "atmz", "devo" }, MinTime = DateTime.Minutes(16), RequiredTargetCharacteristics = { "MassHeavy" } }, -- 19 minutes onwards - { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3", "s3", "s4", "s4" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", CorrupterOrDevourer, GunWalkerSeekerOrLacerator, TripodVariant, AtomizerObliteratorOrRuiner }, Aircraft = { PacOrDevastator, "pac" }, MinTime = DateTime.Minutes(19), }, + { Infantry = { "s3", "s1", "s1", "s1", "s1", "s1", "s2", "s2", "s3", "s3", "s3", "s4", "s4" }, Vehicles = { "intl.ai2", GunWalkerSeekerOrLacerator, "intl.ai2", CorrupterOrDevourer, GunWalkerSeekerOrLacerator, TripodVariant, AtomizerObliteratorOrRuiner }, Aircraft = { PacOrDevastator, "pac" }, MinTime = DateTime.Minutes(19), }, -- Specials - { Infantry = { "brst", "brst", "brst", "brst", "brst", "brst", "brst" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "s3", "s3", "s2", "s2", "s2", "s2", "s2", "s2", "s2", "s2" }, Vehicles = { "tpod", "tpod", "tpod", "tpod", "tpod", "tpod" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, - { Infantry = { "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1" }, Vehicles = { "stcr", "gunw", "stcr", "gunw", "stcr", "gunw", "stcr" }, MinTime = DateTime.Minutes(18), IsSpecial = true } + { Infantry = { "brst", "brst", "brst", "brst", "brst", "brst", "brst" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "s3", "s3", "s2", "s2", "s2", "s2", "s2", "s2", "s2", "s2" }, Vehicles = { "tpod", "tpod", "tpod", "tpod", "tpod", "tpod" }, MinTime = DateTime.Minutes(18), IsSpecial = true }, + { Infantry = { "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1", "s1", "s3", "s1", "s1", "s1" }, Vehicles = { "stcr", "gunw", "stcr", "gunw", "stcr", "gunw", "stcr" }, MinTime = DateTime.Minutes(18), IsSpecial = true } } } @@ -2509,23 +2545,23 @@ ScrinWaterCompositions = { { Vehicles = { "intl", "intl.ai2", "seek" }, }, { Vehicles = { "seek", "seek", "seek" }, }, { Vehicles = { "lace", "lace", "seek", "seek" }, }, - { Vehicles = { "devo", "intl.ai2", "ruin" }, MinTime = DateTime.Minutes(7) }, - { Vehicles = { "intl", "intl.ai2", { "seek", "lace" }, { "devo", "devo", "ruin" }, { "devo", "atmz", "ruin" } }, MinTime = DateTime.Minutes(12) } + { Vehicles = { "devo", "intl.ai2", "ruin" }, MinTime = DateTime.Minutes(7) }, + { Vehicles = { "intl", "intl.ai2", { "seek", "lace" }, { "devo", "devo", "ruin" }, { "devo", "atmz", "ruin" } }, MinTime = DateTime.Minutes(12) } }, vhard = { { Vehicles = { "intl", "intl.ai2", "seek" }, }, { Vehicles = { "seek", "seek", "seek" }, }, { Vehicles = { "lace", "lace", "lace" }, }, - { Vehicles = { "devo", "intl.ai2", "ruin" }, MinTime = DateTime.Minutes(7) }, - { Vehicles = { "intl", "intl.ai2", { "seek", "lace" }, { "devo", "devo", "ruin" }, { "devo", "atmz", "ruin" } }, MinTime = DateTime.Minutes(12) } + { Vehicles = { "devo", "intl.ai2", "ruin" }, MinTime = DateTime.Minutes(7) }, + { Vehicles = { "intl", "intl.ai2", { "seek", "lace" }, { "devo", "devo", "ruin" }, { "devo", "atmz", "ruin" } }, MinTime = DateTime.Minutes(12) } }, brutal = { - { Vehicles = { "intl", "intl.ai2", "seek", "atmz" }, MaxTime = DateTime.Minutes(7) }, - { Vehicles = { "seek", "seek", "seek", "seek" }, MaxTime = DateTime.Minutes(7) }, - { Vehicles = { "lace", "lace", "lace", "lace" }, MaxTime = DateTime.Minutes(8) }, - { Vehicles = { "lace", "lace", "lace", "lace", "lace", "lace", "lace", "lace" }, MinTime = DateTime.Minutes(8) }, - { Vehicles = { "devo", "intl.ai2", "ruin", "ruin" }, MinTime = DateTime.Minutes(7), MaxTime = DateTime.Minutes(13) }, - { Vehicles = { "intl", "intl.ai2", { "seek", "lace" }, { "seek", "lace" }, { "devo", "devo", "ruin" }, { "devo", "atmz", "ruin" }, "ruin", "ruin" }, MinTime = DateTime.Minutes(13) } + { Vehicles = { "intl", "intl.ai2", "seek", "atmz" }, MaxTime = DateTime.Minutes(7) }, + { Vehicles = { "seek", "seek", "seek", "seek" }, MaxTime = DateTime.Minutes(7) }, + { Vehicles = { "lace", "lace", "lace", "lace" }, MaxTime = DateTime.Minutes(8) }, + { Vehicles = { "lace", "lace", "lace", "lace", "lace", "lace", "lace", "lace" }, MinTime = DateTime.Minutes(8) }, + { Vehicles = { "devo", "intl.ai2", "ruin", "ruin" }, MinTime = DateTime.Minutes(7), MaxTime = DateTime.Minutes(13) }, + { Vehicles = { "intl", "intl.ai2", { "seek", "lace" }, { "seek", "lace" }, { "devo", "devo", "ruin" }, { "devo", "atmz", "ruin" }, "ruin", "ruin" }, MinTime = DateTime.Minutes(13) } } } diff --git a/mods/ca/scripts/coop.lua b/mods/ca/scripts/coop.lua index e113e7c8e0..acde0f79cb 100644 --- a/mods/ca/scripts/coop.lua +++ b/mods/ca/scripts/coop.lua @@ -303,26 +303,23 @@ GoodSpread = function() end local function SyncObjectives() - local texts = { - primary = "Primary", - secondary = "Secondary", - newPrimary = "New primary objective", - newSecondary = "New secondary objective" - } + local primaryType = UserInterface.GetFluentMessage("primary") + local newPrimaryMsg = UserInterface.GetFluentMessage("new-primary-objective") + local newSecondaryMsg = UserInterface.GetFluentMessage("new-secondary-objective") Trigger.OnObjectiveAdded(MainPlayer, function(_, obid) local description = MainPlayer.GetObjectiveDescription(obid) local type = MainPlayer.GetObjectiveType(obid) - local required = type == texts.primary + local required = type == primaryType ForEachPlayer(function(player) player.AddObjective(description, type, required) local OBJcolour = HSLColor.Yellow if required then - Media.DisplayMessageToPlayer(player, description, texts.newPrimary, OBJcolour) + Media.DisplayMessageToPlayer(player, description, newPrimaryMsg, OBJcolour) else OBJcolour = HSLColor.Gray - Media.DisplayMessageToPlayer(player, description, texts.newSecondary, OBJcolour) + Media.DisplayMessageToPlayer(player, description, newSecondaryMsg, OBJcolour) end end) end) @@ -348,7 +345,7 @@ local function SyncObjectives() player.MarkCompletedObjective(obid) if player.IsLocalPlayer then Media.PlaySoundNotification(player, "AlertBleep") - Media.DisplayMessage(MainPlayer.GetObjectiveDescription(obid), "Objective completed", HSLColor.LimeGreen) + Media.DisplayMessage(MainPlayer.GetObjectiveDescription(obid), UserInterface.GetFluentMessage("objective-completed"), HSLColor.LimeGreen) end end) end) @@ -358,7 +355,7 @@ local function SyncObjectives() player.MarkFailedObjective(obid) if player.IsLocalPlayer then Media.PlaySoundNotification(player, "AlertBleep") - Media.DisplayMessage(MainPlayer.GetObjectiveDescription(obid), "Objective failed", HSLColor.Red) + Media.DisplayMessage(MainPlayer.GetObjectiveDescription(obid), UserInterface.GetFluentMessage("objective-failed"), HSLColor.Red) end end) end) @@ -444,7 +441,7 @@ PlayerDefeatedOrDisconnected = function(player) local playerName = player.Name if selectedMessage ~= nil then local formattedMessage = string.gsub(selectedMessage, "PID", playerName) - Media.DisplayMessage(formattedMessage, "High Command", player.Color) + Media.DisplayMessage(formattedMessage, UserInterface.GetFluentMessage("high-command"), player.Color) end end end) @@ -854,7 +851,7 @@ local function SetExtraMines() Utils.Do(AllSpawners,function(SID) SID.Destroy() end) - Media.DisplayMessage("All resource spawners are deleted now. Good luck!") + Media.DisplayMessage(UserInterface.GetFluentMessage("resource-spawners-deleted")) end) end end