diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index cf9488457bcf..c18dc861864a 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -34,7 +34,7 @@ public sealed class WorldRenderer : IDisposable readonly HashSet onScreenActors = new HashSet(); readonly HardwarePalette palette = new HardwarePalette(); readonly Dictionary palettes = new Dictionary(); - readonly TerrainRenderer terrainRenderer; + readonly IRenderTerrain terrainRenderer; readonly Lazy debugVis; readonly Func createPaletteReference; readonly bool enableDepthBuffer; diff --git a/OpenRA.Mods.Cnc/Activities/LayMines.cs b/OpenRA.Mods.Cnc/Activities/LayMines.cs index f87a2e7ef270..4bf376cf350a 100644 --- a/OpenRA.Mods.Cnc/Activities/LayMines.cs +++ b/OpenRA.Mods.Cnc/Activities/LayMines.cs @@ -109,7 +109,7 @@ Activity IDockActivity.ApproachDockActivities(Actor host, Actor client, Dock doc Activity IDockActivity.DockActivities(Actor host, Actor client, Dock dock) { return ActivityUtils.SequenceActivities( - new Rearm(client), + new Rearm(client, host, new WDist(512)), new Repair(client, host, new WDist(512))); } diff --git a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj index 4a6b8fd3eb2a..a810338b9c12 100644 --- a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj +++ b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj @@ -66,7 +66,6 @@ - diff --git a/OpenRA.Mods.Cnc/Traits/AttackPopupTurreted.cs b/OpenRA.Mods.Cnc/Traits/Attack/AttackPopupTurreted.cs similarity index 100% rename from OpenRA.Mods.Cnc/Traits/AttackPopupTurreted.cs rename to OpenRA.Mods.Cnc/Traits/Attack/AttackPopupTurreted.cs diff --git a/OpenRA.Mods.Cnc/Traits/Disguise.cs b/OpenRA.Mods.Cnc/Traits/Disguise.cs index cefbaf265d65..34289f8d51ea 100644 --- a/OpenRA.Mods.Cnc/Traits/Disguise.cs +++ b/OpenRA.Mods.Cnc/Traits/Disguise.cs @@ -15,6 +15,7 @@ using System.Linq; using OpenRA.Mods.Common.Orders; using OpenRA.Mods.Common.Traits; +using OpenRA.Mods.Common.Traits.Render; using OpenRA.Primitives; using OpenRA.Traits; diff --git a/OpenRA.Mods.Cnc/Traits/TDGunboat.cs b/OpenRA.Mods.Cnc/Traits/TDGunboat.cs index 10e8a0e36c9f..07a7de712422 100644 --- a/OpenRA.Mods.Cnc/Traits/TDGunboat.cs +++ b/OpenRA.Mods.Cnc/Traits/TDGunboat.cs @@ -206,7 +206,7 @@ public bool CanEnterTargetNow(Actor self, Target target) return false; } - bool IMove.TurnWhileDisabled(Actor self) + public bool TurnWhileDisabled(Actor self) { return false; } diff --git a/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs b/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs index 31df1dbdac73..622cd4abad6e 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs @@ -41,7 +41,7 @@ IEnumerable GetHelipads(Actor self) { return self.World.ActorsHavingTrait().Where(a => a.Owner == self.Owner && - aircraft.Info.RearmBuildings.Contains(a.Info.Name) && + rearmable.Info.RearmActors.Contains(a.Info.Name) && !a.IsDead && !a.Disposed); } @@ -97,9 +97,6 @@ public override Activity Tick(Actor self) NextActivity); } - if (aircraft.Info.TurnToDock) - landingProcedures.Add(new Turn(self, initialFacing)); - // Do we need to land and reload/repair? if (!ShouldLandAtBuilding(self, dest)) { diff --git a/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs b/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs index a4f5617c5617..424f2e47bf85 100644 --- a/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs +++ b/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs @@ -40,9 +40,9 @@ public ReturnToBase(Actor self, bool abortOnResupply, Actor dest = null, bool al public static IEnumerable GetAirfields(Actor self) { - var rearmBuildings = self.Info.TraitInfo().RearmBuildings; + var rearmActors = self.Info.TraitInfo().RearmActors; return self.World.ActorsHavingTrait() - .Where(a => a.Owner == self.Owner && rearmBuildings.Contains(a.Info.Name)); + .Where(a => a.Owner == self.Owner && rearmActors.Contains(a.Info.Name)); } int CalculateTurnRadius(int speed) @@ -53,7 +53,7 @@ int CalculateTurnRadius(int speed) void CalculateLandingPath(Actor self, Dock dock, out WPos w1, out WPos w2, out WPos w3) { var landPos = dock.CenterPosition; - var altitude = aircraftInfo.CruiseAltitude.Length; + var altitude = aircraft.Info.CruiseAltitude.Length; // Distance required for descent. var landDistance = altitude * 1024 / aircraft.Info.MaximumPitch.Tan(); @@ -63,7 +63,7 @@ void CalculateLandingPath(Actor self, Dock dock, out WPos w1, out WPos w2, out W // Add 10% to the turning radius to ensure we have enough room var speed = aircraft.MovementSpeed * 32 / 35; - var turnRadius = CalculateTurnRadius(aircraftInfo, speed); + var turnRadius = CalculateTurnRadius(speed); // Find the center of the turning circles for clockwise and counterclockwise turns var angle = WAngle.FromFacing(aircraft.Facing); @@ -168,7 +168,7 @@ public Activity LandingProcedure(Actor self, Dock dock) List landingProcedures = new List(); - var turnRadius = CalculateTurnRadius(aircraft.Info, aircraft.Info.Speed); + var turnRadius = CalculateTurnRadius(aircraft.Info.Speed); landingProcedures.Add(new Fly(self, Target.FromPos(w1), WDist.Zero, new WDist(turnRadius * 3))); landingProcedures.Add(new Fly(self, Target.FromPos(w2))); diff --git a/OpenRA.Mods.Common/Activities/Transform.cs b/OpenRA.Mods.Common/Activities/Transform.cs index 112c5fda56f0..0c0696ac677f 100644 --- a/OpenRA.Mods.Common/Activities/Transform.cs +++ b/OpenRA.Mods.Common/Activities/Transform.cs @@ -9,6 +9,7 @@ */ #endregion +using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits.Render; diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index f0ccf4457db5..57bd37ebae30 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -156,7 +156,6 @@ - @@ -212,7 +211,6 @@ - diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 396431eb50c9..5a4f6034fb97 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -110,7 +110,7 @@ IEnumerable InnerOrder(World world, CPos cell, MouseInput mi) { var orderType = "PlaceBuilding"; var topLeft = viewport.ViewToWorld(Viewport.LastMousePos + topLeftScreenOffset); - var cannotBuildAudio = buildableInfo.CannotBuildAudio != null ? buildableInfo.CannotBuildAudio : queue.Info.CannotPlaceNotification; + var cannotBuildAudio = buildableInfo.CannotBuildAudio != null ? buildableInfo.CannotBuildAudio : queue.Info.CannotBuildAudio; var plugInfo = actorInfo.TraitInfoOrDefault(); if (plugInfo != null) @@ -125,7 +125,7 @@ IEnumerable InnerOrder(World world, CPos cell, MouseInput mi) else { if (!world.CanPlaceBuilding(topLeft, actorInfo, buildingInfo, null) - || !buildingInfo.IsCloseEnoughToBase(world, owner, actorInfo, topLeft)) + || !buildingInfo.IsCloseEnoughToBase(world, owner, actorInfo, queue.Actor, topLeft)) { foreach (var order in ClearBlockersOrders(world, topLeft)) yield return order; @@ -201,9 +201,9 @@ public IEnumerable RenderAboveShroud(WorldRenderer wr, World world) if (!Game.GetModifierKeys().HasModifier(Modifiers.Shift)) foreach (var t in BuildingUtils.GetLineBuildCells(world, topLeft, actorInfo, buildingInfo)) - cells.Add(t.First, MakeCellType(buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, t.First), true)); + cells.Add(t.First, MakeCellType(buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, queue.Actor, t.First), true)); - cells[topLeft] = MakeCellType(buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, topLeft)); + cells[topLeft] = MakeCellType(buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, queue.Actor, topLeft)); } else { @@ -235,7 +235,7 @@ public IEnumerable RenderAboveShroud(WorldRenderer wr, World world) yield return r; var res = world.WorldActor.TraitOrDefault(); - var isCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, topLeft); + var isCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, queue.Actor, topLeft); foreach (var t in buildingInfo.Tiles(topLeft)) cells.Add(t, MakeCellType(isCloseEnough && world.IsCellBuildable(t, actorInfo, buildingInfo) && (res == null || res.GetResource(t) == null))); } diff --git a/OpenRA.Mods.Common/Scripting/Properties/HackyAIProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/HackyAIProperties.cs deleted file mode 100644 index 77d54dca5564..000000000000 --- a/OpenRA.Mods.Common/Scripting/Properties/HackyAIProperties.cs +++ /dev/null @@ -1,45 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System.Linq; -using OpenRA.Mods.Common.AI; -using OpenRA.Scripting; - -namespace OpenRA.Mods.Common.Scripting -{ - [ScriptPropertyGroup("HackyAI")] - public class HackyAIProperties : ScriptActorProperties - { - readonly HackyAI hackyAI; - - public HackyAIProperties(ScriptContext context, Actor self) - : base(context, self) - { - hackyAI = self.Owner.PlayerActor.TraitsImplementing().Where(b => b.IsEnabled).FirstOrDefault(); - } - - [ScriptContext(ScriptContextType.AI)] - [ScriptActorPropertyActivity] - [Desc("Mark this actor as occupied by lua scripting and disable control from Hacky AI.")] - public bool HackyAIOccupied - { - get - { - return hackyAI.IsLuaOccupied(Self); - } - - set - { - hackyAI.SetLuaOccupied(Self, value); - } - } - } -} \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 58c5d80ed2ad..6394ec2d28cf 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -53,10 +53,6 @@ public class AircraftInfo : ITraitInfo, IPositionableInfo, IFacingInfo, IMoveInf [Desc("Can the actor be ordered to move in to shroud?")] public readonly bool MoveIntoShroud = true; - public virtual object Create(ActorInitializer init) { return new Aircraft(init, this); } - public int GetInitialFacing() { return InitialFacing; } - public WDist GetCruiseAltitude() { return CruiseAltitude; } - [VoiceReference] public readonly string Voice = "Action"; [GrantedConditionReference] @@ -366,8 +362,11 @@ public Actor GetSupplierActorBelow() if (self.World.Map.DistanceAboveTerrain(CenterPosition).Length != 0) return null; // not on the ground. + if (rearmableInfo == null && repairableInfo == null) + return null; + return self.World.ActorMap.GetActorsAt(self.Location) - .FirstOrDefault(a => Info.RearmBuildings.Contains(a.Info.Name) || Info.RepairBuildings.Contains(a.Info.Name)); + .FirstOrDefault(a => rearmableInfo.RearmActors.Contains(a.Info.Name) || repairableInfo.RepairBuildings.Contains(a.Info.Name)); } protected void ReserveSpawnBuilding() diff --git a/OpenRA.Mods.Common/Traits/Armament.cs b/OpenRA.Mods.Common/Traits/Armament.cs index f505e5a311d0..f88cb62d4b79 100644 --- a/OpenRA.Mods.Common/Traits/Armament.cs +++ b/OpenRA.Mods.Common/Traits/Armament.cs @@ -257,7 +257,7 @@ public virtual Barrel CheckFire(Actor self, IFacing facing, Target target) currentBarrel++; foreach (var na in notifyAttacks) - na.PreparingAttack(self, target, this, barrel) + na.PreparingAttack(self, target, this, barrel); Func muzzlePosition = () => self.CenterPosition + MuzzleOffset(self, barrel); var legacyFacing = MuzzleOrientation(self, barrel).Yaw.Angle / 4; diff --git a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs index 6cd175b60379..22b3ba2a35b6 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs @@ -27,7 +27,6 @@ class BaseBuilderQueueManager readonly Player player; readonly PowerManager playerPower; readonly PlayerResources playerResources; - readonly AIScriptContext context; int waitTicks; Actor[] playerBuildings; @@ -42,7 +41,7 @@ class BaseBuilderQueueManager WaterCheck waterState = WaterCheck.NotChecked; public BaseBuilderQueueManager(BaseBuilderBotModule baseBuilder, string category, Player p, PowerManager pm, - PlayerResources pr, BitArray resourceTypeIndices, AIScriptContext context) + PlayerResources pr, BitArray resourceTypeIndices) { this.baseBuilder = baseBuilder; world = p.World; @@ -53,7 +52,6 @@ class BaseBuilderQueueManager failRetryTicks = baseBuilder.Info.StructureProductionResumeDelay; minimumExcessPower = baseBuilder.Info.MinimumExcessPower; this.resourceTypeIndices = resourceTypeIndices; - this.context = context; } public void Tick(IBot bot) @@ -142,7 +140,7 @@ bool TickQueue(IBot bot, ProductionQueue queue) else if (baseBuilder.Info.FragileTypes.Contains(world.Map.Rules.Actors[currentBuilding.Item].Name)) type = BuildingType.Fragile; - var location = ChooseBuildLocation(currentBuilding.Item, true, type); + var location = ChooseBuildLocation(currentBuilding.Item, true, queue.Actor, type); if (location == null) { AIUtils.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); @@ -189,8 +187,8 @@ ActorInfo GetProducibleBuilding(HashSet actors, IEnumerable b var producers = world.Actors.Where(a => a.Owner == player && a.TraitsImplementing() != null); var productionQueues = producers.SelectMany(a => a.TraitsImplementing()); - var activeProductionQueues = productionQueues.Where(pq => pq.CurrentItem() != null); - var queues = activeProductionQueues.Where(pq => pq.CurrentItem().Item == actor.Name); + var activeProductionQueues = productionQueues.Where(pq => pq.AllQueued().Any()); + var queues = activeProductionQueues.Select(pq => pq.AllQueued().Where(q => q.Item == actor.Name)); return playerBuildings.Count(a => a.Info.Name == actor.Name) + queues.Count() < baseBuilder.Info.BuildingLimits[actor.Name]; }); @@ -206,84 +204,14 @@ bool HasSufficientPowerForActor(ActorInfo actorInfo) return playerPower == null || (actorInfo.TraitInfos().Where(i => i.EnabledByDefault) .Sum(p => p.Amount) + playerPower.ExcessPower) >= baseBuilder.Info.MinimumExcessPower; } - - ActorInfo QueryScript(ProductionQueue queue, IEnumerable buildableThings) - { - var luaParams = context.CreateTable(); - - // Lets prepare parameters for lua call. - // Modders are free to add more if necessary. - // We assert that no buildings have names like nil or none or null. - // (Which crazy modders will do that anyway? Unless they are making a mod that is themed computer science/mathematics) - luaParams.Add("queue_type", queue.Info.Type.ToLowerInvariant()); - - var player_buildings = playerBuildings.Select(pb => pb.Info.Name.ToLowerInvariant()).ToArray(); - luaParams.Add("player_buildings", player_buildings.ToLuaValue(context)); - - var buildable_things = buildableThings.Select(th => th.Name.ToLowerInvariant()).ToArray(); - luaParams.Add("builable_things", buildable_things.ToLuaValue(context)); - - var power = GetProducibleBuilding(baseBuilder.Info.PowerTypes, buildableThings, - a => a.TraitInfos().Where(i => i.EnabledByDefault).Sum(p => p.Amount)); - int powerGen = 0; - if (power != null) - powerGen = power.TraitInfos().Where(i => i.EnabledByDefault).Sum(p => p.Amount); - - // Factions like GLA doesn't have powerplants. Must check. - if (power != null && powerGen > 0) - { - luaParams.Add("power", power.Name); - luaParams.Add("power_gen", powerGen); - } - else - { - luaParams.Add("power", null); - luaParams.Add("power_gen", 0); - } - - // excess power information - luaParams.Add("excess_power", playerPower.ExcessPower); - luaParams.Add("minimum_excess_power", baseBuilder.Info.MinimumExcessPower); - - // Finally! Call lua func. - var ret = context.CallLuaFunc("BB_choose_building_to_build", luaParams); - if (ret == null) - return null; // shouldn't happen but just to be sure. - if (ret.Count() == 0) - return null; // hmmm.. this shouldn't happen either. - - // get ret val and dispose stuff. - string n = ret[0].ToString().ToLowerInvariant(); - ret.Dispose(); - luaParams.Dispose(); - - // decode results for AI. - // Modders may not use "nil" as their building name. I'm not sure of a good way to enforce that. - if (n == "nil") - return null; // lua chose to build nothing. - - if (world.Map.Rules.Actors.ContainsKey(n)) - return world.Map.Rules.Actors[n]; - - // If not found, it can be some errorneous lua input. - // However, there is a special keyword that allows AI to do old hacky behavior. - if (n != "hacky_fallback") - return null; - - // Fall back to hacky selection. - return HackyChooseBuildingToBuild(queue, buildableThings); - } - + ActorInfo ChooseBuildingToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); if (!buildableThings.Any()) return null; - if (context != null) - return QueryScript(queue, buildableThings); - else - return HackyChooseBuildingToBuild(queue, buildableThings); + return HackyChooseBuildingToBuild(queue, buildableThings); } ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable buildableThings) @@ -320,7 +248,7 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable 0 && baseBuilder.Info.ProductionMinimumCash <= playerResources.Cash && playerResources.Cash > baseBuilder.Info.NewProductionCashThreshold) + if (baseBuilder.Info.NewProductionCashThreshold > 0 && baseBuilder.Info.ConstructionMinimumCash <= playerResources.Cash && playerResources.Cash > baseBuilder.Info.NewProductionCashThreshold) { var production = GetProducibleBuilding(baseBuilder.Info.ProductionTypes, buildableThings); if (production != null && HasSufficientPowerForActor(production)) @@ -338,7 +266,7 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable 0 - && baseBuilder.Info.ProductionMinimumCash <= playerResources.Cash + && baseBuilder.Info.ConstructionMinimumCash <= playerResources.Cash && playerResources.Resources > baseBuilder.Info.NewProductionCashThreshold && AIUtils.IsAreaAvailable(world, player, world.Map, baseBuilder.Info.CheckForWaterRadius, baseBuilder.Info.WaterTerrainTypes)) { @@ -376,16 +304,12 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable playerResources.Cash) -======= if (baseBuilder.Info.QueueTimeLimits != null && baseBuilder.Info.QueueTimeLimits.ContainsKey(queue.Info.Type) && baseBuilder.Info.QueueTimeLimits[queue.Info.Type] > world.WorldTick) break; - if (baseBuilder.Info.ProductionMinimumCash > playerResources.Cash) ->>>>>>> Added a way to limit A.ı to not build from a queue till a specified time is passed:OpenRA.Mods.Common/AI/BaseBuilder.cs + if (baseBuilder.Info.ConstructionMinimumCash > playerResources.Cash) break; var name = frac.Key; @@ -406,8 +330,8 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable a.Owner == queue.Actor.Owner && a.TraitsImplementing() != null); var productionQueues = producers.SelectMany(a => a.TraitsImplementing()); - var activeProductionQueues = productionQueues.Where(pq => pq.CurrentItem() != null); - var queues = activeProductionQueues.Where(pq => pq.CurrentItem().Item == name); + var activeProductionQueues = productionQueues.Where(pq => pq.AllQueued().Any()); + var queues = activeProductionQueues.Select(pq => pq.AllQueued().Where(q => q.Item == name)); var count = playerBuildings.Count(a => a.Info.Name == name) + (queues == null ? 0 : queues.Count()); if (count * 100 > frac.Value * playerBuildings.Length) @@ -425,6 +349,12 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable(); @@ -482,7 +412,7 @@ ActorInfo HackyChooseBuildingToBuild(ProductionQueue queue, IEnumerable().Where(a => !a.Disposed && player.Stances[a.Owner] == Stance.Enemy) + var closestEnemyDefense = world.ActorsHavingTrait().Where(a => !a.Disposed && player.Stances[a.Owner] == Stance.Enemy) .ClosestTo(world.Map.CenterOfCell(baseBuilder.DefenseCenter)); - var targetCell = closestEnemy != null ? closestEnemy.Location : baseCenter; - return findPos(baseBuilder.DefenseCenter, targetCell, baseBuilder.Info.MinimumDefenseRadius, baseBuilder.Info.MaximumDefenseRadius); + var targetCellDefense = closestEnemyDefense != null ? closestEnemyDefense.Location : baseCenter; + return findPos(baseBuilder.DefenseCenter, targetCellDefense, baseBuilder.Info.MinimumDefenseRadius, baseBuilder.Info.MaximumDefenseRadius); case BuildingType.Fragile: - { - // Build far from the closest enemy structure - var closestEnemy = world.ActorsHavingTrait().Where(a => !a.Disposed && player.Stances[a.Owner] == Stance.Enemy) - .ClosestTo(world.Map.CenterOfCell(baseBuilder.DefenseCenter)); - CVec direction = CVec.Zero; - if (closestEnemy != null) - direction = baseCenter - closestEnemy.Location; + // Build far from the closest enemy structure + var closestEnemyFraigle = world.ActorsHavingTrait().Where(a => !a.Disposed && player.Stances[a.Owner] == Stance.Enemy) + .ClosestTo(world.Map.CenterOfCell(baseBuilder.DefenseCenter)); - // MinFragilePlacementRadius introduced to push fragile buildings away from base center. - // Resilient to nuke. - var pos = findPos(baseCenter, direction, baseBuilder.Info.MinFragilePlacementRadius, - distanceToBaseIsImportant ? baseBuilder.Info.MaxBaseRadius : world.Map.Grid.MaximumTileSearchRange); + var targetCellFraigle = closestEnemyFraigle != null ? closestEnemyFraigle.Location : baseCenter; - if (pos == null) // rear placement failed but we can still try placing anywhere. - pos = findPos(baseCenter, baseCenter, baseBuilder.Info.MinBaseRadius, - distanceToBaseIsImportant ? baseBuilder.Info.MaxBaseRadius : world.Map.Grid.MaximumTileSearchRange); + // MinFragilePlacementRadius introduced to push fragile buildings away from base center. + // Resilient to nuke. + var pos = findPos(baseCenter, targetCellFraigle, baseBuilder.Info.MinFragilePlacementRadius, + distanceToBaseIsImportant ? baseBuilder.Info.MaxBaseRadius : world.Map.Grid.MaximumTileSearchRange); - return pos; - } + return pos; case BuildingType.Refinery: diff --git a/OpenRA.Mods.Common/Traits/BotModules/CaptureManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/CaptureManagerBotModule.cs index a651c467e191..484500f79e48 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/CaptureManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/CaptureManagerBotModule.cs @@ -109,7 +109,7 @@ IEnumerable GetVisibleActorsBelongingToPlayer(Player owner) IEnumerable GetActorsThatCanBeOrderedByPlayer(Player owner) { foreach (var actor in world.Actors) - if (actor.Owner == owner && !actor.IsDead && actor.IsInWorld) + if (actor.Owner == owner && !actor.IsDead && actor.IsInWorld && Info.CapturableActorTypes.Contains(actor.Info.Name)) yield return actor; } @@ -155,9 +155,6 @@ void QueueCaptureOrders(IBot bot) .OrderByDescending(target => target.Actor.GetSellValue()) .Take(maximumCaptureTargetOptions); - if (Info.CapturableActorTypes.Any()) - capturableTargetOptions = capturableTargetOptions.Where(target => Info.CapturableActorTypes.Contains(target.Actor.Info.Name.ToLowerInvariant())); - if (!capturableTargetOptions.Any()) return; diff --git a/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs index bc843be49821..31fa04f261cf 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs @@ -206,7 +206,7 @@ void DeployMcv(IBot bot, Actor mcv, bool move) if (!world.CanPlaceBuilding(cell + offset, actorInfo, bi, null)) continue; - if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, player, actorInfo, cell)) + if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, player, actorInfo, null, cell)) continue; return cell; diff --git a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs index e8de329d5318..6527d13efea2 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs @@ -34,6 +34,14 @@ public class SquadManagerBotModuleInfo : ConditionalTraitInfo [Desc("Enemy building types around which to scan for targets for naval squads.")] public readonly HashSet NavalProductionTypes = new HashSet(); + [Desc("Tells the AI what building types are considered as anti-air defense." + + "Used by air squads to escape from them.")] + public readonly HashSet StaticAATypes = new HashSet(); + + [Desc("Tells the AI what unit types are considered as siege units" + + "Those units are not used by defensive squads.")] + public readonly HashSet SiegeUnitTypes = new HashSet(); + [Desc("Minimum number of units AI must have before attacking.")] public readonly int SquadSize = 8; @@ -273,7 +281,8 @@ void TryToRushAttack(IBot bot) // TODO: This should use common names & ExcludeFromSquads instead of hardcoding TraitInfo checks var ownUnits = activeUnits .Where(unit => unit.IsIdle && unit.Info.HasTraitInfo() - && !unit.Info.HasTraitInfo() && !unit.Info.HasTraitInfo()).ToList(); + && !unit.Info.HasTraitInfo() && !unit.Info.HasTraitInfo() + && !unit.Info.HasTraitInfo()).ToList(); if (!allEnemyBaseBuilder.Any() || ownUnits.Count < Info.SquadSize) return; @@ -312,7 +321,8 @@ void ProtectOwn(IBot bot, Actor attacker) { var ownUnits = World.FindActorsInCircle(World.Map.CenterOfCell(GetRandomBaseCenter()), WDist.FromCells(Info.ProtectUnitScanRadius)) .Where(unit => unit.Owner == Player && !unit.Info.HasTraitInfo() && !unit.Info.HasTraitInfo() - && unit.Info.HasTraitInfo()); + && unit.Info.HasTraitInfo() && unit.Info.HasTraitInfo() + && !Info.ExcludeFromSquadsTypes.Contains(unit.Info.Name)); foreach (var a in ownUnits) protectSq.Units.Add(a); diff --git a/OpenRA.Mods.Common/Traits/BotModules/Squads/States/AirStates.cs b/OpenRA.Mods.Common/Traits/BotModules/Squads/States/AirStates.cs index c0144703943c..5da0db739fc7 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/Squads/States/AirStates.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/Squads/States/AirStates.cs @@ -82,7 +82,7 @@ protected static List ScanEnemyUnits(Squad owner, int maxCount) { sampledCandidates = new List(); for (int i = 0; i < 20; i++) - sampledCandidates.Add(cands.Random(owner.Bot.Random)); + sampledCandidates.Add(cands.Random(owner.Random)); } // Sort them by distance. @@ -215,7 +215,7 @@ protected IEnumerable EnemyStaticAAs(Squad owner) var anyUnit = owner.Units.First(); return owner.World.ActorsHavingTrait().Where(b => // from buildings, b.AppearsHostileTo(anyUnit) && // enemy building and - owner.Bot.Info.BuildingCommonNames.StaticAntiAir.Contains(b.Info.Name)); // registered in Static AA. + owner.SquadManager.Info.StaticAATypes.Contains(b.Info.Name)); // registered in Static AA. } } @@ -342,7 +342,7 @@ public static CPos CalcSafePoint(Squad owner, CPos dest, IEnumerable enem var within20 = owner.World.Map.FindTilesInAnnulus(dest, 2, 20); if (within20.Any()) for (int i = 0; i < 32; i++) - cands.Add(within20.Random(owner.Bot.Random)); + cands.Add(within20.Random(owner.Random)); int best_score = -1; CPos best = CPos.Zero; diff --git a/OpenRA.Mods.Common/Traits/BotModules/Squads/States/ProtectionStates.cs b/OpenRA.Mods.Common/Traits/BotModules/Squads/States/ProtectionStates.cs index 48754d807377..e0ebd1d3a741 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/Squads/States/ProtectionStates.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/Squads/States/ProtectionStates.cs @@ -33,7 +33,7 @@ bool ShouldAttack(Squad owner, Actor target) return true; // Is it seige unit? - if (owner.Bot.Info.UnitsCommonNames.Seige.Contains(target.Info.Name)) + if (owner.SquadManager.Info.SiegeUnitTypes.Contains(target.Info.Name)) return true; return false; diff --git a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs index a73f03ce67a3..34ffa376950d 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs @@ -106,8 +106,8 @@ void BuildUnit(IBot bot, string category, bool buildRandom) { if (Info.QueueTimeLimits != null && Info.QueueTimeLimits.ContainsKey(category) && - Info.QueueTimeLimits[category] > World.WorldTick) - return null; + Info.QueueTimeLimits[category] > world.WorldTick) + return; // Pick a free queue var queue = AIUtils.FindQueues(player, category).FirstOrDefault(q => !q.AllQueued().Any()); @@ -157,7 +157,7 @@ void BuildUnit(IBot bot, string name) { if (Info.QueueTimeLimits != null && Info.QueueTimeLimits.ContainsKey(queue.Info.Type) && - Info.QueueTimeLimits[queue.Info.Type] > World.WorldTick) + Info.QueueTimeLimits[queue.Info.Type] > world.WorldTick) return; bot.QueueOrder(Order.StartProduction(queue.Actor, name, 1)); diff --git a/OpenRA.Mods.Common/Traits/Buildings/Building.cs b/OpenRA.Mods.Common/Traits/Buildings/Building.cs index 7da66ec3ab63..189fe6335286 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Building.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Building.cs @@ -142,7 +142,7 @@ public WVec CenterOffset(World w) return (off - new WVec(0, 0, off.Z)) + LocalCenterOffset; } - public BaseProvider FindBaseProvider(World world, Player p, CPos topLeft) + public BaseProvider FindBaseProvider(World world, Player p, Actor producer, CPos topLeft) { var center = world.Map.CenterOfCell(topLeft) + CenterOffset(world); var mapBuildRadius = world.WorldActor.TraitOrDefault(); @@ -173,7 +173,7 @@ bool ActorGrantsValidArea(Actor a, Actor producer, RequiresBuildableAreaInfo rba .SelectMany(gba => gba.AreaTypes)); } - public virtual bool IsCloseEnoughToBase(World world, Player p, ActorInfo ai, CPos topLeft) + public virtual bool IsCloseEnoughToBase(World world, Player p, ActorInfo ai, Actor producer, CPos topLeft) { var requiresBuildableArea = ai.TraitInfoOrDefault(); var mapBuildRadius = world.WorldActor.TraitOrDefault(); @@ -181,7 +181,7 @@ public virtual bool IsCloseEnoughToBase(World world, Player p, ActorInfo ai, CPo if (requiresBuildableArea == null || p.PlayerActor.Trait().BuildAnywhere) return true; - if (mapBuildRadius != null && mapBuildRadius.BuildRadiusEnabled && RequiresBaseProvider && FindBaseProvider(world, p, topLeft) == null) + if (mapBuildRadius != null && mapBuildRadius.BuildRadiusEnabled && RequiresBaseProvider && FindBaseProvider(world, p, producer, topLeft) == null) return false; var adjacent = requiresBuildableArea.Adjacent; diff --git a/OpenRA.Mods.Common/Traits/CaptureManager.cs b/OpenRA.Mods.Common/Traits/CaptureManager.cs index ad77f88fcc35..eaf9c8aaec16 100644 --- a/OpenRA.Mods.Common/Traits/CaptureManager.cs +++ b/OpenRA.Mods.Common/Traits/CaptureManager.cs @@ -227,11 +227,11 @@ public bool StartCapture(Actor self, Actor target, CaptureManager targetManager, if (captures == null) return false; - if (Info.ShowChatMessage && self.World.LocalPlayer == self.Owner) - Game.AddChatLine(Color.White, "Battlefield Control", Info.ChatMessage); + if (info.ShowChatMessage && self.World.LocalPlayer == self.Owner) + Game.AddChatLine(Color.White, "Battlefield Control", info.ChatMessage); - if (Info.Notification != null) - Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.Notification, self.Owner.Faction.InternalName); + if (info.BeingCapturedNotification != null) + Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.BeingCapturedNotification, self.Owner.Faction.InternalName); if (progressWatchers.Any() || targetManager.progressWatchers.Any()) { diff --git a/OpenRA.Mods.Common/Traits/CaptureProgressBlink.cs b/OpenRA.Mods.Common/Traits/CaptureProgressBlink.cs index afe438e5bf5b..ad9c860d7fee 100644 --- a/OpenRA.Mods.Common/Traits/CaptureProgressBlink.cs +++ b/OpenRA.Mods.Common/Traits/CaptureProgressBlink.cs @@ -73,7 +73,7 @@ void ITick.Tick(Actor self) self.World.Add(new FlashTarget(captor, captorOwner)); if (info.Sound != null) - Game.Sound.Play(SoundType.World, info.Sound, target.CenterPosition); + Game.Sound.Play(SoundType.World, info.Sound, self.CenterPosition); } if (++tick >= info.Interval) diff --git a/OpenRA.Mods.Common/Traits/Captures.cs b/OpenRA.Mods.Common/Traits/Captures.cs index 3b8ec74dda8c..dd1195b75454 100644 --- a/OpenRA.Mods.Common/Traits/Captures.cs +++ b/OpenRA.Mods.Common/Traits/Captures.cs @@ -52,6 +52,7 @@ public class CapturesInfo : ConditionalTraitInfo, Requires public readonly string EnterBlockedCursor = "enter-blocked"; [VoiceReference] public readonly string Voice = "Action"; + [VoiceReference] public readonly string CaptureCompleteVoice = null; public override object Create(ActorInitializer init) { return new Captures(init.Self, this); } } diff --git a/OpenRA.Mods.Common/Traits/CarryableHarvester.cs b/OpenRA.Mods.Common/Traits/CarryableHarvester.cs index bc331a9b6978..27a32456ed13 100644 --- a/OpenRA.Mods.Common/Traits/CarryableHarvester.cs +++ b/OpenRA.Mods.Common/Traits/CarryableHarvester.cs @@ -29,18 +29,22 @@ void INotifyCreated.Created(Actor self) transports = self.TraitsImplementing().ToArray(); } - void INotifyHarvesterAction.MovingToResources(Actor self, CPos targetCell, Activity next) + Activity INotifyHarvesterAction.MovingToResources(Actor self, CPos targetCell, Activity next) { foreach (var t in transports) t.RequestTransport(self, targetCell, next); + + return null; } - void INotifyHarvesterAction.MovingToRefinery(Actor self, Actor refineryActor, Activity next) + Activity INotifyHarvesterAction.MovingToRefinery(Actor self, Actor refineryActor, Activity next) { - var iao = refineryActor.Trait(); - var location = refineryActor.Location + iao.DeliveryOffset; + var dock = refineryActor.TraitsImplementing().First(); + var location = refineryActor.Location + dock.Info.DockOffset; foreach (var t in transports) t.RequestTransport(self, location, next); + + return null; } void INotifyHarvesterAction.MovementCancelled(Actor self) diff --git a/OpenRA.Mods.Common/Traits/ConditionPrerequisite.cs b/OpenRA.Mods.Common/Traits/ConditionPrerequisite.cs index 0eccffe3f6de..0fd87c3e7a74 100644 --- a/OpenRA.Mods.Common/Traits/ConditionPrerequisite.cs +++ b/OpenRA.Mods.Common/Traits/ConditionPrerequisite.cs @@ -53,9 +53,9 @@ void INotifyCreated.Created(Actor self) foreach (var queue in queues.Where(t => t.Enabled)) { queue.CacheProducibles(playerActor); - queue.producible[self.World.Map.Rules.Actors[Info.Actor]].Visible = true; + queue.Producible[self.World.Map.Rules.Actors[Info.Actor]].Visible = true; if (!IsTraitPaused) - queue.producible[self.World.Map.Rules.Actors[Info.Actor]].Buildable = true; + queue.Producible[self.World.Map.Rules.Actors[Info.Actor]].Buildable = true; } } @@ -64,7 +64,7 @@ void INotifyCreated.Created(Actor self) foreach (var queue in queues.Where(t => t.Enabled)) { queue.CacheProducibles(playerActor); - queue.producible[self.World.Map.Rules.Actors[Info.Actor]].Visible = false; + queue.Producible[self.World.Map.Rules.Actors[Info.Actor]].Visible = false; } } } diff --git a/OpenRA.Mods.Common/Traits/Conditions/ExternalConditionThatRevokedOnCapture.cs b/OpenRA.Mods.Common/Traits/Conditions/ExternalConditionThatRevokedOnCapture.cs index c7dd935aadde..23ebfe418679 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/ExternalConditionThatRevokedOnCapture.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/ExternalConditionThatRevokedOnCapture.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Traits; +using OpenRA.Primitives; namespace OpenRA.Mods.Common.Traits { @@ -26,7 +27,7 @@ class ExternalConditionThatRevokedOnCapture : ExternalCondition, INotifyCapture public ExternalConditionThatRevokedOnCapture(Actor self, ExternalConditionThatRevokedOnCaptureInfo info) : base(self, info) { } - public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner) + void INotifyCapture.OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner, BitSet captureTypes) { TryRevokeCondition(self); } diff --git a/OpenRA.Mods.Common/Traits/Harvester.cs b/OpenRA.Mods.Common/Traits/Harvester.cs index c990bb5962c7..1a324be0b6ca 100644 --- a/OpenRA.Mods.Common/Traits/Harvester.cs +++ b/OpenRA.Mods.Common/Traits/Harvester.cs @@ -473,6 +473,7 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) } else if (order.OrderString == "Stop" || order.OrderString == "Move") { + var notify = self.TraitsImplementing(); foreach (var n in notify) n.MovementCancelled(self); diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index 027b18d7f4bc..dfb326b56a28 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -147,7 +147,7 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) else { if (!self.World.CanPlaceBuilding(order.TargetLocation, actorInfo, buildingInfo, null) - || !buildingInfo.IsCloseEnoughToBase(self.World, order.Player, actorInfo, order.TargetLocation)) + || !buildingInfo.IsCloseEnoughToBase(self.World, order.Player, actorInfo, queue.Actor, order.TargetLocation)) return; var replacementInfo = actorInfo.TraitInfoOrDefault(); @@ -184,7 +184,7 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) { // May be null if the build anywhere cheat is active // BuildingInfo.IsCloseEnoughToBase has already verified that this is a valid build location - var provider = buildingInfo.FindBaseProvider(w, self.Owner, order.TargetLocation); + var provider = buildingInfo.FindBaseProvider(w, self.Owner, producer.Actor, order.TargetLocation); if (provider != null) provider.BeginCooldown(); } diff --git a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs index 03834f3b9098..fde4aacf7520 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs @@ -367,8 +367,10 @@ protected void CancelUnbuildableItems() } } - public bool CanQueue(ActorInfo actor) + public bool CanQueue(ActorInfo actor, out string notificationAudio) { + notificationAudio = Info.BlockedAudio; + var bi = actor.TraitInfoOrDefault(); if (bi == null) return false; @@ -548,6 +550,7 @@ protected void CancelProduction(string itemName, uint numberToCancel) var cancelledAudio = bi.CancelledAudio != null ? bi.CancelledAudio : Info.CancelledAudio; Game.Sound.PlayNotification(rules, self.Owner, "Speech", cancelledAudio, self.Owner.Faction.InternalName); + for (var i = 0; i < numberToCancel; i++) if (!CancelProductionInner(itemName)) break; } @@ -567,7 +570,7 @@ bool CancelProductionInner(string itemName) else { // Refund what has been paid - if (cost != 0 && Info.InstantCashDrain) + if (Info.InstantCashDrain) playerResources.GiveCash(item.TotalCost); else playerResources.GiveCash(item.TotalCost - item.RemainingCost); diff --git a/OpenRA.Mods.Common/Traits/Production.cs b/OpenRA.Mods.Common/Traits/Production.cs index 48a633aa03eb..c0e1d03ee666 100644 --- a/OpenRA.Mods.Common/Traits/Production.cs +++ b/OpenRA.Mods.Common/Traits/Production.cs @@ -131,12 +131,13 @@ public virtual bool Produce(Actor self, ActorInfo producee, string productionTyp if (exit != null || self.OccupiesSpace == null) { + var exitInfo = exit == null ? null : exit.Info; var buildable = producee.TraitInfoOrDefault(); if (buildable != null) for (var n = 0; n < buildable.BuildAmount; n++) - DoProduction(self, producee, exit.Info, productionType, inits); + DoProduction(self, producee, exitInfo, productionType, inits); else - DoProduction(self, producee, exit.Info, productionType, inits); + DoProduction(self, producee, exitInfo, productionType, inits); return true; } diff --git a/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs index a8c6876727c3..c23e77b2ba65 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs @@ -59,11 +59,11 @@ void INotifyHarvesterAction.Harvested(Actor self, ResourceType resource) anim.PlayThen(info.Sequence, () => visible = false); } - public Activity INotifyHarvesterAction.MovingToResources(Actor self, CPos targetCell, Activity next) { return null; } - public Activity INotifyHarvesterAction.MovingToRefinery(Actor self, Actor targetRefinery, Activity next) { return null; } - public void INotifyHarvesterAction.MovementCancelled(Actor self) { } - public void INotifyHarvesterAction.Docked() { } - public void INotifyHarvesterAction.Undocked() { } + Activity INotifyHarvesterAction.MovingToResources(Actor self, CPos targetCell, Activity next) { return null; } + Activity INotifyHarvesterAction.MovingToRefinery(Actor self, Actor targetRefinery, Activity next) { return null; } + void INotifyHarvesterAction.MovementCancelled(Actor self) { } + void INotifyHarvesterAction.Docked() { } + void INotifyHarvesterAction.Undocked() { } public static int ZOffsetFromCenter(Actor self, WPos pos, int offset) { diff --git a/OpenRA.Mods.Common/Traits/Repairable.cs b/OpenRA.Mods.Common/Traits/Repairable.cs index 479521aa6f5d..e1cc90192087 100644 --- a/OpenRA.Mods.Common/Traits/Repairable.cs +++ b/OpenRA.Mods.Common/Traits/Repairable.cs @@ -45,12 +45,12 @@ public Repairable(Actor self, RepairableInfo info) Info = info; health = self.Trait(); movement = self.Trait(); + dockClient = self.Trait(); } void INotifyCreated.Created(Actor self) { rearmable = self.TraitOrDefault(); - dockClient = self.Trait(); } public IEnumerable Orders @@ -125,7 +125,7 @@ public void ResolveOrder(Actor self, Order order) public Activity AfterReachActivities(Actor self, Actor host, Dock dock) { if (CanRearmAt(host) && CanRearm()) - return new Rearm(self, targetActor, new WDist(512)) + return new Rearm(self, host, new WDist(512)); // Add a CloseEnough range of 512 to ensure we're at the host actor return new Repair(self, host, new WDist(512)); diff --git a/OpenRA.Mods.Common/Traits/RevealsShroudToIntelligenceOwner.cs b/OpenRA.Mods.Common/Traits/RevealsShroudToIntelligenceOwner.cs index 53a040f03b95..6d9f092b1edb 100644 --- a/OpenRA.Mods.Common/Traits/RevealsShroudToIntelligenceOwner.cs +++ b/OpenRA.Mods.Common/Traits/RevealsShroudToIntelligenceOwner.cs @@ -37,7 +37,7 @@ public RevealsShroudToIntelligenceOwner(Actor self, RevealsShroudToIntelligenceO public override void AddCellsToPlayerShroud(Actor self, Player p, PPos[] uv) { - p.Shroud.AddSource(this, type, uv); + p.Shroud.AddSource(this, Type, uv); } void ITick.Tick(Actor self) diff --git a/OpenRA.Mods.Common/Traits/SharedCargo.cs b/OpenRA.Mods.Common/Traits/SharedCargo.cs index b315238d436a..737261697eb0 100644 --- a/OpenRA.Mods.Common/Traits/SharedCargo.cs +++ b/OpenRA.Mods.Common/Traits/SharedCargo.cs @@ -120,9 +120,9 @@ public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool qu return null; } - Order IIssueDeployOrder.IssueDeployOrder(Actor self) + Order IIssueDeployOrder.IssueDeployOrder(Actor self, bool queued) { - return new Order("UnloadShared", self, false); + return new Order("UnloadShared", self, queued); } bool IIssueDeployOrder.CanIssueDeployOrder(Actor self) { return true; } diff --git a/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs b/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs index a465177c905a..0a94ae84c4d0 100644 --- a/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs +++ b/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs @@ -30,6 +30,9 @@ public class ThrowsShrapnelInfo : ConditionalTraitInfo, IRulesetLoaded [Desc("The minimum and maximum distances the shrapnel may travel.")] public readonly WDist[] Range = { WDist.FromCells(2), WDist.FromCells(5) }; + [Desc("Throw the projectile to where actor is facing.")] + public readonly bool CondierFacing = false; + public WeaponInfo[] WeaponInfos { get; private set; } public override object Create(ActorInitializer actor) { return new ThrowsShrapnel(this); } @@ -65,15 +68,16 @@ public void Killed(Actor self, AttackInfo attack) for (var i = 0; pieces > i; i++) { - var rotation = WRot.FromFacing(self.World.SharedRandom.Next(1024)); + var myFacing = self.TraitOrDefault(); + var rotation = WRot.FromFacing(myFacing != null && Info.CondierFacing ? myFacing.Facing + 64 : self.World.SharedRandom.Next(1024)); var args = new ProjectileArgs { Weapon = wep, - Facing = self.World.SharedRandom.Next(-1, 255), + Facing = myFacing != null && Info.CondierFacing ? myFacing.Facing : self.World.SharedRandom.Next(-1, 255), CurrentMuzzleFacing = () => 0, DamageModifiers = self.TraitsImplementing() - .Select(a => a.GetFirepowerModifier(info.WeaponName)).ToArray(), + .Select(a => a.GetFirepowerModifier(Info.WeaponName)).ToArray(), InaccuracyModifiers = self.TraitsImplementing() .Select(a => a.GetInaccuracyModifier()).ToArray(), diff --git a/OpenRA.Mods.Common/Traits/World/MPStartUnits.cs b/OpenRA.Mods.Common/Traits/World/MPStartUnits.cs index ed72f2f957c7..e554a18321ce 100644 --- a/OpenRA.Mods.Common/Traits/World/MPStartUnits.cs +++ b/OpenRA.Mods.Common/Traits/World/MPStartUnits.cs @@ -29,9 +29,6 @@ public class MPStartUnitsInfo : TraitInfo [Desc("The actor at the center, usually the mobile construction vehicle.")] public readonly string BaseActor = null; - [Desc("Offset from the spawn point, BaseActor will spawn at.")] - public readonly CVec BaseActorOffset = CVec.Zero; - [Desc("A group of units ready to defend or scout.")] public readonly string[] SupportActors = { }; diff --git a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs index aede09f70fa5..156b6ea4f238 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Drawing; +using OpenRA.FileFormats; using OpenRA.Graphics; using OpenRA.Traits; @@ -24,8 +25,8 @@ public class TerrainRendererInfo : ITraitInfo public sealed class TerrainRenderer : IRenderTerrain, IWorldLoaded, INotifyActorDisposing { readonly Map map; - readonly Sprite skyImage; - readonly float2 skySz; + Sprite skyImage; + float2 skySz; readonly Dictionary spriteLayers = new Dictionary(); Theater theater; @@ -45,9 +46,9 @@ void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) skySz = new float2(Game.Renderer.Resolution.Width, Game.Renderer.Resolution.Width); using (var dataStream = map.Package.GetStream(map.SkyboxImage)) { - var bmp = new Bitmap(dataStream); - var sheetBuilder = new SheetBuilder(SheetType.BGRA, bmp.Size.Width); - skyImage = sheetBuilder.Add(bmp); + var png = new Png(dataStream); + var sheetBuilder = new SheetBuilder(SheetType.BGRA, png.Width); + skyImage = sheetBuilder.Add(png); } } diff --git a/OpenRA.Mods.Gen/Activities/Air/ShootableBallisticMissileFly.cs b/OpenRA.Mods.Gen/Activities/Air/ShootableBallisticMissileFly.cs index f67b564a976e..1a753777f707 100644 --- a/OpenRA.Mods.Gen/Activities/Air/ShootableBallisticMissileFly.cs +++ b/OpenRA.Mods.Gen/Activities/Air/ShootableBallisticMissileFly.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of @@ -15,8 +15,6 @@ using OpenRA.Mods.Yupgi_alert.Traits; using OpenRA.Traits; -/* Works with no base engine modification */ - namespace OpenRA.Mods.Yupgi_alert.Activities { public class ShootableBallisticMissileFly : Activity diff --git a/OpenRA.Mods.Gen/Activities/Air/SpawnedFlyAttack.cs b/OpenRA.Mods.Gen/Activities/Air/SpawnedFlyAttack.cs index d414785c2107..995619ebefde 100644 --- a/OpenRA.Mods.Gen/Activities/Air/SpawnedFlyAttack.cs +++ b/OpenRA.Mods.Gen/Activities/Air/SpawnedFlyAttack.cs @@ -29,18 +29,18 @@ public class SpawnedFlyAttack : Activity { readonly Target target; readonly Aircraft aircraft; - readonly AttackPlane attackPlane; + readonly AttackAircraft attackAircraft; + readonly Rearmable rearmable; - readonly bool autoReloads; int ticksUntilTurn; public SpawnedFlyAttack(Actor self, Target target) { this.target = target; aircraft = self.Trait(); - attackPlane = self.TraitOrDefault(); - ticksUntilTurn = attackPlane.AttackPlaneInfo.AttackTurnDelay; - autoReloads = self.TraitsImplementing().All(p => p.AutoReloads); + attackAircraft = self.TraitOrDefault(); + rearmable = self.TraitOrDefault(); + ticksUntilTurn = attackAircraft.AttackAircraftInfo.AttackTurnDelay; } public override Activity Tick(Actor self) @@ -48,12 +48,12 @@ public override Activity Tick(Actor self) if (!target.IsValidFor(self)) return NextActivity; - // If all valid weapons have depleted their ammo and RearmBuilding is defined, return to RearmBuilding to reload and then resume the activity - if (!autoReloads && aircraft.Info.RearmBuildings.Any() && attackPlane.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self))) - return ActivityUtils.SequenceActivities(new ReturnToBase(self, aircraft.Info.AbortOnResupply), this); + // If all valid weapons have depleted their ammo and Rearmable trait exists, return to RearmActor + if (rearmable != null && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self))) + return ActivityUtils.SequenceActivities(new ReturnToBase(self, aircraft.Info.AbortOnResupply)); - if (attackPlane != null) - attackPlane.DoAttack(self, target); + if (attackAircraft != null) + attackAircraft.DoAttack(self, target); if (ChildActivity == null) { @@ -61,7 +61,7 @@ public override Activity Tick(Actor self) return NextActivity; // TODO: This should fire each weapon at its maximum range - if (attackPlane != null && target.IsInRange(self.CenterPosition, attackPlane.Armaments.Select(a => a.Weapon.MinRange).Min())) + if (attackAircraft != null && target.IsInRange(self.CenterPosition, attackAircraft.Armaments.Select(a => a.Weapon.MinRange).Min())) ChildActivity = ActivityUtils.SequenceActivities(new FlyTimed(ticksUntilTurn, self), new Fly(self, target), new FlyTimed(ticksUntilTurn, self)); else ChildActivity = ActivityUtils.SequenceActivities(new Fly(self, target), new FlyTimed(ticksUntilTurn, self)); diff --git a/OpenRA.Mods.Gen/Activities/EnterCarrierMaster.cs b/OpenRA.Mods.Gen/Activities/EnterCarrierMaster.cs index 0e0b3c8f381e..0e9da011ece2 100644 --- a/OpenRA.Mods.Gen/Activities/EnterCarrierMaster.cs +++ b/OpenRA.Mods.Gen/Activities/EnterCarrierMaster.cs @@ -1,20 +1,6 @@ -#region Copyright & License Information +#region Copyright & License Information /* - * Modded by Boolbada of OP mod, from Engineer repair enter activity. - * - * Note: You can still use this without modifying the OpenRA engine itself by deleting - * FindAndTransitionToNextState. I just deleted a few lines of "movement" recovery code so that - * interceptors can enter moving carrier. - * However, for better results, consider modding the engine, as in the following commit: - * https://github.com/forcecore/OpenRA/commit/fd36f63e508b7ad28e7d320355b7d257654b33ee - * - * Also, interceptors sometimes try to land on ground level. - * To mitigate that, I added LnadingDistance in Spawned trait. - * However, that isn't perfect. For perfect results, Land.cs of the engine must be modified: - * https://github.com/forcecore/OpenRA/commit/45970f57283150bc57ce86b8ce8a555018c6ca14 - * I couldn't make it independent as it relies on other stuff in Enter.cs too much. - * - * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of @@ -31,11 +17,6 @@ using OpenRA.Mods.Yupgi_alert.Traits; using OpenRA.Traits; -/* -Requires base engine changes. -Since this inherits "Enter", you need to make several variables "protected". -*/ - namespace OpenRA.Mods.Yupgi_alert.Activities { class EnterCarrierMaster : Enter @@ -43,8 +24,8 @@ class EnterCarrierMaster : Enter readonly Actor master; // remember the spawner. readonly CarrierMaster spawnerMaster; - public EnterCarrierMaster(Actor self, Actor master, CarrierMaster spawnerMaster, EnterBehaviour enterBehaviour, WDist closeEnoughDist) - : base(self, master, enterBehaviour, closeEnoughDist) + public EnterCarrierMaster(Actor self, Actor master, CarrierMaster spawnerMaster, EnterBehaviour enterBehaviour) + : base(self, master, enterBehaviour, WDist.Zero) { this.master = master; this.spawnerMaster = spawnerMaster; @@ -80,7 +61,7 @@ protected override void OnInside(Actor self) if (self.IsDead || master.IsDead) return; - spawnerMaster.PickupSlave(master, self); + spawnerMaster.PickupSlave(master, self); w.Remove(self); // Insta repair. @@ -90,11 +71,11 @@ protected override void OnInside(Actor self) self.InflictDamage(self, new Damage(-health.MaxHP)); } - // Insta re-arm. (Delayed launching is handled at spawner.) - var ammoPools = self.TraitsImplementing().Where(p => !p.AutoReloads).ToArray(); + // Insta re-arm. (Delayed launching is handled at spawner.) + var ammoPools = self.TraitsImplementing().ToArray(); if (ammoPools != null) foreach (var pool in ammoPools) - while (pool.GiveAmmo(self, pool.Info.Ammo)); // fill 'er up. + while (pool.GiveAmmo(self, 1)); // fill 'er up. }); } } diff --git a/OpenRA.Mods.Gen/Activities/OpportunityTeleport.cs b/OpenRA.Mods.Gen/Activities/OpportunityTeleport.cs index d9772a369923..da0c9e532bf6 100644 --- a/OpenRA.Mods.Gen/Activities/OpportunityTeleport.cs +++ b/OpenRA.Mods.Gen/Activities/OpportunityTeleport.cs @@ -21,16 +21,16 @@ namespace OpenRA.Mods.Yupgi_alert.Activities { public class OpportunityTeleport : Activity { - readonly PortableChronoInfo pchronoInfo; - readonly PortableChrono pchrono; + public readonly PortableChronoInfo PChronoInfo; + public readonly PortableChrono PChrono; readonly CPos targetCell; // moveToDest: activities that will make this actor move to the destination. // i.e., Move. public OpportunityTeleport(Actor self, PortableChronoInfo pchronoInfo, CPos targetCell, Activity moveToDest) { - this.pchronoInfo = pchronoInfo; - pchrono = self.Trait(); + this.PChronoInfo = pchronoInfo; + PChrono = self.Trait(); this.targetCell = targetCell; QueueChild(moveToDest); } @@ -44,10 +44,10 @@ public override Activity Tick(Actor self) if (ChildActivity == null) return NextInQueue; - if (pchrono.CanTeleport && (self.Location - targetCell).LengthSquared > 4) + if (PChrono.CanTeleport && (self.Location - targetCell).LengthSquared > 4) { ChildActivity = new Teleport(self, targetCell, null, - pchronoInfo.KillCargo, pchronoInfo.FlashScreen, pchronoInfo.ChronoshiftSound); + PChronoInfo.KillCargo, PChronoInfo.FlashScreen, PChronoInfo.ChronoshiftSound); ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); return this; diff --git a/OpenRA.Mods.Gen/OpenRA.Mods.Gen.csproj b/OpenRA.Mods.Gen/OpenRA.Mods.Gen.csproj index e4c45e265ad8..2d04ed783eea 100644 --- a/OpenRA.Mods.Gen/OpenRA.Mods.Gen.csproj +++ b/OpenRA.Mods.Gen/OpenRA.Mods.Gen.csproj @@ -101,7 +101,6 @@ - diff --git a/OpenRA.Mods.Gen/Traits/AegisAutoTarget.cs b/OpenRA.Mods.Gen/Traits/AegisAutoTarget.cs index b096ef494c03..15bebee2d0b0 100644 --- a/OpenRA.Mods.Gen/Traits/AegisAutoTarget.cs +++ b/OpenRA.Mods.Gen/Traits/AegisAutoTarget.cs @@ -27,7 +27,7 @@ namespace OpenRA.Mods.Yupgi_alert.Traits { [Desc("The actor will fire shots equally to many targets nearby, unless told to focus fire.")] - public class AegisAutoTargetInfo : AutoTargetInfo, Requires, UsesInit + public class AegisAutoTargetInfo : AutoTargetInfo, Requires { public override object Create(ActorInitializer init) { return new AegisAutoTarget(init, this); } } diff --git a/OpenRA.Mods.Gen/Traits/BaseSpawnMaster.cs b/OpenRA.Mods.Gen/Traits/BaseSpawnMaster.cs new file mode 100644 index 000000000000..8ca01fafd0fa --- /dev/null +++ b/OpenRA.Mods.Gen/Traits/BaseSpawnMaster.cs @@ -0,0 +1,293 @@ +#region Copyright & License Information +/* + * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Linq; +using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA2.Traits +{ + // What to do when master is killed or mind controlled + public enum SpawnerSlaveDisposal + { + DoNothing, + KillSlaves, + GiveSlavesToAttacker + } + + public class BaseSpawnerSlaveEntry + { + public string ActorName = null; + public Actor Actor = null; + public BaseSpawnerSlave SpawnerSlave = null; + + public bool IsValid { get { return Actor != null && !Actor.IsDead; } } + } + + [Desc("This actor can spawn actors.")] + public class BaseSpawnerMasterInfo : ConditionalTraitInfo + { + [Desc("Spawn these units. Define this like paradrop support power.")] + public readonly string[] Actors; + + [Desc("Slave actors to contain upon creation. Set to -1 to start with full slaves.")] + public readonly int InitialActorCount = -1; + + [Desc("The armament which will trigger the slaves to attack the target. (== \"Name:\" tag of Armament, not @tag!)")] + public readonly string SpawnerArmamentName = "primary"; + + [Desc("What happens to the slaves when the master is killed?")] + public readonly SpawnerSlaveDisposal SlaveDisposalOnKill = SpawnerSlaveDisposal.KillSlaves; + + [Desc("What happens to the slaves when the master is mind controlled?")] + public readonly SpawnerSlaveDisposal SlaveDisposalOnOwnerChange = SpawnerSlaveDisposal.GiveSlavesToAttacker; + + [Desc("Only spawn initial load of slaves?")] + public readonly bool NoRegeneration = false; + + [Desc("Spawn all slaves at once when regenerating slaves, instead of one by one?")] + public readonly bool SpawnAllAtOnce = false; + + [Desc("Spawn regen delay, in ticks")] + public readonly int RespawnTicks = 150; + + // This can be computed but this should be faster. + [Desc("Air units and ground units have different mobile trait so...")] + public readonly bool SpawnIsGroundUnit = false; + + public override void RulesetLoaded(Ruleset rules, ActorInfo ai) + { + base.RulesetLoaded(rules, ai); + + if (Actors == null || Actors.Length == 0) + throw new YamlException("Actors is null or empty for a spawner trait in actor type {0}!".F(ai.Name)); + + if (InitialActorCount > Actors.Length) + throw new YamlException("InitialActorCount can't be larger than the actors defined! (Actor type = {0})".F(ai.Name)); + + if (InitialActorCount < -1) + throw new YamlException("InitialActorCount must be -1 or non-negative. Actor type = {0}".F(ai.Name)); + } + + public override object Create(ActorInitializer init) { return new BaseSpawnerMaster(init, this); } + } + + public class BaseSpawnerMaster : ConditionalTrait, INotifyCreated, INotifyKilled, INotifyOwnerChanged + { + readonly Actor self; + protected readonly BaseSpawnerSlaveEntry[] SlaveEntries; + + IFacing facing; + ExitInfo[] exits; + RallyPoint rallyPoint; + + public BaseSpawnerMaster(ActorInitializer init, BaseSpawnerMasterInfo info) : base(info) + { + self = init.Self; + + // Initialize slave entries (doesn't instantiate the slaves yet) + SlaveEntries = CreateSlaveEntries(info); + + for (var i = 0; i < info.Actors.Length; i++) + { + var entry = SlaveEntries[i]; + entry.ActorName = info.Actors[i].ToLowerInvariant(); + } + } + + public virtual BaseSpawnerSlaveEntry[] CreateSlaveEntries(BaseSpawnerMasterInfo info) + { + var slaveEntries = new BaseSpawnerSlaveEntry[info.Actors.Length]; + + for (int i = 0; i < slaveEntries.Length; i++) + slaveEntries[i] = new BaseSpawnerSlaveEntry(); + + return slaveEntries; + } + + protected override void Created(Actor self) + { + base.Created(self); + + facing = self.TraitOrDefault(); + exits = self.Info.TraitInfos().ToArray(); + rallyPoint = self.TraitOrDefault(); + + // Spawn initial load. + int burst = Info.InitialActorCount == -1 ? Info.Actors.Length : Info.InitialActorCount; + for (int i = 0; i < burst; i++) + Replenish(self, SlaveEntries); + } + + /// + /// Replenish destoyed slaves or create new ones from nothing. + /// Follows policy defined by Info.OneShotSpawn. + /// + /// true when a new slave actor is created. + public void Replenish(Actor self, BaseSpawnerSlaveEntry[] slaveEntries) + { + if (Info.SpawnAllAtOnce) + { + foreach (var se in slaveEntries) + if (!se.IsValid) + Replenish(self, se); + } + else + { + BaseSpawnerSlaveEntry entry = SelectEntryToSpawn(slaveEntries); + + // All are alive and well. + if (entry == null) + return; + + Replenish(self, entry); + } + } + + /// + /// Replenish one slave entry. + /// + /// true when a new slave actor is created. + public virtual void Replenish(Actor self, BaseSpawnerSlaveEntry entry) + { + if (entry.IsValid) + throw new InvalidOperationException("Replenish must not be run on a valid entry!"); + + // Some members are missing. Create a new one. + var slave = self.World.CreateActor(false, entry.ActorName, + new TypeDictionary { new OwnerInit(self.Owner) }); + + // Initialize slave entry + InitializeSlaveEntry(slave, entry); + entry.SpawnerSlave.LinkMaster(entry.Actor, self, this); + } + + /// + /// Slave entry initializer function. + /// Override this function from derived classes to initialize their own specific stuff. + /// + public virtual void InitializeSlaveEntry(Actor slave, BaseSpawnerSlaveEntry entry) + { + entry.Actor = slave; + entry.SpawnerSlave = slave.Trait(); + } + + protected BaseSpawnerSlaveEntry SelectEntryToSpawn(BaseSpawnerSlaveEntry[] slaveEntries) + { + // If any thing is marked dead or null, that's a candidate. + var candidates = slaveEntries.Where(m => !m.IsValid); + if (!candidates.Any()) + return null; + + return candidates.Random(self.World.SharedRandom); + } + + public virtual void Killed(Actor self, AttackInfo e) + { + // Notify slaves. + foreach (var slaveEntry in SlaveEntries) + if (slaveEntry.IsValid) + slaveEntry.SpawnerSlave.OnMasterKilled(slaveEntry.Actor, e.Attacker, Info.SlaveDisposalOnKill); + } + + public virtual void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) + { + // Owner thing is so difficult and confusing, I'm expecting bugs. + self.World.AddFrameEndTask(w => + { + foreach (var slaveEntry in SlaveEntries) + if (slaveEntry.IsValid) + slaveEntry.SpawnerSlave.OnMasterOwnerChanged(slaveEntry.Actor, oldOwner, newOwner, Info.SlaveDisposalOnOwnerChange); + }); + } + + public virtual void Disposing(Actor self) + { + // Just dispose them regardless of slave disposal options. + foreach (var slaveEntry in SlaveEntries) + if (slaveEntry.IsValid) + slaveEntry.Actor.Dispose(); + } + + public virtual void SpawnIntoWorld(Actor self, Actor slave, WPos centerPosition) + { + var exit = ChooseExit(self); + SetSpawnedFacing(slave, self, exit); + + self.World.AddFrameEndTask(w => + { + if (self.IsDead) + return; + + var spawnOffset = exit == null ? WVec.Zero : exit.SpawnOffset; + slave.Trait().SetVisualPosition(slave, centerPosition + spawnOffset); + + var location = self.World.Map.CellContaining(centerPosition + spawnOffset); + + var mv = slave.Trait(); + slave.QueueActivity(mv.MoveIntoWorld(slave, location)); + + // Move to rally point if any. + if (rallyPoint != null) + slave.QueueActivity(mv.MoveTo(rallyPoint.Location, 2)); + else + { + // Move to a valid position, if no rally point. + slave.QueueActivity(mv.MoveTo(location, 2)); + } + + w.Add(slave); + }); + } + + // Production.cs use random to select an exit. + // Here, we choose one by round robin. + // Start from -1 so that +1 logic below will make it 0. + int exitRoundRobin = -1; + ExitInfo ChooseExit(Actor self) + { + if (exits.Length == 0) + return null; + + exitRoundRobin = (exitRoundRobin + 1) % exits.Length; + return exits[exitRoundRobin]; + } + + void SetSpawnedFacing(Actor spawned, Actor spawner, ExitInfo exit) + { + int facingOffset = facing == null ? 0 : facing.Facing; + + var exitFacing = exit != null ? exit.Facing : 0; + + var spawnFacing = spawned.TraitOrDefault(); + if (spawnFacing != null) + spawnFacing.Facing = (facingOffset + exitFacing) % 256; + + foreach (var t in spawned.TraitsImplementing()) + t.TurretFacing = (facingOffset + exitFacing) % 256; + } + + public void StopSlaves() + { + foreach (var slaveEntry in SlaveEntries) + { + if (!slaveEntry.IsValid) + continue; + + slaveEntry.SpawnerSlave.Stop(slaveEntry.Actor); + } + } + + public virtual void OnSlaveKilled(Actor self, Actor slave) { } + } +} diff --git a/OpenRA.Mods.Gen/Traits/BaseSpawnerMaster.cs b/OpenRA.Mods.Gen/Traits/BaseSpawnerMaster.cs index 4cad8334777e..55aabd54877b 100644 --- a/OpenRA.Mods.Gen/Traits/BaseSpawnerMaster.cs +++ b/OpenRA.Mods.Gen/Traits/BaseSpawnerMaster.cs @@ -60,7 +60,7 @@ public class BaseSpawnerMasterInfo : ConditionalTraitInfo public readonly bool NoRegeneration = false; [Desc("Set slave subcell to this if it allows.")] - public readonly int SubCell = -1; + public readonly byte SubCell = 0; [Desc("Spawn all slaves at once when regenerating slaves, instead of one by one?")] public readonly bool SpawnAllAtOnce = false; @@ -166,7 +166,7 @@ public void Replenish(Actor self, BaseSpawnerSlaveEntry[] slaveEntries) /// Replenish one slave entry. /// /// true when a new slave actor is created. - public void Replenish(Actor self, BaseSpawnerSlaveEntry entry) + public virtual void Replenish(Actor self, BaseSpawnerSlaveEntry entry) { if (entry.IsValid) throw new InvalidOperationException("Replenish must not be run on a valid entry!"); diff --git a/OpenRA.Mods.Gen/Traits/BaseSpawnerSlave.cs b/OpenRA.Mods.Gen/Traits/BaseSpawnerSlave.cs index 2c9cd7b8280c..3958c8016c9b 100644 --- a/OpenRA.Mods.Gen/Traits/BaseSpawnerSlave.cs +++ b/OpenRA.Mods.Gen/Traits/BaseSpawnerSlave.cs @@ -1,8 +1,5 @@ #region Copyright & License Information /* - * Modded by Boolbada of OP Mod. - * Modded from Cargo.cs but a lot changed. - * * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License @@ -16,11 +13,6 @@ using OpenRA.Mods.Common.Traits; using OpenRA.Traits; -/* -Works without base engine modification. -Will work even better if the PR is merged -*/ - namespace OpenRA.Mods.Yupgi_alert.Traits { [Desc("Can be slaved to a SpawnerMaster.")] @@ -55,12 +47,7 @@ public BaseSpawnerSlave(ActorInitializer init, BaseSpawnerSlaveInfo info) this.info = info; } - void INotifyCreated.Created(Actor self) - { - Created(self); - } - - protected virtual void Created(Actor self) + public virtual void Created(Actor self) { attackBases = self.TraitsImplementing().ToArray(); conditionManager = self.Trait(); @@ -102,15 +89,15 @@ public void Stop(Actor self) self.CancelActivity(); - // And tell attack bases to stop attacking. - foreach (var ab in attackBases) + // And tell attack bases to stop attacking. // TODO +/* foreach (var ab in attackBases) if (!ab.IsTraitDisabled) - ab.OnStopOrder(self); + ab.OnStopOrder(self); */ } // Make this actor attack a target. Target lastTarget; - public void Attack(Actor self, Target target) + public virtual void Attack(Actor self, Target target) { // Don't have to change target or alter current activity. if (!TargetSwitched(lastTarget, target)) @@ -143,11 +130,11 @@ public void Attack(Actor self, Target target) } } - // DUMMY FUNCTION to suppress masterDeadToken assigned but unused warning (== error for Travis). - void OnNewMaster(Actor self, Actor master) - { - conditionManager.RevokeCondition(self, masterDeadToken); - } + // DUMMY FUNCTION to suppress masterDeadToken assigned but unused warning (== error for Travis). + void OnNewMaster(Actor self, Actor master) + { + conditionManager.RevokeCondition(self, masterDeadToken); + } public virtual void OnMasterKilled(Actor self, Actor attacker, SpawnerSlaveDisposal disposal) { @@ -157,17 +144,17 @@ public virtual void OnMasterKilled(Actor self, Actor attacker, SpawnerSlaveDispo switch (disposal) { - case SpawnerSlaveDisposal.KillSlaves: - self.Kill(attacker); - break; - case SpawnerSlaveDisposal.GiveSlavesToAttacker: - self.CancelActivity(); - self.ChangeOwner(attacker.Owner); - break; - case SpawnerSlaveDisposal.DoNothing: - // fall through - default: - break; + case SpawnerSlaveDisposal.KillSlaves: + self.Kill(attacker); + break; + case SpawnerSlaveDisposal.GiveSlavesToAttacker: + self.CancelActivity(); + self.ChangeOwner(attacker.Owner); + break; + case SpawnerSlaveDisposal.DoNothing: + // fall through + default: + break; } } @@ -176,17 +163,17 @@ public virtual void OnMasterOwnerChanged(Actor self, Player oldOwner, Player new { switch (disposal) { - case SpawnerSlaveDisposal.KillSlaves: - self.Kill(self); - break; - case SpawnerSlaveDisposal.GiveSlavesToAttacker: - self.CancelActivity(); - self.ChangeOwner(newOwner); - break; - case SpawnerSlaveDisposal.DoNothing: - // fall through - default: - break; + case SpawnerSlaveDisposal.KillSlaves: + self.Kill(self); + break; + case SpawnerSlaveDisposal.GiveSlavesToAttacker: + self.CancelActivity(); + self.ChangeOwner(newOwner); + break; + case SpawnerSlaveDisposal.DoNothing: + // fall through + default: + break; } } @@ -211,4 +198,4 @@ public virtual void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) self.Kill(self); } } -} +} \ No newline at end of file diff --git a/OpenRA.Mods.Gen/Traits/CarrierMaster.cs b/OpenRA.Mods.Gen/Traits/CarrierMaster.cs index b32be3f5b6b7..29dd1548ae51 100644 --- a/OpenRA.Mods.Gen/Traits/CarrierMaster.cs +++ b/OpenRA.Mods.Gen/Traits/CarrierMaster.cs @@ -1,8 +1,5 @@ -#region Copyright & License Information +#region Copyright & License Information /* - * Modded by Boolbada of OP Mod. - * Modded from cargo.cs but a lot changed. - * * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License @@ -14,29 +11,10 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; -/* -Needs base engine modifications... - -For slave miners: -But the docking procedure may need to change to fit your needs. -In OP Mod, docking changed for Harvester.cs and related files to that -these slaves can "dock" to any adjacent cells near the master. - -For airborne carriers: -Those spawned aircrafts do work without any base engine modifcation. -However, land.cs modified so that they will "land" mid air. -Track readonly WDist landHeight; for related changes. - -EnterSpawner needs modifications too, as it inherits Enter.cs -and uses its internal variables. -Fortunately, I made a PR so that you don't have to worry about this in the future. -*/ - namespace OpenRA.Mods.Yupgi_alert.Traits { [Desc("This actor can spawn actors.")] @@ -56,7 +34,7 @@ public class CarrierMasterInfo : BaseSpawnerMasterInfo public readonly int LaunchingTicks = 15; [Desc("Pip color for the spawn count.")] - public readonly PipType PipType = PipType.Yellow; + public readonly PipType PipType = PipType.Green; [Desc("Insta-repair spawners when they return?")] public readonly bool InstaRepair = true; @@ -85,14 +63,11 @@ class CarrierSlaveEntry : BaseSpawnerSlaveEntry public new CarrierSlave SpawnerSlave; } - readonly Dictionary> spawnContainTokens = new Dictionary>(); - public new CarrierMasterInfo Info { get; private set; } CarrierSlaveEntry[] slaveEntries; - ExternalCondition[] externalConditions; ConditionManager conditionManager; - + readonly Dictionary> spawnContainTokens = new Dictionary>(); Stack loadedTokens = new Stack(); int respawnTicks = 0; @@ -105,9 +80,20 @@ public CarrierMaster(ActorInitializer init, CarrierMasterInfo info) : base(init, protected override void Created(Actor self) { base.Created(self); + conditionManager = self.Trait(); - conditionManager = self.TraitOrDefault(); - externalConditions = self.TraitsImplementing().ToArray(); + if (conditionManager != null) + { + foreach (var entry in SlaveEntries) + { + string spawnContainCondition; + if (Info.SpawnContainConditions.TryGetValue(entry.Actor.Info.Name, out spawnContainCondition)) + spawnContainTokens.GetOrAdd(entry.Actor.Info.Name).Push(conditionManager.GrantCondition(self, spawnContainCondition)); + + if (!string.IsNullOrEmpty(Info.LoadedCondition)) + loadedTokens.Push(conditionManager.GrantCondition(self, Info.LoadedCondition)); + } + } } public override BaseSpawnerSlaveEntry[] CreateSlaveEntries(BaseSpawnerMasterInfo info) @@ -122,12 +108,12 @@ public override BaseSpawnerSlaveEntry[] CreateSlaveEntries(BaseSpawnerMasterInfo public override void InitializeSlaveEntry(Actor slave, BaseSpawnerSlaveEntry entry) { - var se = entry as CarrierSlaveEntry; - base.InitializeSlaveEntry(slave, se); + var carrierSlaveEntry = entry as CarrierSlaveEntry; + base.InitializeSlaveEntry(slave, carrierSlaveEntry); - se.RearmTicks = 0; - se.IsLaunched = false; - se.SpawnerSlave = slave.Trait(); + carrierSlaveEntry.RearmTicks = 0; + carrierSlaveEntry.IsLaunched = false; + carrierSlaveEntry.SpawnerSlave = slave.Trait(); } void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { } @@ -147,43 +133,37 @@ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barre if (slave.IsLaunched && slave.IsValid) slave.SpawnerSlave.Attack(slave.Actor, target); - var se = GetLaunchable(); - if (se == null) + var carrierSlaveEntry = GetLaunchable(); + if (carrierSlaveEntry == null) return; - se.IsLaunched = true; // mark as launched + carrierSlaveEntry.IsLaunched = true; // mark as launched // Launching condition is timed, so not saving the token. if (Info.LaunchingCondition != null) - { - var external = externalConditions - .FirstOrDefault(t => t.Info.Condition == Info.LaunchingCondition && t.CanGrantCondition(self, this)); + conditionManager.GrantCondition(self, Info.LaunchingCondition); // TODO removed Info.LaunchingTicks - if (external == null) - throw new InvalidDataException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(Info.LaunchingCondition)); + SpawnIntoWorld(self, carrierSlaveEntry.Actor, self.CenterPosition); - external.GrantCondition(self, Info.LaunchingCondition, Info.LaunchingTicks); - } + Stack spawnContainToken; + if (spawnContainTokens.TryGetValue(a.Info.Name, out spawnContainToken) && spawnContainToken.Any()) + conditionManager.RevokeCondition(self, spawnContainToken.Pop()); - SpawnIntoWorld(self, se.Actor, self.CenterPosition); + if (loadedTokens.Any()) + conditionManager.RevokeCondition(self, loadedTokens.Pop()); // Queue attack order, too. self.World.AddFrameEndTask(w => { // The actor might had been trying to do something before entering the carrier. // Cancel whatever it was trying to do. - se.SpawnerSlave.Stop(se.Actor); + carrierSlaveEntry.SpawnerSlave.Stop(carrierSlaveEntry.Actor); - se.SpawnerSlave.Attack(se.Actor, target); + carrierSlaveEntry.SpawnerSlave.Attack(carrierSlaveEntry.Actor, target); }); } - void INotifyBecomingIdle.OnBecomingIdle(Actor self) - { - OnBecomingIdle(self); - } - - protected virtual void OnBecomingIdle(Actor self) + public virtual void OnBecomingIdle(Actor self) { Recall(self); } @@ -191,9 +171,9 @@ protected virtual void OnBecomingIdle(Actor self) void Recall(Actor self) { // Tell launched slaves to come back and enter me. - foreach (var se in slaveEntries) - if (se.IsLaunched && se.IsValid) - se.SpawnerSlave.EnterSpawner(se.Actor); + foreach (var carrierSlaveEntry in slaveEntries) + if (carrierSlaveEntry.IsLaunched && carrierSlaveEntry.IsValid) + carrierSlaveEntry.SpawnerSlave.EnterSpawner(carrierSlaveEntry.Actor); } public override void OnSlaveKilled(Actor self, Actor slave) @@ -205,9 +185,9 @@ public override void OnSlaveKilled(Actor self, Actor slave) CarrierSlaveEntry GetLaunchable() { - foreach (var se in slaveEntries) - if (se.RearmTicks <= 0 && !se.IsLaunched && se.IsValid) - return se; + foreach (var carrierSlaveEntry in slaveEntries) + if (carrierSlaveEntry.RearmTicks <= 0 && !carrierSlaveEntry.IsLaunched && carrierSlaveEntry.IsValid) + return carrierSlaveEntry; return null; } @@ -218,8 +198,8 @@ public IEnumerable GetPips(Actor self) yield break; int inside = 0; - foreach (var se in slaveEntries) - if (se.IsValid && !se.IsLaunched) + foreach (var carrierSlaveEntry in slaveEntries) + if (carrierSlaveEntry.IsValid && !carrierSlaveEntry.IsLaunched) inside++; for (var i = 0; i < Info.Actors.Length; i++) @@ -234,10 +214,10 @@ public IEnumerable GetPips(Actor self) public void PickupSlave(Actor self, Actor a) { CarrierSlaveEntry slaveEntry = null; - foreach (var se in slaveEntries) - if (se.Actor == a) + foreach (var carrierSlaveEntry in slaveEntries) + if (carrierSlaveEntry.Actor == a) { - slaveEntry = se; + slaveEntry = carrierSlaveEntry; break; } @@ -257,7 +237,22 @@ public void PickupSlave(Actor self, Actor a) loadedTokens.Push(conditionManager.GrantCondition(self, Info.LoadedCondition)); } - void ITick.Tick(Actor self) + public override void Replenish(Actor self, BaseSpawnerSlaveEntry entry) + { + base.Replenish(self, entry); + + string spawnContainCondition; + if (conditionManager != null) + { + if (Info.SpawnContainConditions.TryGetValue(entry.Actor.Info.Name, out spawnContainCondition)) + spawnContainTokens.GetOrAdd(entry.Actor.Info.Name).Push(conditionManager.GrantCondition(self, spawnContainCondition)); + + if (!string.IsNullOrEmpty(Info.LoadedCondition)) + loadedTokens.Push(conditionManager.GrantCondition(self, Info.LoadedCondition)); + } + } + + public void Tick(Actor self) { if (respawnTicks > 0) { @@ -275,11 +270,11 @@ void ITick.Tick(Actor self) } // Rearm - foreach (var se in slaveEntries) + foreach (var carrierSlaveEntry in slaveEntries) { - if (se.RearmTicks > 0) - se.RearmTicks--; + if (carrierSlaveEntry.RearmTicks > 0) + carrierSlaveEntry.RearmTicks--; } } } -} +} \ No newline at end of file diff --git a/OpenRA.Mods.Gen/Traits/CarrierSlave.cs b/OpenRA.Mods.Gen/Traits/CarrierSlave.cs index e7785575ab1e..8927eb47e344 100644 --- a/OpenRA.Mods.Gen/Traits/CarrierSlave.cs +++ b/OpenRA.Mods.Gen/Traits/CarrierSlave.cs @@ -1,8 +1,5 @@ #region Copyright & License Information /* - * Modded by Boolbada of OP Mod. - * Modded from cargo.cs but a lot changed. - * * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License @@ -18,12 +15,6 @@ using OpenRA.Mods.Yupgi_alert.Activities; using OpenRA.Traits; -/* -Works without base engine modification. -However, Mods.Common\Activities\Air\Land.cs is modified to support the air units to land "mid air!" -See landHeight private variable to track the changes. -*/ - namespace OpenRA.Mods.Yupgi_alert.Traits { [Desc("Can be slaved to a spawner.")] @@ -32,10 +23,6 @@ public class CarrierSlaveInfo : BaseSpawnerSlaveInfo [Desc("Move this close to the spawner, before entering it.")] public readonly WDist LandingDistance = new WDist(5 * 1024); - [Desc("We consider this is close enought to the spawner and enter it, instead of trying to reach 0 distance." + - "This allows the spawned unit to enter the spawner while the spawner is moving.")] - public readonly WDist CloseEnoughDistance = new WDist(128); - public override object Create(ActorInitializer init) { return new CarrierSlave(init, this); } } @@ -66,13 +53,17 @@ public void EnterSpawner(Actor self) // Cancel whatever else self was doing and return. self.CancelActivity(); - var tgt = Target.FromActor(Master); + var target = Target.FromActor(Master); - if (self.TraitOrDefault() != null) // Let attack planes approach me first, before landing. - self.QueueActivity(new Fly(self, tgt, WDist.Zero, Info.LandingDistance)); + var aircraft = self.TraitOrDefault(); + if (self.TraitOrDefault() != null) // Let attack planes approach me first, before landing. + if (aircraft != null && aircraft.Info.VTOL == true) + self.QueueActivity(new HeliFly(self, target, WDist.Zero, Info.LandingDistance)); + else + self.QueueActivity(new Fly(self, target, WDist.Zero, Info.LandingDistance)); - self.QueueActivity(new EnterCarrierMaster(self, Master, spawnerMaster, EnterBehaviour.Exit, Info.CloseEnoughDistance)); - } + self.QueueActivity(new EnterCarrierMaster(self, Master, spawnerMaster, EnterBehaviour.Exit)); + } public override void LinkMaster(Actor self, Actor master, BaseSpawnerMaster spawnerMaster) { @@ -86,15 +77,13 @@ bool NeedToReload(Actor self) if (ammoPools.Length == 0) return false; - return ammoPools.All(x => !x.AutoReloads && !x.HasAmmo()); - } - - void INotifyBecomingIdle.OnBecomingIdle(Actor self) - { - OnBecomingIdle(self); - } + return ammoPools.All(x => !x.HasAmmo()); + // AutoReloads seems to be removed and i dunno how exactly to implement this check now. + // Doesn't seem like we actually need it for RA2. + // return ammoPools.All(x => !x.AutoReloads && !x.HasAmmo()); + } - protected virtual void OnBecomingIdle(Actor self) + public virtual void OnBecomingIdle(Actor self) { EnterSpawner(self); } diff --git a/OpenRA.Mods.Gen/Traits/Conditions/GrantConditionOnCapture.cs b/OpenRA.Mods.Gen/Traits/Conditions/GrantConditionOnCapture.cs index 1185704594d9..92c8c645d35b 100644 --- a/OpenRA.Mods.Gen/Traits/Conditions/GrantConditionOnCapture.cs +++ b/OpenRA.Mods.Gen/Traits/Conditions/GrantConditionOnCapture.cs @@ -14,6 +14,7 @@ using System.Linq; using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Yupgi_alert.Traits @@ -26,7 +27,7 @@ public class GrantConditionOnCaptureInfo : ITraitInfo public readonly string Condition = null; [Desc("Grant condition only if the capturer's CaptureTypes overlap with these types. Leave empty to allow all types.")] - public readonly HashSet CaptureTypes = new HashSet(); + public readonly BitSet CaptureTypes = default(BitSet); public object Create(ActorInitializer init) { return new GrantConditionOnCapture(init.Self, this); } } @@ -59,26 +60,10 @@ void GrantCondition(Actor self, string cond) token = manager.GrantCondition(self, cond); } - public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner) + void INotifyCapture.OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner, BitSet captureTypes) { - if (token == ConditionManager.InvalidConditionToken && IsValidCaptor(captor)) + if (!info.CaptureTypes.IsEmpty && !info.CaptureTypes.Overlaps(captureTypes)) GrantCondition(self, info.Condition); } - - bool IsValidCaptor(Actor captor) - { - if (!info.CaptureTypes.Any()) - return true; - - var capturesInfo = captor.Info.TraitInfoOrDefault(); - if (capturesInfo != null && info.CaptureTypes.Overlaps(capturesInfo.CaptureTypes)) - return true; - - var externalCapturesInfo = captor.Info.TraitInfoOrDefault(); - if (externalCapturesInfo != null && info.CaptureTypes.Overlaps(externalCapturesInfo.CaptureTypes)) - return true; - - return false; - } } } diff --git a/OpenRA.Mods.Gen/Traits/Conditions/GrantConditionOnProduction.cs b/OpenRA.Mods.Gen/Traits/Conditions/GrantConditionOnProduction.cs deleted file mode 100644 index c1a9db4b10c6..000000000000 --- a/OpenRA.Mods.Gen/Traits/Conditions/GrantConditionOnProduction.cs +++ /dev/null @@ -1,74 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.Collections.Generic; -using System.Linq; -using OpenRA.Mods.Common.Activities; -using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; - -namespace OpenRA.Mods.Yupgi_alert.Traits -{ - [Desc("Grants a condition when this actor produces a specific actor.")] - public class GrantConditionOnProductionInfo : ITraitInfo - { - [GrantedConditionReference] - [Desc("The condition to grant")] - public readonly string Condition = null; - - [ActorReference] - [Desc("The actors to grant condition for. If empty condition will be granted for all actors.")] - public readonly HashSet Actors = new HashSet(); - - public object Create(ActorInitializer init) { return new GrantConditionOnProduction(init.Self, this); } - } - - public class GrantConditionOnProduction : INotifyCreated, INotifyOtherProduction - { - readonly GrantConditionOnProductionInfo info; - ConditionManager manager; - - int token = ConditionManager.InvalidConditionToken; - - public GrantConditionOnProduction(Actor self, GrantConditionOnProductionInfo info) - { - this.info = info; - } - - void INotifyCreated.Created(Actor self) - { - manager = self.Trait(); - } - - void GrantCondition(Actor self, string cond) - { - if (manager == null) - return; - - if (string.IsNullOrEmpty(cond)) - return; - - token = manager.GrantCondition(self, cond); - } - - public void UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType) - { - // Only grant to self, not others. - if (producer != self) - return; - - if (!info.Actors.Any() || info.Actors.Contains(produced.Info.Name)) - if (token == ConditionManager.InvalidConditionToken) - GrantCondition(self, info.Condition); - } - } -} diff --git a/OpenRA.Mods.Gen/Traits/Conditions/GrantTimedConditionOnDeploy.cs b/OpenRA.Mods.Gen/Traits/Conditions/GrantTimedConditionOnDeploy.cs index e2aab5b0a523..9aec111617e6 100644 --- a/OpenRA.Mods.Gen/Traits/Conditions/GrantTimedConditionOnDeploy.cs +++ b/OpenRA.Mods.Gen/Traits/Conditions/GrantTimedConditionOnDeploy.cs @@ -104,9 +104,9 @@ void INotifyCreated.Created(Actor self) } } - Order IIssueDeployOrder.IssueDeployOrder(Actor self) + Order IIssueDeployOrder.IssueDeployOrder(Actor self, bool queued) { - return new Order("GrantTimedConditionOnDeploy", self, false); + return new Order("GrantTimedConditionOnDeploy", self, queued); } bool IIssueDeployOrder.CanIssueDeployOrder(Actor self) { return !IsTraitPaused && !IsTraitDisabled; } diff --git a/OpenRA.Mods.Gen/Traits/Conditions/HordeBonus.cs b/OpenRA.Mods.Gen/Traits/Conditions/HordeBonus.cs index 5b88ecb1e8f0..6b4abcc20c93 100644 --- a/OpenRA.Mods.Gen/Traits/Conditions/HordeBonus.cs +++ b/OpenRA.Mods.Gen/Traits/Conditions/HordeBonus.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Yupgi_alert.Traits @@ -125,7 +126,7 @@ void ActorEntered(Actor a) UpdateConditionState(); } - void INotifyOtherProduction.UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType) + void INotifyOtherProduction.UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType, TypeDictionary init) { // If the produced Actor doesn't occupy space, it can't be in range if (produced.OccupiesSpace == null) diff --git a/OpenRA.Mods.Gen/Traits/FireWeaponAtSelf.cs b/OpenRA.Mods.Gen/Traits/FireWeaponAtSelf.cs index 4119b7f70b8f..923cf76d8e85 100644 --- a/OpenRA.Mods.Gen/Traits/FireWeaponAtSelf.cs +++ b/OpenRA.Mods.Gen/Traits/FireWeaponAtSelf.cs @@ -37,7 +37,6 @@ class FireWeaponAtSelf : ConditionalTrait, ITick readonly BodyOrientation body; readonly Armament[] armaments; readonly AttackBase attackBase; - readonly Building building; public FireWeaponAtSelf(Actor self, FireWeaponAtSelfInfo info) : base(info) @@ -51,8 +50,6 @@ public FireWeaponAtSelf(Actor self, FireWeaponAtSelfInfo info) // Not sure about attackbase selection. I assert there is only one active at once, // but then if we decide this at creation time... ugh. attackBase = self.TraitsImplementing().Where(a => a.IsTraitEnabled()).First(); - - building = self.TraitOrDefault(); } void ITick.Tick(Actor self) @@ -66,9 +63,6 @@ void ITick.Tick(Actor self) if (attackBase.IsTraitDisabled) return; - if (building != null && !building.BuildComplete) - return; - var localoffset = body != null ? body.LocalToWorld(info.LocalOffset.Rotate(body.QuantizeOrientation(self, self.Orientation))) : info.LocalOffset; diff --git a/OpenRA.Mods.Gen/Traits/LaysMinefield.cs b/OpenRA.Mods.Gen/Traits/LaysMinefield.cs index 39b8087d230a..009d4998d8ba 100644 --- a/OpenRA.Mods.Gen/Traits/LaysMinefield.cs +++ b/OpenRA.Mods.Gen/Traits/LaysMinefield.cs @@ -39,7 +39,7 @@ public class LaysMinefieldInfo : PausableConditionalTraitInfo public override object Create(ActorInitializer init) { return new LaysMinefield(this); } } - public class LaysMinefield : PausableConditionalTrait, INotifyBuildComplete, INotifyKilled, INotifyOwnerChanged, INotifyActorDisposing, ITick, ISync + public class LaysMinefield : PausableConditionalTrait, INotifyKilled, INotifyOwnerChanged, INotifyActorDisposing, ITick, ISync { [Sync] int ticks; List mines = new List(); @@ -64,10 +64,6 @@ void ITick.Tick(Actor self) public void SpawnMines(Actor self) { - var building = self.TraitOrDefault(); - if (building != null && building.Locked) - return; - foreach (var offset in Info.Locations) { var cell = self.Location + offset; @@ -100,12 +96,6 @@ public void RemoveMines() mines.Clear(); } - void INotifyBuildComplete.BuildingComplete(Actor self) - { - if (!IsTraitDisabled && !IsTraitPaused) - SpawnMines(self); - } - void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { foreach (var mine in mines) diff --git a/OpenRA.Mods.Gen/Traits/MissileSpawnerMaster.cs b/OpenRA.Mods.Gen/Traits/MissileSpawnerMaster.cs index 6c7791537b83..b794ae2ff345 100644 --- a/OpenRA.Mods.Gen/Traits/MissileSpawnerMaster.cs +++ b/OpenRA.Mods.Gen/Traits/MissileSpawnerMaster.cs @@ -1,8 +1,5 @@ #region Copyright & License Information /* - * Modded by Boolbada of OP Mod. - * Modded from cargo.cs but a lot changed. - * * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License @@ -13,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.IO; using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Yupgi_alert.Activities; @@ -32,11 +28,8 @@ public class MissileSpawnerMasterInfo : BaseSpawnerMasterInfo [Desc("The condition to grant to self right after launching a spawned unit. (Used by V3 to make immobile.)")] public readonly string LaunchingCondition = null; - [Desc("After this many ticks, we remove the condition.")] - public readonly int LaunchingTicks = 15; - [Desc("Pip color for the spawn count.")] - public readonly PipType PipType = PipType.Yellow; + public readonly PipType PipType = PipType.Green; [GrantedConditionReference] [Desc("The condition to grant to self while spawned units are loaded.", @@ -57,9 +50,11 @@ public class MissileSpawnerMaster : BaseSpawnerMaster, IPips, ITick, INotifyAtta { public new MissileSpawnerMasterInfo Info { get; private set; } - ExternalCondition[] externalConditions; + ConditionManager conditionManager; + readonly Dictionary> spawnContainTokens = new Dictionary>(); + Stack loadedTokens = new Stack(); - int respawnTicks = 0; + int respawnTicks = 0; public MissileSpawnerMaster(ActorInitializer init, MissileSpawnerMasterInfo info) : base(init, info) { @@ -69,15 +64,21 @@ public MissileSpawnerMaster(ActorInitializer init, MissileSpawnerMasterInfo info protected override void Created(Actor self) { base.Created(self); - - externalConditions = self.TraitsImplementing().ToArray(); - } - - public override void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) - { - // Do nothing, because missiles can't be captured or mind controlled. - return; - } + conditionManager = self.Trait(); + + if (conditionManager != null) + { + foreach (var entry in SlaveEntries) + { + string spawnContainCondition; + if (Info.SpawnContainConditions.TryGetValue(entry.Actor.Info.Name, out spawnContainCondition)) + spawnContainTokens.GetOrAdd(entry.Actor.Info.Name).Push(conditionManager.GrantCondition(self, spawnContainCondition)); + + if (!string.IsNullOrEmpty(Info.LoadedCondition)) + loadedTokens.Push(conditionManager.GrantCondition(self, Info.LoadedCondition)); + } + } + } void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { } @@ -102,15 +103,7 @@ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barre // Launching condition is timed, so not saving the token. if (Info.LaunchingCondition != null) - { - var external = externalConditions - .FirstOrDefault(t => t.Info.Condition == Info.LaunchingCondition && t.CanGrantCondition(self, this)); - - if (external == null) - throw new InvalidDataException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(Info.LaunchingCondition)); - - external.GrantCondition(self, Info.LaunchingCondition, Info.LaunchingTicks); - } + conditionManager.GrantCondition(self, Info.LaunchingCondition); // Program the trajectory. var sbm = se.Actor.Trait(); @@ -118,8 +111,15 @@ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barre SpawnIntoWorld(self, se.Actor, self.CenterPosition); - // Queue attack order, too. - self.World.AddFrameEndTask(w => + Stack spawnContainToken; + if (spawnContainTokens.TryGetValue(a.Info.Name, out spawnContainToken) && spawnContainToken.Any()) + conditionManager.RevokeCondition(self, spawnContainToken.Pop()); + + if (loadedTokens.Any()) + conditionManager.RevokeCondition(self, loadedTokens.Pop()); + + // Queue attack order, too. + self.World.AddFrameEndTask(w => { se.Actor.QueueActivity(new ShootableBallisticMissileFly(se.Actor, sbm.Target, sbm)); @@ -160,7 +160,22 @@ public IEnumerable GetPips(Actor self) } } - void ITick.Tick(Actor self) + public override void Replenish(Actor self, BaseSpawnerSlaveEntry entry) + { + base.Replenish(self, entry); + + string spawnContainCondition; + if (conditionManager != null) + { + if (Info.SpawnContainConditions.TryGetValue(entry.Actor.Info.Name, out spawnContainCondition)) + spawnContainTokens.GetOrAdd(entry.Actor.Info.Name).Push(conditionManager.GrantCondition(self, spawnContainCondition)); + + if (!string.IsNullOrEmpty(Info.LoadedCondition)) + loadedTokens.Push(conditionManager.GrantCondition(self, Info.LoadedCondition)); + } + } + + public void Tick(Actor self) { if (respawnTicks > 0) { diff --git a/OpenRA.Mods.Gen/Traits/MissileSpawnerSlave.cs b/OpenRA.Mods.Gen/Traits/MissileSpawnerSlave.cs index 74bf65c28f43..e836d7e421d0 100644 --- a/OpenRA.Mods.Gen/Traits/MissileSpawnerSlave.cs +++ b/OpenRA.Mods.Gen/Traits/MissileSpawnerSlave.cs @@ -1,9 +1,9 @@ #region Copyright & License Information /* * Modded by Boolbada of OP Mod. - * Modded from Cargo.cs but a lot changed. - * - * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) + * Modded from cargo.cs but a lot changed. + * + * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of @@ -12,12 +12,6 @@ */ #endregion -using System.Linq; -using OpenRA.Mods.Common.Activities; -using OpenRA.Mods.Common.Traits; -using OpenRA.Mods.Yupgi_alert.Activities; -using OpenRA.Traits; - /* Works without base engine modification. However, Mods.Common\Activities\Air\Land.cs is modified to support the air units to land "mid air!" diff --git a/OpenRA.Mods.Gen/Traits/MobSpawnerSlave.cs b/OpenRA.Mods.Gen/Traits/MobSpawnerSlave.cs index 3949c66e5e41..aea5057b3642 100644 --- a/OpenRA.Mods.Gen/Traits/MobSpawnerSlave.cs +++ b/OpenRA.Mods.Gen/Traits/MobSpawnerSlave.cs @@ -50,7 +50,7 @@ public MobSpawnerSlave(ActorInitializer init, MobSpawnerSlaveInfo info) : base(i this.self = init.Self; } - protected override void Created(Actor self) + public override void Created(Actor self) { base.Created(self); diff --git a/OpenRA.Mods.Gen/Traits/Render/WithTerrainDependantSpriteBody.cs b/OpenRA.Mods.Gen/Traits/Render/WithTerrainDependantSpriteBody.cs index 0fee85bfb8e0..18c1a7c88acf 100644 --- a/OpenRA.Mods.Gen/Traits/Render/WithTerrainDependantSpriteBody.cs +++ b/OpenRA.Mods.Gen/Traits/Render/WithTerrainDependantSpriteBody.cs @@ -53,11 +53,6 @@ protected WithTerrainDependantSpriteBody(ActorInitializer init, WithTerrainDepen DefaultAnimation.PlayRepeating(NormalizeSequence(init.Self, sequence)); } - protected override void OnBuildComplete(Actor self) - { - DefaultAnimation.PlayRepeating(NormalizeSequence(self, sequence)); - } - public override void PlayCustomAnimation(Actor self, string name, Action after = null) { var anim = DefaultAnimation.HasSequence(name + "-" + terrain) ? name + "-" + terrain : name; diff --git a/OpenRA.Mods.Gen/Traits/ShootableBallisticMissile.cs b/OpenRA.Mods.Gen/Traits/ShootableBallisticMissile.cs index b9097664aade..3401db6c9e35 100644 --- a/OpenRA.Mods.Gen/Traits/ShootableBallisticMissile.cs +++ b/OpenRA.Mods.Gen/Traits/ShootableBallisticMissile.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Linq; using OpenRA.Activities; @@ -19,14 +20,11 @@ using OpenRA.Primitives; using OpenRA.Traits; -/* Works without base engine modification */ - namespace OpenRA.Mods.Yupgi_alert.Traits { [Desc("This unit, when ordered to move, will fly in ballistic path then will detonate itself upon reaching target.")] - public class ShootableBallisticMissileInfo : ITraitInfo, IMoveInfo, IPositionableInfo, IFacingInfo, - UsesInit, UsesInit - { + public class ShootableBallisticMissileInfo : ITraitInfo, IMoveInfo, IPositionableInfo, IFacingInfo + { [Desc("Projectile speed in WDist / tick, two values indicate variable velocity.")] public readonly int Speed = 17; @@ -58,10 +56,10 @@ public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = // set by spawned logic, not this. public int GetInitialFacing() { return 0; } - } + } - public class ShootableBallisticMissile : ISync, IFacing, IPositionable, IMove, - INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, IActorPreviewInitModifier + public class ShootableBallisticMissile : ITick, ISync, IFacing, IMove, IPositionable, + INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyActorDisposing, IOccupySpace { static readonly Pair[] NoCells = { }; @@ -97,18 +95,13 @@ public ShootableBallisticMissile(ActorInitializer init, ShootableBallisticMissil // This kind of missile will not turn anyway. Hard-coding here. public int TurnSpeed { get { return 10; } } - void INotifyCreated.Created(Actor self) - { - Created(self); - } - - protected void Created(Actor self) + public void Created(Actor self) { conditionManager = self.TraitOrDefault(); speedModifiers = self.TraitsImplementing().ToArray().Select(sm => sm.GetSpeedModifier()); } - void INotifyAddedToWorld.AddedToWorld(Actor self) + public void AddedToWorld(Actor self) { self.World.AddToMaps(self, this); @@ -117,12 +110,14 @@ void INotifyAddedToWorld.AddedToWorld(Actor self) OnAirborneAltitudeReached(); } + public virtual void Tick(Actor self) { } + public int MovementSpeed { get { return Util.ApplyPercentageModifiers(Info.Speed, speedModifiers); } } - public Pair[] OccupiedCells() { return NoCells; } + public IEnumerable> OccupiedCells() { return NoCells; } public WVec FlyStep(int facing) { @@ -135,10 +130,10 @@ public WVec FlyStep(int speed, int facing) return speed * dir / 1024; } - #region Implement IPositionable + #region Implement IPositionable - public bool CanExistInCell(CPos cell) { return true; } - public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return false; } // TODO: Handle landing + public bool CanExistInCell(CPos cell) { return true; } + public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return false; } // TODO: Handle landing public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) { return true; } public SubCell GetValidSubCell(SubCell preferred) { return SubCell.Invalid; } public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true) @@ -147,11 +142,6 @@ public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.An return SubCell.Invalid; } - bool IMove.TurnWhileDisabled(Actor self) - { - return false; - } - public void SetVisualPosition(Actor self, WPos pos) { SetPosition(self, pos); } // Changes position, but not altitude @@ -203,8 +193,7 @@ public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange) public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange) { - // You can't follow lol - return new ShootableBallisticMissileFly(self, target); + return null; } public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any) @@ -219,22 +208,21 @@ public Activity MoveToTarget(Actor self, Target target) public Activity MoveIntoTarget(Actor self, Target target) { - // Seriously, you don't want to run this lol - return new ShootableBallisticMissileFly(self, target); + return null; } public Activity VisualMove(Actor self, WPos fromPos, WPos toPos) { - // Not too sure about visual moves haha, - // Probably you shouldn't run this. - - // Leaving old code here just in case. - // return ActivityUtils.SequenceActivities(new CallFunc(() => SetVisualPosition(self, fromPos)), - // new HeliFly(self, Target.FromPos(toPos))); return new ShootableBallisticMissileFly(self, Target.FromPos(toPos)); } - public CPos NearestMoveableCell(CPos cell) { return cell; } + public int EstimatedMoveDuration(Actor self, WPos fromPos, WPos toPos) + { + var speed = MovementSpeed; + return speed > 0 ? (toPos - fromPos).Length / speed : 0; + } + + public CPos NearestMoveableCell(CPos cell) { return cell; } // Technically, ballstic movement always moves non-vertical moves = always false. public bool IsMovingVertically { get { return false; } set { } } @@ -259,6 +247,12 @@ public bool CanEnterTargetNow(Actor self, Target target) return false; } + public bool TurnWhileDisabled(Actor self) + { + // Why would you disable the missile anyway? + return true; + } + #endregion #region Implement order interfaces @@ -277,7 +271,7 @@ public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool qu return new Order(order.OrderID, self, target, queued); if (order.OrderID == "Move") - return new Order(order.OrderID, self, Target.FromCell(self.World, self.World.Map.CellContaining(target.CenterPosition)), queued); + return new Order(order.OrderID, self, target, queued); return null; } @@ -294,7 +288,7 @@ public void ResolveOrder(Actor self, Order order) #endregion - void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) + public void RemovedFromWorld(Actor self) { self.World.RemoveFromMaps(self, this); OnAirborneAltitudeLeft(); @@ -324,10 +318,13 @@ void OnAirborneAltitudeLeft() #endregion - void IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits) + public void Disposing(Actor self) + { + } + + Pair [] IOccupySpace.OccupiedCells () { - if (!inits.Contains() && !inits.Contains()) - inits.Add(new DynamicFacingInit(() => Facing)); + return NoCells; } } } diff --git a/OpenRA.Mods.Gen/Traits/SpawnedExplodes.cs b/OpenRA.Mods.Gen/Traits/SpawnedExplodes.cs index 53b58cc1aebb..f1ea8f103a4f 100644 --- a/OpenRA.Mods.Gen/Traits/SpawnedExplodes.cs +++ b/OpenRA.Mods.Gen/Traits/SpawnedExplodes.cs @@ -1,8 +1,5 @@ #region Copyright & License Information /* - * CnP of Explodes.cs by BoolBada of OP Mod - * I could modify Explodes.cs and add exceptions there but then Mods.Common gets dependency on Mods.yupgi_alert. - * * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License @@ -11,54 +8,18 @@ * information, see COPYING. */ #endregion - -using System.Collections.Generic; + using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.Common.Traits; -using OpenRA.Primitives; using OpenRA.Traits; -/* Works without base engine modification. */ - namespace OpenRA.Mods.Yupgi_alert.Traits { [Desc("This actor explodes when killed and the kill XP goes to the Spawner.")] - public class SpawnedExplodesInfo : ConditionalTraitInfo, IRulesetLoaded, Requires, Requires + public class SpawnedExplodesInfo : ExplodesInfo { - [WeaponReference, FieldLoader.Require, Desc("Default weapon to use for explosion if ammo/payload is loaded.")] - public readonly string Weapon = null; - - [WeaponReference, Desc("Fallback weapon to use for explosion if empty (no ammo/payload).")] - public readonly string EmptyWeapon = "UnitExplode"; - - [Desc("Chance that the explosion will use Weapon instead of EmptyWeapon when exploding, provided the actor has ammo/payload.")] - public readonly int LoadedChance = 100; - - [Desc("Chance that this actor will explode at all.")] - public readonly int Chance = 100; - - [Desc("Health level at which actor will explode.")] - public readonly int DamageThreshold = 0; - - [Desc("DeathType(s) that trigger the explosion. Leave empty to always trigger an explosion.")] - public readonly BitSet DeathTypes = default(BitSet); - - [Desc("Possible values are CenterPosition (explosion at the actors' center) and ", - "Footprint (explosion on each occupied cell).")] - public readonly ExplosionType Type = ExplosionType.CenterPosition; - - public WeaponInfo WeaponInfo { get; private set; } - public WeaponInfo EmptyWeaponInfo { get; private set; } - public override object Create(ActorInitializer init) { return new SpawnedExplodes(this, init.Self); } - public override void RulesetLoaded(Ruleset rules, ActorInfo ai) - { - WeaponInfo = string.IsNullOrEmpty(Weapon) ? null : rules.Weapons[Weapon.ToLowerInvariant()]; - EmptyWeaponInfo = string.IsNullOrEmpty(EmptyWeapon) ? null : rules.Weapons[EmptyWeapon.ToLowerInvariant()]; - - base.RulesetLoaded(rules, ai); - } } public class SpawnedExplodes : ConditionalTrait, INotifyKilled, INotifyDamage, INotifyCreated @@ -85,21 +46,22 @@ void INotifyKilled.Killed(Actor self, AttackInfo e) if (self.World.SharedRandom.Next(100) > Info.Chance) return; - if (!Info.DeathTypes.IsEmpty && !e.Damage.DamageTypes.Overlaps(Info.DeathTypes)) - return; + if (!Info.DeathTypes.IsEmpty && !e.Damage.DamageTypes.Overlaps(Info.DeathTypes)) + return; var weapon = ChooseWeaponForExplosion(self); if (weapon == null) return; - if (weapon.Report != null && weapon.Report.Any()) - Game.Sound.Play(SoundType.World, weapon.Report.Random(e.Attacker.World.SharedRandom), self.CenterPosition); + var source = Info.DamageSource == DamageSource.Self ? self : e.Attacker; + if (weapon.Report != null && weapon.Report.Any()) + Game.Sound.Play(SoundType.World, weapon.Report.Random(source.World.SharedRandom), self.CenterPosition); if (Info.Type == ExplosionType.Footprint && buildingInfo != null) { var cells = buildingInfo.UnpathableTiles(self.Location); foreach (var c in cells) - weapon.Impact(Target.FromPos(self.World.Map.CenterOfCell(c)), e.Attacker, Enumerable.Empty()); + weapon.Impact(Target.FromPos(self.World.Map.CenterOfCell(c)), source, Enumerable.Empty()); return; } @@ -108,23 +70,33 @@ void INotifyKilled.Killed(Actor self, AttackInfo e) weapon.Impact(Target.FromPos(self.CenterPosition), self.Trait().Master, Enumerable.Empty()); } - WeaponInfo ChooseWeaponForExplosion(Actor self) - { - var shouldExplode = self.TraitsImplementing().All(a => a.ShouldExplode(self)); - var useFullExplosion = self.World.SharedRandom.Next(100) <= Info.LoadedChance; - return (shouldExplode && useFullExplosion) ? Info.WeaponInfo : Info.EmptyWeaponInfo; - } - - void INotifyDamage.Damaged(Actor self, AttackInfo e) - { - if (IsTraitDisabled || !self.IsInWorld) - return; - - if (Info.DamageThreshold == 0) - return; - - if (health.HP * 100 < Info.DamageThreshold * health.MaxHP) - self.World.AddFrameEndTask(w => self.Kill(e.Attacker)); - } - } + WeaponInfo ChooseWeaponForExplosion(Actor self) + { + var armaments = self.TraitsImplementing(); + if (!armaments.Any()) + return Info.WeaponInfo; + + // TODO: EmptyWeapon should be removed in favour of conditions + var shouldExplode = !armaments.All(a => a.IsReloading); + var useFullExplosion = self.World.SharedRandom.Next(100) <= Info.LoadedChance; + return (shouldExplode && useFullExplosion) ? Info.WeaponInfo : Info.EmptyWeaponInfo; + } + + void INotifyDamage.Damaged(Actor self, AttackInfo e) + { + if (IsTraitDisabled || !self.IsInWorld) + return; + + if (Info.DamageThreshold == 0) + return; + + if (!Info.DeathTypes.IsEmpty && !e.Damage.DamageTypes.Overlaps(Info.DeathTypes)) + return; + + // Cast to long to avoid overflow when multiplying by the health + var source = Info.DamageSource == DamageSource.Self ? self : e.Attacker; + if (health.HP * 100L < Info.DamageThreshold * (long)health.MaxHP) + self.World.AddFrameEndTask(w => self.Kill(source)); + } + } } diff --git a/OpenRA.Mods.Gen/Traits/SpawnerHarvesterMaster.cs b/OpenRA.Mods.Gen/Traits/SpawnerHarvesterMaster.cs index f2fb3720bb87..5ee2029907ab 100644 --- a/OpenRA.Mods.Gen/Traits/SpawnerHarvesterMaster.cs +++ b/OpenRA.Mods.Gen/Traits/SpawnerHarvesterMaster.cs @@ -66,7 +66,7 @@ public enum MiningState Kick // Check if there's ore field is close enough. } - public class SpawnerHarvesterMaster : BaseSpawnerMaster, INotifyBuildComplete, INotifyIdle, + public class SpawnerHarvesterMaster : BaseSpawnerMaster, INotifyIdle, ITick, IIssueOrder, IResolveOrder, IOrderVoice, INotifyDeployComplete { readonly SpawnerHarvesterMasterInfo info; @@ -98,13 +98,6 @@ public SpawnerHarvesterMaster(ActorInitializer init, SpawnerHarvesterMasterInfo kickTicks = info.KickDelay; } - void INotifyBuildComplete.BuildingComplete(Actor self) - { - // Search for resources upon creation. - if (info.SearchOnCreation) - self.QueueActivity(new SpawnerHarvesterHarvest(self)); - } - // Modify Harvester trait's states to do the mining. void AssignTargetForSpawned(Actor slave, CPos targetLocation) { diff --git a/OpenRA.Mods.Gen/Traits/Warheads/CaptureActorWarhead.cs b/OpenRA.Mods.Gen/Traits/Warheads/CaptureActorWarhead.cs index d387cebc6b93..de5af5acc833 100644 --- a/OpenRA.Mods.Gen/Traits/Warheads/CaptureActorWarhead.cs +++ b/OpenRA.Mods.Gen/Traits/Warheads/CaptureActorWarhead.cs @@ -10,7 +10,9 @@ using System.Collections.Generic; using System.Linq; +using OpenRA.Mods.Common; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Yupgi_alert.Warheads @@ -22,10 +24,14 @@ public class CaptureActorWarhead : WarheadAS public readonly WDist Range = new WDist(64); [Desc("Types of actors that it can capture, as long as the type also exists in the Capturable Type: trait.")] - public readonly HashSet CaptureTypes = new HashSet { "building" }; + public readonly BitSet CaptureTypes = default(BitSet); - [Desc("If set, the target will be captured regardless of threshold.")] - public readonly bool IgnoreCaptureThreshold = false; + [Desc("Targets with health above this percentage will be sabotaged instead of captured.", + "Set to 0 to disable sabotaging.")] + public readonly int SabotageThreshold = 0; + + [Desc("Sabotage damage expressed as a percentage of maximum target health.")] + public readonly int SabotageHPRemoval = 50; [Desc("Experience granted to the capturing actor.")] public readonly int Experience = 0; @@ -41,12 +47,15 @@ public class CaptureActorWarhead : WarheadAS public override void DoImpact(Target target, Actor firedBy, IEnumerable damageModifiers) { + if (!target.IsValidFor(firedBy)) + return; + var pos = target.CenterPosition; if (!IsValidImpact(pos, firedBy)) return; - var availableActors = firedBy.World.FindActorsInCircle(pos, Range); + var availableActors = firedBy.World.FindActorsOnCircle(pos, Range); foreach (var a in availableActors) { @@ -62,50 +71,50 @@ public override void DoImpact(Target target, Actor firedBy, IEnumerable dam if (distance > Range) continue; - var capturable = a.TraitOrDefault(); - var building = a.TraitOrDefault(); - var health = a.Trait(); + var capturable = a.TraitsImplementing() + .FirstOrDefault(c => !c.IsTraitDisabled && c.Info.Types.Overlaps(CaptureTypes)); - if (a.IsDead || capturable.BeingCaptured) - continue; - - if (building != null && !building.Lock()) + if (a.IsDead || capturable == null) continue; firedBy.World.AddFrameEndTask(w => { - if (building != null && building.Locked) - building.Unlock(); - - if (a.IsDead || capturable.BeingCaptured) + if (a.IsDead) return; - var lowEnoughHealth = health.HP <= capturable.Info.CaptureThreshold * health.MaxHP / 100; - if (IgnoreCaptureThreshold || lowEnoughHealth || a.Owner.NonCombatant) + if (SabotageThreshold > 0 && !a.Owner.NonCombatant) { - var oldOwner = a.Owner; + var health = a.Trait(); - a.ChangeOwner(firedBy.Owner); + // Cast to long to avoid overflow when multiplying by the health + if (100 * (long)health.HP > SabotageThreshold * (long)health.MaxHP) + { + var damage = (int)((long)health.MaxHP * SabotageHPRemoval / 100); + a.InflictDamage(firedBy, new Damage(damage)); - foreach (var t in a.TraitsImplementing()) - t.OnCapture(a, firedBy, oldOwner, a.Owner); + return; + } + } - if (building != null && building.Locked) - building.Unlock(); + var oldOwner = a.Owner; - if (firedBy.Owner.Stances[oldOwner].HasStance(ExperienceStances)) - { - var exp = firedBy.TraitOrDefault(); - if (exp != null) - exp.GiveExperience(Experience); - } + a.ChangeOwner(firedBy.Owner); - if (firedBy.Owner.Stances[oldOwner].HasStance(PlayerExperienceStances)) - { - var exp = firedBy.Owner.PlayerActor.TraitOrDefault(); - if (exp != null) - exp.GiveExperience(PlayerExperience); - } + foreach (var t in a.TraitsImplementing()) + t.OnCapture(a, firedBy, oldOwner, a.Owner, CaptureTypes); + + if (firedBy.Owner.Stances[oldOwner].HasStance(ExperienceStances)) + { + var exp = firedBy.TraitOrDefault(); + if (exp != null) + exp.GiveExperience(Experience); + } + + if (firedBy.Owner.Stances[oldOwner].HasStance(PlayerExperienceStances)) + { + var exp = firedBy.Owner.PlayerActor.TraitOrDefault(); + if (exp != null) + exp.GiveExperience(PlayerExperience); } }); } @@ -113,19 +122,10 @@ public override void DoImpact(Target target, Actor firedBy, IEnumerable dam public override bool IsValidAgainst(Actor victim, Actor firedBy) { - var capturable = victim.TraitsImplementing().ToArray(); - var activeCapturable = capturable.FirstOrDefault(c => !c.IsTraitDisabled); - if (activeCapturable == null || !CaptureTypes.Overlaps(activeCapturable.Info.Types)) - return false; - - var playerRelationship = victim.Owner.Stances[firedBy.Owner]; - if (playerRelationship == Stance.Ally && !activeCapturable.Info.ValidStances.HasStance(Stance.Ally)) - return false; - - if (playerRelationship == Stance.Enemy && !activeCapturable.Info.ValidStances.HasStance(Stance.Enemy)) - return false; + var capturable = victim.TraitsImplementing() + .FirstOrDefault(c => !c.IsTraitDisabled && c.Info.Types.Overlaps(CaptureTypes)); - if (playerRelationship == Stance.Neutral && !activeCapturable.Info.ValidStances.HasStance(Stance.Neutral)) + if (capturable == null || !capturable.Info.ValidStances.HasStance(victim.Owner.Stances[firedBy.Owner])) return false; return base.IsValidAgainst(victim, firedBy);