/// <summary> /// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated. /// </summary> public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1) { attackResult = default(AttackResult); float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos)); bool wasRunning = attack.IsRunning; attack.UpdateAttackTimer(deltaTime); bool wasHit = false; Body structureBody = null; if (damageTarget != null) { switch (attack.HitDetectionType) { case HitDetection.Distance: if (dist < attack.DamageRange) { List <Body> ignoredBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList(); ignoredBodies.Add(character.AnimController.Collider.FarseerBody); structureBody = Submarine.PickBody( SimPosition, attackSimPos, ignoredBodies, Physics.CollisionWall); if (damageTarget is Item) { // If the attack is aimed to an item and hits an item, it's successful. // Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example. wasHit = true; } else if (damageTarget is Structure wall && structureBody != null && (structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine))) { // If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful wasHit = true; } else { // If there is nothing between, the hit is successful wasHit = structureBody == null; } } break;
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker) { if (attack.Range <= 0.0f) { return; } //long range for the broad distance check, because large characters may still be in range even if their collider isn't float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f); foreach (Character c in Character.CharacterList) { if (!c.Enabled || Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange || Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange) { continue; } Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } Hull hull = Hull.FindHull(explosionPos, null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); Dictionary <Limb, float> damages = new Dictionary <Limb, float>(); foreach (Limb limb in c.AnimController.Limbs) { float dist = Vector2.Distance(limb.WorldPosition, worldPosition); //calculate distance from the "outer surface" of the physics body //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = limb.body.GetMaxExtent(); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > attack.Range) { continue; } float distFactor = 1.0f - dist / attack.Range; //solid obstacles between the explosion and the limb reduce the effect of the explosion by 90% if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) { distFactor *= 0.1f; } distFactors.Add(limb, distFactor); List <Affliction> modifiedAfflictions = new List <Affliction>(); int limbCount = c.AnimController.Limbs.Count(l => !l.IsSevered && !l.ignoreCollisions); foreach (Affliction affliction in attack.Afflictions.Keys) { modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / limbCount)); } c.LastDamageSource = damageSource; if (attacker == null) { if (damageSource is Item item) { attacker = item.GetComponent <Projectile>()?.User; if (attacker == null) { attacker = item.GetComponent <MeleeWeapon>()?.User; } } } //use a position slightly from the limb's position towards the explosion //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods Vector2 hitPos = limb.WorldPosition + (worldPosition - limb.WorldPosition) / dist * 0.01f; AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); damages.Add(limb, attackResult.Damage); if (attack.StatusEffects != null && attack.StatusEffects.Any()) { attack.SetUser(attacker); var statusEffectTargets = new List <ISerializableEntity>() { c, limb }; foreach (StatusEffect statusEffect in attack.StatusEffects) { statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets); } } if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f)) { Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition); if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); } Vector2 impulse = limbDiff * distFactor * force; Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius; limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.2f); } } //sever joints if (attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (limb.character.Removed || limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; } if (distFactors.TryGetValue(limb, out float distFactor)) { if (damages.TryGetValue(limb, out float damage)) { c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true); } } } } } }
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker) { if (attack.Range <= 0.0f) { return; } //long range for the broad distance check, because large characters may still be in range even if their collider isn't float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f); foreach (Character c in Character.CharacterList) { if (!c.Enabled || Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange || Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange) { continue; } Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } Hull hull = Hull.FindHull(explosionPos, null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); Dictionary <Limb, float> damages = new Dictionary <Limb, float>(); List <Affliction> modifiedAfflictions = new List <Affliction>(); foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered || limb.ignoreCollisions) { continue; } float dist = Vector2.Distance(limb.WorldPosition, worldPosition); //calculate distance from the "outer surface" of the physics body //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = limb.body.GetMaxExtent(); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > attack.Range) { continue; } float distFactor = 1.0f - dist / attack.Range; //solid obstacles between the explosion and the limb reduce the effect of the explosion var obstacles = Submarine.PickBodies(limb.SimPosition, explosionPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall); foreach (var body in obstacles) { if (body.UserData is Item item) { var door = item.GetComponent <Door>(); if (door != null && !door.IsBroken) { distFactor *= 0.01f; } } else if (body.UserData is Structure structure) { int sectionIndex = structure.FindSectionIndex(worldPosition, world: true, clamp: true); if (structure.SectionBodyDisabled(sectionIndex)) { continue; } else if (structure.SectionIsLeaking(sectionIndex)) { distFactor *= 0.1f; } else { distFactor *= 0.01f; } } else { distFactor *= 0.1f; } } if (distFactor <= 0.05f) { continue; } distFactors.Add(limb, distFactor); modifiedAfflictions.Clear(); foreach (Affliction affliction in attack.Afflictions.Keys) { //previously the damage would be divided by the number of limbs (the intention was to prevent characters with more limbs taking more damage from explosions) //that didn't work well on large characters like molochs and endworms: the explosions tend to only damage one or two of their limbs, and since the characters //have lots of limbs, they tended to only take a fraction of the damage they should //now we just divide by 10, which keeps the damage to normal-sized characters roughly the same as before and fixes the large characters modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / 10)); } c.LastDamageSource = damageSource; if (attacker == null) { if (damageSource is Item item) { attacker = item.GetComponent <Projectile>()?.User; if (attacker == null) { attacker = item.GetComponent <MeleeWeapon>()?.User; } } } //use a position slightly from the limb's position towards the explosion //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods Vector2 dir = worldPosition - limb.WorldPosition; Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f; AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); damages.Add(limb, attackResult.Damage); if (attack.StatusEffects != null && attack.StatusEffects.Any()) { attack.SetUser(attacker); var statusEffectTargets = new List <ISerializableEntity>() { c, limb }; foreach (StatusEffect statusEffect in attack.StatusEffects) { statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets); } } if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f)) { Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition); if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); } Vector2 impulse = limbDiff * distFactor * force; Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius; limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.2f); } } //sever joints if (attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (limb.character.Removed || limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; } if (distFactors.TryGetValue(limb, out float distFactor)) { if (damages.TryGetValue(limb, out float damage)) { c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true); } } } } } }
public override void OnAttacked(Character attacker, AttackResult attackResult) { float damage = attackResult.Damage; if (damage <= 0) { return; } if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; } if (attacker == null || attacker.IsDead || attacker.Removed) { // Ignore damage from falling etc that we shouldn't react to. if (Character.LastDamageSource == null) { return; } AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); } else if (IsFriendly(attacker)) { if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character) { // Don't attack characters that damage you while doing cpr, because let's assume that they are helping you. // Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate). return; } if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled) { // Don't retaliate on damage done by friendly ai, because we know that it's accidental AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); } else { float currentVitality = Character.CharacterHealth.Vitality; float dmgPercentage = damage / currentVitality * 100; if (dmgPercentage < currentVitality / 10) { // Don't retaliate on minor (accidental) dmg done by friendly characters AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); } else { AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); } } } else { AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive); } void AddCombatObjective(AIObjectiveCombat.CombatMode mode, float delay = 0) { if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective) { if (combatObjective.Enemy != attacker || (combatObjective.Enemy == null && attacker == null)) { // Replace the old objective with the new. ObjectiveManager.Objectives.Remove(combatObjective); objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager)); } } else { if (delay > 0) { objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager), delay); } else { objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager)); } } } }
public virtual void OnAttacked(Character attacker, AttackResult attackResult) { }
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun) { GameMain.Server.KarmaManager.OnCharacterHealthChanged(this, attacker, attackResult.Damage, stun, attackResult.Afflictions); }
/// <summary> /// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated. /// </summary> public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null) { attackResult = default(AttackResult); float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos)); bool wasRunning = attack.IsRunning; attack.UpdateAttackTimer(deltaTime); bool wasHit = false; Body structureBody = null; if (damageTarget != null) { switch (attack.HitDetectionType) { case HitDetection.Distance: if (dist < attack.DamageRange) { structureBody = Submarine.PickBody(SimPosition, attackSimPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true); if (damageTarget is Item i && i.GetComponent <Items.Components.Door>() != null) { // If the attack is aimed to an item and hits an item, it's successful. // Ignore blocking checks on doors, because it causes cases where a Mudraptor cannot hit the hatch, for example. wasHit = true; } else if (damageTarget is Structure wall && structureBody != null && (structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine))) { // If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful wasHit = true; } else { // If there is nothing between, the hit is successful wasHit = structureBody == null; } }
public override void OnAttacked(Character attacker, AttackResult attackResult) { float damage = attackResult.Damage; if (damage <= 0) { return; } if (attacker == null || attacker.IsDead || attacker.Removed) { if (objectiveManager.CurrentOrder == null) { objectiveManager.GetObjective <AIObjectiveFindSafety>().Priority = 100; } return; } if (IsFriendly(attacker)) { if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character) { // Don't attack characters that damage you while doing cpr, because let's assume that they are helping you. // Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate). return; } if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled) { // Don't react to damage done by friendly ai, because we know that it's accidental if (objectiveManager.CurrentOrder == null) { objectiveManager.GetObjective <AIObjectiveFindSafety>().Priority = 100; } return; } float currentVitality = Character.CharacterHealth.Vitality; float dmgPercentage = damage / currentVitality * 100; if (dmgPercentage < currentVitality / 10) { // Don't react to a minor amount of (accidental) dmg done by friendly characters if (objectiveManager.CurrentOrder == null) { objectiveManager.GetObjective <AIObjectiveFindSafety>().Priority = 100; } } if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective) { if (combatObjective.Enemy != attacker) { // Replace the old objective with the new. ObjectiveManager.Objectives.Remove(combatObjective); objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker)); } } else { objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); } } else { if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective) { if (combatObjective.Enemy != attacker) { // Replace the old objective with the new. ObjectiveManager.Objectives.Remove(combatObjective); objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker)); } } else { objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker)); } } }