Shift Actor.Health onto a trait.

Known regressions:
 - cnc only
 - health bar colors
 - can't repair buildings
This commit is contained in:
Paul Chote
2010-07-30 00:01:59 +12:00
parent 1e08dc6301
commit 6fba888d45
56 changed files with 530 additions and 303 deletions

View File

@@ -31,8 +31,6 @@ namespace OpenRA
public int2 Location { get { return traits.Get<IOccupySpace>().TopLeft; } } public int2 Location { get { return traits.Get<IOccupySpace>().TopLeft; } }
[Sync] [Sync]
public Player Owner; public Player Owner;
[Sync]
public int Health;
IActivity currentActivity; IActivity currentActivity;
public Group Group; public Group Group;
@@ -51,8 +49,6 @@ namespace OpenRA
throw new NotImplementedException("No rules definition for unit {0}".F(name.ToLowerInvariant())); throw new NotImplementedException("No rules definition for unit {0}".F(name.ToLowerInvariant()));
Info = Rules.Info[name.ToLowerInvariant()]; Info = Rules.Info[name.ToLowerInvariant()];
Health = this.GetMaxHP();
foreach (var trait in Info.TraitsInConstructOrder()) foreach (var trait in Info.TraitsInConstructOrder())
traits.Add(trait.Create(init)); traits.Add(trait.Create(init));
} }
@@ -147,66 +143,7 @@ namespace OpenRA
return new RectangleF(loc.X, loc.Y, size.X, size.Y); return new RectangleF(loc.X, loc.Y, size.X, size.Y);
} }
public bool IsDead { get { return Health <= 0; } }
public bool IsInWorld { get; set; } public bool IsInWorld { get; set; }
public bool RemoveOnDeath = true;
public DamageState GetDamageState()
{
if (Health <= 0)
return DamageState.Dead;
if (Health < this.GetMaxHP() * World.Defaults.ConditionYellow)
return DamageState.Half;
return DamageState.Normal;
}
public void InflictDamage(Actor attacker, int damage, WarheadInfo warhead)
{
if (IsDead) return; /* overkill! don't count extra hits as more kills! */
var oldState = GetDamageState();
/* apply the damage modifiers, if we have any. */
var modifier = (float)traits.WithInterface<IDamageModifier>()
.Select(t => t.GetDamageModifier(warhead)).Product();
damage = (int)(damage * modifier);
Health -= damage;
if (Health <= 0)
{
Health = 0;
attacker.Owner.Kills++;
Owner.Deaths++;
if (RemoveOnDeath)
World.AddFrameEndTask(w => w.Remove(this));
Log.Write("debug", "{0} #{1} killed by {2} #{3}", Info.Name, ActorID, attacker.Info.Name, attacker.ActorID);
}
var maxHP = this.GetMaxHP();
if (Health > maxHP) Health = maxHP;
// Log.Write("debug", "InflictDamage: {0} #{1} -> {2} #{3} raw={4} adj={5} hp={6} mod={7}",
// attacker.Info.Name, attacker.ActorID, Info.Name, ActorID, rawDamage, damage, Health, modifier);
var newState = GetDamageState();
foreach (var nd in traits.WithInterface<INotifyDamage>())
nd.Damaged(this, new AttackInfo
{
Attacker = attacker,
Damage = damage,
DamageState = newState,
DamageStateChanged = newState != oldState,
Warhead = warhead
});
}
public void QueueActivity( IActivity nextActivity ) public void QueueActivity( IActivity nextActivity )
{ {

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Effects
public void Tick( World world ) public void Tick( World world )
{ {
if (--framesLeft == 0 || a.IsDead) if (--framesLeft == 0 || a.IsDead())
world.AddFrameEndTask(w => w.Remove(this)); world.AddFrameEndTask(w => w.Remove(this));
} }

View File

@@ -34,13 +34,6 @@ namespace OpenRA
return xs.Aggregate(1f, (a, x) => a * x); return xs.Aggregate(1f, (a, x) => a * x);
} }
public static int GetMaxHP(this Actor self)
{
var oai = self.Info.Traits.GetOrDefault<OwnedActorInfo>();
if (oai == null) return 0;
return oai.HP;
}
public static V GetOrAdd<K, V>( this Dictionary<K, V> d, K k ) public static V GetOrAdd<K, V>( this Dictionary<K, V> d, K k )
where V : new() where V : new()
{ {

View File

@@ -31,7 +31,13 @@ namespace OpenRA.GameRules
public readonly int Delay = 0; // delay in ticks before dealing the damage. 0=instant (old model) public readonly int Delay = 0; // delay in ticks before dealing the damage. 0=instant (old model)
public readonly DamageModel DamageModel = DamageModel.Normal; // which damage model to use public readonly DamageModel DamageModel = DamageModel.Normal; // which damage model to use
public float EffectivenessAgainst(ArmorType at) { return Verses[(int)at]; } public float EffectivenessAgainst(Actor self)
{
var health = self.Info.Traits.GetOrDefault<HealthInfo>();
if (health == null) return 0f;
return Verses[(int)(health.Armor)];
}
} }
public enum ArmorType public enum ArmorType

View File

