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; } }
[Sync]
public Player Owner;
[Sync]
public int Health;
IActivity currentActivity;
public Group Group;
@@ -51,8 +49,6 @@ namespace OpenRA
throw new NotImplementedException("No rules definition for unit {0}".F(name.ToLowerInvariant()));
Info = Rules.Info[name.ToLowerInvariant()];
Health = this.GetMaxHP();
foreach (var trait in Info.TraitsInConstructOrder())
traits.Add(trait.Create(init));
}
@@ -147,66 +143,7 @@ namespace OpenRA
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 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 )
{

View File

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

View File

@@ -34,13 +34,6 @@ namespace OpenRA
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 )
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 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

View File

@@ -229,6 +229,8 @@
<Compile Include="Traits\Player\DeveloperMode.cs" />
<Compile Include="Traits\RevealsShroud.cs" />
<Compile Include="Traits\Targetable.cs" />
<Compile Include="Traits\Health.cs" />
<Compile Include="Traits\RepairableBuilding.cs" />
</ItemGroup>
<ItemGroup>
<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 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;
var health = self.traits.GetOrDefault<Health>();
var refundFraction = self.World.Defaults.RefundPercent * (health == null ? 1f : health.HPFraction);
self.Owner.PlayerActor.traits.Get<PlayerResources>().GiveCash((int)refund);
self.Health = 0;
self.Owner.PlayerActor.traits.Get<PlayerResources>().GiveCash((int)(refundFraction * cost));
self.Kill(self);
foreach (var ns in self.traits.WithInterface<INotifySold>())
ns.Sold(self);
self.World.AddFrameEndTask( _ => self.World.Remove( self ) );

View File

@@ -18,13 +18,7 @@ using OpenRA.Traits.Activities;
namespace OpenRA.Traits
{
public class OwnedActorInfo
{
public readonly int HP = 0;
public readonly ArmorType Armor = ArmorType.none;
}
public class BuildingInfo : OwnedActorInfo, ITraitInfo
public class BuildingInfo : ITraitInfo
{
public readonly int Power = 0;
public readonly bool BaseNormal = true;
@@ -44,14 +38,12 @@ namespace OpenRA.Traits
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;
public readonly BuildingInfo Info;
[Sync]
readonly int2 topLeft;
[Sync]
bool isRepairing = false;
public bool Disabled
{
@@ -73,11 +65,13 @@ namespace OpenRA.Traits
.WithInterface<IPowerModifier>()
.Select(t => t.GetPowerModifier())
.Product();
var maxHP = self.Info.Traits.Get<BuildingInfo>().HP;
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
return (int)(modifier * Info.Power);
}
@@ -98,44 +92,6 @@ namespace OpenRA.Traits
self.CancelActivity();
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)

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)
{
var health = self.traits.GetOrDefault<Health>();
if (health == null)
return;
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);
var healthAmount = (float)self.Health / self.Info.Traits.Get<OwnedActorInfo>().HP;
var healthColor = (healthAmount < self.World.Defaults.ConditionRed) ? Color.Red
: (healthAmount < self.World.Defaults.ConditionYellow) ? Color.Yellow
: Color.LimeGreen;
var healthColor = health.HealthColor;
var healthColor2 = Color.FromArgb(
255,
healthColor.R / 2,
healthColor.G / 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, -2), Xy + new float2(0, -2), c, c);

View File

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

View File

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

View File

@@ -81,7 +81,7 @@ namespace OpenRA.Traits
public void Update(Actor self, IOccupyAir 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 RepairRate = 0.016f;
public readonly int RepairStep = 7;
/* Audo/Visual Map Controls */
public readonly float ConditionRed = 0.25f;
public readonly float ConditionYellow = 0.5f;
}
public class GlobalDefaults {}

View File

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