Both writing to perf.log frequently as well as GetTimestamp aren't free and hurt performance particularly on slower systems (which can have notably higher output to perf.log, further amplifying the problem). Therefore we make simulation perf logging opt-in. Additionally, logging of the current tick and tick type (local/net) is removed from debug.log, and some remnant debug logging for kills and pips is removed to keep performance-sensitive logging limited to perf.log.
263 lines
7.2 KiB
C#
263 lines
7.2 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenRA.Primitives;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common.Traits
|
|
{
|
|
public class HealthInfo : TraitInfo, IHealthInfo, IRulesetLoaded, IEditorActorOptions
|
|
{
|
|
[Desc("HitPoints")]
|
|
public readonly int HP = 0;
|
|
|
|
[Desc("Trigger interfaces such as AnnounceOnKill?")]
|
|
public readonly bool NotifyAppliedDamage = true;
|
|
|
|
[Desc("Display order for the health slider in the map editor")]
|
|
public readonly int EditorHealthDisplayOrder = 2;
|
|
|
|
public override object Create(ActorInitializer init) { return new Health(init, this); }
|
|
|
|
public void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
|
{
|
|
if (!ai.HasTraitInfo<HitShapeInfo>())
|
|
throw new YamlException("Actors with Health need at least one HitShape trait!");
|
|
}
|
|
|
|
int IHealthInfo.MaxHP => HP;
|
|
|
|
IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, World world)
|
|
{
|
|
yield return new EditorActorSlider("Health", EditorHealthDisplayOrder, 0, 100, 5,
|
|
actor =>
|
|
{
|
|
var init = actor.GetInitOrDefault<HealthInit>();
|
|
return init?.Value ?? 100;
|
|
},
|
|
(actor, value) => actor.ReplaceInit(new HealthInit((int)value)));
|
|
}
|
|
}
|
|
|
|
public class Health : IHealth, ISync, ITick, INotifyCreated, INotifyOwnerChanged
|
|
{
|
|
public readonly HealthInfo Info;
|
|
INotifyDamageStateChanged[] notifyDamageStateChanged;
|
|
INotifyDamage[] notifyDamage;
|
|
INotifyDamage[] notifyDamagePlayer;
|
|
IDamageModifier[] damageModifiers;
|
|
IDamageModifier[] damageModifiersPlayer;
|
|
INotifyKilled[] notifyKilled;
|
|
INotifyKilled[] notifyKilledPlayer;
|
|
|
|
[Sync]
|
|
int hp;
|
|
|
|
public int DisplayHP { get; private set; }
|
|
|
|
public Health(ActorInitializer init, HealthInfo info)
|
|
{
|
|
Info = info;
|
|
MaxHP = hp = info.HP > 0 ? info.HP : 1;
|
|
|
|
// Cast to long to avoid overflow when multiplying by the health
|
|
var healthInit = init.GetOrDefault<HealthInit>();
|
|
if (healthInit != null)
|
|
hp = (int)(healthInit.Value * (long)MaxHP / 100);
|
|
|
|
DisplayHP = hp;
|
|
}
|
|
|
|
public int HP => hp;
|
|
public int MaxHP { get; private set; }
|
|
|
|
public bool IsDead => hp <= 0;
|
|
public bool RemoveOnDeath = true;
|
|
|
|
public DamageState DamageState
|
|
{
|
|
get
|
|
{
|
|
if (hp == MaxHP)
|
|
return DamageState.Undamaged;
|
|
|
|
if (hp <= 0)
|
|
return DamageState.Dead;
|
|
|
|
if (hp * 100L < MaxHP * 25L)
|
|
return DamageState.Critical;
|
|
|
|
if (hp * 100L < MaxHP * 50L)
|
|
return DamageState.Heavy;
|
|
|
|
if (hp * 100L < MaxHP * 75L)
|
|
return DamageState.Medium;
|
|
|
|
return DamageState.Light;
|
|
}
|
|
}
|
|
|
|
void INotifyCreated.Created(Actor self)
|
|
{
|
|
notifyDamageStateChanged = self.TraitsImplementing<INotifyDamageStateChanged>().ToArray();
|
|
notifyDamage = self.TraitsImplementing<INotifyDamage>().ToArray();
|
|
notifyDamagePlayer = self.Owner.PlayerActor.TraitsImplementing<INotifyDamage>().ToArray();
|
|
damageModifiers = self.TraitsImplementing<IDamageModifier>().ToArray();
|
|
damageModifiersPlayer = self.Owner.PlayerActor.TraitsImplementing<IDamageModifier>().ToArray();
|
|
notifyKilled = self.TraitsImplementing<INotifyKilled>().ToArray();
|
|
notifyKilledPlayer = self.Owner.PlayerActor.TraitsImplementing<INotifyKilled>().ToArray();
|
|
}
|
|
|
|
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
|
{
|
|
notifyDamagePlayer = newOwner.PlayerActor.TraitsImplementing<INotifyDamage>().ToArray();
|
|
damageModifiersPlayer = newOwner.PlayerActor.TraitsImplementing<IDamageModifier>().ToArray();
|
|
notifyKilledPlayer = newOwner.PlayerActor.TraitsImplementing<INotifyKilled>().ToArray();
|
|
}
|
|
|
|
public void Resurrect(Actor self, Actor repairer)
|
|
{
|
|
if (!IsDead)
|
|
return;
|
|
|
|
hp = MaxHP;
|
|
|
|
var ai = new AttackInfo
|
|
{
|
|
Attacker = repairer,
|
|
Damage = new Damage(-MaxHP),
|
|
DamageState = DamageState,
|
|
PreviousDamageState = DamageState.Dead,
|
|
};
|
|
|
|
foreach (var nd in notifyDamage)
|
|
nd.Damaged(self, ai);
|
|
foreach (var nd in notifyDamagePlayer)
|
|
nd.Damaged(self, ai);
|
|
|
|
foreach (var nd in notifyDamageStateChanged)
|
|
nd.DamageStateChanged(self, ai);
|
|
|
|
if (Info.NotifyAppliedDamage && repairer != null && repairer.IsInWorld && !repairer.IsDead)
|
|
{
|
|
foreach (var nd in repairer.TraitsImplementing<INotifyAppliedDamage>())
|
|
nd.AppliedDamage(repairer, self, ai);
|
|
foreach (var nd in repairer.Owner.PlayerActor.TraitsImplementing<INotifyAppliedDamage>())
|
|
nd.AppliedDamage(repairer, self, ai);
|
|
}
|
|
}
|
|
|
|
public void InflictDamage(Actor self, Actor attacker, Damage damage, bool ignoreModifiers)
|
|
{
|
|
// Overkill! Don't count extra hits as more kills!
|
|
if (IsDead)
|
|
return;
|
|
|
|
var oldState = DamageState;
|
|
|
|
// Apply any damage modifiers
|
|
if (!ignoreModifiers && damage.Value > 0)
|
|
{
|
|
// PERF: Util.ApplyPercentageModifiers has been manually inlined to
|
|
// avoid unnecessary loop enumerations and allocations
|
|
var appliedDamage = (decimal)damage.Value;
|
|
foreach (var dm in damageModifiers)
|
|
{
|
|
var modifier = dm.GetDamageModifier(attacker, damage);
|
|
if (modifier != 100)
|
|
appliedDamage *= modifier / 100m;
|
|
}
|
|
|
|
foreach (var dm in damageModifiersPlayer)
|
|
{
|
|
var modifier = dm.GetDamageModifier(attacker, damage);
|
|
if (modifier != 100)
|
|
appliedDamage *= modifier / 100m;
|
|
}
|
|
|
|
damage = new Damage((int)appliedDamage, damage.DamageTypes);
|
|
}
|
|
|
|
hp = (hp - damage.Value).Clamp(0, MaxHP);
|
|
|
|
var ai = new AttackInfo
|
|
{
|
|
Attacker = attacker,
|
|
Damage = damage,
|
|
DamageState = DamageState,
|
|
PreviousDamageState = oldState,
|
|
};
|
|
|
|
foreach (var nd in notifyDamage)
|
|
nd.Damaged(self, ai);
|
|
foreach (var nd in notifyDamagePlayer)
|
|
nd.Damaged(self, ai);
|
|
|
|
if (DamageState != oldState)
|
|
foreach (var nd in notifyDamageStateChanged)
|
|
nd.DamageStateChanged(self, ai);
|
|
|
|
if (Info.NotifyAppliedDamage && attacker != null && attacker.IsInWorld && !attacker.IsDead)
|
|
{
|
|
foreach (var nd in attacker.TraitsImplementing<INotifyAppliedDamage>())
|
|
nd.AppliedDamage(attacker, self, ai);
|
|
foreach (var nd in attacker.Owner.PlayerActor.TraitsImplementing<INotifyAppliedDamage>())
|
|
nd.AppliedDamage(attacker, self, ai);
|
|
}
|
|
|
|
if (hp == 0)
|
|
{
|
|
foreach (var nd in notifyKilled)
|
|
nd.Killed(self, ai);
|
|
foreach (var nd in notifyKilledPlayer)
|
|
nd.Killed(self, ai);
|
|
|
|
if (RemoveOnDeath)
|
|
self.Dispose();
|
|
}
|
|
}
|
|
|
|
public void Kill(Actor self, Actor attacker, BitSet<DamageType> damageTypes)
|
|
{
|
|
InflictDamage(self, attacker, new Damage(MaxHP, damageTypes), true);
|
|
}
|
|
|
|
void ITick.Tick(Actor self)
|
|
{
|
|
if (hp >= DisplayHP)
|
|
DisplayHP = hp;
|
|
else
|
|
DisplayHP = (2 * DisplayHP + hp) / 3;
|
|
}
|
|
}
|
|
|
|
public class HealthInit : ValueActorInit<int>, ISingleInstanceInit
|
|
{
|
|
readonly bool allowZero;
|
|
|
|
public HealthInit(int value, bool allowZero = false)
|
|
: base(value) { this.allowZero = allowZero; }
|
|
|
|
public override int Value
|
|
{
|
|
get
|
|
{
|
|
if (value < 0 || (value == 0 && !allowZero))
|
|
return 1;
|
|
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
}
|