@@ -229,6 +229,8 @@
<Compile Include="Traits\Player\DeveloperMode.cs" /> <Compile Include="Traits\Player\DeveloperMode.cs" />
<Compile Include="Traits\RevealsShroud.cs" /> <Compile Include="Traits\RevealsShroud.cs" />
<Compile Include="Traits\Targetable.cs" /> <Compile Include="Traits\Targetable.cs" />
<Compile Include="Traits\Health.cs" />
<Compile Include="Traits\RepairableBuilding.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -22,11 +22,13 @@ namespace OpenRA.Traits.Activities
{ {
var csv = self.Info.Traits.GetOrDefault<CustomSellValueInfo>(); var csv = self.Info.Traits.GetOrDefault<CustomSellValueInfo>();
var cost = csv != null ? csv.Value : self.Info.Traits.Get<ValuedInfo>().Cost; var cost = csv != null ? csv.Value : self.Info.Traits.Get<ValuedInfo>().Cost;
var hp = self.Info.Traits.Get<OwnedActorInfo>().HP;
var refund = self.World.Defaults.RefundPercent * self.Health * cost / hp;
self.Owner.PlayerActor.traits.Get<PlayerResources>().GiveCash((int)refund); var health = self.traits.GetOrDefault<Health>();
self.Health = 0; var refundFraction = self.World.Defaults.RefundPercent * (health == null ? 1f : health.HPFraction);
self.Owner.PlayerActor.traits.Get<PlayerResources>().GiveCash((int)(refundFraction * cost));
self.Kill(self);
foreach (var ns in self.traits.WithInterface<INotifySold>()) foreach (var ns in self.traits.WithInterface<INotifySold>())
ns.Sold(self); ns.Sold(self);
self.World.AddFrameEndTask( _ => self.World.Remove( self ) ); self.World.AddFrameEndTask( _ => self.World.Remove( self ) );

View File

@@ -18,13 +18,7 @@ using OpenRA.Traits.Activities;
namespace OpenRA.Traits namespace OpenRA.Traits
{ {
public class OwnedActorInfo public class BuildingInfo : ITraitInfo
{
public readonly int HP = 0;
public readonly ArmorType Armor = ArmorType.none;
}
public class BuildingInfo : OwnedActorInfo, ITraitInfo
{ {
public readonly int Power = 0; public readonly int Power = 0;
public readonly bool BaseNormal = true; public readonly bool BaseNormal = true;
@@ -44,14 +38,12 @@ namespace OpenRA.Traits
public object Create(ActorInitializer init) { return new Building(init); } public object Create(ActorInitializer init) { return new Building(init); }
} }
public class Building : INotifyDamage, IResolveOrder, ITick, IRenderModifier, IOccupySpace, IRadarSignature public class Building : INotifyDamage, IResolveOrder, IRenderModifier, IOccupySpace, IRadarSignature
{ {
readonly Actor self; readonly Actor self;
public readonly BuildingInfo Info; public readonly BuildingInfo Info;
[Sync] [Sync]
readonly int2 topLeft; readonly int2 topLeft;
[Sync]
bool isRepairing = false;
public bool Disabled public bool Disabled
{ {
@@ -74,10 +66,12 @@ namespace OpenRA.Traits
.Select(t => t.GetPowerModifier()) .Select(t => t.GetPowerModifier())
.Product(); .Product();
var maxHP = self.Info.Traits.Get<BuildingInfo>().HP;
if (Info.Power > 0) if (Info.Power > 0)
return (int)(modifier*(self.Health * Info.Power) / maxHP); {
var health = self.traits.GetOrDefault<Health>();
var healthFraction = (health == null) ? 1f : health.HPFraction;
return (int)(modifier * healthFraction * Info.Power);
}
else else
return (int)(modifier * Info.Power); return (int)(modifier * Info.Power);
} }
@@ -98,44 +92,6 @@ namespace OpenRA.Traits
self.CancelActivity(); self.CancelActivity();
self.QueueActivity(new Sell()); self.QueueActivity(new Sell());
} }
if (order.OrderString == "Repair")
{
isRepairing = !isRepairing;
}
}
int remainingTicks;
public void Tick(Actor self)
{
if (!isRepairing) return;
if (remainingTicks == 0)
{
var csv = self.Info.Traits.GetOrDefault<CustomSellValueInfo>();
var buildingValue = csv != null ? csv.Value : self.Info.Traits.Get<ValuedInfo>().Cost;
var maxHP = self.Info.Traits.Get<BuildingInfo>().HP;
var costPerHp = (self.World.Defaults.RepairPercent * buildingValue) / maxHP;
var hpToRepair = Math.Min(self.World.Defaults.RepairStep, maxHP - self.Health);
var cost = (int)Math.Ceiling(costPerHp * hpToRepair);
if (!self.Owner.PlayerActor.traits.Get<PlayerResources>().TakeCash(cost))
{
remainingTicks = 1;
return;
}
self.World.AddFrameEndTask(w => w.Add(new RepairIndicator(self)));
self.InflictDamage(self, -hpToRepair, null);
if (self.Health == maxHP)
{
isRepairing = false;
return;
}
remainingTicks = (int)(self.World.Defaults.RepairRate * 60 * 25);
}
else
--remainingTicks;
} }
public IEnumerable<Renderable> ModifyRender(Actor self, IEnumerable<Renderable> r) public IEnumerable<Renderable> ModifyRender(Actor self, IEnumerable<Renderable> r)

View File

@@ -0,0 +1,186 @@
#region Copyright & License Information
/*
* Copyright 2007-2010 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 LICENSE.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Effects;
using OpenRA.Traits.Activities;
using OpenRA.GameRules;
namespace OpenRA.Traits
{
public class HealthInfo : ITraitInfo
{
public readonly int HP = 0;
public readonly int InitialHP = 0;
public readonly ArmorType Armor = ArmorType.none;
public readonly float ConditionRed = 0.25f;
public readonly float ConditionYellow = 0.5f;
public virtual object Create(ActorInitializer init) { return new Health(init, this); }
}
public enum ExtendedDamageState { Normal, ThreeQuarter, Half, Quarter, Dead };
public class Health
{
public readonly HealthInfo Info;
[Sync]
int hp;
public Health(ActorInitializer init, HealthInfo info)
{
Info = info;
MaxHP = info.HP;
hp = (info.InitialHP == 0) ? MaxHP : info.InitialHP;
}
public int HP { get { return hp; } }
public readonly int MaxHP;
public float HPFraction { get { return hp*1f/MaxHP; } }
public bool IsDead { get { return hp <= 0; } }
public bool RemoveOnDeath = true;
public Color HealthColor
{
get
{
return (hp < Info.ConditionRed) ? Color.Red
: (hp < Info.ConditionYellow) ? Color.Yellow
: Color.LimeGreen;
}
}
public DamageState DamageState
{
get
{
if (hp <= 0)
return DamageState.Dead;
if (hp < MaxHP * Info.ConditionYellow)
return DamageState.Half;
return DamageState.Normal;
}
}
public ExtendedDamageState ExtendedDamageState
{
get
{
if (hp <= 0)
return ExtendedDamageState.Dead;
if (hp < MaxHP * Info.ConditionRed)
return ExtendedDamageState.Quarter;
if (hp < MaxHP * Info.ConditionYellow)
return ExtendedDamageState.Half;
if (hp < MaxHP * 0.75)
return ExtendedDamageState.ThreeQuarter;
return ExtendedDamageState.Normal;
}
}
public void InflictDamage(Actor self, Actor attacker, int damage, WarheadInfo warhead)
{
if (IsDead) return; /* overkill! don't count extra hits as more kills! */
var oldState = this.DamageState;
var oldExtendedState = this.ExtendedDamageState;
/* apply the damage modifiers, if we have any. */
var modifier = (float)self.traits.WithInterface<IDamageModifier>()
.Select(t => t.GetDamageModifier(warhead)).Product();
damage = (int)(damage * modifier);
hp -= damage;
if (hp <= 0)
{
hp = 0;
attacker.Owner.Kills++;
self.Owner.Deaths++;
if (RemoveOnDeath)
self.World.AddFrameEndTask(w => w.Remove(self));
Log.Write("debug", "{0} #{1} killed by {2} #{3}", self.Info.Name, self.ActorID, attacker.Info.Name, attacker.ActorID);
}
if (hp > MaxHP) hp = MaxHP;
foreach (var nd in self.traits.WithInterface<INotifyDamage>())
nd.Damaged(self, new AttackInfo
{
Attacker = attacker,
Damage = damage,
DamageState = this.DamageState,
ExtendedDamageState = this.ExtendedDamageState,
DamageStateChanged = this.DamageState != oldState,
ExtendedDamageStateChanged = this.ExtendedDamageState != oldExtendedState,
Warhead = warhead
});
}
public void TransferHPFromActor(Actor self, Actor from, bool transferPercentage)
{
var fromHealth = from.traits.GetOrDefault<Health>();
if (fromHealth == null)
return;
hp = (transferPercentage) ? (int)(fromHealth.HPFraction*MaxHP) : Math.Min(fromHealth.HP, MaxHP);
}
}
public static class HealthExts
{
public static int Health(this Actor self)
{
var health = self.traits.GetOrDefault<Health>();
return (health == null) ? 0 : health.HP;
}
public static bool IsDead(this Actor self)
{
var health = self.traits.GetOrDefault<Health>();
return (health == null) ? true : health.IsDead;
}
public static DamageState GetDamageState(this Actor self)
{
var health = self.traits.GetOrDefault<Health>();
return (health == null) ? DamageState.Normal : health.DamageState;
}
public static void InflictDamage(this Actor self, Actor attacker, int damage, WarheadInfo warhead)
{
var health = self.traits.GetOrDefault<Health>();
if (health == null) return;
health.InflictDamage(self, attacker, damage, warhead);
}
public static void Kill(this Actor self, Actor attacker)
{
var health = self.traits.GetOrDefault<Health>();
if (health == null) return;
health.InflictDamage(self, attacker, health.HP, null);
}
}
}

View File

@@ -0,0 +1,85 @@
#region Copyright & License Information
/*
* Copyright 2007-2010 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 LICENSE.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Effects;
using OpenRA.GameRules;
using OpenRA.Traits.Activities;
namespace OpenRA.Traits
{
public class RepairableBuildingInfo : ITraitInfo, ITraitPrerequisite<HealthInfo>
{
public readonly float RepairPercent = 0.2f;
public readonly float RepairRate = 0.016f;
public readonly int RepairStep = 7;
public object Create(ActorInitializer init) { return new RepairableBuilding(init.self, this); }
}
public class RepairableBuilding : ITick, IResolveOrder
{
[Sync]
bool isRepairing = false;
Health Health;
RepairableBuildingInfo Info;
public RepairableBuilding(Actor self, RepairableBuildingInfo info)
{
Health = self.traits.Get<Health>();
Info = info;
}
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "Repair")
{
isRepairing = !isRepairing;
}
}
int remainingTicks;
public void Tick(Actor self)
{
if (!isRepairing) return;
if (remainingTicks == 0)
{
var csv = self.Info.Traits.GetOrDefault<CustomSellValueInfo>();
var buildingValue = csv != null ? csv.Value : self.Info.Traits.Get<ValuedInfo>().Cost;
var costPerHp = (Info.RepairPercent * buildingValue) / Health.MaxHP;
var hpToRepair = Math.Min(Info.RepairStep, Health.MaxHP - Health.HP);
var cost = (int)Math.Ceiling(costPerHp * hpToRepair);
if (!self.Owner.PlayerActor.traits.Get<PlayerResources>().TakeCash(cost))
{
remainingTicks = 1;
return;
}
self.World.AddFrameEndTask(w => w.Add(new RepairIndicator(self)));
self.InflictDamage(self, -hpToRepair, null);
if (Health.HP == Health.MaxHP)
{
isRepairing = false;
return;
}
remainingTicks = (int)(Info.RepairRate * 60 * 25);
}
else
--remainingTicks;
}
}
public class AllowsBuildingRepairInfo : TraitInfo<AllowsBuildingRepair> {}
public class AllowsBuildingRepair {}
}

View File

@@ -61,22 +61,22 @@ namespace OpenRA.Traits
void DrawHealthBar(Actor self, float2 xy, float2 Xy) void DrawHealthBar(Actor self, float2 xy, float2 Xy)
{ {
var health = self.traits.GetOrDefault<Health>();
if (health == null)
return;
var c = Color.Gray; var c = Color.Gray;
Game.Renderer.LineRenderer.DrawLine(xy + new float2(0, -2), xy + new float2(0, -4), c, c); Game.Renderer.LineRenderer.DrawLine(xy + new float2(0, -2), xy + new float2(0, -4), c, c);
Game.Renderer.LineRenderer.DrawLine(Xy + new float2(0, -2), Xy + new float2(0, -4), c, c); Game.Renderer.LineRenderer.DrawLine(Xy + new float2(0, -2), Xy + new float2(0, -4), c, c);
var healthAmount = (float)self.Health / self.Info.Traits.Get<OwnedActorInfo>().HP; var healthColor = health.HealthColor;
var healthColor = (healthAmount < self.World.Defaults.ConditionRed) ? Color.Red
: (healthAmount < self.World.Defaults.ConditionYellow) ? Color.Yellow
: Color.LimeGreen;
var healthColor2 = Color.FromArgb( var healthColor2 = Color.FromArgb(
255, 255,
healthColor.R / 2, healthColor.R / 2,
healthColor.G / 2, healthColor.G / 2,
healthColor.B / 2); healthColor.B / 2);
var z = float2.Lerp(xy, Xy, healthAmount); var z = float2.Lerp(xy, Xy, health.HPFraction);
Game.Renderer.LineRenderer.DrawLine(z + new float2(0, -4), Xy + new float2(0, -4), c, c); Game.Renderer.LineRenderer.DrawLine(z + new float2(0, -4), Xy + new float2(0, -4), c, c);
Game.Renderer.LineRenderer.DrawLine(z + new float2(0, -2), Xy + new float2(0, -2), c, c); Game.Renderer.LineRenderer.DrawLine(z + new float2(0, -2), Xy + new float2(0, -2), c, c);

View File

@@ -28,7 +28,9 @@ namespace OpenRA.Traits
public WarheadInfo Warhead; public WarheadInfo Warhead;
public int Damage; public int Damage;
public DamageState DamageState; public DamageState DamageState;
public ExtendedDamageState ExtendedDamageState;
public bool DamageStateChanged; public bool DamageStateChanged;
public bool ExtendedDamageStateChanged;
} }
public interface ITick { void Tick(Actor self); } public interface ITick { void Tick(Actor self); }

View File

@@ -14,7 +14,7 @@ using System.Linq;
namespace OpenRA.Traits namespace OpenRA.Traits
{ {
public class UnitInfo : OwnedActorInfo, ITraitInfo public class UnitInfo : ITraitInfo
{ {
public readonly int InitialFacing = 128; public readonly int InitialFacing = 128;
public readonly int ROT = 255; public readonly int ROT = 255;

View File

@@ -81,7 +81,7 @@ namespace OpenRA.Traits
public void Update(Actor self, IOccupyAir unit) public void Update(Actor self, IOccupyAir unit)
{ {
Remove(self, unit); Remove(self, unit);
if (!self.IsDead) Add(self, unit); if (!self.IsDead()) Add(self, unit);
} }
} }
} }

View File

@@ -19,10 +19,6 @@ namespace OpenRA.Traits
public readonly float RepairPercent = 0.2f; public readonly float RepairPercent = 0.2f;
public readonly float RepairRate = 0.016f; public readonly float RepairRate = 0.016f;
public readonly int RepairStep = 7; public readonly int RepairStep = 7;
/* Audo/Visual Map Controls */
public readonly float ConditionRed = 0.25f;
public readonly float ConditionYellow = 0.5f;
} }
public class GlobalDefaults {} public class GlobalDefaults {}

