4 using FarseerPhysics.Dynamics;
5 using FarseerPhysics.Dynamics.Contacts;
6 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
40 private readonly List<StatusEffect> statusEffects =
new List<StatusEffect>();
43 get {
return statusEffects; }
49 private readonly List<Attack> attacks =
new List<Attack>();
51 private readonly
float cameraShake;
52 private Vector2 unrotatedForce;
53 private float forceFluctuationTimer, currentForceFluctuation = 1.0f;
55 private readonly HashSet<Entity> triggerers =
new HashSet<Entity>();
59 private readonly
float randomTriggerInterval;
60 private readonly
float randomTriggerProbability;
61 private float randomTriggerTimer;
63 private float triggeredTimer;
64 private readonly HashSet<string> tags =
new HashSet<string>();
67 private readonly HashSet<string> allowedOtherTriggerTags =
new HashSet<string>();
72 private readonly
float stayTriggeredDelay;
82 private Vector2 worldPosition;
85 get {
return worldPosition; }
88 worldPosition = value;
100 CalculateDirectionalForce();
110 get {
return triggerers.AsEnumerable(); }
117 return (triggerers.Count > 0 || triggeredTimer > 0.0f) &&
156 get {
return forceMode; }
199 private bool triggeredOnce;
200 private readonly
bool triggerOnce;
206 worldPosition = position;
211 CollisionCategories = Physics.CollisionLevel,
212 CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall,
232 randomTriggerInterval = element.
GetAttributeFloat(
"randomtriggerinterval", 0.0f);
233 randomTriggerProbability = element.
GetAttributeFloat(
"randomtriggerprobability", 0.0f);
249 if (!Enum.TryParse(forceModeStr, out forceMode))
251 DebugConsole.ThrowError(
"Error in LevelTrigger config: \"" + forceModeStr +
"\" is not a valid force mode.");
253 CalculateDirectionalForce();
256 if (!Enum.TryParse(triggeredByStr, out triggeredBy))
258 DebugConsole.ThrowError(
"Error in LevelTrigger config: \"" + triggeredByStr +
"\" is not a valid triggerer type.");
268 foreach (
string tag
in tagsArray)
270 tags.Add(tag.ToLowerInvariant());
275 var otherTagsArray = element.GetAttributeStringArray(
"allowedothertriggertags", Array.Empty<
string>());
276 foreach (
string tag
in otherTagsArray)
278 allowedOtherTriggerTags.Add(tag.ToLowerInvariant());
282 string debugName =
string.IsNullOrEmpty(parentDebugName) ?
"LevelTrigger" : $
"LevelTrigger in {parentDebugName}";
283 foreach (var subElement
in element.Elements())
285 switch (subElement.Name.ToString().ToLowerInvariant())
292 LoadAttack(subElement, debugName, triggerOnce, attacks);
298 randomTriggerTimer = Rand.Range(0.0f, randomTriggerInterval);
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; }
310 private void CalculateDirectionalForce()
312 var ca = (float)Math.Cos(-
Rotation);
313 var sa = (float)Math.Sin(-
Rotation);
316 ca * unrotatedForce.X + sa * unrotatedForce.Y,
317 -sa * unrotatedForce.X + ca * unrotatedForce.Y);
327 var attack =
new Attack(element, parentDebugName);
330 var multipliedAfflictions = attack.GetMultipliedAfflictions((
float)Timing.Step);
331 attack.Afflictions.Clear();
332 foreach (
Affliction affliction
in multipliedAfflictions)
334 attack.Afflictions.Add(affliction,
null);
340 private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact)
343 if (entity ==
null) {
return false; }
345 if (!triggerers.Contains(entity))
352 triggerers.Add(entity);
361 if (mustBeOutside && character.CurrentHull !=
null) {
return false; }
362 if (mustBeOnSpecificSub.mustBe && character.Submarine != mustBeOnSpecificSub.sub) {
return false; }
363 if (character.IsHuman)
365 if (!triggeredBy.HasFlag(
TriggererType.Human)) {
return false; }
369 if (!triggeredBy.HasFlag(
TriggererType.Creature)) {
return false; }
372 else if (entity is
Item item)
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; }
380 if (!triggeredBy.HasFlag(
TriggererType.Submarine)) {
return false; }
385 private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact)
388 if (entity ==
null) {
return; }
390 if (entity is Character character &&
391 (!character.Enabled || character.Removed) &&
392 triggerers.Contains(entity))
395 triggerers.Remove(entity);
401 if (triggerers.Contains(entity))
404 triggerers.Remove(entity);
420 foreach (Fixture triggerFixture
in triggerBody.FarseerBody.FixtureList)
422 ContactEdge contactEdge = triggerFixture.Body.ContactList;
423 while (contactEdge !=
null)
425 if (contactEdge.Contact !=
null &&
426 contactEdge.Contact.Enabled &&
427 contactEdge.Contact.IsTouching)
430 Fixture otherFixture =
431 contactEdge.Contact.FixtureA == triggerFixture ?
432 contactEdge.Contact.FixtureB :
433 contactEdge.Contact.FixtureA;
434 if (otherFixture != separatingFixture)
436 var otherEntity =
GetEntity(otherFixture);
437 if (otherEntity == separatingEntity) {
return true; }
440 contactEdge = contactEdge.Next;
451 foreach (Fixture fixture
in triggerBody.FarseerBody.FixtureList)
453 ContactEdge contactEdge = fixture.Body.ContactList;
454 while (contactEdge !=
null)
456 if (contactEdge.Contact !=
null &&
457 contactEdge.Contact.Enabled &&
458 contactEdge.Contact.IsTouching)
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))
466 contactEdge = contactEdge.Next;
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; }
486 if (!triggeredBy.HasFlag(
TriggererType.OtherTrigger) || stayTriggeredDelay <= 0.0f) {
return; }
489 if (allowedOtherTriggerTags.Count > 0)
491 if (!allowedOtherTriggerTags.Any(t => otherTrigger.tags.Contains(t))) {
return; }
497 triggeredTimer = stayTriggeredDelay;
498 if (!wasAlreadyTriggered)
501 if (!triggerers.Contains(triggerer))
508 triggerers.Add(triggerer);
514 private readonly List<ISerializableEntity> targets =
new List<ISerializableEntity>();
521 bool isNotClient =
true;
532 currentForceFluctuation = 0.0f;
539 forceFluctuationTimer += deltaTime;
544 forceFluctuationTimer = 0.0f;
549 if (randomTriggerProbability > 0.0f)
551 randomTriggerTimer += deltaTime;
552 if (randomTriggerTimer > randomTriggerInterval)
554 if (Rand.Range(0.0f, 1.0f) < randomTriggerProbability)
557 triggeredTimer = stayTriggeredDelay;
559 randomTriggerTimer = 0.0f;
566 if (stayTriggeredDelay > 0.0f)
568 if (triggerers.Count == 0)
570 triggeredTimer -= deltaTime;
574 triggeredTimer = stayTriggeredDelay;
578 if (triggerOnce && triggeredOnce)
583 foreach (
Entity triggerer
in triggerers)
585 if (triggerer.
Removed) {
continue; }
591 ApplyAttacks(attacks, damageable, worldPosition, deltaTime);
593 else if (triggerer is
Submarine submarine)
602 if (
Force.LengthSquared() > 0.01f)
606 ApplyForce(character.AnimController.Collider);
607 foreach (
Limb limb
in character.AnimController.Limbs)
610 ApplyForce(limb.
body);
613 else if (triggerer is
Submarine submarine)
615 ApplyForce(submarine.SubBody.Body);
625 if (triggerOnce && triggerers.Count > 0)
628 triggeredOnce =
true;
632 private static readonly List<Entity> triggerersToRemove =
new List<Entity>();
635 if (physicsBody ==
null) {
return; }
637 triggerersToRemove.Clear();
638 foreach (var triggerer
in triggerers)
642 triggerersToRemove.Add(triggerer);
646 triggerersToRemove.Add(triggerer);
649 foreach (var triggerer
in triggerersToRemove)
651 triggerers.Remove(triggerer);
655 public static void ApplyStatusEffects(List<StatusEffect> statusEffects, Vector2 worldPosition,
Entity triggerer,
float deltaTime, List<ISerializableEntity> targets)
660 Vector2? position =
null;
664 effect.
Apply(effect.
type, deltaTime, triggerer, character, position);
667 foreach (
Item item
in character.Inventory.AllItemsMod)
677 else if (triggerer is
Item item)
679 effect.
Apply(effect.
type, deltaTime, triggerer, item.AllPropertyObjects, position);
689 effect.
Apply(effect.
type, deltaTime, triggerer, targets);
699 foreach (
Attack attack
in attacks)
701 attack.
DoDamage(
null, damageable, worldPosition, deltaTime,
false);
708 public static void ApplyAttacks(List<Attack> attacks, Vector2 worldPosition,
float deltaTime)
710 foreach (
Attack attack
in attacks)
713 if (structureDamage > 0.0f)
722 if (body ==
null) {
return; }
724 float distFactor = 1.0f;
728 if (distFactor < 0.0f)
return;
757 Force.Length() * body.
Mass * currentForceFluctuation * distFactor,
758 maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
766 return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.
SimPosition, triggerBody.
SimPosition)) / colliderRadius;
772 if (baseVel.LengthSquared() < 0.1f)
return Vector2.Zero;
776 if (dist > triggerSize)
return Vector2.Zero;
778 return baseVel * (1.0f - dist / triggerSize);
792 vel /= (float)Timing.Step;
794 return vel.ClampLength(ConvertUnits.ToDisplayUnits(
ForceVelocityLimit)) * currentForceFluctuation;
801 msg.
WriteRangedSingle(MathHelper.Clamp(currentForceFluctuation, 0.0f, 1.0f), 0.0f, 1.0f, 8);
803 if (stayTriggeredDelay > 0.0f)
805 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
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.
static void ApplyStatusEffects(List< StatusEffect > statusEffects, Vector2 worldPosition, Entity triggerer, float deltaTime, List< ISerializableEntity > targets)
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 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
void Update(float deltaTime)
static void RemoveInActiveTriggerers(PhysicsBody physicsBody, HashSet< Entity > triggerers)
IEnumerable< StatusEffect > StatusEffects
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)
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.