Merge pull request #7324 from reaperrr/limitedammo2

Refactored LimitedAmmo to AmmoPool
This commit is contained in:
Pavel Penev
2015-03-26 15:59:56 -07:00
24 changed files with 392 additions and 255 deletions

View File

@@ -229,7 +229,7 @@ namespace OpenRA.Mods.Common.AI
return null; return null;
var unit = buildableThings.Random(Random); var unit = buildableThings.Random(Random);
return HasAdequateAirUnits(unit) ? unit : null; return HasAdequateAirUnitReloadBuildings(unit) ? unit : null;
} }
ActorInfo ChooseUnitToBuild(ProductionQueue queue) ActorInfo ChooseUnitToBuild(ProductionQueue queue)
@@ -246,7 +246,7 @@ namespace OpenRA.Mods.Common.AI
foreach (var unit in Info.UnitsToBuild.Shuffle(Random)) foreach (var unit in Info.UnitsToBuild.Shuffle(Random))
if (buildableThings.Any(b => b.Name == unit.Key)) if (buildableThings.Any(b => b.Name == unit.Key))
if (myUnits.Count(a => a == unit.Key) < unit.Value * myUnits.Count) 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 Map.Rules.Actors[unit.Key];
return null; return null;
@@ -318,13 +318,18 @@ namespace OpenRA.Mods.Common.AI
} }
// For mods like RA (number of building must match the number of aircraft) // 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<ReloadsInfo>() && actorInfo.Traits.Contains<LimitedAmmoInfo>() var aircraftInfo = actorInfo.Traits.GetOrDefault<AircraftInfo>();
&& actorInfo.Traits.Contains<AircraftInfo>()) if (aircraftInfo == null)
return true;
var ammoPoolsInfo = actorInfo.Traits.WithInterface<AmmoPoolInfo>();
if (ammoPoolsInfo.Any(x => !x.SelfReloads))
{ {
var countOwnAir = CountUnits(actorInfo.Name, Player); var countOwnAir = CountUnits(actorInfo.Name, Player);
var countBuildings = CountBuilding(actorInfo.Traits.Get<AircraftInfo>().RearmBuildings.FirstOrDefault(), Player); var countBuildings = CountBuilding(aircraftInfo.RearmBuildings.FirstOrDefault(), Player);
if (countOwnAir >= countBuildings) if (countOwnAir >= countBuildings)
return false; return false;
} }

View File

@@ -104,19 +104,20 @@ namespace OpenRA.Mods.Common.AI
protected static bool FullAmmo(Actor a) protected static bool FullAmmo(Actor a)
{ {
var limitedAmmo = a.TraitOrDefault<LimitedAmmo>(); var ammoPools = a.TraitsImplementing<AmmoPool>();
return limitedAmmo != null && limitedAmmo.FullAmmo(); return ammoPools.All(x => x.FullAmmo());
} }
protected static bool HasAmmo(Actor a) protected static bool HasAmmo(Actor a)
{ {
var limitedAmmo = a.TraitOrDefault<LimitedAmmo>(); var ammoPools = a.TraitsImplementing<AmmoPool>();
return limitedAmmo != null && limitedAmmo.HasAmmo(); return ammoPools.All(x => x.HasAmmo());
} }
protected static bool ReloadsAutomatically(Actor a) protected static bool ReloadsAutomatically(Actor a)
{ {
return a.HasTrait<Reloads>(); var ammoPools = a.TraitsImplementing<AmmoPool>();
return ammoPools.All(x => x.Info.SelfReloads);
} }
protected static bool IsRearm(Actor a) protected static bool IsRearm(Actor a)

View File

