diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index e62674e68a..724dec9dd2 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -229,7 +229,7 @@ namespace OpenRA.Mods.Common.AI return null; var unit = buildableThings.Random(Random); - return HasAdequateAirUnits(unit) ? unit : null; + return HasAdequateAirUnitReloadBuildings(unit) ? unit : null; } ActorInfo ChooseUnitToBuild(ProductionQueue queue) @@ -246,7 +246,7 @@ namespace OpenRA.Mods.Common.AI foreach (var unit in Info.UnitsToBuild.Shuffle(Random)) if (buildableThings.Any(b => b.Name == unit.Key)) if (myUnits.Count(a => a == unit.Key) < unit.Value * myUnits.Count) - if (HasAdequateAirUnits(Map.Rules.Actors[unit.Key])) + if (HasAdequateAirUnitReloadBuildings(Map.Rules.Actors[unit.Key])) return Map.Rules.Actors[unit.Key]; return null; @@ -318,13 +318,18 @@ namespace OpenRA.Mods.Common.AI } // For mods like RA (number of building must match the number of aircraft) - bool HasAdequateAirUnits(ActorInfo actorInfo) + bool HasAdequateAirUnitReloadBuildings(ActorInfo actorInfo) { - if (!actorInfo.Traits.Contains() && actorInfo.Traits.Contains() - && actorInfo.Traits.Contains()) + var aircraftInfo = actorInfo.Traits.GetOrDefault(); + if (aircraftInfo == null) + return true; + + var ammoPoolsInfo = actorInfo.Traits.WithInterface(); + + if (ammoPoolsInfo.Any(x => !x.SelfReloads)) { var countOwnAir = CountUnits(actorInfo.Name, Player); - var countBuildings = CountBuilding(actorInfo.Traits.Get().RearmBuildings.FirstOrDefault(), Player); + var countBuildings = CountBuilding(aircraftInfo.RearmBuildings.FirstOrDefault(), Player); if (countOwnAir >= countBuildings) return false; } diff --git a/OpenRA.Mods.Common/AI/States/AirStates.cs b/OpenRA.Mods.Common/AI/States/AirStates.cs index 7351f18b34..c5d4e95258 100644 --- a/OpenRA.Mods.Common/AI/States/AirStates.cs +++ b/OpenRA.Mods.Common/AI/States/AirStates.cs @@ -104,19 +104,20 @@ namespace OpenRA.Mods.Common.AI protected static bool FullAmmo(Actor a) { - var limitedAmmo = a.TraitOrDefault(); - return limitedAmmo != null && limitedAmmo.FullAmmo(); + var ammoPools = a.TraitsImplementing(); + return ammoPools.All(x => x.FullAmmo()); } protected static bool HasAmmo(Actor a) { - var limitedAmmo = a.TraitOrDefault(); - return limitedAmmo != null && limitedAmmo.HasAmmo(); + var ammoPools = a.TraitsImplementing(); + return ammoPools.All(x => x.HasAmmo()); } protected static bool ReloadsAutomatically(Actor a) { - return a.HasTrait(); + var ammoPools = a.TraitsImplementing(); + return ammoPools.All(x => x.Info.SelfReloads); } protected static bool IsRearm(Actor a) diff --git a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs index 82c29d371a..3e204f419b 100644 --- a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs @@ -8,6 +8,8 @@ */ #endregion +using System; +using System.Collections.Generic; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; @@ -18,30 +20,38 @@ namespace OpenRA.Mods.Common.Activities public class FlyAttack : Activity { readonly Target target; + readonly AttackPlane attackPlane; + readonly IEnumerable ammoPools; Activity inner; int ticksUntilTurn = 50; - public FlyAttack(Target target) { this.target = target; } + public FlyAttack(Actor self, Target target) + { + this.target = target; + attackPlane = self.TraitOrDefault(); + ammoPools = self.TraitsImplementing(); + } public override Activity Tick(Actor self) { if (!target.IsValidFor(self)) return NextActivity; - var limitedAmmo = self.TraitOrDefault(); - if (limitedAmmo != null && !limitedAmmo.HasAmmo()) + // Move to the next activity only if all ammo pools are depleted and none reload automatically + // TODO: This should check whether there is ammo left that is actually suitable for the target + if (ammoPools != null && ammoPools.All(x => !x.Info.SelfReloads && !x.HasAmmo())) return NextActivity; - var attack = self.TraitOrDefault(); - if (attack != null) - attack.DoAttack(self, target); + if (attackPlane != null) + attackPlane.DoAttack(self, target); if (inner == null) { if (IsCanceled) return NextActivity; - if (target.IsInRange(self.CenterPosition, attack.Armaments.Select(a => a.Weapon.MinRange).Min())) + // TODO: This should fire each weapon at its maximum range + if (target.IsInRange(self.CenterPosition, attackPlane.Armaments.Select(a => a.Weapon.MinRange).Min())) inner = Util.SequenceActivities(new FlyTimed(ticksUntilTurn), new Fly(self, target), new FlyTimed(ticksUntilTurn)); else inner = Util.SequenceActivities(new Fly(self, target), new FlyTimed(ticksUntilTurn)); diff --git a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs index 110b85a8b1..c420365531 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs @@ -8,6 +8,9 @@ */ #endregion +using System; +using System.Collections.Generic; +using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; @@ -16,21 +19,29 @@ namespace OpenRA.Mods.Common.Activities { public class HeliAttack : Activity { - Target target; - public HeliAttack(Target target) { this.target = target; } + readonly Target target; + readonly Helicopter helicopter; + readonly AttackHeli attackHeli; + readonly IEnumerable ammoPools; + + public HeliAttack(Actor self, Target target) + { + this.target = target; + helicopter = self.Trait(); + attackHeli = self.Trait(); + ammoPools = self.TraitsImplementing(); + } public override Activity Tick(Actor self) { if (IsCanceled || !target.IsValidFor(self)) return NextActivity; - var limitedAmmo = self.TraitOrDefault(); - var reloads = self.TraitOrDefault(); - if (limitedAmmo != null && !limitedAmmo.HasAmmo() && reloads == null) + // If all ammo pools are depleted and none reload automatically, return to helipad to reload and then move to next activity + // TODO: This should check whether there is ammo left that is actually suitable for the target + if (ammoPools != null && ammoPools.All(x => !x.Info.SelfReloads && !x.HasAmmo())) return Util.SequenceActivities(new HeliReturn(), NextActivity); - var helicopter = self.Trait(); - var attack = self.Trait(); var dist = target.CenterPosition - self.CenterPosition; // Can rotate facing while ascending @@ -41,10 +52,12 @@ namespace OpenRA.Mods.Common.Activities return this; // Fly towards the target - if (!target.IsInRange(self.CenterPosition, attack.GetMaximumRange())) + // TODO: Fix that the helicopter won't do anything if it has multiple weapons with different ranges + // and the weapon with the longest range is out of ammo + if (!target.IsInRange(self.CenterPosition, attackHeli.GetMaximumRange())) helicopter.SetPosition(self, helicopter.CenterPosition + helicopter.FlyStep(desiredFacing)); - attack.DoAttack(self, target); + attackHeli.DoAttack(self, target); return this; } diff --git a/OpenRA.Mods.Common/Activities/Rearm.cs b/OpenRA.Mods.Common/Activities/Rearm.cs index d3c913293e..3a63d52d94 100644 --- a/OpenRA.Mods.Common/Activities/Rearm.cs +++ b/OpenRA.Mods.Common/Activities/Rearm.cs @@ -8,53 +8,64 @@ */ #endregion +using System.Collections.Generic; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { public class Rearm : Activity { - readonly LimitedAmmo limitedAmmo; - int ticksPerPip = 25 * 2; - int remainingTicks = 25 * 2; - string sound = null; + readonly IEnumerable ammoPools; + readonly Dictionary ammoPoolsReloadTimes; - public Rearm(Actor self, string sound = null) + public Rearm(Actor self) { - limitedAmmo = self.TraitOrDefault(); - if (limitedAmmo != null) - ticksPerPip = limitedAmmo.ReloadTimePerAmmo(); - remainingTicks = ticksPerPip; - this.sound = sound; + ammoPools = self.TraitsImplementing().Where(p => !p.Info.SelfReloads); + + if (ammoPools == null) + return; + + ammoPoolsReloadTimes = ammoPools.ToDictionary(x => x, y => y.Info.ReloadTicks); } public override Activity Tick(Actor self) { - if (IsCanceled || limitedAmmo == null) + if (IsCanceled || ammoPoolsReloadTimes == null) return NextActivity; - if (--remainingTicks == 0) + var needsReloading = false; + + foreach (var pool in ammoPools) { - var hostBuilding = self.World.ActorMap.GetUnitsAt(self.Location) - .FirstOrDefault(a => a.HasTrait()); + if (pool.FullAmmo()) + continue; + + needsReloading = true; + + if (--ammoPoolsReloadTimes[pool] > 0) + continue; + + // HACK to check if we are on the helipad/airfield/etc. + var hostBuilding = self.World.ActorMap.GetUnitsAt(self.Location).FirstOrDefault(a => a.HasTrait()); if (hostBuilding == null || !hostBuilding.IsInWorld) return NextActivity; - if (!limitedAmmo.GiveAmmo()) - return NextActivity; + if (!pool.GiveAmmo()) + continue; hostBuilding.Trait().PlayCustomAnim(hostBuilding, "active"); + + var sound = pool.Info.RearmSound; if (sound != null) Sound.Play(sound, self.CenterPosition); - remainingTicks = limitedAmmo.ReloadTimePerAmmo(); + ammoPoolsReloadTimes[pool] = pool.Info.ReloadTicks; } - return this; + return needsReloading ? this : NextActivity; } } } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 56e8a65815..260fe44cbd 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -320,7 +320,7 @@ - + @@ -364,7 +364,6 @@ - diff --git a/OpenRA.Mods.Common/Scripting/Properties/PlaneProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/PlaneProperties.cs index ab3b29cdbb..919c5149f9 100644 --- a/OpenRA.Mods.Common/Scripting/Properties/PlaneProperties.cs +++ b/OpenRA.Mods.Common/Scripting/Properties/PlaneProperties.cs @@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.Scripting [Desc("Fly an attack against the target actor.")] public void Attack(Actor target) { - Self.QueueActivity(new FlyAttack(Target.FromActor(target))); + Self.QueueActivity(new FlyAttack(Self, Target.FromActor(target))); } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs b/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs index a40a9d6ac5..42c10ac2dc 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackHeli.cs @@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { - return new HeliAttack(newTarget); + return new HeliAttack(self, newTarget); } } } diff --git a/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs b/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs index 7c7c5593ba..26f89f4df6 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackPlane.cs @@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { - return new FlyAttack(newTarget); + return new FlyAttack(self, newTarget); } protected override bool CanAttack(Actor self, Target target) diff --git a/OpenRA.Mods.Common/Traits/AmmoPool.cs b/OpenRA.Mods.Common/Traits/AmmoPool.cs new file mode 100644 index 0000000000..23ffcf5dd6 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/AmmoPool.cs @@ -0,0 +1,138 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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. For more information, + * see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Actor has a limited amount of ammo, after using it all the actor must reload in some way.")] + public class AmmoPoolInfo : ITraitInfo + { + [Desc("Name of this ammo pool, used to link armaments to this pool.")] + public readonly string Name = "primary"; + + [Desc("How much ammo does this pool contain when fully loaded.")] + public readonly int Ammo = 1; + + [Desc("Initial ammo the actor is created with. Defaults to Ammo.")] + public readonly int InitialAmmo = -1; + + [Desc("Defaults to value in Ammo. 0 means no visible pips.")] + public readonly int PipCount = -1; + + [Desc("PipType to use for loaded ammo.")] + public readonly PipType PipType = PipType.Green; + + [Desc("PipType to use for empty ammo.")] + public readonly PipType PipTypeEmpty = PipType.Transparent; + + [Desc("How much ammo is reloaded after a certain period.")] + public readonly int ReloadCount = 1; + + [Desc("Sound to play for each reloaded ammo magazine.")] + public readonly string RearmSound = null; + + [Desc("Time to reload per ReloadCount on airfield etc.")] + public readonly int ReloadTicks = 25 * 2; + + [Desc("Whether or not ammo is replenished on its own.")] + public readonly bool SelfReloads = false; + + [Desc("Time to reload per ReloadCount when actor 'SelfReloads'.")] + public readonly int SelfReloadTicks = 25 * 2; + + [Desc("Whether or not reload timer should be reset when ammo has been fired.")] + public readonly bool ResetOnFire = false; + + public object Create(ActorInitializer init) { return new AmmoPool(init.Self, this); } + } + + public class AmmoPool : INotifyAttack, IPips, ITick, ISync + { + public readonly AmmoPoolInfo Info; + [Sync] public int CurrentAmmo; + [Sync] public int RemainingTicks; + public int PreviousAmmo; + + public AmmoPool(Actor self, AmmoPoolInfo info) + { + Info = info; + if (Info.InitialAmmo < Info.Ammo && Info.InitialAmmo >= 0) + CurrentAmmo = Info.InitialAmmo; + else + CurrentAmmo = Info.Ammo; + + RemainingTicks = Info.SelfReloadTicks; + PreviousAmmo = GetAmmoCount(); + } + + public int GetAmmoCount() { return CurrentAmmo; } + public bool FullAmmo() { return CurrentAmmo == Info.Ammo; } + public bool HasAmmo() { return CurrentAmmo > 0; } + + public bool GiveAmmo() + { + if (CurrentAmmo >= Info.Ammo) + return false; + + ++CurrentAmmo; + return true; + } + + public bool TakeAmmo() + { + if (CurrentAmmo <= 0) + return false; + + --CurrentAmmo; + return true; + } + + public void Attacking(Actor self, Target target, Armament a, Barrel barrel) + { + if (a.Info.AmmoPoolName == Info.Name) + TakeAmmo(); + } + + public void Tick(Actor self) + { + if (!Info.SelfReloads) + return; + + // Resets the tick counter if ammo was fired. + if (Info.ResetOnFire && GetAmmoCount() < PreviousAmmo) + { + RemainingTicks = Info.SelfReloadTicks; + PreviousAmmo = GetAmmoCount(); + } + + if (!FullAmmo() && --RemainingTicks == 0) + { + RemainingTicks = Info.SelfReloadTicks; + + for (var i = 0; i < Info.ReloadCount; i++) + GiveAmmo(); + + PreviousAmmo = GetAmmoCount(); + } + } + + public IEnumerable GetPips(Actor self) + { + var pips = Info.PipCount >= 0 ? Info.PipCount : Info.Ammo; + + return Enumerable.Range(0, pips).Select(i => + (CurrentAmmo * pips) / Info.Ammo > i ? + Info.PipType : Info.PipTypeEmpty); + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Armament.cs b/OpenRA.Mods.Common/Traits/Armament.cs index 72804be748..c28b6453c2 100644 --- a/OpenRA.Mods.Common/Traits/Armament.cs +++ b/OpenRA.Mods.Common/Traits/Armament.cs @@ -31,16 +31,25 @@ namespace OpenRA.Mods.Common.Traits [WeaponReference] [Desc("Has to be defined here and in weapons.yaml.")] public readonly string Weapon = null; + + [Desc("Which limited ammo pool (if present) should this armament be assigned to.")] + public readonly string AmmoPoolName = "primary"; + + [Desc("Which turret (if present) should this armament be assigned to.")] public readonly string Turret = "primary"; + [Desc("Time (in frames) until the weapon can fire again.")] public readonly int FireDelay = 0; [Desc("Muzzle position relative to turret or body. (forward, right, up) triples")] public readonly WVec[] LocalOffset = { }; + [Desc("Muzzle yaw relative to turret or body.")] public readonly WAngle[] LocalYaw = { }; + [Desc("Move the turret backwards when firing.")] public readonly WRange Recoil = WRange.Zero; + [Desc("Recoil recovery per-frame")] public readonly WRange RecoilRecovery = new WRange(9); @@ -64,7 +73,7 @@ namespace OpenRA.Mods.Common.Traits readonly Actor self; Lazy turret; Lazy coords; - Lazy limitedAmmo; + Lazy ammoPool; List> delayedActions = new List>(); public WRange Recoil; @@ -79,7 +88,7 @@ namespace OpenRA.Mods.Common.Traits // We can't resolve these until runtime turret = Exts.Lazy(() => self.TraitsImplementing().FirstOrDefault(t => t.Name == info.Turret)); coords = Exts.Lazy(() => self.Trait()); - limitedAmmo = Exts.Lazy(() => self.TraitOrDefault()); + ammoPool = Exts.Lazy(() => self.TraitsImplementing().FirstOrDefault(la => la.Info.Name == info.AmmoPoolName)); Weapon = self.World.Map.Rules.Weapons[info.Weapon.ToLowerInvariant()]; Burst = Weapon.Burst; @@ -136,7 +145,7 @@ namespace OpenRA.Mods.Common.Traits if (IsReloading) return null; - if (limitedAmmo.Value != null && !limitedAmmo.Value.HasAmmo()) + if (ammoPool.Value != null && !ammoPool.Value.HasAmmo()) return null; if (!target.IsInRange(self.CenterPosition, Weapon.Range)) diff --git a/OpenRA.Mods.Common/Traits/LimitedAmmo.cs b/OpenRA.Mods.Common/Traits/LimitedAmmo.cs deleted file mode 100644 index 976590a441..0000000000 --- a/OpenRA.Mods.Common/Traits/LimitedAmmo.cs +++ /dev/null @@ -1,75 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2015 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. For more information, - * see COPYING. - */ -#endregion - -using System.Collections.Generic; -using System.Linq; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - [Desc("Actor has a limited amount of ammo, after using it all the actor must reload in some way.")] - public class LimitedAmmoInfo : ITraitInfo - { - public readonly int Ammo = 0; - - [Desc("Defaults to value in Ammo.")] - public readonly int PipCount = 0; - - public readonly PipType PipType = PipType.Green; - public readonly PipType PipTypeEmpty = PipType.Transparent; - - [Desc("Time to reload measured in ticks.")] - public readonly int ReloadTicks = 25 * 2; - - public object Create(ActorInitializer init) { return new LimitedAmmo(this); } - } - - public class LimitedAmmo : INotifyAttack, IPips, ISync - { - [Sync] int ammo; - LimitedAmmoInfo info; - - public LimitedAmmo(LimitedAmmoInfo info) - { - ammo = info.Ammo; - this.info = info; - } - - public bool FullAmmo() { return ammo == info.Ammo; } - public bool HasAmmo() { return ammo > 0; } - public bool GiveAmmo() - { - if (ammo >= info.Ammo) return false; - ++ammo; - return true; - } - - public bool TakeAmmo() - { - if (ammo <= 0) return false; - --ammo; - return true; - } - - public int ReloadTimePerAmmo() { return info.ReloadTicks; } - - public void Attacking(Actor self, Target target, Armament a, Barrel barrel) { TakeAmmo(); } - - public int GetAmmoCount() { return ammo; } - - public IEnumerable GetPips(Actor self) - { - var pips = info.PipCount != 0 ? info.PipCount : info.Ammo; - return Enumerable.Range(0, pips).Select(i => - (ammo * pips) / info.Ammo > i ? - info.PipType : info.PipTypeEmpty); - } - } -} diff --git a/OpenRA.Mods.Common/Traits/Reloads.cs b/OpenRA.Mods.Common/Traits/Reloads.cs deleted file mode 100644 index 60a906f4ce..0000000000 --- a/OpenRA.Mods.Common/Traits/Reloads.cs +++ /dev/null @@ -1,63 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2015 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. For more information, - * see COPYING. - */ -#endregion - -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - [Desc("Unit will reload its limited ammo itself.")] - public class ReloadsInfo : ITraitInfo, Requires - { - [Desc("How much ammo is reloaded after a certain period.")] - public readonly int Count = 0; - [Desc("How long it takes to do so.")] - public readonly int Period = 50; - [Desc("Whether or not reload counter should be reset when ammo has been fired.")] - public readonly bool ResetOnFire = false; - - public object Create(ActorInitializer init) { return new Reloads(init.Self, this); } - } - - public class Reloads : ITick - { - [Sync] int remainingTicks; - ReloadsInfo info; - LimitedAmmo la; - int previousAmmo; - - public Reloads(Actor self, ReloadsInfo info) - { - this.info = info; - remainingTicks = info.Period; - la = self.Trait(); - previousAmmo = la.GetAmmoCount(); - } - - public void Tick(Actor self) - { - if (!la.FullAmmo() && --remainingTicks == 0) - { - remainingTicks = info.Period; - - for (var i = 0; i < info.Count; i++) - la.GiveAmmo(); - - previousAmmo = la.GetAmmoCount(); - } - - // Resets the tick counter if ammo was fired. - if (info.ResetOnFire && la.GetAmmoCount() < previousAmmo) - { - remainingTicks = info.Period; - previousAmmo = la.GetAmmoCount(); - } - } - } -} diff --git a/OpenRA.Mods.Common/Traits/Repairable.cs b/OpenRA.Mods.Common/Traits/Repairable.cs index 901e58bd0b..e3df40b5c5 100644 --- a/OpenRA.Mods.Common/Traits/Repairable.cs +++ b/OpenRA.Mods.Common/Traits/Repairable.cs @@ -22,18 +22,20 @@ namespace OpenRA.Mods.Common.Traits class RepairableInfo : ITraitInfo, Requires { public readonly string[] RepairBuildings = { "fix" }; - public virtual object Create(ActorInitializer init) { return new Repairable(init.Self); } + public virtual object Create(ActorInitializer init) { return new Repairable(init.Self, this); } } class Repairable : IIssueOrder, IResolveOrder, IOrderVoice { - readonly Actor self; + readonly RepairableInfo info; readonly Health health; + readonly IEnumerable ammoPools; - public Repairable(Actor self) + public Repairable(Actor self, RepairableInfo info) { - this.self = self; + this.info = info; health = self.Trait(); + ammoPools = self.TraitsImplementing(); } public IEnumerable Orders @@ -41,7 +43,7 @@ namespace OpenRA.Mods.Common.Traits get { yield return new EnterAlliedActorTargeter("Repair", 5, - target => CanRepairAt(target), _ => CanRepair()); + target => CanRepairAt(target), _ => CanRepair() || CanRearm()); } } @@ -55,13 +57,25 @@ namespace OpenRA.Mods.Common.Traits bool CanRepairAt(Actor target) { - return self.Info.Traits.Get().RepairBuildings.Contains(target.Info.Name); + return info.RepairBuildings.Contains(target.Info.Name); + } + + bool CanRearmAt(Actor target) + { + return info.RepairBuildings.Contains(target.Info.Name); } bool CanRepair() { - var li = self.TraitOrDefault(); - return health.DamageState > DamageState.Undamaged || (li != null && !li.FullAmmo()); + return health.DamageState > DamageState.Undamaged; + } + + bool CanRearm() + { + if (ammoPools != null) + return ammoPools.Any(x => !x.Info.SelfReloads && !x.FullAmmo()); + else + return false; } public string VoicePhraseForOrder(Actor self, Order order) @@ -73,7 +87,7 @@ namespace OpenRA.Mods.Common.Traits { if (order.OrderString == "Repair") { - if (!CanRepairAt(order.TargetActor) || !CanRepair()) + if (!CanRepairAt(order.TargetActor) || (!CanRepair() && !CanRearm())) return; var movement = self.Trait(); @@ -83,7 +97,9 @@ namespace OpenRA.Mods.Common.Traits self.CancelActivity(); self.QueueActivity(new MoveAdjacentTo(self, target)); self.QueueActivity(movement.MoveTo(self.World.Map.CellContaining(order.TargetActor.CenterPosition), order.TargetActor)); - self.QueueActivity(new Rearm(self)); + if (CanRearmAt(order.TargetActor) && CanRearm()) + self.QueueActivity(new Rearm(self)); + self.QueueActivity(new Repair(order.TargetActor)); var rp = order.TargetActor.TraitOrDefault(); diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index ac40f3962f..7cd4a1b2ec 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -774,6 +774,61 @@ namespace OpenRA.Mods.Common.UtilityCommands node.Key = "StandSequences"; } + if (engineVersion < 20150323) + { + // Moved Reloads functionality to LimitedAmmo and refactored the latter into AmmoPool + if (depth == 0) + { + var actorTraits = node.Value.Nodes; + var limitedAmmo = actorTraits.FirstOrDefault(l => l.Key == "LimitedAmmo"); + var reloads = actorTraits.FirstOrDefault(r => r.Key == "Reloads"); + + if (reloads != null) + { + var reloadsFields = reloads.Value.Nodes; + var limitedAmmoFields = limitedAmmo.Value.Nodes; + var count = reloadsFields.FirstOrDefault(c => c.Key == "Count"); + var period = reloadsFields.FirstOrDefault(p => p.Key == "Period"); + var resets = reloadsFields.FirstOrDefault(res => res.Key == "ResetOnFire"); + + var reloadsCount = count != null ? FieldLoader.GetValue("Count", count.Value.Value) : -1; + var reloadsPeriod = period != null ? FieldLoader.GetValue("Period", period.Value.Value) : 50; + var reloadsResetOnFire = resets != null ? FieldLoader.GetValue("ResetOnFire", resets.Value.Value) : false; + + limitedAmmoFields.Add(new MiniYamlNode("SelfReloads", "true")); + limitedAmmoFields.Add(new MiniYamlNode("ReloadCount", reloadsCount.ToString())); + limitedAmmoFields.Add(new MiniYamlNode("SelfReloadTicks", reloadsPeriod.ToString())); + limitedAmmoFields.Add(new MiniYamlNode("ResetOnFire", reloadsResetOnFire.ToString())); + + node.Value.Nodes.RemoveAll(n => n.Key == "Reloads"); + node.Value.Nodes.RemoveAll(n => n.Key == "-Reloads"); + } + } + + // Moved RearmSound from Minelayer to LimitedAmmo/AmmoPool + if (depth == 0) + { + var actorTraits = node.Value.Nodes; + var limitedAmmo = actorTraits.FirstOrDefault(la => la.Key == "LimitedAmmo"); + var minelayer = actorTraits.FirstOrDefault(ml => ml.Key == "Minelayer"); + + if (minelayer != null) + { + var minelayerFields = minelayer.Value.Nodes; + var limitedAmmoFields = limitedAmmo.Value.Nodes; + var rearmSound = minelayerFields.FirstOrDefault(rs => rs.Key == "RearmSound"); + var minelayerRearmSound = rearmSound != null ? FieldLoader.GetValue("RearmSound", rearmSound.Value.Value) : "minelay1.aud"; + + limitedAmmoFields.Add(new MiniYamlNode("RearmSound", minelayerRearmSound.ToString())); + minelayerFields.Remove(rearmSound); + } + } + + // Rename LimitedAmmo to AmmoPool + if (node.Key == "LimitedAmmo") + node.Key = "AmmoPool"; + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } diff --git a/OpenRA.Mods.RA/Activities/LayMines.cs b/OpenRA.Mods.RA/Activities/LayMines.cs index 2f178ce979..b788fd3128 100644 --- a/OpenRA.Mods.RA/Activities/LayMines.cs +++ b/OpenRA.Mods.RA/Activities/LayMines.cs @@ -8,6 +8,8 @@ */ #endregion +using System; +using System.Collections.Generic; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Activities; @@ -18,23 +20,34 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Activities { - // assumes you have Minelayer on that unit - class LayMines : Activity + // Assumes you have Minelayer on that unit + public class LayMines : Activity { + readonly Minelayer minelayer; + readonly MinelayerInfo info; + readonly IEnumerable ammoPools; + readonly IMove movement; + readonly string[] rearmBuildings; + + public LayMines(Actor self) + { + minelayer = self.TraitOrDefault(); + info = self.Info.Traits.Get(); + ammoPools = self.TraitsImplementing(); + movement = self.Trait(); + rearmBuildings = info.RearmBuildings; + } + public override Activity Tick(Actor self) { - if (IsCanceled) return NextActivity; + if (IsCanceled) + return NextActivity; - var movement = self.Trait(); - var limitedAmmo = self.TraitOrDefault(); - if (!limitedAmmo.HasAmmo()) + if (ammoPools != null && ammoPools.Any(p => p.Info.Name == info.AmmoPoolName && !p.HasAmmo())) { - var info = self.Info.Traits.Get(); - - // rearm & repair at fix, then back out here to refill the minefield some more - var buildings = info.RearmBuildings; + // Rearm (and possibly repair) at rearm building, then back out here to refill the minefield some more var rearmTarget = self.World.Actors.Where(a => self.Owner.Stances[a.Owner] == Stance.Ally - && buildings.Contains(a.Info.Name)) + && rearmBuildings.Contains(a.Info.Name)) .ClosestTo(self); if (rearmTarget == null) @@ -43,47 +56,50 @@ namespace OpenRA.Mods.RA.Activities return Util.SequenceActivities( new MoveAdjacentTo(self, Target.FromActor(rearmTarget)), movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget), - new Rearm(self, info.RearmSound), + new Rearm(self), new Repair(rearmTarget), this); } - var ml = self.Trait(); - if (ml.Minefield.Contains(self.Location) && ShouldLayMine(self, self.Location)) + if (minelayer.Minefield.Contains(self.Location) && ShouldLayMine(self, self.Location)) { LayMine(self); - return Util.SequenceActivities(new Wait(20), this); // a little wait after placing each mine, for show + return Util.SequenceActivities(new Wait(20), this); // A little wait after placing each mine, for show } - if (ml.Minefield.Length > 0) + if (minelayer.Minefield.Length > 0) { - // dont get stuck forever here + // Don't get stuck forever here for (var n = 0; n < 20; n++) { - var p = ml.Minefield.Random(self.World.SharedRandom); + var p = minelayer.Minefield.Random(self.World.SharedRandom); if (ShouldLayMine(self, p)) return Util.SequenceActivities(movement.MoveTo(p, 0), this); } } - // TODO: return somewhere likely to be safe (near fix) so we're not sitting out in the minefield. + // TODO: Return somewhere likely to be safe (near rearm building) so we're not sitting out in the minefield. return new Wait(20); // nothing to do here } static bool ShouldLayMine(Actor self, CPos p) { - // if there is no unit (other than me) here, we want to place a mine here + // If there is no unit (other than me) here, we want to place a mine here return !self.World.ActorMap.GetUnitsAt(p).Any(a => a != self); } - static void LayMine(Actor self) + void LayMine(Actor self) { - var limitedAmmo = self.TraitOrDefault(); - if (limitedAmmo != null) - limitedAmmo.TakeAmmo(); + if (ammoPools != null) + { + var pool = ammoPools.FirstOrDefault(x => x.Info.Name == info.AmmoPoolName); + if (pool == null) + return; + pool.TakeAmmo(); + } self.World.AddFrameEndTask( - w => w.CreateActor(self.Info.Traits.Get().Mine, new TypeDictionary + w => w.CreateActor(info.Mine, new TypeDictionary { new LocationInit(self.Location), new OwnerInit(self.Owner), diff --git a/OpenRA.Mods.RA/Traits/Minelayer.cs b/OpenRA.Mods.RA/Traits/Minelayer.cs index fda7ea187b..554e281674 100644 --- a/OpenRA.Mods.RA/Traits/Minelayer.cs +++ b/OpenRA.Mods.RA/Traits/Minelayer.cs @@ -18,19 +18,19 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Traits { - class MinelayerInfo : ITraitInfo + public class MinelayerInfo : ITraitInfo { [ActorReference] public readonly string Mine = "minv"; [ActorReference] public readonly string[] RearmBuildings = { "fix" }; - public readonly string RearmSound = "minelay1.aud"; + public readonly string AmmoPoolName = "primary"; public readonly float MinefieldDepth = 1.5f; public object Create(ActorInitializer init) { return new Minelayer(init.Self); } } - class Minelayer : IIssueOrder, IResolveOrder, IPostRenderSelection, ISync + public class Minelayer : IIssueOrder, IResolveOrder, IPostRenderSelection, ISync { /* TODO: [Sync] when sync can cope with arrays! */ public CPos[] Minefield = null; @@ -80,7 +80,7 @@ namespace OpenRA.Mods.RA.Traits minefieldStart = order.TargetLocation; Minefield = new CPos[] { order.TargetLocation }; self.CancelActivity(); - self.QueueActivity(new LayMines()); + self.QueueActivity(new LayMines(self)); } if (order.OrderString == "PlaceMinefield") @@ -92,7 +92,7 @@ namespace OpenRA.Mods.RA.Traits .Where(p => movement.CanEnterCell(p, null, false)).ToArray(); self.CancelActivity(); - self.QueueActivity(new LayMines()); + self.QueueActivity(new LayMines(self)); } } diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index dec4cdf776..eeaedb7db5 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -76,12 +76,12 @@ HELI: MuzzleSequence: muzzle AttackHeli: FacingTolerance: 20 - LimitedAmmo: + AmmoPool: Ammo: 10 PipCount: 5 - Reloads: - Count: 10 - Period: 200 + SelfReloads: true + ReloadCount: 10 + SelfReloadTicks: 200 RenderUnit: WithRotor: Offset: 0,0,85 @@ -124,12 +124,12 @@ ORCA: LocalOffset: 427,-171,-213, 427,171,-213 AttackHeli: FacingTolerance: 20 - LimitedAmmo: + AmmoPool: Ammo: 6 PipCount: 6 - Reloads: - Count: 2 - Period: 100 + SelfReloads: true + ReloadCount: 2 + SelfReloadTicks: 100 RenderUnit: LeavesHusk: HuskActor: ORCA.Husk diff --git a/mods/d2k/rules/aircraft.yaml b/mods/d2k/rules/aircraft.yaml index 8afebf7d3f..d138ba5c32 100644 --- a/mods/d2k/rules/aircraft.yaml +++ b/mods/d2k/rules/aircraft.yaml @@ -159,7 +159,7 @@ orni.bomber: RepairBuildings: repair RearmBuildings: Repulsable: False - LimitedAmmo: + AmmoPool: Ammo: 5 RenderUnit: Image: orni diff --git a/mods/ra/maps/fort-lonestar/map.yaml b/mods/ra/maps/fort-lonestar/map.yaml index a9b20d2ca8..0df65bd94a 100644 --- a/mods/ra/maps/fort-lonestar/map.yaml +++ b/mods/ra/maps/fort-lonestar/map.yaml @@ -681,7 +681,7 @@ Rules: Plane: ROT: 5 Speed: 280 - LimitedAmmo: + AmmoPool: Ammo: 30 RenderUnit: Image: mig diff --git a/mods/ra/maps/intervention/map.yaml b/mods/ra/maps/intervention/map.yaml index 8c0c80a0c6..19673a5546 100644 --- a/mods/ra/maps/intervention/map.yaml +++ b/mods/ra/maps/intervention/map.yaml @@ -2273,7 +2273,7 @@ Rules: MIG: Buildable: Prerequisites: ~afld - LimitedAmmo: + AmmoPool: Ammo: 2 HELI: Buildable: diff --git a/mods/ra/rules/aircraft.yaml b/mods/ra/rules/aircraft.yaml index f19312b90f..df78ba8f6e 100644 --- a/mods/ra/rules/aircraft.yaml +++ b/mods/ra/rules/aircraft.yaml @@ -51,7 +51,7 @@ BADR.Bomber: Speed: 149 Repulsable: False MaximumPitch: 56 - LimitedAmmo: + AmmoPool: Ammo: 7 RenderUnit: Image: badr @@ -113,7 +113,7 @@ MIG: EnableStances: false RenderUnit: CameraPitch: 99 - LimitedAmmo: + AmmoPool: Ammo: 8 ReturnOnIdle: Selectable: @@ -171,7 +171,7 @@ YAK: EnableStances: false RenderUnit: CameraPitch: 99 - LimitedAmmo: + AmmoPool: Ammo: 18 PipCount: 6 ReloadTicks: 11 @@ -264,7 +264,7 @@ HELI: RenderUnit: WithRotor: Offset: 0,0,85 - LimitedAmmo: + AmmoPool: Ammo: 8 Selectable: Bounds: 36,28,0,0 @@ -312,7 +312,7 @@ HIND: InitialStance: HoldFire RenderUnit: WithRotor: - LimitedAmmo: + AmmoPool: Ammo: 24 PipCount: 6 ReloadTicks: 8 diff --git a/mods/ra/rules/vehicles.yaml b/mods/ra/rules/vehicles.yaml index 68f6292d39..d43db333be 100644 --- a/mods/ra/rules/vehicles.yaml +++ b/mods/ra/rules/vehicles.yaml @@ -419,8 +419,9 @@ MNLY.AP: Minelayer: Mine: MINP MineImmune: - LimitedAmmo: + AmmoPool: Ammo: 5 + RearmSound: minelay1.aud DetectCloaked: Range: 5 CloakTypes: Mine @@ -453,8 +454,9 @@ MNLY.AT: Minelayer: Mine: MINV MineImmune: - LimitedAmmo: + AmmoPool: Ammo: 5 + RearmSound: minelay1.aud DetectCloaked: Range: 5 CloakTypes: Mine diff --git a/mods/ts/rules/aircraft.yaml b/mods/ts/rules/aircraft.yaml index 69ab8a22d7..e066bafe70 100644 --- a/mods/ts/rules/aircraft.yaml +++ b/mods/ts/rules/aircraft.yaml @@ -27,7 +27,7 @@ DPOD: Armament: Weapon: Vulcan2 AttackHeli: - LimitedAmmo: + AmmoPool: Ammo: 5 PipCount: 5 PipType: Ammo @@ -95,7 +95,7 @@ ORCA: Weapon: Hellfire AttackHeli: FacingTolerance: 20 - LimitedAmmo: + AmmoPool: Ammo: 5 PipCount: 5 PipType: Ammo @@ -132,7 +132,7 @@ ORCAB: Weapon: Bomb AttackHeli: FacingTolerance: 20 - LimitedAmmo: + AmmoPool: Ammo: 2 PipCount: 2 PipType: Ammo @@ -228,7 +228,7 @@ SCRIN: Weapon: Proton AttackHeli: FacingTolerance: 20 - LimitedAmmo: + AmmoPool: Ammo: 3 PipCount: 3 PipType: Ammo @@ -264,7 +264,7 @@ APACHE: Weapon: HarpyClaw AttackHeli: FacingTolerance: 20 - LimitedAmmo: + AmmoPool: Ammo: 12 PipCount: 4 PipType: Ammo