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