@@ -8,6 +8,8 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Activities; using OpenRA.Activities;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
@@ -18,30 +20,38 @@ namespace OpenRA.Mods.Common.Activities
public class FlyAttack : Activity public class FlyAttack : Activity
{ {
readonly Target target; readonly Target target;
readonly AttackPlane attackPlane;
readonly IEnumerable<AmmoPool> ammoPools;
Activity inner; Activity inner;
int ticksUntilTurn = 50; int ticksUntilTurn = 50;
public FlyAttack(Target target) { this.target = target; } public FlyAttack(Actor self, Target target)
{
this.target = target;
attackPlane = self.TraitOrDefault<AttackPlane>();
ammoPools = self.TraitsImplementing<AmmoPool>();
}
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (!target.IsValidFor(self)) if (!target.IsValidFor(self))
return NextActivity; return NextActivity;
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>(); // Move to the next activity only if all ammo pools are depleted and none reload automatically
if (limitedAmmo != null && !limitedAmmo.HasAmmo()) // 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; return NextActivity;
var attack = self.TraitOrDefault<AttackPlane>(); if (attackPlane != null)
if (attack != null) attackPlane.DoAttack(self, target);
attack.DoAttack(self, target);
if (inner == null) if (inner == null)
{ {
if (IsCanceled) if (IsCanceled)
return NextActivity; 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)); inner = Util.SequenceActivities(new FlyTimed(ticksUntilTurn), new Fly(self, target), new FlyTimed(ticksUntilTurn));
else else
inner = Util.SequenceActivities(new Fly(self, target), new FlyTimed(ticksUntilTurn)); inner = Util.SequenceActivities(new Fly(self, target), new FlyTimed(ticksUntilTurn));

View File

@@ -8,6 +8,9 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Activities; using OpenRA.Activities;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
using OpenRA.Traits; using OpenRA.Traits;
@@ -16,21 +19,29 @@ namespace OpenRA.Mods.Common.Activities
{ {
public class HeliAttack : Activity public class HeliAttack : Activity
{ {
Target target; readonly Target target;
public HeliAttack(Target target) { this.target = target; } readonly Helicopter helicopter;
readonly AttackHeli attackHeli;
readonly IEnumerable<AmmoPool> ammoPools;
public HeliAttack(Actor self, Target target)
{
this.target = target;
helicopter = self.Trait<Helicopter>();
attackHeli = self.Trait<AttackHeli>();
ammoPools = self.TraitsImplementing<AmmoPool>();
}
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (IsCanceled || !target.IsValidFor(self)) if (IsCanceled || !target.IsValidFor(self))
return NextActivity; return NextActivity;
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>(); // If all ammo pools are depleted and none reload automatically, return to helipad to reload and then move to next activity
var reloads = self.TraitOrDefault<Reloads>(); // TODO: This should check whether there is ammo left that is actually suitable for the target
if (limitedAmmo != null && !limitedAmmo.HasAmmo() && reloads == null) if (ammoPools != null && ammoPools.All(x => !x.Info.SelfReloads && !x.HasAmmo()))
return Util.SequenceActivities(new HeliReturn(), NextActivity); return Util.SequenceActivities(new HeliReturn(), NextActivity);
var helicopter = self.Trait<Helicopter>();
var attack = self.Trait<AttackHeli>();
var dist = target.CenterPosition - self.CenterPosition; var dist = target.CenterPosition - self.CenterPosition;
// Can rotate facing while ascending // Can rotate facing while ascending
@@ -41,10 +52,12 @@ namespace OpenRA.Mods.Common.Activities
return this; return this;
// Fly towards the target // 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)); helicopter.SetPosition(self, helicopter.CenterPosition + helicopter.FlyStep(desiredFacing));
attack.DoAttack(self, target); attackHeli.DoAttack(self, target);
return this; return this;
} }

View File

@@ -8,53 +8,64 @@
*/ */
#endregion #endregion
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Activities; using OpenRA.Activities;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Activities namespace OpenRA.Mods.Common.Activities
{ {
public class Rearm : Activity public class Rearm : Activity
{ {
readonly LimitedAmmo limitedAmmo; readonly IEnumerable<AmmoPool> ammoPools;
int ticksPerPip = 25 * 2; readonly Dictionary<AmmoPool, int> ammoPoolsReloadTimes;
int remainingTicks = 25 * 2;
string sound = null;
public Rearm(Actor self, string sound = null) public Rearm(Actor self)
{ {
limitedAmmo = self.TraitOrDefault<LimitedAmmo>(); ammoPools = self.TraitsImplementing<AmmoPool>().Where(p => !p.Info.SelfReloads);
if (limitedAmmo != null)
ticksPerPip = limitedAmmo.ReloadTimePerAmmo(); if (ammoPools == null)
remainingTicks = ticksPerPip; return;
this.sound = sound;
ammoPoolsReloadTimes = ammoPools.ToDictionary(x => x, y => y.Info.ReloadTicks);
} }
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (IsCanceled || limitedAmmo == null) if (IsCanceled || ammoPoolsReloadTimes == null)
return NextActivity; return NextActivity;
if (--remainingTicks == 0) var needsReloading = false;
foreach (var pool in ammoPools)
{ {
var hostBuilding = self.World.ActorMap.GetUnitsAt(self.Location) if (pool.FullAmmo())
.FirstOrDefault(a => a.HasTrait<RenderBuilding>()); 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<RenderBuilding>());
if (hostBuilding == null || !hostBuilding.IsInWorld) if (hostBuilding == null || !hostBuilding.IsInWorld)
return NextActivity; return NextActivity;
if (!limitedAmmo.GiveAmmo()) if (!pool.GiveAmmo())
return NextActivity; continue;
hostBuilding.Trait<RenderBuilding>().PlayCustomAnim(hostBuilding, "active"); hostBuilding.Trait<RenderBuilding>().PlayCustomAnim(hostBuilding, "active");
var sound = pool.Info.RearmSound;
if (sound != null) if (sound != null)
Sound.Play(sound, self.CenterPosition); Sound.Play(sound, self.CenterPosition);
remainingTicks = limitedAmmo.ReloadTimePerAmmo(); ammoPoolsReloadTimes[pool] = pool.Info.ReloadTicks;
} }
return this; return needsReloading ? this : NextActivity;
} }
} }
} }

View File

@@ -320,7 +320,7 @@
<Compile Include="Traits\Invulnerable.cs" /> <Compile Include="Traits\Invulnerable.cs" />
<Compile Include="Traits\JamsMissiles.cs" /> <Compile Include="Traits\JamsMissiles.cs" />
<Compile Include="Traits\KillsSelf.cs" /> <Compile Include="Traits\KillsSelf.cs" />
<Compile Include="Traits\LimitedAmmo.cs" /> <Compile Include="Traits\AmmoPool.cs" />
<Compile Include="Traits\Mobile.cs" /> <Compile Include="Traits\Mobile.cs" />
<Compile Include="Traits\Modifiers\DisabledOverlay.cs" /> <Compile Include="Traits\Modifiers\DisabledOverlay.cs" />
<Compile Include="Traits\Modifiers\FrozenUnderFog.cs" /> <Compile Include="Traits\Modifiers\FrozenUnderFog.cs" />
@@ -364,7 +364,6 @@
<Compile Include="Traits\ProximityCaptor.cs" /> <Compile Include="Traits\ProximityCaptor.cs" />
<Compile Include="Traits\ProximityCapturable.cs" /> <Compile Include="Traits\ProximityCapturable.cs" />
<Compile Include="Traits\RadarColorFromTerrain.cs" /> <Compile Include="Traits\RadarColorFromTerrain.cs" />
<Compile Include="Traits\Reloads.cs" />
<Compile Include="Traits\Render\Hovers.cs" /> <Compile Include="Traits\Render\Hovers.cs" />
<Compile Include="Traits\Render\RenderBuilding.cs" /> <Compile Include="Traits\Render\RenderBuilding.cs" />
<Compile Include="Traits\Render\RenderBuildingCharge.cs" /> <Compile Include="Traits\Render\RenderBuildingCharge.cs" />

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Fly an attack against the target actor.")] [Desc("Fly an attack against the target actor.")]
public void Attack(Actor target) public void Attack(Actor target)
{ {
Self.QueueActivity(new FlyAttack(Target.FromActor(target))); Self.QueueActivity(new FlyAttack(Self, Target.FromActor(target)));
} }
} }
} }

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{ {
return new HeliAttack(newTarget); return new HeliAttack(self, newTarget);
} }
} }
} }

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) 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) protected override bool CanAttack(Actor self, Target target)

