Server LuaCsForBarotrauma
LevelTrigger.cs
3 using FarseerPhysics;
4 using FarseerPhysics.Dynamics;
5 using FarseerPhysics.Dynamics.Contacts;
6 using Microsoft.Xna.Framework;
7 using System;
8 using System.Collections.Generic;
9 using System.Collections.Immutable;
10 using System.Linq;
12 
13 namespace Barotrauma
14 {
15  partial class LevelTrigger
16  {
17  [Flags]
18  public enum TriggererType
19  {
20  None = 0,
21  Human = 1,
22  Creature = 2,
23  Character = Human | Creature,
24  Submarine = 4,
25  Item = 8,
26  OtherTrigger = 16
27  }
28 
29  public enum TriggerForceMode
30  {
31  Force, //default, apply a force to the object over time
32  Acceleration, //apply an acceleration to the object, ignoring it's mass
33  Impulse, //apply an instant force, ignoring deltaTime
34  LimitVelocity //clamp the velocity of the triggerer to some value
35  }
36 
37  public Action<LevelTrigger, Entity> OnTriggered;
38 
42  private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
43  public IEnumerable<StatusEffect> StatusEffects
44  {
45  get { return statusEffects; }
46  }
47 
51  private readonly List<Attack> attacks = new List<Attack>();
52 
53  private readonly float cameraShake;
54  private Vector2 unrotatedForce;
55  private float forceFluctuationTimer, currentForceFluctuation = 1.0f;
56 
57  private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
58 
59  private readonly TriggererType triggeredBy;
60  private readonly Identifier triggerSpeciesOrGroup;
61  private readonly PropertyConditional.LogicalComparison conditionals;
62 
63  private readonly float randomTriggerInterval;
64  private readonly float randomTriggerProbability;
65  private float randomTriggerTimer;
66 
67  private float triggeredTimer;
68  private readonly HashSet<string> tags = new HashSet<string>();
69 
70  //other triggers have to have at least one of these tags to trigger this one
71  private readonly HashSet<string> allowedOtherTriggerTags = new HashSet<string>();
72 
76  private readonly float stayTriggeredDelay;
77 
79 
80  public Dictionary<Entity, Vector2> TriggererPosition
81  {
82  get;
83  private set;
84  }
85 
86  private Vector2 worldPosition;
87  public Vector2 WorldPosition
88  {
89  get { return worldPosition; }
90  set
91  {
92  worldPosition = value;
93  PhysicsBody?.SetTransform(ConvertUnits.ToSimUnits(value), PhysicsBody.Rotation);
94  }
95  }
96 
97  public float Rotation
98  {
99  get { return PhysicsBody == null ? 0.0f : PhysicsBody.Rotation; }
100  set
101  {
102  if (PhysicsBody == null) return;
104  CalculateDirectionalForce();
105  }
106  }
107 
108  public PhysicsBody PhysicsBody { get; private set; }
109 
110  public float TriggerOthersDistance { get; private set; }
111 
112  public IEnumerable<Entity> Triggerers
113  {
114  get { return triggerers.AsEnumerable(); }
115  }
116 
117  public bool IsTriggered
118  {
119  get
120  {
121  return (triggerers.Count > 0 || triggeredTimer > 0.0f) &&
123  }
124  }
125 
126  public Vector2 Force
127  {
128  get;
129  private set;
130  }
131 
135  public bool ForceFalloff
136  {
137  get;
138  private set;
139  }
140 
142  {
143  get;
144  private set;
145  }
147  {
148  get;
149  private set;
150  }
152  {
153  get;
154  private set;
155  }
156 
157  private readonly TriggerForceMode forceMode;
159  {
160  get { return forceMode; }
161  }
162 
166  public float ForceVelocityLimit
167  {
168  get;
169  private set;
170  }
171 
172  public float ColliderRadius
173  {
174  get;
175  private set;
176  }
177 
178 
179  public bool UseNetworkSyncing
180  {
181  get;
182  private set;
183  }
184 
186  {
187  get;
188  set;
189  }
190 
191  public Identifier InfectIdentifier
192  {
193  get;
194  set;
195  }
196 
197  public float InfectionChance
198  {
199  get;
200  set;
201  }
202 
203  private bool triggeredOnce;
204  private readonly bool triggerOnce;
205 
206  public LevelTrigger(ContentXElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
207  {
208  TriggererPosition = new Dictionary<Entity, Vector2>();
209 
210  worldPosition = position;
211  if (element.Attributes("radius").Any() || element.Attributes("width").Any() || element.Attributes("height").Any())
212  {
213  PhysicsBody = new PhysicsBody(element, scale)
214  {
215  CollisionCategories = Physics.CollisionLevel,
216  CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall,
217  };
218  PhysicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
219  PhysicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
220  PhysicsBody.FarseerBody.SetIsSensor(element.GetAttributeBool("sensor", true));
221  PhysicsBody.FarseerBody.BodyType = BodyType.Static;
222 
223  ColliderRadius = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.Radius, PhysicsBody.Width / 2.0f), PhysicsBody.Height / 2.0f));
224 
225  PhysicsBody.SetTransform(ConvertUnits.ToSimUnits(position), rotation);
226  }
227 
228  cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
229 
230  InfectIdentifier = element.GetAttributeIdentifier("infectidentifier", Identifier.Empty);
231  InfectionChance = element.GetAttributeFloat("infectionchance", 0.05f);
232 
233  triggerOnce = element.GetAttributeBool("triggeronce", false);
234 
235  stayTriggeredDelay = element.GetAttributeFloat("staytriggereddelay", 0.0f);
236  randomTriggerInterval = element.GetAttributeFloat("randomtriggerinterval", 0.0f);
237  randomTriggerProbability = element.GetAttributeFloat("randomtriggerprobability", 0.0f);
238 
239  UseNetworkSyncing = element.GetAttributeBool("networksyncing", false);
240 
241  unrotatedForce =
242  element.GetAttribute("force") != null && element.GetAttribute("force").Value.Contains(',') ?
243  element.GetAttributeVector2("force", Vector2.Zero) :
244  new Vector2(element.GetAttributeFloat("force", 0.0f), 0.0f);
245 
246  ForceFluctuationInterval = element.GetAttributeFloat("forcefluctuationinterval", 0.01f);
247  ForceFluctuationStrength = Math.Max(element.GetAttributeFloat("forcefluctuationstrength", 0.0f), 0.0f);
248  ForceFalloff = element.GetAttributeBool("forcefalloff", true);
249  GlobalForceDecreaseInterval = element.GetAttributeFloat("globalforcedecreaseinterval", 0.0f);
250 
251  ForceVelocityLimit = ConvertUnits.ToSimUnits(element.GetAttributeFloat("forcevelocitylimit", float.MaxValue));
252  string forceModeStr = element.GetAttributeString("forcemode", "Force");
253  if (!Enum.TryParse(forceModeStr, out forceMode))
254  {
255  DebugConsole.ThrowError("Error in LevelTrigger config: \"" + forceModeStr + "\" is not a valid force mode.");
256  }
257  CalculateDirectionalForce();
258 
259  string triggeredByStr = element.GetAttributeString("triggeredby", "Character");
260  if (!Enum.TryParse(triggeredByStr, out triggeredBy))
261  {
262  Identifier speciesOrGroup = triggeredByStr.ToIdentifier();
263  if (CharacterPrefab.Prefabs.Any(p => p.MatchesSpeciesNameOrGroup(speciesOrGroup)))
264  {
265  triggerSpeciesOrGroup = speciesOrGroup;
266  triggeredBy = TriggererType.Character;
267  }
268  else
269  {
270  DebugConsole.ThrowError("Error in LevelTrigger config: \"" + triggeredByStr + "\" is not a valid triggerer type.");
271  }
272  }
273  if (PhysicsBody != null)
274  {
276  }
277 
278  TriggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
279 
280  var tagsArray = element.GetAttributeStringArray("tags", Array.Empty<string>());
281  foreach (string tag in tagsArray)
282  {
283  tags.Add(tag.ToLowerInvariant());
284  }
285 
286  if (triggeredBy.HasFlag(TriggererType.OtherTrigger))
287  {
288  var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", Array.Empty<string>());
289  foreach (string tag in otherTagsArray)
290  {
291  allowedOtherTriggerTags.Add(tag.ToLowerInvariant());
292  }
293  }
294 
295  string debugName = string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : $"LevelTrigger in {parentDebugName}";
296  foreach (var subElement in element.Elements())
297  {
298  switch (subElement.Name.ToString().ToLowerInvariant())
299  {
300  case "statuseffect":
301  LoadStatusEffect(statusEffects, subElement, debugName);
302  break;
303  case "attack":
304  case "damage":
305  LoadAttack(subElement, debugName, triggerOnce, attacks);
306  break;
307  }
308  }
309 
310  conditionals = PropertyConditional.LoadConditionals(element);
311 
312  forceFluctuationTimer = Rand.Range(0.0f, ForceFluctuationInterval);
313  randomTriggerTimer = Rand.Range(0.0f, randomTriggerInterval);
314  }
315 
316  public static Category GetCollisionCategories(TriggererType triggeredBy)
317  {
318  var collidesWith = Physics.CollisionNone;
319  if (triggeredBy.HasFlag(TriggererType.Human) || triggeredBy.HasFlag(TriggererType.Creature)) { collidesWith |= Physics.CollisionCharacter; }
320  if (triggeredBy.HasFlag(TriggererType.Item)) { collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile; }
321  if (triggeredBy.HasFlag(TriggererType.Submarine)) { collidesWith |= Physics.CollisionWall; }
322  return collidesWith;
323  }
324 
325  private void CalculateDirectionalForce()
326  {
327  var ca = (float)Math.Cos(-Rotation);
328  var sa = (float)Math.Sin(-Rotation);
329 
330  Force = new Vector2(
331  ca * unrotatedForce.X + sa * unrotatedForce.Y,
332  -sa * unrotatedForce.X + ca * unrotatedForce.Y);
333  }
334 
335  public static void LoadStatusEffect(List<StatusEffect> statusEffects, ContentXElement element, string parentDebugName)
336  {
337  statusEffects.Add(StatusEffect.Load(element, parentDebugName));
338  }
339 
340  public static void LoadAttack(ContentXElement element, string parentDebugName, bool triggerOnce, List<Attack> attacks)
341  {
342  var attack = new Attack(element, parentDebugName);
343  if (!triggerOnce)
344  {
345  var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
346  attack.Afflictions.Clear();
347  foreach (Affliction affliction in multipliedAfflictions)
348  {
349  attack.Afflictions.Add(affliction, null);
350  }
351  }
352  attacks.Add(attack);
353  }
354 
355  private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact)
356  {
357  Entity entity = GetEntity(fixtureB);
358  if (entity == null) { return false; }
359  if (!IsTriggeredByEntity(entity, triggeredBy, triggerSpeciesOrGroup: triggerSpeciesOrGroup, conditionals: conditionals, mustBeOutside: true)) { return false; }
360  if (!triggerers.Contains(entity))
361  {
362  if (!IsTriggered)
363  {
364  OnTriggered?.Invoke(this, entity);
365  }
366  TriggererPosition[entity] = entity.WorldPosition;
367  triggerers.Add(entity);
368  }
369  return true;
370  }
371 
372  public static bool IsTriggeredByEntity(
373  Entity entity,
374  TriggererType triggeredBy,
375  Identifier triggerSpeciesOrGroup,
377  (bool mustBe, Submarine sub) mustBeOnSpecificSub = default,
378  bool mustBeOutside = false)
379  {
380  if (entity is Character character)
381  {
382  if (mustBeOutside && character.CurrentHull != null) { return false; }
383  if (mustBeOnSpecificSub.mustBe && character.Submarine != mustBeOnSpecificSub.sub) { return false; }
384  if (!triggerSpeciesOrGroup.IsEmpty)
385  {
386  if (character.SpeciesName != triggerSpeciesOrGroup && character.Group != triggerSpeciesOrGroup) { return false; }
387  }
388  if (character.IsHuman)
389  {
390  if (!triggeredBy.HasFlag(TriggererType.Human)) { return false; }
391  }
392  else
393  {
394  if (!triggeredBy.HasFlag(TriggererType.Creature)) { return false; }
395  }
396  }
397  else if (entity is Item item)
398  {
399  if (mustBeOutside && item.CurrentHull != null) { return false; }
400  if (mustBeOnSpecificSub.mustBe && item.Submarine != mustBeOnSpecificSub.sub) { return false; }
401  if (!triggeredBy.HasFlag(TriggererType.Item)) { return false; }
402  }
403  else if (entity is Submarine)
404  {
405  if (!triggeredBy.HasFlag(TriggererType.Submarine)) { return false; }
406  }
407  if (conditionals != null && entity is ISerializableEntity serializableEntity)
408  {
409  if (!PropertyConditional.CheckConditionals(serializableEntity, conditionals.Conditionals, conditionals.LogicalOperator)) { return false; }
410  }
411  return true;
412  }
413 
414  private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact)
415  {
416  Entity entity = GetEntity(fixtureB);
417  if (entity == null) { return; }
418 
419  if (entity is Character character &&
420  (!character.Enabled || character.Removed) &&
421  triggerers.Contains(entity))
422  {
423  TriggererPosition.Remove(entity);
424  triggerers.Remove(entity);
425  return;
426  }
427 
428  if (CheckContactsForOtherFixtures(PhysicsBody, fixtureB, entity)) { return; }
429 
430  if (triggerers.Contains(entity))
431  {
432  TriggererPosition.Remove(entity);
433  triggerers.Remove(entity);
434  }
435  }
436 
444  public static bool CheckContactsForOtherFixtures(PhysicsBody triggerBody, Fixture separatingFixture, Entity separatingEntity)
445  {
446  //check if there are contacts with any other fixture of the trigger
447  //(the OnSeparation callback happens when two fixtures separate,
448  //e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body)
449  foreach (Fixture triggerFixture in triggerBody.FarseerBody.FixtureList)
450  {
451  ContactEdge contactEdge = triggerFixture.Body.ContactList;
452  while (contactEdge != null)
453  {
454  if (contactEdge.Contact != null &&
455  contactEdge.Contact.Enabled &&
456  contactEdge.Contact.IsTouching)
457  {
458  //which fixture of this contact belongs to the "other" body (not the trigger itself)
459  Fixture otherFixture =
460  contactEdge.Contact.FixtureA == triggerFixture ?
461  contactEdge.Contact.FixtureB :
462  contactEdge.Contact.FixtureA;
463  if (otherFixture != separatingFixture)
464  {
465  var otherEntity = GetEntity(otherFixture);
466  if (otherEntity == separatingEntity) { return true; }
467  }
468  }
469  contactEdge = contactEdge.Next;
470  }
471  }
472  return false;
473  }
474 
478  public static bool CheckContactsForEntity(PhysicsBody triggerBody, Entity targetEntity)
479  {
480  foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList)
481  {
482  ContactEdge contactEdge = fixture.Body.ContactList;
483  while (contactEdge != null)
484  {
485  if (contactEdge.Contact != null &&
486  contactEdge.Contact.Enabled &&
487  contactEdge.Contact.IsTouching)
488  {
489  if ((contactEdge.Contact.FixtureA.Body == triggerBody.FarseerBody && GetEntity(contactEdge.Contact.FixtureB) == targetEntity) ||
490  (contactEdge.Contact.FixtureB.Body == triggerBody.FarseerBody && GetEntity(contactEdge.Contact.FixtureA) == targetEntity))
491  {
492  return true;
493  }
494  }
495  contactEdge = contactEdge.Next;
496  }
497  }
498  return false;
499  }
500 
501  public static Entity GetEntity(Fixture fixture)
502  {
503  if (fixture.Body == null || fixture.Body.UserData == null) { return null; }
504  if (fixture.Body.UserData is Entity entity) { return entity; }
505  if (fixture.Body.UserData is Limb limb) { return limb.character; }
506  if (fixture.Body.UserData is SubmarineBody subBody) { return subBody.Submarine; }
507  return null;
508  }
509 
513  public void OtherTriggered(LevelTrigger otherTrigger, Entity triggerer)
514  {
515  if (!triggeredBy.HasFlag(TriggererType.OtherTrigger) || stayTriggeredDelay <= 0.0f) { return; }
516 
517  //check if the other trigger has appropriate tags
518  if (allowedOtherTriggerTags.Count > 0)
519  {
520  if (!allowedOtherTriggerTags.Any(t => otherTrigger.tags.Contains(t))) { return; }
521  }
522 
523  if (Vector2.DistanceSquared(WorldPosition, otherTrigger.WorldPosition) <= otherTrigger.TriggerOthersDistance * otherTrigger.TriggerOthersDistance)
524  {
525  bool wasAlreadyTriggered = IsTriggered;
526  triggeredTimer = stayTriggeredDelay;
527  if (!wasAlreadyTriggered)
528  {
529  if (!IsTriggeredByEntity(triggerer, triggeredBy, triggerSpeciesOrGroup, conditionals, mustBeOutside: true)) { return; }
530  if (!triggerers.Contains(triggerer))
531  {
532  if (!IsTriggered)
533  {
534  OnTriggered?.Invoke(this, triggerer);
535  }
536  TriggererPosition[triggerer] = triggerer.WorldPosition;
537  triggerers.Add(triggerer);
538  }
539  }
540  }
541  }
542 
543  private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
544 
545  public void Update(float deltaTime)
546  {
547  if (ParentTrigger != null && !ParentTrigger.IsTriggered) { return; }
548 
549 
550  bool isNotClient = true;
551 #if CLIENT
552  isNotClient = GameMain.Client == null;
553 #endif
554 
555  if (!UseNetworkSyncing || isNotClient)
556  {
559  {
560  NeedsNetworkSyncing |= currentForceFluctuation > 0.0f;
561  currentForceFluctuation = 0.0f;
562  }
563  else if (ForceFluctuationStrength > 0.0f)
564  {
565  //no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers
566  if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any())
567  {
568  forceFluctuationTimer += deltaTime;
569  if (forceFluctuationTimer > ForceFluctuationInterval)
570  {
571  NeedsNetworkSyncing = true;
572  currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f);
573  forceFluctuationTimer = 0.0f;
574  }
575  }
576  }
577 
578  if (randomTriggerProbability > 0.0f)
579  {
580  randomTriggerTimer += deltaTime;
581  if (randomTriggerTimer > randomTriggerInterval)
582  {
583  if (Rand.Range(0.0f, 1.0f) < randomTriggerProbability)
584  {
585  NeedsNetworkSyncing = true;
586  triggeredTimer = stayTriggeredDelay;
587  }
588  randomTriggerTimer = 0.0f;
589  }
590  }
591  }
592 
594 
595  if (stayTriggeredDelay > 0.0f)
596  {
597  if (triggerers.Count == 0)
598  {
599  triggeredTimer -= deltaTime;
600  }
601  else
602  {
603  triggeredTimer = stayTriggeredDelay;
604  }
605  }
606 
607  if (triggerOnce && triggeredOnce)
608  {
609  return;
610  }
611 
612  foreach (Entity triggerer in triggerers)
613  {
614  if (triggerer.Removed) { continue; }
615 
616  ApplyStatusEffects(statusEffects, worldPosition, triggerer, deltaTime, targets);
617 
618  if (triggerer is IDamageable damageable)
619  {
620  ApplyAttacks(attacks, damageable, worldPosition, deltaTime);
621  }
622  else if (triggerer is Submarine submarine)
623  {
624  ApplyAttacks(attacks, worldPosition, deltaTime);
625  if (!InfectIdentifier.IsEmpty)
626  {
627  submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance);
628  }
629  }
630 
631  if (Force.LengthSquared() > 0.01f)
632  {
633  if (triggerer is Character character)
634  {
635  ApplyForce(character.AnimController.Collider);
636  foreach (Limb limb in character.AnimController.Limbs)
637  {
638  if (limb.IsSevered) { continue; }
639  ApplyForce(limb.body);
640  }
641  }
642  else if (triggerer is Submarine submarine)
643  {
644  ApplyForce(submarine.SubBody.Body);
645  }
646  }
647 
648  if (triggerer == Character.Controlled || triggerer == Character.Controlled?.Submarine)
649  {
650  GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, cameraShake);
651  }
652  }
653 
654  if (triggerOnce && triggerers.Count > 0)
655  {
656  PhysicsBody.Enabled = false;
657  triggeredOnce = true;
658  }
659  }
660 
661  private static readonly List<Entity> triggerersToRemove = new List<Entity>();
662  public static void RemoveInActiveTriggerers(PhysicsBody physicsBody, HashSet<Entity> triggerers)
663  {
664  if (physicsBody == null) { return; }
665 
666  triggerersToRemove.Clear();
667  foreach (var triggerer in triggerers)
668  {
669  if (triggerer.Removed)
670  {
671  triggerersToRemove.Add(triggerer);
672  }
673  else if (!CheckContactsForEntity(physicsBody, triggerer))
674  {
675  triggerersToRemove.Add(triggerer);
676  }
677  }
678  foreach (var triggerer in triggerersToRemove)
679  {
680  triggerers.Remove(triggerer);
681  }
682  }
683 
684  public static void ApplyStatusEffects(List<StatusEffect> statusEffects, Vector2 worldPosition, Entity triggerer, float deltaTime, List<ISerializableEntity> targets, Item targetItem = null)
685  {
686  foreach (StatusEffect effect in statusEffects)
687  {
688  if (effect.type == ActionType.OnBroken) { return; }
689  Vector2? position = null;
690  if (effect.HasTargetType(StatusEffect.TargetType.This))
691  {
692  position = worldPosition;
693  if (targetItem != null)
694  {
695  effect.Apply(effect.type, deltaTime, triggerer, targetItem.AllPropertyObjects, position);
696  }
697  }
698  if (triggerer is Character character)
699  {
700  effect.Apply(effect.type, deltaTime, triggerer, character, position);
701  if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null)
702  {
703  foreach (Item item in character.Inventory.AllItemsMod)
704  {
705  if (item.ContainedItems == null) { continue; }
706  foreach (Item containedItem in item.ContainedItems)
707  {
708  effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position);
709  }
710  }
711  }
712  }
713  else if (triggerer is Item item)
714  {
715  effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position);
716  }
717  else if (triggerer is Submarine sub)
718  {
719  effect.Apply(effect.type, deltaTime, sub, Array.Empty<ISerializableEntity>(), position);
720  }
721  if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
722  {
723  targets.Clear();
724  effect.AddNearbyTargets(worldPosition, targets);
725  effect.Apply(effect.type, deltaTime, triggerer, targets);
726  }
727  }
728  }
729 
733  public static void ApplyAttacks(List<Attack> attacks, IDamageable damageable, Vector2 worldPosition, float deltaTime)
734  {
735  foreach (Attack attack in attacks)
736  {
737  attack.DoDamage(null, damageable, worldPosition, deltaTime, false);
738  }
739  }
740 
744  public static void ApplyAttacks(List<Attack> attacks, Vector2 worldPosition, float deltaTime)
745  {
746  foreach (Attack attack in attacks)
747  {
748  float structureDamage = attack.GetStructureDamage(deltaTime);
749  if (structureDamage > 0.0f)
750  {
751  Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f, emitWallDamageParticles: attack.EmitStructureDamageParticles);
752  }
753  }
754  }
755 
756  private void ApplyForce(PhysicsBody body)
757  {
758  if (body == null) { return; }
759 
760  float distFactor = 1.0f;
761  if (ForceFalloff)
762  {
763  distFactor = GetDistanceFactor(body, PhysicsBody, ColliderRadius);
764  if (distFactor < 0.0f) return;
765  }
766 
767  if (MathUtils.NearlyEqual(currentForceFluctuation, 0.0f)) { return; }
768 
769  switch (ForceMode)
770  {
771  case TriggerForceMode.Force:
772  if (ForceVelocityLimit < 1000.0f)
773  body.ApplyForce(Force * currentForceFluctuation * distFactor, ForceVelocityLimit);
774  else
775  body.ApplyForce(Force * currentForceFluctuation * distFactor);
776  break;
777  case TriggerForceMode.Acceleration:
778  if (ForceVelocityLimit < 1000.0f)
779  body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, ForceVelocityLimit);
780  else
781  body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor);
782  break;
783  case TriggerForceMode.Impulse:
784  if (ForceVelocityLimit < 1000.0f)
785  body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: ForceVelocityLimit);
786  else
787  body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor);
788  break;
789  case TriggerForceMode.LimitVelocity:
790  float maxVel = ForceVelocityLimit * currentForceFluctuation * distFactor;
791  if (body.LinearVelocity.LengthSquared() > maxVel * maxVel)
792  {
793  body.ApplyForce(
794  Vector2.Normalize(-body.LinearVelocity) *
795  Force.Length() * body.Mass * currentForceFluctuation * distFactor,
796  maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
797  }
798  break;
799  }
800  }
801 
802  public static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius)
803  {
804  return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition)) / colliderRadius;
805  }
806 
807  public Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
808  {
809  Vector2 baseVel = GetWaterFlowVelocity();
810  if (baseVel.LengthSquared() < 0.1f) return Vector2.Zero;
811 
812  float triggerSize = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.Radius, PhysicsBody.Width / 2.0f), PhysicsBody.Height / 2.0f));
813  float dist = Vector2.Distance(viewPosition, WorldPosition);
814  if (dist > triggerSize) return Vector2.Zero;
815 
816  return baseVel * (1.0f - dist / triggerSize);
817  }
818 
819  public Vector2 GetWaterFlowVelocity()
820  {
821  if (Force == Vector2.Zero || ForceMode == TriggerForceMode.LimitVelocity) { return Vector2.Zero; }
822 
823  Vector2 vel = Force;
824  if (ForceMode == TriggerForceMode.Acceleration)
825  {
826  vel *= 1000.0f;
827  }
828  else if (ForceMode == TriggerForceMode.Impulse)
829  {
830  vel /= (float)Timing.Step;
831  }
832  return vel.ClampLength(ConvertUnits.ToDisplayUnits(ForceVelocityLimit)) * currentForceFluctuation;
833  }
834 
835  public void ServerWrite(IWriteMessage msg, Client c)
836  {
837  if (ForceFluctuationStrength > 0.0f)
838  {
839  msg.WriteRangedSingle(MathHelper.Clamp(currentForceFluctuation, 0.0f, 1.0f), 0.0f, 1.0f, 8);
840  }
841  if (stayTriggeredDelay > 0.0f)
842  {
843  msg.WriteRangedSingle(MathHelper.Clamp(triggeredTimer, 0.0f, stayTriggeredDelay), 0.0f, stayTriggeredDelay, 16);
844  }
845  }
846  }
847 }
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
Definition: Attack.cs:102
float GetStructureDamage(float deltaTime)
Definition: Attack.cs:368
bool EmitStructureDamageParticles
Definition: Attack.cs:184
AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
Definition: Attack.cs:518
float DamageRange
Definition: Attack.cs:148
static readonly PrefabCollection< CharacterPrefab > Prefabs
string? GetAttributeString(string key, string? def)
IEnumerable< XAttribute > Attributes()
float GetAttributeFloat(string key, float def)
Vector2 GetAttributeVector2(string key, in Vector2 def)
bool GetAttributeBool(string key, bool def)
string?[] GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant=false)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
Explosions are area of effect attacks that can damage characters, items and structures.
Definition: Explosion.cs:22
static Dictionary< Structure, float > RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker=null, IEnumerable< Submarine > ignoredSubmarines=null, bool emitWallDamageParticles=true, bool createWallDamageProjectiles=false, bool distanceFalloff=true)
Returns a dictionary where the keys are the structures that took damage and the values are the amount...
Definition: Explosion.cs:652
static GameScreen GameScreen
Definition: GameMain.cs:56
override Camera Cam
Definition: GameScreen.cs:27
IReadOnlyList< ISerializableEntity > AllPropertyObjects
LevelTrigger(ContentXElement element, Vector2 position, float rotation, float scale=1.0f, string parentDebugName="")
static void LoadStatusEffect(List< StatusEffect > statusEffects, ContentXElement element, string parentDebugName)
bool ForceFalloff
does the force diminish by distance
static void ApplyAttacks(List< Attack > attacks, Vector2 worldPosition, float deltaTime)
Applies attacks to structures.
Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
Vector2 GetWaterFlowVelocity()
void OtherTriggered(LevelTrigger otherTrigger, Entity triggerer)
Another trigger was triggered, check if this one should react to it
IEnumerable< Entity > Triggerers
static Entity GetEntity(Fixture fixture)
static void LoadAttack(ContentXElement element, string parentDebugName, bool triggerOnce, List< Attack > attacks)
void ServerWrite(IWriteMessage msg, Client c)
LevelTrigger ParentTrigger
Definition: LevelTrigger.cs:78
Action< LevelTrigger, Entity > OnTriggered
Definition: LevelTrigger.cs:37
static bool CheckContactsForOtherFixtures(PhysicsBody triggerBody, Fixture separatingFixture, Entity separatingEntity)
Checks whether any fixture of the trigger body is in contact with any fixture belonging to the physic...
float ForceVelocityLimit
Stop applying forces to objects if they're moving faster than this
TriggerForceMode ForceMode
Dictionary< Entity, Vector2 > TriggererPosition
Definition: LevelTrigger.cs:81
static bool CheckContactsForEntity(PhysicsBody triggerBody, Entity targetEntity)
Are there any active contacts between the physics body and the target entity
static bool IsTriggeredByEntity(Entity entity, TriggererType triggeredBy, Identifier triggerSpeciesOrGroup, PropertyConditional.LogicalComparison conditionals,(bool mustBe, Submarine sub) mustBeOnSpecificSub=default, bool mustBeOutside=false)
void Update(float deltaTime)
static void RemoveInActiveTriggerers(PhysicsBody physicsBody, HashSet< Entity > triggerers)
IEnumerable< StatusEffect > StatusEffects
Definition: LevelTrigger.cs:44
static void ApplyStatusEffects(List< StatusEffect > statusEffects, Vector2 worldPosition, Entity triggerer, float deltaTime, List< ISerializableEntity > targets, Item targetItem=null)
static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius)
static void ApplyAttacks(List< Attack > attacks, IDamageable damageable, Vector2 worldPosition, float deltaTime)
Applies attacks to a damageable.
static Category GetCollisionCategories(TriggererType triggeredBy)
bool? IsSevered
Definition: Limb.cs:351
PhysicsBody body
Definition: Limb.cs:217
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
Bundles up a bunch of conditionals with a logical operator.
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
static bool CheckConditionals(ISerializableEntity conditionalTarget, IEnumerable< PropertyConditional > conditionals, LogicalOperatorType logicalOperator)
static ? LogicalComparison LoadConditionals(ContentXElement element, LogicalOperatorType defaultOperatorType=LogicalOperatorType.And)
Seeks for child elements of name "conditional" and bundles them with an attribute of name "comparison...
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)
static StatusEffect Load(ContentXElement element, string parentDebugName)
void AddNearbyTargets(Vector2 worldPosition, List< ISerializableEntity > targets)
readonly ActionType type
virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition=null)
void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:26