4 using FarseerPhysics.Dynamics;
5 using FarseerPhysics.Dynamics.Contacts;
6 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
9 using System.Collections.Immutable;
42 private readonly List<StatusEffect> statusEffects =
new List<StatusEffect>();
45 get {
return statusEffects; }
51 private readonly List<Attack> attacks =
new List<Attack>();
53 private readonly
float cameraShake;
54 private Vector2 unrotatedForce;
55 private float forceFluctuationTimer, currentForceFluctuation = 1.0f;
57 private readonly HashSet<Entity> triggerers =
new HashSet<Entity>();
60 private readonly Identifier triggerSpeciesOrGroup;
63 private readonly
float randomTriggerInterval;
64 private readonly
float randomTriggerProbability;
65 private float randomTriggerTimer;
67 private float triggeredTimer;
68 private readonly HashSet<string> tags =
new HashSet<string>();
71 private readonly HashSet<string> allowedOtherTriggerTags =
new HashSet<string>();
76 private readonly
float stayTriggeredDelay;
86 private Vector2 worldPosition;
89 get {
return worldPosition; }
92 worldPosition = value;
104 CalculateDirectionalForce();
114 get {
return triggerers.AsEnumerable(); }
121 return (triggerers.Count > 0 || triggeredTimer > 0.0f) &&
160 get {
return forceMode; }
203 private bool triggeredOnce;
204 private readonly
bool triggerOnce;
210 worldPosition = position;
215 CollisionCategories = Physics.CollisionLevel,
216 CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall,
236 randomTriggerInterval = element.
GetAttributeFloat(
"randomtriggerinterval", 0.0f);
237 randomTriggerProbability = element.
GetAttributeFloat(
"randomtriggerprobability", 0.0f);
253 if (!Enum.TryParse(forceModeStr, out forceMode))
255 DebugConsole.ThrowError(
"Error in LevelTrigger config: \"" + forceModeStr +
"\" is not a valid force mode.");
257 CalculateDirectionalForce();
260 if (!Enum.TryParse(triggeredByStr, out triggeredBy))
262 Identifier speciesOrGroup = triggeredByStr.ToIdentifier();
265 triggerSpeciesOrGroup = speciesOrGroup;
270 DebugConsole.ThrowError(
"Error in LevelTrigger config: \"" + triggeredByStr +
"\" is not a valid triggerer type.");
281 foreach (
string tag
in tagsArray)
283 tags.Add(tag.ToLowerInvariant());
288 var otherTagsArray = element.GetAttributeStringArray(
"allowedothertriggertags", Array.Empty<
string>());
289 foreach (
string tag
in otherTagsArray)
291 allowedOtherTriggerTags.Add(tag.ToLowerInvariant());
295 string debugName =
string.IsNullOrEmpty(parentDebugName) ?
"LevelTrigger" : $
"LevelTrigger in {parentDebugName}";
296 foreach (var subElement
in element.Elements())
298 switch (subElement.Name.ToString().ToLowerInvariant())
305 LoadAttack(subElement, debugName, triggerOnce, attacks);
313 randomTriggerTimer = Rand.Range(0.0f, randomTriggerInterval);
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; }
325 private void CalculateDirectionalForce()
327 var ca = (float)Math.Cos(-
Rotation);
328 var sa = (float)Math.Sin(-
Rotation);
331 ca * unrotatedForce.X + sa * unrotatedForce.Y,
332 -sa * unrotatedForce.X + ca * unrotatedForce.Y);
342 var attack =
new Attack(element, parentDebugName);
345 var multipliedAfflictions = attack.GetMultipliedAfflictions((
float)Timing.Step);
346 attack.Afflictions.Clear();
347 foreach (
Affliction affliction
in multipliedAfflictions)
349 attack.Afflictions.Add(affliction,
null);
355 private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact)
358 if (entity ==
null) {
return false; }
359 if (!
IsTriggeredByEntity(entity, triggeredBy, triggerSpeciesOrGroup: triggerSpeciesOrGroup, conditionals: conditionals, mustBeOutside:
true)) {
return false; }
360 if (!triggerers.Contains(entity))
367 triggerers.Add(entity);
375 Identifier triggerSpeciesOrGroup,
377 (
bool mustBe,
Submarine sub) mustBeOnSpecificSub =
default,
378 bool mustBeOutside =
false)
382 if (mustBeOutside && character.CurrentHull !=
null) {
return false; }
383 if (mustBeOnSpecificSub.mustBe && character.Submarine != mustBeOnSpecificSub.sub) {
return false; }
384 if (!triggerSpeciesOrGroup.IsEmpty)
386 if (character.SpeciesName != triggerSpeciesOrGroup && character.Group != triggerSpeciesOrGroup) {
return false; }
388 if (character.IsHuman)
390 if (!triggeredBy.HasFlag(
TriggererType.Human)) {
return false; }
394 if (!triggeredBy.HasFlag(
TriggererType.Creature)) {
return false; }
397 else if (entity is
Item item)
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; }
405 if (!triggeredBy.HasFlag(
TriggererType.Submarine)) {
return false; }
414 private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact)
417 if (entity ==
null) {
return; }
419 if (entity is Character character &&
420 (!character.Enabled || character.Removed) &&
421 triggerers.Contains(entity))
424 triggerers.Remove(entity);
430 if (triggerers.Contains(entity))
433 triggerers.Remove(entity);
449 foreach (Fixture triggerFixture
in triggerBody.FarseerBody.FixtureList)
451 ContactEdge contactEdge = triggerFixture.Body.ContactList;
452 while (contactEdge !=
null)
454 if (contactEdge.Contact !=
null &&
455 contactEdge.Contact.Enabled &&
456 contactEdge.Contact.IsTouching)
459 Fixture otherFixture =
460 contactEdge.Contact.FixtureA == triggerFixture ?
461 contactEdge.Contact.FixtureB :
462 contactEdge.Contact.FixtureA;
463 if (otherFixture != separatingFixture)
465 var otherEntity =
GetEntity(otherFixture);
466 if (otherEntity == separatingEntity) {
return true; }
469 contactEdge = contactEdge.Next;
480 foreach (Fixture fixture
in triggerBody.FarseerBody.FixtureList)
482 ContactEdge contactEdge = fixture.Body.ContactList;
483 while (contactEdge !=
null)
485 if (contactEdge.Contact !=
null &&
486 contactEdge.Contact.Enabled &&
487 contactEdge.Contact.IsTouching)
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))
495 contactEdge = contactEdge.Next;
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; }
515 if (!triggeredBy.HasFlag(
TriggererType.OtherTrigger) || stayTriggeredDelay <= 0.0f) {
return; }
518 if (allowedOtherTriggerTags.Count > 0)
520 if (!allowedOtherTriggerTags.Any(t => otherTrigger.tags.Contains(t))) {
return; }
526 triggeredTimer = stayTriggeredDelay;
527 if (!wasAlreadyTriggered)
529 if (!
IsTriggeredByEntity(triggerer, triggeredBy, triggerSpeciesOrGroup, conditionals, mustBeOutside:
true)) {
return; }
530 if (!triggerers.Contains(triggerer))
537 triggerers.Add(triggerer);
543 private readonly List<ISerializableEntity> targets =
new List<ISerializableEntity>();
550 bool isNotClient =
true;
552 isNotClient =
GameMain.Client ==
null;
561 currentForceFluctuation = 0.0f;
568 forceFluctuationTimer += deltaTime;
573 forceFluctuationTimer = 0.0f;
578 if (randomTriggerProbability > 0.0f)
580 randomTriggerTimer += deltaTime;
581 if (randomTriggerTimer > randomTriggerInterval)
583 if (Rand.Range(0.0f, 1.0f) < randomTriggerProbability)
586 triggeredTimer = stayTriggeredDelay;
588 randomTriggerTimer = 0.0f;
595 if (stayTriggeredDelay > 0.0f)
597 if (triggerers.Count == 0)
599 triggeredTimer -= deltaTime;
603 triggeredTimer = stayTriggeredDelay;
607 if (triggerOnce && triggeredOnce)
612 foreach (
Entity triggerer
in triggerers)
614 if (triggerer.
Removed) {
continue; }
620 ApplyAttacks(attacks, damageable, worldPosition, deltaTime);
622 else if (triggerer is
Submarine submarine)
631 if (
Force.LengthSquared() > 0.01f)
635 ApplyForce(character.AnimController.Collider);
636 foreach (
Limb limb
in character.AnimController.Limbs)
639 ApplyForce(limb.
body);
642 else if (triggerer is
Submarine submarine)
644 ApplyForce(submarine.SubBody.Body);
654 if (triggerOnce && triggerers.Count > 0)
657 triggeredOnce =
true;
661 private static readonly List<Entity> triggerersToRemove =
new List<Entity>();
664 if (physicsBody ==
null) {
return; }
666 triggerersToRemove.Clear();
667 foreach (var triggerer
in triggerers)
671 triggerersToRemove.Add(triggerer);
675 triggerersToRemove.Add(triggerer);
678 foreach (var triggerer
in triggerersToRemove)
680 triggerers.Remove(triggerer);
684 public static void ApplyStatusEffects(List<StatusEffect> statusEffects, Vector2 worldPosition,
Entity triggerer,
float deltaTime, List<ISerializableEntity> targets,
Item targetItem =
null)
689 Vector2? position =
null;
692 position = worldPosition;
693 if (targetItem !=
null)
695 effect.
Apply(effect.
type, deltaTime, triggerer, targetItem.AllPropertyObjects, position);
700 effect.
Apply(effect.
type, deltaTime, triggerer, character, position);
703 foreach (
Item item
in character.Inventory.AllItemsMod)
713 else if (triggerer is
Item item)
715 effect.
Apply(effect.
type, deltaTime, triggerer, item.AllPropertyObjects, position);
725 effect.
Apply(effect.
type, deltaTime, triggerer, targets);
735 foreach (
Attack attack
in attacks)
737 attack.
DoDamage(
null, damageable, worldPosition, deltaTime,
false);
744 public static void ApplyAttacks(List<Attack> attacks, Vector2 worldPosition,
float deltaTime)
746 foreach (
Attack attack
in attacks)
749 if (structureDamage > 0.0f)
758 if (body ==
null) {
return; }
760 float distFactor = 1.0f;
764 if (distFactor < 0.0f)
return;
767 if (MathUtils.NearlyEqual(currentForceFluctuation, 0.0f)) {
return; }
795 Force.Length() * body.
Mass * currentForceFluctuation * distFactor,
796 maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
804 return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.
SimPosition, triggerBody.
SimPosition)) / colliderRadius;
810 if (baseVel.LengthSquared() < 0.1f)
return Vector2.Zero;
814 if (dist > triggerSize)
return Vector2.Zero;
816 return baseVel * (1.0f - dist / triggerSize);
830 vel /= (float)Timing.Step;
832 return vel.ClampLength(ConvertUnits.ToDisplayUnits(
ForceVelocityLimit)) * currentForceFluctuation;
839 msg.
WriteRangedSingle(MathHelper.Clamp(currentForceFluctuation, 0.0f, 1.0f), 0.0f, 1.0f, 8);
841 if (stayTriggeredDelay > 0.0f)
843 msg.
WriteRangedSingle(MathHelper.Clamp(triggeredTimer, 0.0f, stayTriggeredDelay), 0.0f, stayTriggeredDelay, 16);
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
float GetStructureDamage(float deltaTime)
bool EmitStructureDamageParticles
AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
static Character Controlled
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
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
IReadOnlyList< ISerializableEntity > AllPropertyObjects
IEnumerable< Item > ContainedItems
LevelObjectManager LevelObjectManager
float GlobalForceDecreaseTimer
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
Identifier InfectIdentifier
IEnumerable< Entity > Triggerers
float TriggerOthersDistance
static Entity GetEntity(Fixture fixture)
static void LoadAttack(ContentXElement element, string parentDebugName, bool triggerOnce, List< Attack > attacks)
float ForceFluctuationInterval
void ServerWrite(IWriteMessage msg, Client c)
float GlobalForceDecreaseInterval
LevelTrigger ParentTrigger
Action< LevelTrigger, Entity > OnTriggered
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
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
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.
float ForceFluctuationStrength
static Category GetCollisionCategories(TriggererType triggeredBy)
void ApplyLinearImpulse(Vector2 impulse)
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 ...
bool HasTargetType(TargetType targetType)
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.