View File

@@ -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<PipType> 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);
}
}
}

View File

@@ -31,16 +31,25 @@ namespace OpenRA.Mods.Common.Traits
[WeaponReference] [WeaponReference]
[Desc("Has to be defined here and in weapons.yaml.")] [Desc("Has to be defined here and in weapons.yaml.")]
public readonly string Weapon = null; 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"; public readonly string Turret = "primary";
[Desc("Time (in frames) until the weapon can fire again.")] [Desc("Time (in frames) until the weapon can fire again.")]
public readonly int FireDelay = 0; public readonly int FireDelay = 0;
[Desc("Muzzle position relative to turret or body. (forward, right, up) triples")] [Desc("Muzzle position relative to turret or body. (forward, right, up) triples")]
public readonly WVec[] LocalOffset = { }; public readonly WVec[] LocalOffset = { };
[Desc("Muzzle yaw relative to turret or body.")] [Desc("Muzzle yaw relative to turret or body.")]
public readonly WAngle[] LocalYaw = { }; public readonly WAngle[] LocalYaw = { };
[Desc("Move the turret backwards when firing.")] [Desc("Move the turret backwards when firing.")]
public readonly WRange Recoil = WRange.Zero; public readonly WRange Recoil = WRange.Zero;
[Desc("Recoil recovery per-frame")] [Desc("Recoil recovery per-frame")]
public readonly WRange RecoilRecovery = new WRange(9); public readonly WRange RecoilRecovery = new WRange(9);
@@ -64,7 +73,7 @@ namespace OpenRA.Mods.Common.Traits
readonly Actor self; readonly Actor self;
Lazy<Turreted> turret; Lazy<Turreted> turret;
Lazy<IBodyOrientation> coords; Lazy<IBodyOrientation> coords;
Lazy<LimitedAmmo> limitedAmmo; Lazy<AmmoPool> ammoPool;
List<Pair<int, Action>> delayedActions = new List<Pair<int, Action>>(); List<Pair<int, Action>> delayedActions = new List<Pair<int, Action>>();
public WRange Recoil; public WRange Recoil;
@@ -79,7 +88,7 @@ namespace OpenRA.Mods.Common.Traits
// We can't resolve these until runtime // We can't resolve these until runtime
turret = Exts.Lazy(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == info.Turret)); turret = Exts.Lazy(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == info.Turret));
coords = Exts.Lazy(() => self.Trait<IBodyOrientation>()); coords = Exts.Lazy(() => self.Trait<IBodyOrientation>());
limitedAmmo = Exts.Lazy(() => self.TraitOrDefault<LimitedAmmo>()); ammoPool = Exts.Lazy(() => self.TraitsImplementing<AmmoPool>().FirstOrDefault(la => la.Info.Name == info.AmmoPoolName));
Weapon = self.World.Map.Rules.Weapons[info.Weapon.ToLowerInvariant()]; Weapon = self.World.Map.Rules.Weapons[info.Weapon.ToLowerInvariant()];
Burst = Weapon.Burst; Burst = Weapon.Burst;
@@ -136,7 +145,7 @@ namespace OpenRA.Mods.Common.Traits
if (IsReloading) if (IsReloading)
return null; return null;
if (limitedAmmo.Value != null && !limitedAmmo.Value.HasAmmo()) if (ammoPool.Value != null && !ammoPool.Value.HasAmmo())
return null; return null;
if (!target.IsInRange(self.CenterPosition, Weapon.Range)) if (!target.IsInRange(self.CenterPosition, Weapon.Range))