View File

@@ -86,7 +86,7 @@ namespace OpenRA.Traits
public void Update(Actor self, IOccupySpace unit) public void Update(Actor self, IOccupySpace unit)
{ {
Remove(self, unit); Remove(self, unit);
if (!self.IsDead) Add(self, unit); if (!self.IsDead()) Add(self, unit);
} }
} }
} }

View File

@@ -13,7 +13,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Cnc namespace OpenRA.Mods.Cnc
{ {
class CriticalBuildingStateInfo : ITraitInfo class CriticalBuildingStateInfo : ITraitInfo, ITraitPrerequisite<HealthInfo>
{ {
public readonly int LingerTime = 20; public readonly int LingerTime = 20;
public object Create(ActorInitializer init) { return new CriticalBuildingState(init.self, this); } public object Create(ActorInitializer init) { return new CriticalBuildingState(init.self, this); }
@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Cnc
public CriticalBuildingState(Actor self, CriticalBuildingStateInfo info) public CriticalBuildingState(Actor self, CriticalBuildingStateInfo info)
{ {
this.info = info; this.info = info;
self.RemoveOnDeath = false; self.traits.Get<Health>().RemoveOnDeath = false;
} }
public void Damaged(Actor self, AttackInfo e) public void Damaged(Actor self, AttackInfo e)

View File

@@ -51,7 +51,7 @@ namespace OpenRA.Mods.Cnc
a.QueueActivity(new Land(self)); a.QueueActivity(new Land(self));
a.QueueActivity(new CallFunc(() => a.QueueActivity(new CallFunc(() =>
{ {
if (self.IsDead) if (self.IsDead())
return; return;
var actor = cargo.Unload(self); var actor = cargo.Unload(self);

View File

@@ -82,7 +82,7 @@ namespace OpenRA.Mods.Cnc
public void Damaged (Actor self, AttackInfo e) public void Damaged (Actor self, AttackInfo e)
{ {
if (self.IsDead) if (e.DamageState == DamageState.Dead)
CancelDock(self, dockedHarv); CancelDock(self, dockedHarv);
} }

View File

@@ -22,7 +22,7 @@ namespace OpenRA.Mods.RA.Activities
public IActivity Tick(Actor self) public IActivity Tick(Actor self)
{ {
if (target == null || target.IsDead) return NextActivity; if (target == null || target.IsDead()) return NextActivity;
target.World.AddFrameEndTask(w => target.World.AddFrameEndTask(w =>
{ {

View File

@@ -25,9 +25,9 @@ namespace OpenRA.Mods.RA.Activities
public IActivity Tick(Actor self) public IActivity Tick(Actor self)
{ {
if (target == null || target.IsDead) return NextActivity; if (target == null || target.IsDead()) return NextActivity;
self.World.AddFrameEndTask(w => w.Add(new DelayedAction(25 * 2, self.World.AddFrameEndTask(w => w.Add(new DelayedAction(25 * 2,
() => target.InflictDamage(self, target.Health, null)))); () => target.Kill(self))));
return NextActivity; return NextActivity;
} }

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Mods.RA.Activities
public IActivity Tick(Actor self) public IActivity Tick(Actor self)
{ {
if (target == null || target.IsDead) return NextActivity; if (target == null || target.IsDead()) return NextActivity;
if (target.Owner == self.Owner) return NextActivity; if (target.Owner == self.Owner) return NextActivity;
foreach (var t in target.traits.WithInterface<IAcceptSpy>()) foreach (var t in target.traits.WithInterface<IAcceptSpy>())

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA.Activities
public IActivity Tick(Actor self) public IActivity Tick(Actor self)
{ {
if (Structure != null && Structure.IsDead) if (Structure != null && Structure.IsDead())
{ {
Structure = null; Structure = null;
isCanceled = true; isCanceled = true;

View File

@@ -48,7 +48,7 @@ namespace OpenRA.Mods.RA.Activities
.SetPosition(self, Util.CellContaining(target.CenterLocation)); .SetPosition(self, Util.CellContaining(target.CenterLocation));
if (target.IsActor) if (target.IsActor)
target.Actor.InflictDamage(self, target.Actor.Health, null); // kill it target.Actor.Kill(self);
return NextActivity; return NextActivity;
} }

View File

@@ -28,11 +28,13 @@ namespace OpenRA.Mods.RA.Activities
if (isCanceled) return NextActivity; if (isCanceled) return NextActivity;
if (remainingTicks == 0) if (remainingTicks == 0)
{ {
var unitCost = self.Info.Traits.Get<ValuedInfo>().Cost; var health = self.traits.GetOrDefault<Health>();
var hp = self.Info.Traits.Get<OwnedActorInfo>().HP; if (health == null) return NextActivity;
var costPerHp = (host.Info.Traits.Get<RepairsUnitsInfo>().URepairPercent * unitCost) / hp; var unitCost = self.Info.Traits.Get<ValuedInfo>().Cost;
var hpToRepair = Math.Min(host.Info.Traits.Get<RepairsUnitsInfo>().URepairStep, hp - self.Health);
var costPerHp = (host.Info.Traits.Get<RepairsUnitsInfo>().URepairPercent * unitCost) / health.MaxHP;
var hpToRepair = Math.Min(host.Info.Traits.Get<RepairsUnitsInfo>().URepairStep, health.MaxHP - health.HP);
var cost = (int)Math.Ceiling(costPerHp * hpToRepair); var cost = (int)Math.Ceiling(costPerHp * hpToRepair);
if (!self.Owner.PlayerActor.traits.Get<PlayerResources>().TakeCash(cost)) if (!self.Owner.PlayerActor.traits.Get<PlayerResources>().TakeCash(cost))
{ {
@@ -41,7 +43,7 @@ namespace OpenRA.Mods.RA.Activities
} }
self.InflictDamage(self, -hpToRepair, null); self.InflictDamage(self, -hpToRepair, null);
if (self.Health == hp) if (health.MaxHP == health.HP)
return NextActivity; return NextActivity;
if (host != null) if (host != null)

View File

@@ -22,11 +22,12 @@ namespace OpenRA.Mods.RA.Activities
public IActivity Tick(Actor self) public IActivity Tick(Actor self)
{ {
if (target == null || target.IsDead) return NextActivity; if (target == null || target.IsDead()) return NextActivity;
if (target.Health == target.GetMaxHP()) var health = target.traits.Get<Health>();
if (health.HP == health.MaxHP)
return NextActivity; return NextActivity;
target.InflictDamage(self, -target.GetMaxHP(), null); target.InflictDamage(self, -health.MaxHP, null);
self.World.AddFrameEndTask(w => w.Remove(self)); self.World.AddFrameEndTask(w => w.Remove(self));
return NextActivity; return NextActivity;

View File

@@ -47,7 +47,11 @@ namespace OpenRA.Mods.RA.Activities
Sound.PlayToPlayer(self.Owner, s, self.CenterLocation); Sound.PlayToPlayer(self.Owner, s, self.CenterLocation);
var a = w.CreateActor(actor, self.Location + offset, self.Owner); var a = w.CreateActor(actor, self.Location + offset, self.Owner);
a.Health = GetHealthToTransfer(self, a, transferPercentage); var health = a.traits.GetOrDefault<Health>();
if (health != null)
{
health.TransferHPFromActor(a, self, transferPercentage);
}
if (selected) if (selected)
w.Selection.Add(w, a); w.Selection.Add(w, a);
@@ -56,14 +60,5 @@ namespace OpenRA.Mods.RA.Activities
} }
public void Cancel(Actor self) { isCanceled = true; NextActivity = null; } public void Cancel(Actor self) { isCanceled = true; NextActivity = null; }
public static int GetHealthToTransfer(Actor from, Actor to, bool transferPercentage)
{
var oldHP = from.GetMaxHP();
var newHP = to.GetMaxHP();
return (transferPercentage)
? (int)((float)from.Health / oldHP * newHP)
: Math.Min(from.Health, newHP);
}
} }
} }

View File

@@ -24,7 +24,13 @@ namespace OpenRA.Mods.RA.Activities
w.Remove(self); w.Remove(self);
var mcv = w.CreateActor("mcv", self.Location + new int2(1, 1), self.Owner); var mcv = w.CreateActor("mcv", self.Location + new int2(1, 1), self.Owner);
mcv.Health = TransformIntoActor.GetHealthToTransfer(self, mcv, true);
var health = mcv.traits.GetOrDefault<Health>();
if (health != null)
{
health.TransferHPFromActor(mcv, self, true);
}
mcv.traits.Get<Unit>().Facing = 96; mcv.traits.Get<Unit>().Facing = 96;
if (selected) if (selected)

View File

@@ -218,10 +218,16 @@ namespace OpenRA.Mods.RA
{ {
// we can never "heal ground"; that makes no sense. // we can never "heal ground"; that makes no sense.
if (!target.IsActor) return null; if (!target.IsActor) return null;
// unless forced, only heal allies. // unless forced, only heal allies.
if (self.Owner.Stances[underCursor.Owner] != Stance.Ally && !forceFire) return null; if (self.Owner.Stances[underCursor.Owner] != Stance.Ally && !forceFire) return null;
// we can only heal actors with health
var health = underCursor.traits.GetOrDefault<Health>();
if (health == null) return null;
// don't allow healing of fully-healed stuff! // don't allow healing of fully-healed stuff!
if (underCursor.Health >= underCursor.GetMaxHP()) return null; if (health.HP >= health.MaxHP) return null;
} }
else else
{ {

View File

@@ -37,8 +37,9 @@ namespace OpenRA.Mods.RA
return true; // he's dead. return true; // he's dead.
if ((attack.target.CenterLocation - self.Location).LengthSquared > range * range + 2) if ((attack.target.CenterLocation - self.Location).LengthSquared > range * range + 2)
return true; // wandered off faster than we could follow return true; // wandered off faster than we could follow
if (attack.target.IsActor && attack.target.Actor.Health
== attack.target.Actor.Info.Traits.Get<OwnedActorInfo>().HP) var health = attack.target.Actor.traits.GetOrDefault<Health>();
if (attack.target.IsActor && health.HP == health.MaxHP)
return true; // fully healed return true; // fully healed
return false; return false;
@@ -59,7 +60,7 @@ namespace OpenRA.Mods.RA
return inRange return inRange
.Where(a => a != self && self.Owner.Stances[ a.Owner ] == Stance.Ally) .Where(a => a != self && self.Owner.Stances[ a.Owner ] == Stance.Ally)
.Where(a => Combat.HasAnyValidWeapons(self, Target.FromActor(a))) .Where(a => Combat.HasAnyValidWeapons(self, Target.FromActor(a)))
.Where(a => a.Health < a.Info.Traits.Get<OwnedActorInfo>().HP) .Where(a => a.traits.Contains<Health>() && a.traits.Get<Health>().HPFraction < 1f)
.OrderBy(a => (a.Location - self.Location).LengthSquared) .OrderBy(a => (a.Location - self.Location).LengthSquared)
.FirstOrDefault(); .FirstOrDefault();
} }

View File

@@ -66,8 +66,6 @@ namespace OpenRA.Mods.RA
{ {
if (!self.IsIdle) return; if (!self.IsIdle) return;
if (!e.Attacker.Info.Traits.Contains<OwnedActorInfo>()) return;
// not a lot we can do about things we can't hurt... although maybe we should automatically run away? // not a lot we can do about things we can't hurt... although maybe we should automatically run away?
if (!Combat.HasAnyValidWeapons(self, Target.FromActor(e.Attacker))) return; if (!Combat.HasAnyValidWeapons(self, Target.FromActor(e.Attacker))) return;

View File

@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
class BridgeInfo : ITraitInfo class BridgeInfo : ITraitInfo, ITraitPrerequisite<HealthInfo>
{ {
public readonly bool Long = false; public readonly bool Long = false;
@@ -75,11 +75,13 @@ namespace OpenRA.Mods.RA
BridgeInfo Info; BridgeInfo Info;
public string Type; public string Type;
Bridge northNeighbour, southNeighbour; Bridge northNeighbour, southNeighbour;
Health Health;
public Bridge(Actor self, BridgeInfo info) public Bridge(Actor self, BridgeInfo info)
{ {
this.self = self; this.self = self;
self.RemoveOnDeath = false; Health = self.traits.Get<Health>();
Health.RemoveOnDeath = false;
this.Info = info; this.Info = info;
this.Type = self.Info.Name; this.Type = self.Info.Name;
} }
@@ -88,9 +90,9 @@ namespace OpenRA.Mods.RA
{ {
currentTemplate = template; currentTemplate = template;
if (template == Info.DamagedTemplate) if (template == Info.DamagedTemplate)
self.Health = (int)(self.World.Defaults.ConditionYellow*self.GetMaxHP()); Health.InflictDamage(self, self, Health.MaxHP/2, null);
else if (template != Info.Template) else if (template != Info.Template)
self.Health = 0; Health.InflictDamage(self, self, Health.MaxHP, null);
// Create a new cache to store the tile data // Create a new cache to store the tile data
if (cachedTileset != self.World.Map.Tileset) if (cachedTileset != self.World.Map.Tileset)
@@ -141,7 +143,7 @@ namespace OpenRA.Mods.RA
bool IsIntact(Bridge b) bool IsIntact(Bridge b)
{ {
return b != null && b.self.IsInWorld && b.self.Health > 0; return b != null && b.self.IsInWorld && !b.self.IsDead();
} }
void KillUnitsOnBridge() void KillUnitsOnBridge()
@@ -151,23 +153,21 @@ namespace OpenRA.Mods.RA
foreach (var c in TileSprites[currentTemplate].Keys) foreach (var c in TileSprites[currentTemplate].Keys)
foreach (var a in uim.GetUnitsAt(c)) foreach (var a in uim.GetUnitsAt(c))
if (!a.traits.Get<IMove>().CanEnterCell(c)) if (!a.traits.Get<IMove>().CanEnterCell(c))
a.InflictDamage(self, a.Health, null); a.Kill(self);
} }
bool dead = false; bool dead = false;
void UpdateState() void UpdateState()
{ {
var ds = self.GetDamageState();
// If this is a long bridge next to a destroyed shore piece, we need die to give clean edges to the break // If this is a long bridge next to a destroyed shore piece, we need die to give clean edges to the break
if (Info.Long && ds != DamageState.Dead && if (Info.Long && Health.DamageState != DamageState.Dead &&
((southNeighbour != null && Info.ShorePieces.Contains(southNeighbour.Type) && !IsIntact(southNeighbour)) || ((southNeighbour != null && Info.ShorePieces.Contains(southNeighbour.Type) && !IsIntact(southNeighbour)) ||
(northNeighbour != null && Info.ShorePieces.Contains(northNeighbour.Type) && !IsIntact(northNeighbour)))) (northNeighbour != null && Info.ShorePieces.Contains(northNeighbour.Type) && !IsIntact(northNeighbour))))
{ {
self.Health = 0; self.Kill(self); // this changes the damagestate
ds = DamageState.Dead;
} }
var ds = Health.DamageState;
currentTemplate = (ds == DamageState.Half && Info.DamagedTemplate > 0) ? Info.DamagedTemplate : currentTemplate = (ds == DamageState.Half && Info.DamagedTemplate > 0) ? Info.DamagedTemplate :
(ds == DamageState.Dead && Info.DestroyedTemplate > 0) ? Info.DestroyedTemplate : Info.Template; (ds == DamageState.Dead && Info.DestroyedTemplate > 0) ? Info.DestroyedTemplate : Info.Template;

View File

@@ -94,8 +94,7 @@ namespace OpenRA.Mods.RA
foreach (var t in world.FindTilesInCircle(targetTile, warhead.Size[0])) foreach (var t in world.FindTilesInCircle(targetTile, warhead.Size[0]))
foreach (var unit in world.FindUnits(Game.CellSize * t, Game.CellSize * (t + new float2(1,1)))) foreach (var unit in world.FindUnits(Game.CellSize * t, Game.CellSize * (t + new float2(1,1))))
unit.InflictDamage(args.firedBy, unit.InflictDamage(args.firedBy,
(int)(warhead.Damage * warhead.EffectivenessAgainst( (int)(warhead.Damage * warhead.EffectivenessAgainst(unit)), warhead);
unit.Info.Traits.Get<OwnedActorInfo>().Armor)), warhead);
} break; } break;
} }
} }
@@ -154,7 +153,7 @@ namespace OpenRA.Mods.RA
var distance = (int)Math.Max(0, (target.CenterLocation - args.dest).Length - radius); var distance = (int)Math.Max(0, (target.CenterLocation - args.dest).Length - radius);
var falloff = (float)GetDamageFalloff(distance / warhead.Spread); var falloff = (float)GetDamageFalloff(distance / warhead.Spread);
var rawDamage = (float)(warhead.Damage * modifier * falloff); var rawDamage = (float)(warhead.Damage * modifier * falloff);
var multiplier = (float)warhead.EffectivenessAgainst(target.Info.Traits.Get<OwnedActorInfo>().Armor); var multiplier = (float)warhead.EffectivenessAgainst(target);
return (float)(rawDamage * multiplier); return (float)(rawDamage * multiplier);
} }
@@ -171,8 +170,7 @@ namespace OpenRA.Mods.RA
if (targetable == null || !weapon.ValidTargets.Intersect(targetable.TargetTypes).Any()) if (targetable == null || !weapon.ValidTargets.Intersect(targetable.TargetTypes).Any())
return false; return false;
var ownedInfo = target.Actor.Info.Traits.GetOrDefault<OwnedActorInfo>(); if (weapon.Warheads.All( w => w.EffectivenessAgainst(target.Actor) <= 0))
if (weapon.Warheads.All( w => w.EffectivenessAgainst(ownedInfo.Armor) <= 0))
return false; return false;
return true; return true;

View File

@@ -47,7 +47,7 @@ namespace OpenRA.Mods.RA
Game.Debug("{0} is defeated.".F(self.Owner.PlayerName)); Game.Debug("{0} is defeated.".F(self.Owner.PlayerName));
foreach (var a in self.World.Queries.OwnedBy[self.Owner]) foreach (var a in self.World.Queries.OwnedBy[self.Owner])
a.InflictDamage(a, a.Health, null); a.Kill(a);
self.Owner.Shroud.Disabled = true; self.Owner.Shroud.Disabled = true;

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Mods.RA
Sound.Play(report + ".aud", self.CenterLocation); Sound.Play(report + ".aud", self.CenterLocation);
// Remove from world // Remove from world
self.Health = 0; self.Kill(self);
detonatedBy.Owner.Kills++; detonatedBy.Owner.Kills++;
self.Owner.Deaths++; self.Owner.Deaths++;
w.Remove(self); w.Remove(self);

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA.Effects
public void Tick( World world ) public void Tick( World world )
{ {
if (a.IsDead || b.GetDamageModifier(null) > 0) if (a.IsDead() || b.GetDamageModifier(null) > 0)
world.AddFrameEndTask(w => w.Remove(this)); world.AddFrameEndTask(w => w.Remove(this));
} }

View File

@@ -34,8 +34,9 @@ namespace OpenRA.Mods.RA
var csv = self.Info.Traits.GetOrDefault<CustomSellValueInfo>(); var csv = self.Info.Traits.GetOrDefault<CustomSellValueInfo>();
var valued = self.Info.Traits.GetOrDefault<ValuedInfo>(); var valued = self.Info.Traits.GetOrDefault<ValuedInfo>();
var cost = csv != null ? csv.Value : (valued != null ? valued.Cost : 0); var cost = csv != null ? csv.Value : (valued != null ? valued.Cost : 0);
var hp = self.Info.Traits.Get<OwnedActorInfo>().HP;
var hpFraction = Math.Max(info.MinHpFraction, hp / self.GetMaxHP()); var health = self.traits.GetOrDefault<Health>();
var hpFraction = (health == null) ? 1f : health.HPFraction;
var dudesValue = (int)(hpFraction * info.ValueFraction * cost); var dudesValue = (int)(hpFraction * info.ValueFraction * cost);
var eligibleLocations = Footprint.Tiles(self).ToList(); var eligibleLocations = Footprint.Tiles(self).ToList();
var actorTypes = info.ActorTypes.Select(a => new { Name = a, Cost = Rules.Info[a].Traits.Get<ValuedInfo>().Cost }).ToArray(); var actorTypes = info.ActorTypes.Select(a => new { Name = a, Cost = Rules.Info[a].Traits.Get<ValuedInfo>().Cost }).ToArray();

View File

@@ -39,19 +39,25 @@ namespace OpenRA.Mods.RA
{ {
if (order.OrderString != "EngineerRepair") return null; if (order.OrderString != "EngineerRepair") return null;
if (order.TargetActor == null) return null; if (order.TargetActor == null) return null;
var health = order.TargetActor.traits.GetOrDefault<Health>();
return (order.TargetActor.Health == order.TargetActor.GetMaxHP()) ? "goldwrench-blocked" : "goldwrench"; if (health == null) return null;
return (health.HP == health.MaxHP) ? "goldwrench-blocked" : "goldwrench";
} }
public string VoicePhraseForOrder(Actor self, Order order) public string VoicePhraseForOrder(Actor self, Order order)
{ {
var health = order.TargetActor.traits.GetOrDefault<Health>();
if (health == null) return null;
return (order.OrderString == "EngineerRepair" return (order.OrderString == "EngineerRepair"
&& order.TargetActor.Health < order.TargetActor.GetMaxHP()) ? "Attack" : null; && health.HP < health.MaxHP) ? "Attack" : null;
} }
public void ResolveOrder(Actor self, Order order) public void ResolveOrder(Actor self, Order order)
{ {
if (order.OrderString == "EngineerRepair" && order.TargetActor.Health < order.TargetActor.GetMaxHP()) var health = order.TargetActor.traits.GetOrDefault<Health>();
if (health == null) return;
if (order.OrderString == "EngineerRepair" && health.HP < health.MaxHP)
{ {
if (self.Owner == self.World.LocalPlayer) if (self.Owner == self.World.LocalPlayer)
self.World.AddFrameEndTask(w => self.World.AddFrameEndTask(w =>

View File

@@ -25,7 +25,7 @@ namespace OpenRA.Mods.RA
{ {
public void Damaged(Actor self, AttackInfo e) public void Damaged(Actor self, AttackInfo e)
{ {
if (self.IsDead) if (e.DamageState == DamageState.Dead)
{ {
var weapon = ChooseWeaponForExplosion(self); var weapon = ChooseWeaponForExplosion(self);
if (weapon != null) if (weapon != null)

View File

@@ -164,7 +164,7 @@ namespace OpenRA.Mods.RA
public void Damaged(Actor self, AttackInfo e) public void Damaged(Actor self, AttackInfo e)
{ {
if (self.IsDead) if (e.DamageState == DamageState.Dead)
if (LinkedProc != null) if (LinkedProc != null)
LinkedProc.traits.WithInterface<IAcceptOre>().FirstOrDefault().UnlinkHarvester(LinkedProc,self); LinkedProc.traits.WithInterface<IAcceptOre>().FirstOrDefault().UnlinkHarvester(LinkedProc,self);
} }

View File

@@ -115,7 +115,7 @@ namespace OpenRA.Mods.RA
public void Tick(World world) public void Tick(World world)
{ {
if (minelayer.IsDead || !minelayer.IsInWorld) if (minelayer.IsDead() || !minelayer.IsInWorld)
world.CancelInputMode(); world.CancelInputMode();
} }

View File

@@ -34,8 +34,9 @@ namespace OpenRA.Mods.RA.Orders
&& a.traits.Contains<Selectable>()).FirstOrDefault(); && a.traits.Contains<Selectable>()).FirstOrDefault();
var building = underCursor != null ? underCursor.Info.Traits.Get<BuildingInfo>() : null; var building = underCursor != null ? underCursor.Info.Traits.Get<BuildingInfo>() : null;
var health = underCursor != null ? underCursor.traits.GetOrDefault<Health>() : null;
if (building != null && building.Repairable && underCursor.Health < building.HP) if (building != null && building.Repairable && health != null && health.HPFraction < 1f)
yield return new Order("Repair", underCursor); yield return new Order("Repair", underCursor);
} }
} }

View File

@@ -92,7 +92,7 @@ namespace OpenRA.Mods.RA
public void Damaged (Actor self, AttackInfo e) public void Damaged (Actor self, AttackInfo e)
{ {
if (self.IsDead) if (e.DamageState == DamageState.Dead)
foreach (var harv in LinkedHarv) foreach (var harv in LinkedHarv)
harv.traits.Get<Harvester> ().UnlinkProc(harv, self); harv.traits.Get<Harvester> ().UnlinkProc(harv, self);
} }

View File

@@ -31,37 +31,13 @@ namespace OpenRA.Mods.RA.Render
anim.PlayFetchIndex(seqName, () => adjacentWalls); anim.PlayFetchIndex(seqName, () => adjacentWalls);
} }
enum ExtendedDamageState { Normal, ThreeQuarter, Half, Quarter, Dead };
ExtendedDamageState GetExtendedState( Actor self, int damage )
{
var effectiveHealth = self.Health + damage;
if (effectiveHealth <= 0)
return ExtendedDamageState.Dead;
if (effectiveHealth < self.GetMaxHP() * self.World.Defaults.ConditionRed)
return ExtendedDamageState.Quarter;
if (effectiveHealth < self.GetMaxHP() * self.World.Defaults.ConditionYellow)
return ExtendedDamageState.Half;
if (effectiveHealth < self.GetMaxHP() * 0.75)
return ExtendedDamageState.ThreeQuarter;
return ExtendedDamageState.Normal;
}
public override void Damaged(Actor self, AttackInfo e) public override void Damaged(Actor self, AttackInfo e)
{ {
var oldState = GetExtendedState(self, e.Damage);
var newState = GetExtendedState(self, 0);
var numStates = self.Info.Traits.Get<RenderBuildingWallInfo>().DamageStates; var numStates = self.Info.Traits.Get<RenderBuildingWallInfo>().DamageStates;
if (oldState == newState) return; if (!e.ExtendedDamageStateChanged) return;
switch (newState) switch (e.ExtendedDamageState)
{ {
case ExtendedDamageState.Normal: case ExtendedDamageState.Normal:
seqName = "idle"; seqName = "idle";

View File

@@ -17,10 +17,21 @@ using System.Drawing;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
class RepairableInfo : TraitInfo<Repairable> { public readonly string[] RepairBuildings = { "fix" }; } class RepairableInfo : ITraitInfo, ITraitPrerequisite<HealthInfo>
{
public readonly string[] RepairBuildings = { "fix" };
public virtual object Create(ActorInitializer init) { return new Repairable(init.self); }
}
class Repairable : IIssueOrder, IResolveOrder, IOrderCursor, IOrderVoice class Repairable : IIssueOrder, IResolveOrder, IOrderCursor, IOrderVoice
{ {
Health Health;
public Repairable(Actor self)
{
Health = self.traits.Get<Health>();
}
public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor)
{ {
if (mi.Button != MouseButton.Right) return null; if (mi.Button != MouseButton.Right) return null;
@@ -36,7 +47,7 @@ namespace OpenRA.Mods.RA
bool CanRepair(Actor self) bool CanRepair(Actor self)
{ {
var li = self.traits.GetOrDefault<LimitedAmmo>(); var li = self.traits.GetOrDefault<LimitedAmmo>();
return (self.Health < self.GetMaxHP() || (li != null && !li.FullAmmo()) ); return (Health.HPFraction < 1f || (li != null && !li.FullAmmo()) );
} }
public string CursorForOrder(Actor self, Order order) public string CursorForOrder(Actor self, Order order)

View File

@@ -16,7 +16,7 @@ using System.Drawing;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
class RepairableNearInfo : TraitInfo<RepairableNear> class RepairableNearInfo : TraitInfo<RepairableNear>, ITraitPrerequisite<HealthInfo>
{ {
[ActorReference] [ActorReference]
public readonly string[] Buildings = { "spen", "syrd" }; public readonly string[] Buildings = { "spen", "syrd" };
@@ -31,7 +31,7 @@ namespace OpenRA.Mods.RA
if (underCursor.Owner == self.Owner && if (underCursor.Owner == self.Owner &&
self.Info.Traits.Get<RepairableNearInfo>().Buildings.Contains( underCursor.Info.Name ) && self.Info.Traits.Get<RepairableNearInfo>().Buildings.Contains( underCursor.Info.Name ) &&
self.Health < self.GetMaxHP()) self.traits.Get<Health>().HPFraction < 1f)
return new Order("Enter", self, underCursor); return new Order("Enter", self, underCursor);
return null; return null;

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA
if (reservedFor == null) if (reservedFor == null)
return; /* nothing to do */ return; /* nothing to do */
if (reservedFor.IsDead) reservedFor = null; /* not likely to arrive now. */ if (reservedFor.IsDead()) reservedFor = null; /* not likely to arrive now. */
} }
public IDisposable Reserve(Actor forActor) public IDisposable Reserve(Actor forActor)

View File

@@ -12,7 +12,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
class SelfHealingInfo : TraitInfo<SelfHealing> class SelfHealingInfo : TraitInfo<SelfHealing>, ITraitPrerequisite<HealthInfo>
{ {
public readonly int Step = 5; public readonly int Step = 5;
public readonly int Ticks = 5; public readonly int Ticks = 5;
@@ -27,8 +27,7 @@ namespace OpenRA.Mods.RA
public void Tick(Actor self) public void Tick(Actor self)
{ {
var info = self.Info.Traits.Get<SelfHealingInfo>(); var info = self.Info.Traits.Get<SelfHealingInfo>();
if (self.traits.Get<Health>().HPFraction >= info.HealIfBelow)
if ((float)self.Health / self.GetMaxHP() >= info.HealIfBelow)
return; return;
if (--ticks <= 0) if (--ticks <= 0)

View File

@@ -49,7 +49,7 @@ namespace OpenRA.Mods.RA
public void Damaged(Actor self, AttackInfo e) public void Damaged(Actor self, AttackInfo e)
{ {
if (self.IsDead && Player.GetSiloFullness() > 0) if (self.IsDead() && Player.GetSiloFullness() > 0)
Player.TakeOre(Stored(self)); // Lose the stored ore Player.TakeOre(Stored(self)); // Lose the stored ore
} }

View File

@@ -33,6 +33,6 @@ namespace OpenRA.Mods.RA
} }
public IEnumerable<string> CrushClasses { get { return info.CrushClasses; } } public IEnumerable<string> CrushClasses { get { return info.CrushClasses; } }
public void OnCrush(Actor crusher) { self.InflictDamage(crusher, self.Health, null); } public void OnCrush(Actor crusher) { self.Kill(crusher); }
} }
} }

View File

@@ -145,6 +145,7 @@ V33:
BARB: BARB:
Inherits: ^Wall Inherits: ^Wall
Building: Building:
Health:
HP: 100 HP: 100
Armor: none Armor: none
Valued: Valued:
@@ -152,6 +153,7 @@ BARB:
WOOD: WOOD:
Inherits: ^Wall Inherits: ^Wall
Building: Building:
Health:
HP: 100 HP: 100
Armor: none Armor: none
Valued: Valued:

View File

@@ -58,6 +58,7 @@
^Infantry: ^Infantry:
Category: Infantry Category: Infantry
Unit: Unit:
Health:
Armor: none Armor: none
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
@@ -119,10 +120,11 @@
^CivBuilding: ^CivBuilding:
Inherits: ^Building Inherits: ^Building
Building: Health:
Repairable: false
HP: 400 HP: 400
Armor: wood Armor: wood
Building:
Repairable: false
Valued: Valued:
Description: Civilian Building Description: Civilian Building
@@ -186,6 +188,7 @@
Unit: Unit:
ROT: 0 ROT: 0
Speed: 0 Speed: 0
Health:
HP: 140 HP: 140
Armor: Heavy Armor: Heavy
Husk: Husk:
@@ -201,11 +204,12 @@
Targetable: Targetable:
TargetTypes: Ground, Water TargetTypes: Ground, Water
BelowUnits: BelowUnits:
Health:
HP: 1000
Building: Building:
DamagedSound: xplos.aud DamagedSound: xplos.aud
DestroyedSound: xplobig4.aud DestroyedSound: xplobig4.aud
Footprint: ______ ______ ______ ______ Footprint: ______ ______ ______ ______
Dimensions: 6,4 Dimensions: 6,4
HP: 1000
RadarColorFromTerrain: RadarColorFromTerrain:
Terrain: Road Terrain: Road

View File

@@ -10,8 +10,9 @@ E1:
Selectable: Selectable:
Bounds: 12,17,0,-6 Bounds: 12,17,0,-6
Unit: Unit:
HP: 50
Speed: 4 Speed: 4
Health:
HP: 50
AttackBase: AttackBase:
PrimaryWeapon: M16 PrimaryWeapon: M16
TakeCover: TakeCover:
@@ -29,8 +30,9 @@ E2:
Selectable: Selectable:
Bounds: 12,17,0,-6 Bounds: 12,17,0,-6
Unit: Unit:
HP: 50
Speed: 4 Speed: 4
Health:
HP: 50
AttackBase: AttackBase:
PrimaryWeapon: Grenade PrimaryWeapon: Grenade
PrimaryOffset: 0,0,0,-10 PrimaryOffset: 0,0,0,-10
@@ -49,8 +51,9 @@ E3:
Selectable: Selectable:
Bounds: 12,17,0,-6 Bounds: 12,17,0,-6
Unit: Unit:
HP: 45
Speed: 3 Speed: 3
Health:
HP: 45
AttackBase: AttackBase:
PrimaryWeapon: Rockets PrimaryWeapon: Rockets
PrimaryOffset: 0,0,0,-10 PrimaryOffset: 0,0,0,-10
@@ -70,8 +73,9 @@ E4:
Selectable: Selectable:
Bounds: 12,17,0,-6 Bounds: 12,17,0,-6
Unit: Unit:
HP: 90
Speed: 4 Speed: 4
Health:
HP: 90
AttackBase: AttackBase:
PrimaryWeapon: Flamethrower PrimaryWeapon: Flamethrower
PrimaryOffset: 0,0,0,-5 PrimaryOffset: 0,0,0,-5
@@ -94,8 +98,9 @@ E5:
Selectable: Selectable:
Bounds: 12,17,0,-6 Bounds: 12,17,0,-6
Unit: Unit:
HP: 90
Speed: 4 Speed: 4
Health:
HP: 90
AttackBase: AttackBase:
PrimaryWeapon: Chemspray PrimaryWeapon: Chemspray
PrimaryOffset: 0,0,0,-5 PrimaryOffset: 0,0,0,-5
@@ -118,8 +123,9 @@ E6:
Selectable: Selectable:
Bounds: 12,17,0,-6 Bounds: 12,17,0,-6
Unit: Unit:
HP: 25
Speed: 4 Speed: 4
Health:
HP: 25
TakeCover: TakeCover:
Passenger: Passenger:
ColorOfCargoPip: Yellow ColorOfCargoPip: Yellow
@@ -141,8 +147,9 @@ RMBO:
Bounds: 12,17,0,-6 Bounds: 12,17,0,-6
Voice: CommandoVoice Voice: CommandoVoice
Unit: Unit:
HP: 200
Speed: 5 Speed: 5
Health:
HP: 200
RevealsShroud: RevealsShroud:
Range: 5 Range: 5
AttackBase: AttackBase:
@@ -160,8 +167,9 @@ C1:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -176,8 +184,9 @@ C2:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -192,8 +201,9 @@ C3:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -208,8 +218,9 @@ C4:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -224,8 +235,9 @@ C5:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -240,8 +252,9 @@ C6:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -256,8 +269,9 @@ C7:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -271,8 +285,9 @@ C8:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -287,8 +302,9 @@ C9:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:
@@ -303,8 +319,9 @@ C10:
Cost: 70 Cost: 70
Description: Technician Description: Technician
Unit: Unit:
HP: 20
Speed: 4 Speed: 4
Health:
HP: 20
RevealsShroud: RevealsShroud:
Range: 2 Range: 2
AttackBase: AttackBase:

View File

@@ -8,6 +8,7 @@ FACT:
Footprint: xxx xxx xxx Footprint: xxx xxx xxx
Dimensions: 3,3 Dimensions: 3,3
Capturable: true Capturable: true
Health:
HP: 800 HP: 800
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -31,6 +32,7 @@ NUKE:
Footprint: x_ xx Footprint: x_ xx
Dimensions: 2,2 Dimensions: 2,2
Capturable: true Capturable: true
Health:
HP: 400 HP: 400
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -52,6 +54,7 @@ PROC.proxy:
Footprint: ___xx xxxxx xxx__ xxx__ Footprint: ___xx xxxxx xxx__ xxx__
Dimensions: 5,4 Dimensions: 5,4
Capturable: true Capturable: true
Health:
HP: 900 HP: 900
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -70,6 +73,7 @@ PROC:
Footprint: ___ xxx === Footprint: ___ xxx ===
Dimensions: 3,3 Dimensions: 3,3
Capturable: true Capturable: true
Health:
HP: 900 HP: 900
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -112,6 +116,7 @@ SILO:
Footprint: xx Footprint: xx
Dimensions: 2,1 Dimensions: 2,1
Capturable: true Capturable: true
Health:
HP: 300 HP: 300
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -140,6 +145,7 @@ PYLE:
Footprint: xx xx Footprint: xx xx
Dimensions: 2,2 Dimensions: 2,2
Capturable: true Capturable: true
Health:
HP: 800 HP: 800
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -165,6 +171,7 @@ HAND:
Footprint: __ xx xx Footprint: __ xx xx
Dimensions: 2,3 Dimensions: 2,3
Capturable: true Capturable: true
Health:
HP: 800 HP: 800
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -190,6 +197,7 @@ AFLD:
Footprint: xxxx xxxx Footprint: xxxx xxxx
Dimensions: 4,2 Dimensions: 4,2
Capturable: true Capturable: true
Health:
HP: 1000 HP: 1000
Armor: heavy Armor: heavy
RevealsShroud: RevealsShroud:
@@ -216,6 +224,7 @@ WEAP:
Footprint: ___ xxx === Footprint: ___ xxx ===
Dimensions: 3,3 Dimensions: 3,3
Capturable: true Capturable: true
Health:
HP: 400 HP: 400
Armor: light Armor: light
RevealsShroud: RevealsShroud:
@@ -243,6 +252,7 @@ HQ:
Footprint: __ xx Footprint: __ xx
Dimensions: 2,2 Dimensions: 2,2
Capturable: true Capturable: true
Health:
HP: 1000 HP: 1000
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -267,6 +277,7 @@ NUK2:
Footprint: xx xx Footprint: xx xx
Dimensions: 2,2 Dimensions: 2,2
Capturable: true Capturable: true
Health:
HP: 600 HP: 600
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -288,6 +299,7 @@ FIX:
Footprint: _x_ xxx _x_ Footprint: _x_ xxx _x_
Dimensions: 3,3 Dimensions: 3,3
Capturable: true Capturable: true
Health:
HP: 800 HP: 800
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -312,6 +324,7 @@ HPAD:
Footprint: xx xx Footprint: xx xx
Dimensions: 2,2 Dimensions: 2,2
Capturable: true Capturable: true
Health:
HP: 800 HP: 800
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -343,6 +356,7 @@ EYE:
Footprint: __ xx Footprint: __ xx
Dimensions: 2,2 Dimensions: 2,2
Capturable: true Capturable: true
Health:
HP: 1000 HP: 1000
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -369,6 +383,7 @@ TMPL:
Footprint: ___ xxx xxx Footprint: ___ xxx xxx
Dimensions: 3,3 Dimensions: 3,3
Capturable: false Capturable: false
Health:
HP: 2000 HP: 2000
Armor: light Armor: light
RevealsShroud: RevealsShroud:
@@ -393,6 +408,7 @@ OBLI:
Power: -150 Power: -150
Footprint: _ x Footprint: _ x
Dimensions: 1,2 Dimensions: 1,2
Health:
HP: 400 HP: 400
Armor: light Armor: light
RevealsShroud: RevealsShroud:
@@ -419,7 +435,7 @@ CYCL:
Cost: 25 Cost: 25
Description: Chain Link Barrier Description: Chain Link Barrier
LongDesc: Stops infantry and blocks enemy fire.\nCan be crushed by tanks. LongDesc: Stops infantry and blocks enemy fire.\nCan be crushed by tanks.
Building: Health:
HP: 300 HP: 300
Armor: none Armor: none
RenderBuildingWall: RenderBuildingWall:
@@ -436,7 +452,7 @@ SBAG:
Cost: 25 Cost: 25
Description: Sandbag Barrier Description: Sandbag Barrier
LongDesc: Stops infantry and blocks enemy fire.\nCan be crushed by tanks. LongDesc: Stops infantry and blocks enemy fire.\nCan be crushed by tanks.
Building: Health:
HP: 250 HP: 250
Armor: none Armor: none
@@ -451,7 +467,7 @@ BRIK:
Cost: 100 Cost: 100
Description: Concrete Barrier Description: Concrete Barrier
LongDesc: Stop units and blocks enemy fire. LongDesc: Stop units and blocks enemy fire.
Building: Health:
HP: 1000 HP: 1000
Armor: heavy Armor: heavy
RenderBuildingWall: RenderBuildingWall:
@@ -472,6 +488,7 @@ GUN:
LongDesc: Anti-Armor base defense.\n Strong vs Tanks\n Weak vs Infantry, Aircraft LongDesc: Anti-Armor base defense.\n Strong vs Tanks\n Weak vs Infantry, Aircraft
Building: Building:
Power: -20 Power: -20
Health:
HP: 400 HP: 400
Armor: heavy Armor: heavy
RevealsShroud: RevealsShroud:
@@ -501,6 +518,7 @@ SAM:
Power: -20 Power: -20
Footprint: xx Footprint: xx
Dimensions: 2,1 Dimensions: 2,1
Health:
HP: 400 HP: 400
Armor: heavy Armor: heavy
RevealsShroud: RevealsShroud:
@@ -528,6 +546,7 @@ GTWR:
LongDesc: Basic defensive structure.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft LongDesc: Basic defensive structure.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft
Building: Building:
Power: -10 Power: -10
Health:
HP: 400 HP: 400
Armor: wood Armor: wood
RevealsShroud: RevealsShroud:
@@ -552,6 +571,7 @@ ATWR:
LongDesc: Anti-armor defensive structure.\n Strong vs Light Vehicles, Tanks\n Weak vs Infantry LongDesc: Anti-armor defensive structure.\n Strong vs Light Vehicles, Tanks\n Weak vs Infantry
Building: Building:
Power: -20 Power: -20
Health:
HP: 600 HP: 600
Armor: light Armor: light
RevealsShroud: RevealsShroud:

View File

@@ -158,6 +158,5 @@ CRATE:
SelectionShares: 5 SelectionShares: 5
Effect: hide-map Effect: hide-map
Unit: Unit:
HP: 1
RenderUnit: RenderUnit:
BelowUnits: BelowUnits:

View File

@@ -1,5 +1,5 @@
SPLIT2: SPLIT2:
Inherits: ^Building Inherits: ^Tree
RenderBuilding: RenderBuilding:
Palette: terrain Palette: terrain
SeedsResource: SeedsResource:
@@ -11,7 +11,7 @@ SPLIT2:
Terrain: Ore Terrain: Ore
SPLIT3: SPLIT3:
Inherits: ^Building Inherits: ^Tree
RenderBuilding: RenderBuilding:
Palette: terrain Palette: terrain
SeedsResource: SeedsResource:

View File

@@ -11,9 +11,10 @@ MCV:
Selectable: Selectable:
Priority: 3 Priority: 3
Unit: Unit:
Speed: 6
Health:
HP: 600 HP: 600
Armor: light Armor: light
Speed: 6
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
TransformsOnDeploy: TransformsOnDeploy:
@@ -43,9 +44,10 @@ HARV:
PipColor: Green PipColor: Green
Capacity: 28 Capacity: 28
Unit: Unit:
Speed: 6
Health:
HP: 600 HP: 600
Armor: light Armor: light
Speed: 6
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
RenderUnit: RenderUnit:
@@ -61,10 +63,11 @@ APC:
Description: Armored Personnel Carrier Description: Armored Personnel Carrier
LongDesc: Tough infantry transport.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft LongDesc: Tough infantry transport.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft
Unit: Unit:
HP: 200
Armor: heavy
ROT: 5 ROT: 5
Speed: 15 Speed: 15
Health:
HP: 200
Armor: heavy
RevealsShroud: RevealsShroud:
Range: 5 Range: 5
AttackBase: AttackBase:
@@ -90,10 +93,11 @@ ARTY:
Description: Artillery Description: Artillery
LongDesc: Long-range artillery.\n Strong vs Infantry, Buildings\n Weak vs Tanks, Aircraft LongDesc: Long-range artillery.\n Strong vs Infantry, Buildings\n Weak vs Tanks, Aircraft
Unit: Unit:
HP: 75
Armor: light
ROT: 2 ROT: 2
Speed: 6 Speed: 6
Health:
HP: 75
Armor: light
RevealsShroud: RevealsShroud:
Range: 6 Range: 6
AttackBase: AttackBase:
@@ -113,10 +117,11 @@ FTNK:
Description: Flame Tank Description: Flame Tank
LongDesc: Heavily armored flame-throwing vehicle.\n Strong vs Infantry, Buildings\n Weak vs Aircraft LongDesc: Heavily armored flame-throwing vehicle.\n Strong vs Infantry, Buildings\n Weak vs Aircraft
Unit: Unit:
HP: 300
Armor: heavy
ROT: 5 ROT: 5
Speed: 9 Speed: 9
Health:
HP: 300
Armor: heavy
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
AttackBase: AttackBase:
@@ -137,10 +142,11 @@ BGGY:
Description: Nod Buggy Description: Nod Buggy
LongDesc: Fast scout & anti-infantry vehicle.\n Strong vs Infantry\n Weak vs Tanks, Aircraft LongDesc: Fast scout & anti-infantry vehicle.\n Strong vs Infantry\n Weak vs Tanks, Aircraft
Unit: Unit:
HP: 140
Armor: light
ROT: 10 ROT: 10
Speed: 18 Speed: 18
Health:
HP: 140
Armor: light
RevealsShroud: RevealsShroud:
Range: 5 Range: 5
Turreted: Turreted:
@@ -163,10 +169,11 @@ BIKE:
Description: Recon Bike Description: Recon Bike
LongDesc: Fast scout vehicle, armed with \nrockets.\n Strong vs Vehicles, Aircraft\n Weak vs Infantry LongDesc: Fast scout vehicle, armed with \nrockets.\n Strong vs Vehicles, Aircraft\n Weak vs Infantry
Unit: Unit:
HP: 160
Armor: none
ROT: 10 ROT: 10
Speed: 20 Speed: 20
Health:
HP: 160
Armor: none
RevealsShroud: RevealsShroud:
Range: 7 Range: 7
AttackBase: AttackBase:
@@ -190,10 +197,11 @@ JEEP:
Description: Hum-Vee Description: Hum-Vee
LongDesc: Fast scout & anti-infantry vehicle.\n Strong vs Infantry\n Weak vs Tanks, Aircraft LongDesc: Fast scout & anti-infantry vehicle.\n Strong vs Infantry\n Weak vs Tanks, Aircraft
Unit: Unit:
HP: 150
Armor: light
ROT: 10 ROT: 10
Speed: 15 Speed: 15
Health:
HP: 150
Armor: light
RevealsShroud: RevealsShroud:
Range: 7 Range: 7
Turreted: Turreted:
@@ -216,9 +224,10 @@ LTNK:
Description: Light Tank Description: Light Tank
LongDesc: Light Tank, good for scouting.\n Strong vs Light Vehicles\n Weak vs Tanks, Aircraft LongDesc: Light Tank, good for scouting.\n Strong vs Light Vehicles\n Weak vs Tanks, Aircraft
Unit: Unit:
Speed: 9
Health:
HP: 300 HP: 300
Armor: Heavy Armor: Heavy
Speed: 9
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
Turreted: Turreted:
@@ -245,9 +254,10 @@ MTNK:
Description: Medium Tank Description: Medium Tank
LongDesc: General-Purpose GDI Tank.\n Strong vs Tanks, Light Vehicles\n Weak vs Infantry, Aircraft LongDesc: General-Purpose GDI Tank.\n Strong vs Tanks, Light Vehicles\n Weak vs Infantry, Aircraft
Unit: Unit:
Speed: 9
Health:
HP: 400 HP: 400
Armor: heavy Armor: heavy
Speed: 9
RevealsShroud: RevealsShroud:
Range: 5 Range: 5
Turreted: Turreted:
@@ -274,9 +284,10 @@ HTNK:
Description: Mammoth Tank Description: Mammoth Tank
LongDesc: Heavily armored GDI Tank.\n Strong vs Everything LongDesc: Heavily armored GDI Tank.\n Strong vs Everything
Unit: Unit:
Speed: 3
Health:
HP: 600 HP: 600
Armor: heavy Armor: heavy
Speed: 3
RevealsShroud: RevealsShroud:
Range: 6 Range: 6
Turreted: Turreted:
@@ -309,9 +320,10 @@ MSAM:
Description: Rocket Launcher Description: Rocket Launcher
LongDesc: Long range artillery.\n Strong vs Infantry, Buildings\n Weak vs Tanks, Aircraft LongDesc: Long range artillery.\n Strong vs Infantry, Buildings\n Weak vs Tanks, Aircraft
Unit: Unit:
Speed: 6
Health:
HP: 120 HP: 120
Armor: light Armor: light
Speed: 6
RevealsShroud: RevealsShroud:
Range: 6 Range: 6
Turreted: Turreted:
@@ -337,9 +349,10 @@ MLRS:
Description: SSM Launcher Description: SSM Launcher
LongDesc: Long range artillery.\n Strong vs Infantry, Aircraft\n Weak vs Tanks, Aircraft LongDesc: Long range artillery.\n Strong vs Infantry, Aircraft\n Weak vs Tanks, Aircraft
Unit: Unit:
Speed: 6
Health:
HP: 120 HP: 120
Armor: light Armor: light
Speed: 6
RevealsShroud: RevealsShroud:
Range: 10 Range: 10
Turreted: Turreted:
@@ -361,9 +374,10 @@ STNK:
Description: Stealth Tank Description: Stealth Tank
LongDesc: Missile tank that can bend light around \nitself to become invisible\n Strong vs Infantry, Aircraft\n Weak vs Tanks LongDesc: Missile tank that can bend light around \nitself to become invisible\n Strong vs Infantry, Aircraft\n Weak vs Tanks
Unit: Unit:
Speed: 15
Health:
HP: 110 HP: 110
Armor: light Armor: light
Speed: 15
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
Cloak: Cloak:
@@ -388,11 +402,12 @@ TRAN:
Description: Chinook Transport Description: Chinook Transport
LongDesc: Fast Infantry Transport Helicopter.\n Unarmed LongDesc: Fast Infantry Transport Helicopter.\n Unarmed
Unit: Unit:
HP: 90
Armor: light
InitialFacing: 20 InitialFacing: 20
ROT: 5 ROT: 5
Speed: 15 Speed: 15
Health:
HP: 90
Armor: light
RevealsShroud: RevealsShroud:
Range: 8 Range: 8
RenderUnitRotor: RenderUnitRotor:
@@ -415,11 +430,12 @@ HELI:
Description: Apache Longbow Description: Apache Longbow
LongDesc: Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry LongDesc: Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry
Unit: Unit:
HP: 125
Armor: heavy
InitialFacing: 20 InitialFacing: 20
ROT: 4 ROT: 4
Speed: 20 Speed: 20
Health:
HP: 125
Armor: heavy
RevealsShroud: RevealsShroud:
Range: 8 Range: 8
AttackBase: AttackBase:
@@ -443,11 +459,12 @@ ORCA:
Description: Orca Description: Orca
LongDesc: Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry LongDesc: Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry
Unit: Unit:
HP: 125
Armor: heavy
InitialFacing: 20 InitialFacing: 20
ROT: 4 ROT: 4
Speed: 20 Speed: 20
Health:
HP: 125
Armor: heavy
RevealsShroud: RevealsShroud:
Range: 8 Range: 8
AttackBase: AttackBase:
@@ -463,10 +480,11 @@ C17:
LZRange: 1 LZRange: 1
Inherits: ^Plane Inherits: ^Plane
Unit: Unit:
HP: 25
Armor: light
ROT: 5 ROT: 5
Speed: 25 Speed: 25
Health:
HP: 25
Armor: light
Plane: Plane:
RenderUnit: RenderUnit:
WithShadow: WithShadow:
@@ -480,10 +498,11 @@ C17:
A10: A10:
Inherits: ^Plane Inherits: ^Plane
Unit: Unit:
HP: 60
Armor: light
ROT: 4 ROT: 4
Speed: 25 Speed: 25
Health:
HP: 60
Armor: light
Plane: Plane:
RenderUnit: RenderUnit:
WithShadow: WithShadow: