Added a Character.AddDamage override method that does damage to a specific limb instead of the limb closest to the source of the damage. Projectiles and melee weapons now always do damage to the limb they hit, even if the center point of some other limb happens to be closer.

Also refactored the IDamageable interface to make more sense; now the attacker has to be a character instead of an IDamageable and damageable classes don't need to have an AiTarget.

Closes #69
This commit is contained in:
Joonas Rikkonen
2017-12-05 18:03:00 +02:00
parent 258047e638
commit 63493d2b9d
11 changed files with 107 additions and 55 deletions

View File

@@ -53,7 +53,7 @@ namespace Barotrauma
Enabled = true;
}
public virtual void OnAttacked(IDamageable attacker, float amount) { }
public virtual void OnAttacked(Character attacker, float amount) { }
public virtual void SelectTarget(AITarget target) { }

View File

@@ -445,7 +445,7 @@ namespace Barotrauma
targetEntity = closestBody.UserData as IDamageable;
}
public override void OnAttacked(IDamageable attacker, float amount)
public override void OnAttacked(Character attacker, float amount)
{
updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f);
coolDownTimer *= 0.1f;

View File

@@ -137,7 +137,7 @@ namespace Barotrauma
}
}
public override void OnAttacked(IDamageable attacker, float amount)
public override void OnAttacked(Character attacker, float amount)
{
if (amount <= 0.0f) return;

View File

@@ -79,16 +79,16 @@ namespace Barotrauma
}
partial void SoundUpdate(float deltaTime);
public override void AddDamage(CauseOfDeath causeOfDeath, float amount, IDamageable attacker)
public override void AddDamage(CauseOfDeath causeOfDeath, float amount, Character attacker)
{
base.AddDamage(causeOfDeath, amount, attacker);
if (attacker != null) aiController.OnAttacked(attacker, amount);
}
public override AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
public override AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false, Limb limb = null)
{
AttackResult result = base.AddDamage(attacker, worldPosition, attack, deltaTime, playSound);
AttackResult result = base.ApplyAttack(attacker, worldPosition, attack, deltaTime, playSound, limb);
aiController.OnAttacked(attacker, result.Damage + result.Bleeding);

View File

@@ -156,7 +156,7 @@ namespace Barotrauma
}
partial void InitProjSpecific(XElement element);
public AttackResult DoDamage(IDamageable attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true)
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true)
{
if (onlyHumans)
{
@@ -167,19 +167,14 @@ namespace Barotrauma
DamageParticles(deltaTime, worldPosition);
var attackResult = target.AddDamage(attacker, worldPosition, this, deltaTime, playSound);
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
if (statusEffects == null)
{
return attackResult;
}
if (statusEffects == null) return attackResult;
foreach (StatusEffect effect in statusEffects)
{
if (effect.Targets.HasFlag(StatusEffect.TargetType.This) && attacker is Character)
if (effect.Targets.HasFlag(StatusEffect.TargetType.This))
{
effect.Apply(effectType, deltaTime, (Character)attacker, (Character)attacker);
effect.Apply(effectType, deltaTime, attacker, attacker);
}
if (effect.Targets.HasFlag(StatusEffect.TargetType.Character) && target is Character)
{
@@ -189,6 +184,37 @@ namespace Barotrauma
return attackResult;
}
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true)
{
if (targetLimb == null) return new AttackResult();
if (onlyHumans)
{
if (targetLimb.character != null && targetLimb.character.ConfigPath != Character.HumanConfigFile) return new AttackResult();
}
DamageParticles(deltaTime, worldPosition);
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb);
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
if (statusEffects == null) return attackResult;
foreach (StatusEffect effect in statusEffects)
{
if (effect.Targets.HasFlag(StatusEffect.TargetType.This))
{
effect.Apply(effectType, deltaTime, attacker, attacker);
}
if (effect.Targets.HasFlag(StatusEffect.TargetType.Character))
{
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb.character);
}
}
return attackResult;
}
partial void DamageParticles(float deltaTime, Vector2 worldPosition);
}
}

View File