View File

@@ -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<PipType> 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);
}
}
}

View File

@@ -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<LimitedAmmoInfo>
{
[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<LimitedAmmo>();
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();
}
}
}
}

View File

@@ -22,18 +22,20 @@ namespace OpenRA.Mods.Common.Traits
class RepairableInfo : ITraitInfo, Requires<HealthInfo> class RepairableInfo : ITraitInfo, Requires<HealthInfo>
{ {
public readonly string[] RepairBuildings = { "fix" }; 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 class Repairable : IIssueOrder, IResolveOrder, IOrderVoice
{ {
readonly Actor self; readonly RepairableInfo info;
readonly Health health; readonly Health health;
readonly IEnumerable<AmmoPool> ammoPools;
public Repairable(Actor self) public Repairable(Actor self, RepairableInfo info)
{ {
this.self = self; this.info = info;
health = self.Trait<Health>(); health = self.Trait<Health>();
ammoPools = self.TraitsImplementing<AmmoPool>();
} }
public IEnumerable<IOrderTargeter> Orders public IEnumerable<IOrderTargeter> Orders
@@ -41,7 +43,7 @@ namespace OpenRA.Mods.Common.Traits
get get
{ {
yield return new EnterAlliedActorTargeter<Building>("Repair", 5, yield return new EnterAlliedActorTargeter<Building>("Repair", 5,
target => CanRepairAt(target), _ => CanRepair()); target => CanRepairAt(target), _ => CanRepair() || CanRearm());
} }
} }
@@ -55,13 +57,25 @@ namespace OpenRA.Mods.Common.Traits
bool CanRepairAt(Actor target) bool CanRepairAt(Actor target)
{ {
return self.Info.Traits.Get<RepairableInfo>().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() bool CanRepair()
{ {
var li = self.TraitOrDefault<LimitedAmmo>(); return health.DamageState > DamageState.Undamaged;
return health.DamageState > DamageState.Undamaged || (li != null && !li.FullAmmo()); }
bool CanRearm()
{
if (ammoPools != null)
return ammoPools.Any(x => !x.Info.SelfReloads && !x.FullAmmo());
else
return false;
} }
public string VoicePhraseForOrder(Actor self, Order order) public string VoicePhraseForOrder(Actor self, Order order)
@@ -73,7 +87,7 @@ namespace OpenRA.Mods.Common.Traits
{ {
if (order.OrderString == "Repair") if (order.OrderString == "Repair")
{ {
if (!CanRepairAt(order.TargetActor) || !CanRepair()) if (!CanRepairAt(order.TargetActor) || (!CanRepair() && !CanRearm()))
return; return;
var movement = self.Trait<IMove>(); var movement = self.Trait<IMove>();
@@ -83,7 +97,9 @@ namespace OpenRA.Mods.Common.Traits
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new MoveAdjacentTo(self, target)); self.QueueActivity(new MoveAdjacentTo(self, target));
self.QueueActivity(movement.MoveTo(self.World.Map.CellContaining(order.TargetActor.CenterPosition), order.TargetActor)); 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)); self.QueueActivity(new Repair(order.TargetActor));
var rp = order.TargetActor.TraitOrDefault<RallyPoint>(); var rp = order.TargetActor.TraitOrDefault<RallyPoint>();

View File

@@ -774,6 +774,61 @@ namespace OpenRA.Mods.Common.UtilityCommands
node.Key = "StandSequences"; 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<int>("Count", count.Value.Value) : -1;
var reloadsPeriod = period != null ? FieldLoader.GetValue<int>("Period", period.Value.Value) : 50;
var reloadsResetOnFire = resets != null ? FieldLoader.GetValue<bool>("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<string>("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); UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
} }
} }

View File

@@ -8,6 +8,8 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Activities; using OpenRA.Activities;
using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Activities;
@@ -18,23 +20,34 @@ using OpenRA.Traits;
namespace OpenRA.Mods.RA.Activities namespace OpenRA.Mods.RA.Activities
{ {
// assumes you have Minelayer on that unit // Assumes you have Minelayer on that unit
class LayMines : Activity public class LayMines : Activity
{ {
readonly Minelayer minelayer;
readonly MinelayerInfo info;
readonly IEnumerable<AmmoPool> ammoPools;
readonly IMove movement;
readonly string[] rearmBuildings;
public LayMines(Actor self)
{
minelayer = self.TraitOrDefault<Minelayer>();
info = self.Info.Traits.Get<MinelayerInfo>();
ammoPools = self.TraitsImplementing<AmmoPool>();
movement = self.Trait<IMove>();
rearmBuildings = info.RearmBuildings;
}
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (IsCanceled) return NextActivity; if (IsCanceled)
return NextActivity;
var movement = self.Trait<IMove>(); if (ammoPools != null && ammoPools.Any(p => p.Info.Name == info.AmmoPoolName && !p.HasAmmo()))
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
if (!limitedAmmo.HasAmmo())
{ {
var info = self.Info.Traits.Get<MinelayerInfo>(); // Rearm (and possibly repair) at rearm building, then back out here to refill the minefield some more
// rearm & repair at fix, then back out here to refill the minefield some more
var buildings = info.RearmBuildings;
var rearmTarget = self.World.Actors.Where(a => self.Owner.Stances[a.Owner] == Stance.Ally 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); .ClosestTo(self);
if (rearmTarget == null) if (rearmTarget == null)
@@ -43,47 +56,50 @@ namespace OpenRA.Mods.RA.Activities
return Util.SequenceActivities( return Util.SequenceActivities(
new MoveAdjacentTo(self, Target.FromActor(rearmTarget)), new MoveAdjacentTo(self, Target.FromActor(rearmTarget)),
movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget), movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget),
new Rearm(self, info.RearmSound), new Rearm(self),
new Repair(rearmTarget), new Repair(rearmTarget),
this); this);
} }
var ml = self.Trait<Minelayer>(); if (minelayer.Minefield.Contains(self.Location) && ShouldLayMine(self, self.Location))
if (ml.Minefield.Contains(self.Location) && ShouldLayMine(self, self.Location))
{ {
LayMine(self); 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++) 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)) if (ShouldLayMine(self, p))
return Util.SequenceActivities(movement.MoveTo(p, 0), this); 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 return new Wait(20); // nothing to do here
} }
static bool ShouldLayMine(Actor self, CPos p) 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); return !self.World.ActorMap.GetUnitsAt(p).Any(a => a != self);
} }
static void LayMine(Actor self) void LayMine(Actor self)
{ {
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>(); if (ammoPools != null)
if (limitedAmmo != null) {
limitedAmmo.TakeAmmo(); var pool = ammoPools.FirstOrDefault(x => x.Info.Name == info.AmmoPoolName);
if (pool == null)
return;
pool.TakeAmmo();
}
self.World.AddFrameEndTask( self.World.AddFrameEndTask(
w => w.CreateActor(self.Info.Traits.Get<MinelayerInfo>().Mine, new TypeDictionary w => w.CreateActor(info.Mine, new TypeDictionary
{ {
new LocationInit(self.Location), new LocationInit(self.Location),
new OwnerInit(self.Owner), new OwnerInit(self.Owner),

View File

@@ -18,19 +18,19 @@ using OpenRA.Traits;
namespace OpenRA.Mods.RA.Traits namespace OpenRA.Mods.RA.Traits
{ {
class MinelayerInfo : ITraitInfo public class MinelayerInfo : ITraitInfo
{ {
[ActorReference] public readonly string Mine = "minv"; [ActorReference] public readonly string Mine = "minv";
[ActorReference] public readonly string[] RearmBuildings = { "fix" }; [ActorReference] public readonly string[] RearmBuildings = { "fix" };
public readonly string RearmSound = "minelay1.aud"; public readonly string AmmoPoolName = "primary";
public readonly float MinefieldDepth = 1.5f; public readonly float MinefieldDepth = 1.5f;
public object Create(ActorInitializer init) { return new Minelayer(init.Self); } 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! */ /* TODO: [Sync] when sync can cope with arrays! */
public CPos[] Minefield = null; public CPos[] Minefield = null;
@@ -80,7 +80,7 @@ namespace OpenRA.Mods.RA.Traits
minefieldStart = order.TargetLocation; minefieldStart = order.TargetLocation;
Minefield = new CPos[] { order.TargetLocation }; Minefield = new CPos[] { order.TargetLocation };
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new LayMines()); self.QueueActivity(new LayMines(self));
} }
if (order.OrderString == "PlaceMinefield") if (order.OrderString == "PlaceMinefield")
@@ -92,7 +92,7 @@ namespace OpenRA.Mods.RA.Traits
.Where(p => movement.CanEnterCell(p, null, false)).ToArray(); .Where(p => movement.CanEnterCell(p, null, false)).ToArray();
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new LayMines()); self.QueueActivity(new LayMines(self));
} }
} }

View File

@@ -76,12 +76,12 @@ HELI:
MuzzleSequence: muzzle MuzzleSequence: muzzle
AttackHeli: AttackHeli:
FacingTolerance: 20 FacingTolerance: 20
LimitedAmmo: AmmoPool:
Ammo: 10 Ammo: 10
PipCount: 5 PipCount: 5
Reloads: SelfReloads: true
Count: 10 ReloadCount: 10
Period: 200 SelfReloadTicks: 200
RenderUnit: RenderUnit:
WithRotor: WithRotor:
Offset: 0,0,85 Offset: 0,0,85
@@ -124,12 +124,12 @@ ORCA:
LocalOffset: 427,-171,-213, 427,171,-213 LocalOffset: 427,-171,-213, 427,171,-213
AttackHeli: AttackHeli:
FacingTolerance: 20 FacingTolerance: 20
LimitedAmmo: AmmoPool:
Ammo: 6 Ammo: 6
PipCount: 6 PipCount: 6
Reloads: SelfReloads: true
Count: 2 ReloadCount: 2
Period: 100 SelfReloadTicks: 100
RenderUnit: RenderUnit:
LeavesHusk: LeavesHusk:
HuskActor: ORCA.Husk HuskActor: ORCA.Husk

View File

@@ -159,7 +159,7 @@ orni.bomber:
RepairBuildings: repair RepairBuildings: repair
RearmBuildings: RearmBuildings:
Repulsable: False Repulsable: False
LimitedAmmo: AmmoPool:
Ammo: 5 Ammo: 5
RenderUnit: RenderUnit:
Image: orni Image: orni

View File

@@ -681,7 +681,7 @@ Rules:
Plane: Plane:
ROT: 5 ROT: 5
Speed: 280 Speed: 280
LimitedAmmo: AmmoPool:
Ammo: 30 Ammo: 30
RenderUnit: RenderUnit:
Image: mig Image: mig

View File

@@ -2273,7 +2273,7 @@ Rules:
MIG: MIG:
Buildable: Buildable:
Prerequisites: ~afld Prerequisites: ~afld
LimitedAmmo: AmmoPool:
Ammo: 2 Ammo: 2
HELI: HELI:
Buildable: Buildable:

View File

@@ -51,7 +51,7 @@ BADR.Bomber:
Speed: 149 Speed: 149
Repulsable: False Repulsable: False
MaximumPitch: 56 MaximumPitch: 56
LimitedAmmo: AmmoPool:
Ammo: 7 Ammo: 7
RenderUnit: RenderUnit:
Image: badr Image: badr
@@ -113,7 +113,7 @@ MIG:
EnableStances: false EnableStances: false
RenderUnit: RenderUnit:
CameraPitch: 99 CameraPitch: 99
LimitedAmmo: AmmoPool:
Ammo: 8 Ammo: 8
ReturnOnIdle: ReturnOnIdle:
Selectable: Selectable:
@@ -171,7 +171,7 @@ YAK:
EnableStances: false EnableStances: false
RenderUnit: RenderUnit:
CameraPitch: 99 CameraPitch: 99
LimitedAmmo: AmmoPool:
Ammo: 18 Ammo: 18
PipCount: 6 PipCount: 6
ReloadTicks: 11 ReloadTicks: 11
@@ -264,7 +264,7 @@ HELI:
RenderUnit: RenderUnit:
WithRotor: WithRotor:
Offset: 0,0,85 Offset: 0,0,85
LimitedAmmo: AmmoPool:
Ammo: 8 Ammo: 8
Selectable: Selectable:
Bounds: 36,28,0,0 Bounds: 36,28,0,0
@@ -312,7 +312,7 @@ HIND:
InitialStance: HoldFire InitialStance: HoldFire
RenderUnit: RenderUnit:
WithRotor: WithRotor:
LimitedAmmo: AmmoPool:
Ammo: 24 Ammo: 24
PipCount: 6 PipCount: 6
ReloadTicks: 8 ReloadTicks: 8

View File

@@ -419,8 +419,9 @@ MNLY.AP:
Minelayer: Minelayer:
Mine: MINP Mine: MINP
MineImmune: MineImmune:
LimitedAmmo: AmmoPool:
Ammo: 5 Ammo: 5
RearmSound: minelay1.aud
DetectCloaked: DetectCloaked:
Range: 5 Range: 5
CloakTypes: Mine CloakTypes: Mine
@@ -453,8 +454,9 @@ MNLY.AT:
Minelayer: Minelayer:
Mine: MINV Mine: MINV
MineImmune: MineImmune:
LimitedAmmo: AmmoPool:
Ammo: 5 Ammo: 5
RearmSound: minelay1.aud
DetectCloaked: DetectCloaked:
Range: 5 Range: 5
CloakTypes: Mine CloakTypes: Mine

View File

@@ -27,7 +27,7 @@ DPOD:
Armament: Armament:
Weapon: Vulcan2 Weapon: Vulcan2
AttackHeli: AttackHeli:
LimitedAmmo: AmmoPool:
Ammo: 5 Ammo: 5
PipCount: 5 PipCount: 5
PipType: Ammo PipType: Ammo
@@ -95,7 +95,7 @@ ORCA:
Weapon: Hellfire Weapon: Hellfire
AttackHeli: AttackHeli:
FacingTolerance: 20 FacingTolerance: 20
LimitedAmmo: AmmoPool:
Ammo: 5 Ammo: 5
PipCount: 5 PipCount: 5
PipType: Ammo PipType: Ammo
@@ -132,7 +132,7 @@ ORCAB:
Weapon: Bomb Weapon: Bomb
AttackHeli: AttackHeli:
FacingTolerance: 20 FacingTolerance: 20
LimitedAmmo: AmmoPool:
Ammo: 2 Ammo: 2
PipCount: 2 PipCount: 2
PipType: Ammo PipType: Ammo
@@ -228,7 +228,7 @@ SCRIN:
Weapon: Proton Weapon: Proton
AttackHeli: AttackHeli:
FacingTolerance: 20 FacingTolerance: 20
LimitedAmmo: AmmoPool:
Ammo: 3 Ammo: 3
PipCount: 3 PipCount: 3
PipType: Ammo PipType: Ammo
@@ -264,7 +264,7 @@ APACHE:
Weapon: HarpyClaw Weapon: HarpyClaw
AttackHeli: AttackHeli:
FacingTolerance: 20 FacingTolerance: 20
LimitedAmmo: AmmoPool:
Ammo: 12 Ammo: 12
PipCount: 4 PipCount: 4
PipType: Ammo PipType: Ammo