Server LuaCsForBarotrauma
Attack.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Xml.Linq;
8 
9 namespace Barotrauma
10 {
11  public enum HitDetection
12  {
13  Distance,
14  Contact,
15  None
16  }
17 
18  public enum AttackContext
19  {
20  Any,
21  Water,
22  Ground,
23  Inside,
24  Outside,
26  }
27 
28  [Flags]
29  public enum AttackTarget
30  {
31  Any = 0,
35  Character = 1,
39  Structure = 2,
43  Item = 4
44  }
45 
47  {
48  FallBack,
51  Pursue,
52  Eat,
57  Reverse,
59  }
60 
61  struct AttackResult
62  {
63  public float Damage
64  {
65  get;
66  private set;
67  }
68  public readonly List<Affliction> Afflictions;
69 
70  public readonly Limb HitLimb;
71 
72  public readonly List<DamageModifier> AppliedDamageModifiers;
73 
74  public AttackResult(List<Affliction> afflictions, Limb hitLimb, List<DamageModifier> appliedDamageModifiers = null)
75  {
76  HitLimb = hitLimb;
77  Afflictions = new List<Affliction>();
78 
79  foreach (Affliction affliction in afflictions)
80  {
81  Afflictions.Add(affliction.Prefab.Instantiate(affliction.Strength, affliction.Source));
82  }
83  AppliedDamageModifiers = appliedDamageModifiers;
84  Damage = Afflictions.Sum(a => a.GetVitalityDecrease(null));
85  }
86 
87  public AttackResult(float damage, List<DamageModifier> appliedDamageModifiers = null)
88  {
89  Damage = damage;
90  HitLimb = null;
91  Afflictions = null;
92  AppliedDamageModifiers = appliedDamageModifiers;
93  }
94  }
95 
101  partial class Attack : ISerializableEntity
102  {
103  [Serialize(AttackContext.Any, IsPropertySaveable.Yes, description: "The attack will be used only in this context."), Editable]
104  public AttackContext Context { get; private set; }
105 
106  [Serialize(AttackTarget.Any, IsPropertySaveable.Yes, description: "Does the attack target only specific targets?"), Editable]
107  public AttackTarget TargetType { get; private set; }
108 
109  [Serialize(LimbType.None, IsPropertySaveable.Yes, description: "To which limb is the attack aimed at? If not defined or set to none, the closest limb is used (default)."), Editable]
110  public LimbType TargetLimbType { get; private set; }
111 
112  [Serialize(HitDetection.Distance, IsPropertySaveable.Yes, description: "Collision detection is more accurate, but it only affects targets that are in contact with the limb."), Editable]
113  public HitDetection HitDetectionType { get; private set; }
114 
115  [Serialize(AIBehaviorAfterAttack.FallBack, IsPropertySaveable.Yes, description: "The preferred AI behavior after the attack."), Editable]
116  public AIBehaviorAfterAttack AfterAttack { get; set; }
117 
118  [Serialize(0f, IsPropertySaveable.Yes, description: "A delay before reacting after performing an attack."), Editable]
119  public float AfterAttackDelay { get; set; }
120 
122  description: "Secondary AI behavior after the attack. The character first executes the AfterAttack behavior, then after AfterAttackSecondaryDelay passes, switches to this one. Ignored if AfterAttackSecondaryDelay is 0 or less."), Editable]
124 
125  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How long the character executes the AfterAttack before switching to AfterAttackSecondary. The secondary behavior is ignored if this value is 0 or less."), Editable]
126  public float AfterAttackSecondaryDelay { get; set; }
127 
128  [Serialize(false, IsPropertySaveable.Yes, description: "Should the AI try to turn around when aiming with this attack?"), Editable]
129  public bool Reverse { get; private set; }
130 
131  [Serialize(true, IsPropertySaveable.Yes, description: "Should the rope attached to this limb snap upon choosing a new attack?"), Editable]
132  public bool SnapRopeOnNewAttack { get; private set; }
133 
134  [Serialize(false, IsPropertySaveable.Yes, description: "Should the AI try to steer away from the target when aiming with this attack? Best combined with PassiveAggressive behavior."), Editable]
135  public bool Retreat { get; private set; }
136 
137  private float _range;
138  [Serialize(0.0f, IsPropertySaveable.Yes, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
139  public float Range
140  {
141  get => _range * RangeMultiplier;
142  set => _range = value;
143  }
144 
145  private float _damageRange;
146  [Serialize(0.0f, IsPropertySaveable.Yes, description: "The min distance from the attack limb to the target to do damage. In distance-based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
147  public float DamageRange
148  {
149  get => _damageRange * RangeMultiplier;
150  set => _damageRange = value;
151  }
152 
153  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Used by enemy AI to determine the minimum range required for the attack to hit."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
154  public float MinRange { get; private set; }
155 
156  [Serialize(0.25f, IsPropertySaveable.Yes, description: "An approximation of the attack duration. Effectively defines the time window in which the hit can be registered. If set to too low value, it's possible that the attack won't hit the target in time."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, DecimalCount = 2)]
157  public float Duration { get; private set; }
158 
159  [Serialize(5f, IsPropertySaveable.Yes, description: "How long the AI must wait before it can use this attack again."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2)]
160  public float CoolDown { get; set; } = 5;
161 
162 
163  [Serialize(0f, IsPropertySaveable.Yes, description: "When the attack cooldown is running and when there are other valid attacks possible for the character to use, the secondary cooldown is used instead of the regular cooldown. Does not have an effect, if set to 0 or less than the regular cooldown value."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2)]
164  public float SecondaryCoolDown { get; set; } = 0;
165 
166  [Serialize(0f, IsPropertySaveable.Yes, description: "A random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases)."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
167  public float CoolDownRandomFactor { get; private set; } = 0;
168 
169  [Serialize(false, IsPropertySaveable.Yes, description: "When set to true, causes the enemy AI to use the fast movement animations when the attack is on cooldown."), Editable]
170  public bool FullSpeedAfterAttack { get; private set; }
171 
172  private float _structureDamage;
173  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How much damage the attack does to submarine walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
174  public float StructureDamage
175  {
176  get => _structureDamage * DamageMultiplier;
177  set => _structureDamage = value;
178  }
179 
180  [Serialize(false, IsPropertySaveable.Yes, description: "If the attack causes an explosion of wall damage shrapnel, should some of the shrapnel be launched as projectiles that can go through walls?"), Editable]
181  public bool CreateWallDamageProjectiles { get; private set; }
182 
183  [Serialize(true, IsPropertySaveable.Yes, description: "Whether or not damaging structures with the attack causes damage particles to emit."), Editable]
184  public bool EmitStructureDamageParticles { get; private set; }
185 
186  private float _itemDamage;
187  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How much damage the attack does to items."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
188  public float ItemDamage
189  {
190  get =>_itemDamage * DamageMultiplier;
191  set => _itemDamage = value;
192  }
193 
194  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Percentage of damage mitigation ignored when hitting armored body parts (deflecting limbs)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1f)]
195  public float Penetration { get; private set; }
196 
200  public float DamageMultiplier { get; set; } = 1;
201 
205  public float RangeMultiplier { get; set; } = 1;
206 
210  public float ImpactMultiplier { get; set; } = 1;
211 
212  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How much damage the attack does to level walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
213  public float LevelWallDamage { get; set; }
214 
215  [Serialize(false, IsPropertySaveable.Yes, description: "Sets whether or not the attack is ranged or not."), Editable]
216  public bool Ranged { get; set; }
217 
218  [Serialize(false, IsPropertySaveable.Yes, description:"When enabled the attack will not be launched when there's a friendly character in the way. Only affects ranged attacks."), Editable]
219  public bool AvoidFriendlyFire { get; set; }
220 
221  [Serialize(20f, IsPropertySaveable.Yes, description: "Used by enemy AI to determine how accurately the attack needs to be aimed for the attack to trigger. Only affects ranged attacks."), Editable]
222  public float RequiredAngle { get; set; }
223 
224  [Serialize(0f, IsPropertySaveable.Yes, description: "By default uses the same value as RequiredAngle. Use if you want to allow selecting the attack but not shooting until the angle is smaller. Only affects ranged attacks."), Editable]
225  public float RequiredAngleToShoot { get; set; }
226 
227  [Serialize(0f, IsPropertySaveable.Yes, description: "How much the attack limb is rotated towards the target. Default 0 = no rotation. Only affects ranged attacks."), Editable]
228  public float AimRotationTorque { get; set; }
229 
230  [Serialize(-1, IsPropertySaveable.Yes, description: "Reference to the limb we apply the aim rotation to. By default same as the attack limb. Only affects ranged attacks."), Editable]
231  public int RotationLimbIndex { get; set; }
232 
233  [Serialize(0f, IsPropertySaveable.Yes, description:"How much the held weapon is swayed back and forth while aiming. Only affects monsters using ranged weapons (items). Default 0 means the weapon is not swayed at all."), Editable]
234  public float SwayAmount { get; set; }
235 
236  [Serialize(5f, IsPropertySaveable.Yes, description: "How fast the held weapon is swayed back and forth while aiming. Only affects monsters using ranged weapons (items)."), Editable]
237  public float SwayFrequency { get; set; }
238 
239  [Serialize(0.0f, IsPropertySaveable.No, description: "Legacy functionality. Behaves otherwise the same as stuns defined as afflictions, but explosions only apply the stun once instead of dividing it between the limbs.")]
240  public float Stun { get; set; }
241 
242  [Serialize(false, IsPropertySaveable.Yes, description: "Can damage only Humans."), Editable]
243  public bool OnlyHumans { get; set; }
244 
245  [Serialize("", IsPropertySaveable.Yes, description: "List of limb indices to apply the force into."), Editable]
246  public string ApplyForceOnLimbs
247  {
248  get
249  {
250  return string.Join(", ", ForceOnLimbIndices);
251  }
252  set
253  {
254  ForceOnLimbIndices.Clear();
255  if (string.IsNullOrEmpty(value)) { return; }
256  foreach (string limbIndexStr in value.Split(','))
257  {
258  if (int.TryParse(limbIndexStr.Trim(), out int limbIndex))
259  {
260  ForceOnLimbIndices.Add(limbIndex);
261  }
262  }
263  }
264  }
265 
266  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs). The direction of the force is towards the target that's being attacked."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
267  public float Force { get; private set; }
268 
269  [Serialize("0.0, 0.0", IsPropertySaveable.Yes, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
270  public Vector2 RootForceWorldStart { get; private set; }
271 
272  [Serialize("0.0, 0.0", IsPropertySaveable.Yes, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
273  public Vector2 RootForceWorldMiddle { get; private set; }
274 
275  [Serialize("0.0, 0.0", IsPropertySaveable.Yes, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
276  public Vector2 RootForceWorldEnd { get; private set; }
277 
278  [Serialize(TransitionMode.Linear, IsPropertySaveable.Yes, description:"Applied to the main limb. The transition smoothing of the applied force."), Editable]
279  public TransitionMode RootTransitionEasing { get; private set; }
280 
281  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs)"), Editable(MinValueFloat = -10000.0f, MaxValueFloat = 10000.0f)]
282  public float Torque { get; private set; }
283 
284  [Serialize(false, IsPropertySaveable.Yes, description: "Only apply the force once during the attacks lifetime."), Editable]
285  public bool ApplyForcesOnlyOnce { get; private set; }
286 
287  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Applied to the target the attack hits. The direction of the impulse is from this limb towards the target (use negative values to pull the target closer)."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
288  public float TargetImpulse { get; private set; }
289 
290  [Serialize("0.0, 0.0", IsPropertySaveable.Yes, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
291  public Vector2 TargetImpulseWorld { get; private set; }
292 
293  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Applied to the target the attack hits. The direction of the force is from this limb towards the target (use negative values to pull the target closer)."), Editable(-1000.0f, 1000.0f)]
294  public float TargetForce { get; private set; }
295 
296  [Serialize("0.0, 0.0", IsPropertySaveable.Yes, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
297  public Vector2 TargetForceWorld { get; private set; }
298 
299  [Serialize(1.0f, IsPropertySaveable.Yes, description: "Affects the strength of the impact effects the limb causes when it hits a submarine."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
300  public float SubmarineImpactMultiplier { get; private set; }
301 
302  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How likely the attack causes target limbs to be severed."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
303  public float SeverLimbsProbability { get; set; }
304 
305  // TODO: disabled because not synced
306  //[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
307  //public float StickChance { get; set; }
308  public float StickChance => 0f;
309 
310  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Used by enemy AI to determine the priority when selecting attacks. When random attacks are disabled on the character it is multiplied with distance to determine the which attack to use. Only attacks that are currently valid are taken into consideration when making the decision."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
311  public float Priority { get; private set; }
312 
313  [Serialize(false, IsPropertySaveable.Yes, description: "Triggers the 'blink' animation on the attacking limbs when the attack executes. Used e.g. by abyss monsters to make their jaws close when attacking."), Editable]
314  public bool Blink { get; private set; }
315 
316  public IEnumerable<StatusEffect> StatusEffects
317  {
318  get { return statusEffects; }
319  }
320 
321  public string Name => "Attack";
322 
323  public Dictionary<Identifier, SerializableProperty> SerializableProperties
324  {
325  get;
326  private set;
327  } = new Dictionary<Identifier, SerializableProperty>();
328 
329  //the indices of the limbs Force is applied on
330  //(if none, force is applied only to the limb the attack is attached to)
331  public readonly List<int> ForceOnLimbIndices = new List<int>();
332 
333  public readonly Dictionary<Affliction, XElement> Afflictions = new Dictionary<Affliction, XElement>();
334 
338  public List<PropertyConditional> Conditionals { get; private set; } = new List<PropertyConditional>();
339 
344  private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
345 
346  public void SetUser(Character user)
347  {
348  if (statusEffects == null) { return; }
349  foreach (StatusEffect statusEffect in statusEffects)
350  {
351  statusEffect.SetUser(user);
352  }
353  }
354 
355  // used for talents/ability conditions
356  public Item SourceItem { get; set; }
357 
358  public List<Affliction> GetMultipliedAfflictions(float multiplier)
359  {
360  List<Affliction> multipliedAfflictions = new List<Affliction>();
361  foreach (Affliction affliction in Afflictions.Keys)
362  {
363  multipliedAfflictions.Add(affliction.CreateMultiplied(multiplier, affliction));
364  }
365  return multipliedAfflictions;
366  }
367 
368  public float GetStructureDamage(float deltaTime)
369  {
370  return (Duration == 0.0f) ? StructureDamage : StructureDamage * deltaTime;
371  }
372 
373  public float GetLevelWallDamage(float deltaTime)
374  {
375  return (Duration == 0.0f) ? LevelWallDamage : LevelWallDamage * deltaTime;
376  }
377 
378  public float GetItemDamage(float deltaTime, float multiplier = 1)
379  {
380  float dmg = ItemDamage * multiplier;
381  return (Duration == 0.0f) ? dmg : dmg * deltaTime;
382  }
383 
384  public float GetTotalDamage(bool includeStructureDamage = false)
385  {
386  float totalDamage = includeStructureDamage ? StructureDamage : 0.0f;
387  foreach (Affliction affliction in Afflictions.Keys)
388  {
389  totalDamage += affliction.GetVitalityDecrease(null);
390  }
391  return totalDamage * DamageMultiplier;
392  }
393 
394  public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range = 0.0f)
395  {
396  if (damage > 0.0f) { Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage), null); }
397  if (bleedingDamage > 0.0f) { Afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage), null); }
398  if (burnDamage > 0.0f) { Afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamage), null); }
399 
400  Range = range;
401  DamageRange = range;
402  StructureDamage = LevelWallDamage = structureDamage;
403  ItemDamage = itemDamage;
404  }
405 
406  public Attack(ContentXElement element, string parentDebugName, Item sourceItem) : this(element, parentDebugName)
407  {
408  SourceItem = sourceItem;
409  }
410 
411  public Attack(ContentXElement element, string parentDebugName)
412  {
413  Deserialize(element, parentDebugName);
414 
415  if (element.GetAttribute("damage") != null ||
416  element.GetAttribute("bluntdamage") != null ||
417  element.GetAttribute("burndamage") != null ||
418  element.GetAttribute("bleedingdamage") != null)
419  {
420  DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Define damage as afflictions instead of using the damage attribute (e.g. <Affliction identifier=\"internaldamage\" strength=\"10\" />).",
421  contentPackage: element.ContentPackage);
422  }
423 
424  //if level wall damage is not defined, default to the structure damage
425  if (element.GetAttribute("LevelWallDamage") == null &&
426  element.GetAttribute("levelwalldamage") == null)
427  {
429  }
430 
431  InitProjSpecific(element);
432 
433  foreach (var subElement in element.Elements())
434  {
435  switch (subElement.Name.ToString().ToLowerInvariant())
436  {
437  case "statuseffect":
438  statusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
439  break;
440  case "affliction":
441  AfflictionPrefab afflictionPrefab;
442  if (subElement.GetAttribute("name") != null)
443  {
444  DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - define afflictions using identifiers instead of names.",
445  contentPackage: element.ContentPackage);
446  string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant();
447  afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, System.StringComparison.OrdinalIgnoreCase));
448  if (afflictionPrefab == null)
449  {
450  DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.",
451  contentPackage: element.ContentPackage);
452  continue;
453  }
454  }
455  else
456  {
457  Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
458  if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out afflictionPrefab))
459  {
460  DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.",
461  contentPackage: element.ContentPackage);
462  continue;
463  }
464  }
465  break;
466  case "conditional":
467  Conditionals.AddRange(PropertyConditional.FromXElement(subElement));
468  break;
469  }
470  }
471 
473  {
474  DebugConsole.AddWarning($"Potentially misconfigured attack in {parentDebugName}. Secondary cooldown should not be longer than the primary cooldown.",
475  contentPackage: element.ContentPackage);
476  }
477  }
478  partial void InitProjSpecific(ContentXElement element);
479 
480  public void ReloadAfflictions(ContentXElement element, string parentDebugName)
481  {
482  Afflictions.Clear();
483  foreach (var subElement in element.GetChildElements("affliction"))
484  {
485  Affliction affliction;
486  Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
487  if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab afflictionPrefab))
488  {
489  DebugConsole.ThrowError($"Error in an Attack defined in \"{parentDebugName}\" - could not find an affliction with the identifier \"{afflictionIdentifier}\".",
490  contentPackage: element.ContentPackage);
491  continue;
492  }
493  affliction = afflictionPrefab.Instantiate(0.0f);
494  affliction.Deserialize(subElement);
495  // add the affliction anyway, so that it can be shown in the editor.
496  Afflictions.Add(affliction, subElement);
497  }
498  }
499 
500  public void Serialize(ContentXElement element)
501  {
502  SerializableProperty.SerializeProperties(this, element, true);
503  foreach (var affliction in Afflictions)
504  {
505  if (affliction.Value != null)
506  {
507  affliction.Key.Serialize(affliction.Value);
508  }
509  }
510  }
511 
512  public void Deserialize(ContentXElement element, string parentDebugName)
513  {
515  ReloadAfflictions(element, parentDebugName);
516  }
517 
518  public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
519  {
520  Character targetCharacter = target as Character;
521  if (OnlyHumans)
522  {
523  if (targetCharacter != null && !targetCharacter.IsHuman)
524  {
525  return new AttackResult();
526  }
527  }
528 
529  SetUser(attacker);
530 
531  DamageParticles(deltaTime, worldPosition);
532 
533  Vector2 impulseDirection = GetImpulseDirection(target as ISpatialEntity, worldPosition, SourceItem);
534  var attackResult = target?.AddDamage(attacker, worldPosition, this, impulseDirection, deltaTime, playSound) ?? new AttackResult();
535  var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure;
536  var additionalEffectType = ActionType.OnUse;
537  if (targetCharacter != null && targetCharacter.IsDead)
538  {
539  additionalEffectType = ActionType.OnEating;
540  }
541 
542  foreach (StatusEffect effect in statusEffects)
543  {
544  effect.sourceBody = sourceBody;
545  if (effect.HasTargetType(StatusEffect.TargetType.This) || effect.HasTargetType(StatusEffect.TargetType.Character))
546  {
547  var t = sourceLimb ?? attacker as ISerializableEntity;
548  if (additionalEffectType != ActionType.OnEating)
549  {
550  effect.Apply(conditionalEffectType, deltaTime, attacker, t, worldPosition);
551  }
552  effect.Apply(additionalEffectType, deltaTime, attacker, t, worldPosition);
553  }
554  if (effect.HasTargetType(StatusEffect.TargetType.Parent))
555  {
556  if (additionalEffectType != ActionType.OnEating)
557  {
558  effect.Apply(conditionalEffectType, deltaTime, attacker, attacker);
559  }
560  effect.Apply(additionalEffectType, deltaTime, attacker, attacker);
561  }
562  if (targetCharacter != null)
563  {
564  if (effect.HasTargetType(StatusEffect.TargetType.Limb))
565  {
566  if (additionalEffectType != ActionType.OnEating)
567  {
568  effect.Apply(conditionalEffectType, deltaTime, targetCharacter, attackResult.HitLimb);
569  }
570  effect.Apply(additionalEffectType, deltaTime, targetCharacter, attackResult.HitLimb);
571  }
572  if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
573  {
574  var targets = targetCharacter.AnimController.Limbs;
575  if (additionalEffectType != ActionType.OnEating)
576  {
577  effect.Apply(conditionalEffectType, deltaTime, targetCharacter, targets);
578  }
579  effect.Apply(additionalEffectType, deltaTime, targetCharacter, targets);
580  }
581  }
582  if (target is Entity targetEntity)
583  {
584  if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
585  effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
586  {
587  targets.Clear();
588  effect.AddNearbyTargets(worldPosition, targets);
589  if (additionalEffectType != ActionType.OnEating)
590  {
591  effect.Apply(conditionalEffectType, deltaTime, targetEntity, targets);
592  }
593  effect.Apply(additionalEffectType, deltaTime, targetEntity, targets);
594  }
595  if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
596  {
597  if (additionalEffectType != ActionType.OnEating)
598  {
599  effect.Apply(conditionalEffectType, deltaTime, targetEntity, targetEntity as ISerializableEntity, worldPosition);
600  }
601  effect.Apply(additionalEffectType, deltaTime, targetEntity, targetEntity as ISerializableEntity, worldPosition);
602  }
603  }
604  if (effect.HasTargetType(StatusEffect.TargetType.Contained))
605  {
606  targets.Clear();
607  targets.AddRange(attacker.Inventory.AllItems);
608  if (additionalEffectType != ActionType.OnEating)
609  {
610  effect.Apply(conditionalEffectType, deltaTime, attacker, targets);
611  }
612  effect.Apply(additionalEffectType, deltaTime, attacker, targets);
613  }
614  }
615 
616  return attackResult;
617  }
618 
619  readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
620  public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
621  {
622  if (targetLimb == null)
623  {
624  return new AttackResult();
625  }
626 
627  if (OnlyHumans)
628  {
629  if (targetLimb.character != null && !targetLimb.character.IsHuman)
630  {
631  return new AttackResult();
632  }
633  }
634 
635  SetUser(attacker);
636 
637  DamageParticles(deltaTime, worldPosition);
638 
639  float penetration = Penetration;
640 
641  RangedWeapon weapon =
642  SourceItem?.GetComponent<RangedWeapon>() ??
643  SourceItem?.GetComponent<Projectile>()?.Launcher?.GetComponent<RangedWeapon>();
644  float? penetrationValue = weapon?.Penetration;
645  if (penetrationValue.HasValue)
646  {
647  penetration += penetrationValue.Value;
648  }
649 
650  Vector2 impulseDirection = GetImpulseDirection(targetLimb, worldPosition, SourceItem);
651  var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, impulseDirection, playSound, targetLimb, penetration);
652  var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure;
653 
654  foreach (StatusEffect effect in statusEffects)
655  {
656  effect.sourceBody = sourceBody;
657  if (effect.HasTargetType(StatusEffect.TargetType.This) || effect.HasTargetType(StatusEffect.TargetType.Character))
658  {
659  effect.Apply(conditionalEffectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
660  effect.Apply(ActionType.OnUse, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
661  }
662  if (effect.HasTargetType(StatusEffect.TargetType.Parent))
663  {
664  effect.Apply(conditionalEffectType, deltaTime, attacker, attacker);
665  effect.Apply(ActionType.OnUse, deltaTime, attacker, attacker);
666  }
667  if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
668  {
669  effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targetLimb.character);
670  effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targetLimb.character);
671  }
672  if (effect.HasTargetType(StatusEffect.TargetType.Limb))
673  {
674  effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targetLimb);
675  effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targetLimb);
676  }
677  if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
678  {
679  var targets = targetLimb.character.AnimController.Limbs;
680  effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
681  effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targets);
682  }
683  if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
684  effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
685  {
686  targets.Clear();
687  effect.AddNearbyTargets(worldPosition, targets);
688  effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
689  effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targets);
690  }
691  if (effect.HasTargetType(StatusEffect.TargetType.Contained))
692  {
693  targets.Clear();
694  targets.AddRange(attacker.Inventory.AllItems);
695  effect.Apply(conditionalEffectType, deltaTime, attacker, targets);
696  effect.Apply(ActionType.OnUse, deltaTime, attacker, targets);
697  }
698  }
699 
700  return attackResult;
701  }
702 
703  private Vector2 GetImpulseDirection(ISpatialEntity target, Vector2 sourceWorldPosition, Item sourceItem)
704  {
705  Vector2 impulseDirection = Vector2.Zero;
706  if (target != null)
707  {
708  impulseDirection = target.WorldPosition - sourceWorldPosition;
709  }
710 
711  if (sourceItem?.body != null && sourceItem.body.Enabled && sourceItem.body.LinearVelocity.LengthSquared() > 0.0f)
712  {
713  impulseDirection = sourceItem.body.LinearVelocity;
714  }
715  else
716  {
717  var projectileComponent = sourceItem?.GetComponent<Projectile>();
718  if (projectileComponent != null)
719  {
720  impulseDirection = new Vector2(MathF.Cos(SourceItem.Rotation), MathF.Sin(SourceItem.Rotation));
721  }
722  }
723 
724  if (impulseDirection.LengthSquared() > 0.0001f)
725  {
726  impulseDirection = Vector2.Normalize(impulseDirection);
727  }
728  return impulseDirection;
729  }
730 
731  public float AttackTimer { get; private set; }
732  public float CoolDownTimer { get; set; }
733  public float CurrentRandomCoolDown { get; private set; }
734  public float SecondaryCoolDownTimer { get; set; }
735  public bool IsRunning { get; private set; }
736 
737  public float AfterAttackTimer { get; set; }
738 
739  public void UpdateCoolDown(float deltaTime)
740  {
741  CoolDownTimer -= deltaTime;
742  SecondaryCoolDownTimer -= deltaTime;
743  if (CoolDownTimer < 0) { CoolDownTimer = 0; }
745  }
746 
747  public void UpdateAttackTimer(float deltaTime, Character character)
748  {
749  IsRunning = true;
750  AttackTimer += deltaTime;
751  if (AttackTimer >= Duration)
752  {
754  SetCoolDown(applyRandom: !character.IsPlayer);
755  }
756  }
757 
758  public void ResetAttackTimer()
759  {
760  AfterAttackTimer = 0;
761  AttackTimer = 0;
762  IsRunning = false;
763  }
764 
765  public void SetCoolDown(bool applyRandom)
766  {
767  if (applyRandom)
768  {
769  float randomFraction = CoolDown * CoolDownRandomFactor;
770  CurrentRandomCoolDown = MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value());
772  randomFraction = SecondaryCoolDown * CoolDownRandomFactor;
773  SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value());
774  }
775  else
776  {
780  }
781  }
782 
783  public void ResetCoolDown()
784  {
785  CoolDownTimer = 0;
788  }
789 
790  partial void DamageParticles(float deltaTime, Vector2 worldPosition);
791 
792  public bool IsValidContext(AttackContext context) => Context == context || Context == AttackContext.Any || Context == AttackContext.NotDefined;
793 
794  public bool IsValidContext(IEnumerable<AttackContext> contexts)
795  {
796  foreach (var context in contexts)
797  {
798  switch (context)
799  {
800  case AttackContext.Ground:
801  if (Context == AttackContext.Water)
802  {
803  return false;
804  }
805  break;
806  case AttackContext.Water:
807  if (Context == AttackContext.Ground)
808  {
809  return false;
810  }
811  break;
812  case AttackContext.Inside:
813  if (Context == AttackContext.Outside)
814  {
815  return false;
816  }
817  break;
818  case AttackContext.Outside:
819  if (Context == AttackContext.Inside)
820  {
821  return false;
822  }
823  break;
824  default:
825  continue;
826  }
827  }
828  return true;
829  }
830 
831  public bool IsValidTarget(AttackTarget targetType) => TargetType == AttackTarget.Any || TargetType.HasAnyFlag(targetType);
832 
833  public bool IsValidTarget(Entity target)
834  {
835  return TargetType switch
836  {
837  AttackTarget.Character => target is Character,
838  AttackTarget.Structure => target is Structure or Hull or Item, // Items are intentionally included for backwards-support.
839  AttackTarget.Item => target is Item,
840  _ => IsValidTarget(GetAttackTargetTypeFromEntity(target))
841  };
842  }
843 
844  private static AttackTarget GetAttackTargetTypeFromEntity(Entity entity)
845  {
846  return entity switch
847  {
851  Hull => AttackTarget.Structure,
852  _ => AttackTarget.Any
853  };
854  }
855 
856  public Vector2 CalculateAttackPhase(TransitionMode easing = TransitionMode.Linear)
857  {
858  float t = AttackTimer / Duration;
859  return MathUtils.Bezier(RootForceWorldStart, RootForceWorldMiddle, RootForceWorldEnd, ToolBox.GetEasing(easing, t));
860  }
861  }
862 }
void Deserialize(XElement element)
Definition: Affliction.cs:130
float GetVitalityDecrease(CharacterHealth characterHealth)
Definition: Affliction.cs:171
Affliction CreateMultiplied(float multiplier, Affliction affliction)
Definition: Affliction.cs:140
virtual float Strength
Definition: Affliction.cs:31
void Serialize(XElement element)
Definition: Affliction.cs:125
Character Source
Which character gave this affliction
Definition: Affliction.cs:88
readonly AfflictionPrefab Prefab
Definition: Affliction.cs:12
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
Affliction Instantiate(float strength, Character source=null)
static AfflictionPrefab InternalDamage
static IEnumerable< AfflictionPrefab > List
static AfflictionPrefab Burn
static AfflictionPrefab Bleeding
static readonly PrefabCollection< AfflictionPrefab > Prefabs
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
Definition: Attack.cs:102
float StickChance
Definition: Attack.cs:308
void ResetAttackTimer()
Definition: Attack.cs:758
void Deserialize(ContentXElement element, string parentDebugName)
Definition: Attack.cs:512
float SwayFrequency
Definition: Attack.cs:237
float GetStructureDamage(float deltaTime)
Definition: Attack.cs:368
AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
Definition: Attack.cs:620
Attack(ContentXElement element, string parentDebugName, Item sourceItem)
Definition: Attack.cs:406
float CurrentRandomCoolDown
Definition: Attack.cs:733
bool IsValidTarget(Entity target)
Definition: Attack.cs:833
float AfterAttackDelay
Definition: Attack.cs:119
void UpdateCoolDown(float deltaTime)
Definition: Attack.cs:739
float Penetration
Definition: Attack.cs:195
AttackTarget TargetType
Definition: Attack.cs:107
float SecondaryCoolDownTimer
Definition: Attack.cs:734
bool IsValidContext(IEnumerable< AttackContext > contexts)
Definition: Attack.cs:794
float RangeMultiplier
Used for multiplying all the ranges.
Definition: Attack.cs:205
float LevelWallDamage
Definition: Attack.cs:213
void Serialize(ContentXElement element)
Definition: Attack.cs:500
string ApplyForceOnLimbs
Definition: Attack.cs:247
bool EmitStructureDamageParticles
Definition: Attack.cs:184
Dictionary< Identifier, SerializableProperty > SerializableProperties
Definition: Attack.cs:324
bool IsValidContext(AttackContext context)
float CoolDownRandomFactor
Definition: Attack.cs:167
float RequiredAngleToShoot
Definition: Attack.cs:225
float Priority
Definition: Attack.cs:311
void SetCoolDown(bool applyRandom)
Definition: Attack.cs:765
TransitionMode RootTransitionEasing
Definition: Attack.cs:279
AIBehaviorAfterAttack AfterAttackSecondary
Definition: Attack.cs:123
float AimRotationTorque
Definition: Attack.cs:228
float GetLevelWallDamage(float deltaTime)
Definition: Attack.cs:373
float SwayAmount
Definition: Attack.cs:234
readonly List< int > ForceOnLimbIndices
Definition: Attack.cs:331
float Duration
Definition: Attack.cs:157
Vector2 CalculateAttackPhase(TransitionMode easing=TransitionMode.Linear)
Definition: Attack.cs:856
float ImpactMultiplier
Used for multiplying the physics forces.
Definition: Attack.cs:210
bool AvoidFriendlyFire
Definition: Attack.cs:219
float GetTotalDamage(bool includeStructureDamage=false)
Definition: Attack.cs:384
void ResetCoolDown()
Definition: Attack.cs:783
AIBehaviorAfterAttack AfterAttack
Definition: Attack.cs:116
void UpdateAttackTimer(float deltaTime, Character character)
Definition: Attack.cs:747
bool SnapRopeOnNewAttack
Definition: Attack.cs:132
Vector2 RootForceWorldStart
Definition: Attack.cs:270
bool IsValidTarget(AttackTarget targetType)
bool FullSpeedAfterAttack
Definition: Attack.cs:170
Vector2 TargetImpulseWorld
Definition: Attack.cs:291
Vector2 RootForceWorldEnd
Definition: Attack.cs:276
Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range=0.0f)
Definition: Attack.cs:394
void ReloadAfflictions(ContentXElement element, string parentDebugName)
Definition: Attack.cs:480
float AfterAttackSecondaryDelay
Definition: Attack.cs:126
float StructureDamage
Definition: Attack.cs:175
AttackContext Context
Definition: Attack.cs:104
float DamageMultiplier
Used for multiplying all the damage.
Definition: Attack.cs:200
float MinRange
Definition: Attack.cs:154
bool CreateWallDamageProjectiles
Definition: Attack.cs:181
int RotationLimbIndex
Definition: Attack.cs:231
Vector2 TargetForceWorld
Definition: Attack.cs:297
float AttackTimer
Definition: Attack.cs:731
float AfterAttackTimer
Definition: Attack.cs:737
void SetUser(Character user)
Definition: Attack.cs:346
float TargetForce
Definition: Attack.cs:294
AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
Definition: Attack.cs:518
Vector2 RootForceWorldMiddle
Definition: Attack.cs:273
float SubmarineImpactMultiplier
Definition: Attack.cs:300
LimbType TargetLimbType
Definition: Attack.cs:110
List< Affliction > GetMultipliedAfflictions(float multiplier)
Definition: Attack.cs:358
float RequiredAngle
Definition: Attack.cs:222
IEnumerable< StatusEffect > StatusEffects
Definition: Attack.cs:317
float CoolDown
Definition: Attack.cs:160
float CoolDownTimer
Definition: Attack.cs:732
float ItemDamage
Definition: Attack.cs:189
float TargetImpulse
Definition: Attack.cs:288
readonly Dictionary< Affliction, XElement > Afflictions
Definition: Attack.cs:333
float DamageRange
Definition: Attack.cs:148
float GetItemDamage(float deltaTime, float multiplier=1)
Definition: Attack.cs:378
bool ApplyForcesOnlyOnce
Definition: Attack.cs:285
HitDetection HitDetectionType
Definition: Attack.cs:113
List< PropertyConditional > Conditionals
Only affects ai decision making. All the conditionals has to be met in order to select the attack....
Definition: Attack.cs:338
float SecondaryCoolDown
Definition: Attack.cs:164
float SeverLimbsProbability
Definition: Attack.cs:303
Attack(ContentXElement element, string parentDebugName)
Definition: Attack.cs:411
Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo=null, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, RagdollParams ragdollParams=null, bool spawnInitialItems=true)
AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, Vector2 impulseDirection, bool playSound=false, Limb targetLimb=null, float penetration=0f)
Apply the specified attack to this character. If the targetLimb is not specified, the limb closest to...
ContentPackage? ContentPackage
XAttribute? GetAttribute(string name)
Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id=Entity.NullEntityID, bool callOnItemLoaded=true)
readonly Character character
Definition: Limb.cs:209
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
static IEnumerable< PropertyConditional > FromXElement(ContentXElement element, Predicate< XAttribute >? predicate=null)
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
Definition: StatusEffect.cs:72
bool HasTargetType(TargetType targetType)
void SetUser(Character user)
static StatusEffect Load(ContentXElement element, string parentDebugName)
void AddNearbyTargets(Vector2 worldPosition, List< ISerializableEntity > targets)
virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition=null)
Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine, ushort id=Entity.NullEntityID, XElement element=null)
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound=true)
LimbType
Definition: Limb.cs:19
AttackContext
Definition: Attack.cs:19
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:26
TransitionMode
Definition: Enums.cs:6
AttackTarget
Definition: Attack.cs:30
AIBehaviorAfterAttack
Definition: Attack.cs:47
HitDetection
Definition: Attack.cs:12
readonly List< DamageModifier > AppliedDamageModifiers
Definition: Attack.cs:72
readonly Limb HitLimb
Definition: Attack.cs:70
readonly List< Affliction > Afflictions
Definition: Attack.cs:68
AttackResult(float damage, List< DamageModifier > appliedDamageModifiers=null)
Definition: Attack.cs:87
AttackResult(List< Affliction > afflictions, Limb hitLimb, List< DamageModifier > appliedDamageModifiers=null)
Definition: Attack.cs:74