@@ -1523,19 +1523,19 @@ namespace Barotrauma
speechBubbleTimer = Math.Max(speechBubbleTimer, duration);
speechBubbleColor = color;
}
private void AdjustKarma(IDamageable attacker,float amount)
private void AdjustKarma(Character attacker, float amount)
{
if (GameMain.Server != null)
{
if (attacker is Character)
{
Character attackerCharacter = attacker as Character;
Barotrauma.Networking.Client attackerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == attackerCharacter);
Client attackerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == attackerCharacter);
if (attackerClient != null)
{
Barotrauma.Networking.Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
if (targetClient != null || this == Character.Controlled)
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
if (targetClient != null || this == Controlled)
{
if (attackerCharacter.TeamID == TeamID)
{
@@ -1548,9 +1548,12 @@ namespace Barotrauma
}
}
public virtual void AddDamage(CauseOfDeath causeOfDeath, float amount, IDamageable attacker)
/// <summary>
/// Directly reduce the health of the character without any additional effects (particles, sounds, status effects...)
/// </summary>
public virtual void AddDamage(CauseOfDeath causeOfDeath, float amount, Character attacker)
{
Health = health-amount;
Health = health - amount;
if (amount > 0.0f)
{
lastAttackCauseOfDeath = causeOfDeath;
@@ -1562,10 +1565,21 @@ namespace Barotrauma
}
partial void DamageHUD(float amount);
public virtual AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
{
Limb limbHit = null;
var attackResult = AddDamage(worldPosition, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound, attack.TargetForce, out limbHit, attacker);
return ApplyAttack(attacker, worldPosition, attack, deltaTime, playSound, null);
}
/// <summary>
/// Apply the specified attack to this character. If the targetLimb is not specified, the limb closest to worldPosition will receive the damage.
/// </summary>
public virtual AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false, Limb targetLimb = null)
{
Limb limbHit = targetLimb;
var attackResult = targetLimb == null ?
AddDamage(worldPosition, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound, attack.TargetForce, out limbHit, attacker) :
DamageLimb(worldPosition, targetLimb, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound, attack.TargetForce, attacker);
if (limbHit == null) return new AttackResult();
var attackingCharacter = attacker as Character;
@@ -1612,14 +1626,12 @@ namespace Barotrauma
return AddDamage(worldPosition, damageType, amount, bleedingAmount, stun, playSound, attackForce, out temp);
}
public AttackResult AddDamage(Vector2 worldPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound, float attackForce, out Limb hitLimb,IDamageable attacker=null)
public AttackResult AddDamage(Vector2 worldPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound, float attackForce, out Limb hitLimb, Character attacker = null)
{
hitLimb = null;
if (Removed) return new AttackResult();
SetStun(stun);
float closestDistance = 0.0f;
foreach (Limb limb in AnimController.Limbs)
{
@@ -1630,13 +1642,22 @@ namespace Barotrauma
closestDistance = distance;
}
}
return DamageLimb(worldPosition, hitLimb, damageType, amount, bleedingAmount, stun, playSound, attackForce, attacker);
}
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound, float attackForce, Character attacker = null)
{
if (Removed) return new AttackResult();
SetStun(stun);
if (Math.Abs(attackForce) > 0.0f)
{
Vector2 diff = hitLimb.WorldPosition - worldPosition;
if (diff == Vector2.Zero) diff = Rand.Vector(1.0f);
hitLimb.body.ApplyForce(Vector2.Normalize(diff) * attackForce, hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff));
}
}
AttackResult attackResult = hitLimb.AddDamage(worldPosition, damageType, amount, bleedingAmount, playSound);

View File

@@ -213,28 +213,39 @@ namespace Barotrauma.Items.Components
private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
{
Character target = null;
Character targetCharacter = null;
Limb targetLimb = null;
Limb limb = f2.Body.UserData as Limb;
if (limb != null)
if (f2.Body.UserData is Limb)
{
if (limb.character == picker) return false;
target = limb.character;
targetLimb = (Limb)f2.Body.UserData;
if (targetLimb.IsSevered || targetLimb.character == null) return false;
targetCharacter = targetLimb.character;
}
else if (f2.Body.UserData is Character)
{
targetCharacter = (Character)f2.Body.UserData;
}
else
{
return false;
}
if (target == null)
if (targetCharacter == picker) return false;
if (attack != null)
{
target = f2.Body.UserData as Character;
if (targetLimb == null)
{
attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
}
else
{
attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
}
}
if (target == null) return false;
if (attack != null) attack.DoDamage(user, target, item.WorldPosition, 1.0f);
RestoreCollision();
hitting = false;
@@ -242,18 +253,18 @@ namespace Barotrauma.Items.Components
if (GameMain.Server != null)
{
GameMain.Server.CreateEntityEvent(item, new object[] { Networking.NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, target.ID });
GameMain.Server.CreateEntityEvent(item, new object[] { Networking.NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, targetCharacter.ID });
string logStr = picker?.Name + " used " + item.Name;
if (item.ContainedItems != null && item.ContainedItems.Length > 0)
{
logStr += "(" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
}
logStr += " on " + target + ".";
logStr += " on " + targetCharacter + ".";
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
}
ApplyStatusEffects(ActionType.OnUse, 1.0f, limb.character);
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetLimb.character);
return true;
}

View File

@@ -245,7 +245,6 @@ namespace Barotrauma.Items.Components
item.Move(-submarine.Position);
item.Submarine = submarine;
item.body.Submarine = submarine;
//item.FindHull();
return true;
}
@@ -253,7 +252,7 @@ namespace Barotrauma.Items.Components
Structure structure;
if ((limb = (target.Body.UserData as Limb)) != null)
{
attackResult = attack.DoDamage(User, limb.character, item.WorldPosition, 1.0f);
attackResult = attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f);
}
else if ((structure = (target.Body.UserData as Structure)) != null)
{

View File

@@ -762,7 +762,7 @@ namespace Barotrauma
}
public AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
{
if (prefab.Indestructible) return new AttackResult();

View File

@@ -18,12 +18,7 @@ namespace Barotrauma
{
get;
}
AITarget AiTarget
{
get;
}
AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound=true);
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound=true);
}
}

View File

@@ -643,7 +643,7 @@ namespace Barotrauma
}
}
public AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
{
if (Submarine != null && Submarine.GodMode) return new AttackResult(0.0f, 0.0f);
if (!prefab.Body || prefab.Platform) return new AttackResult(0.0f, 0.0f);