2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
74 public AttackResult(List<Affliction> afflictions,
Limb hitLimb, List<DamageModifier> appliedDamageModifiers =
null)
87 public AttackResult(
float damage, List<DamageModifier> appliedDamageModifiers =
null)
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]
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]
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]
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)]
142 set => _range = value;
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)]
150 set => _damageRange = value;
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)]
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)]
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)]
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)]
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)]
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]
172 private float _structureDamage;
177 set => _structureDamage = value;
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]
186 private float _itemDamage;
191 set => _itemDamage = value;
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)]
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]
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]
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]
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]
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]
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]
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]
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; }
255 if (
string.IsNullOrEmpty(value)) {
return; }
256 foreach (
string limbIndexStr
in value.Split(
','))
258 if (
int.TryParse(limbIndexStr.Trim(), out
int limbIndex))
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; }
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]
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]
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]
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; }
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)]
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]
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)]
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]
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)]
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)]
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; }
318 get {
return statusEffects; }
321 public string Name =>
"Attack";
327 } =
new Dictionary<Identifier, SerializableProperty>();
333 public readonly Dictionary<Affliction, XElement>
Afflictions =
new Dictionary<Affliction, XElement>();
338 public List<PropertyConditional>
Conditionals {
get;
private set; } =
new List<PropertyConditional>();
344 private readonly List<StatusEffect> statusEffects =
new List<StatusEffect>();
348 if (statusEffects ==
null) {
return; }
360 List<Affliction> multipliedAfflictions =
new List<Affliction>();
363 multipliedAfflictions.Add(affliction.
CreateMultiplied(multiplier, affliction));
365 return multipliedAfflictions;
381 return (
Duration == 0.0f) ? dmg : dmg * deltaTime;
394 public Attack(
float damage,
float bleedingDamage,
float burnDamage,
float structureDamage,
float itemDamage,
float range = 0.0f)
420 DebugConsole.ThrowError(
"Error in Attack (" + parentDebugName +
") - Define damage as afflictions instead of using the damage attribute (e.g. <Affliction identifier=\"internaldamage\" strength=\"10\" />).",
431 InitProjSpecific(element);
433 foreach (var subElement
in element.Elements())
435 switch (subElement.Name.ToString().ToLowerInvariant())
442 if (subElement.GetAttribute(
"name") !=
null)
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)
450 DebugConsole.ThrowError(
"Error in Attack (" + parentDebugName +
") - Affliction prefab \"" + afflictionName +
"\" not found.",
451 contentPackage: element.ContentPackage);
457 Identifier afflictionIdentifier = subElement.GetAttributeIdentifier(
"identifier",
"");
460 DebugConsole.ThrowError(
"Error in Attack (" + parentDebugName +
") - Affliction prefab \"" + afflictionIdentifier +
"\" not found.",
461 contentPackage: element.ContentPackage);
474 DebugConsole.AddWarning($
"Potentially misconfigured attack in {parentDebugName}. Secondary cooldown should not be longer than the primary cooldown.",
475 contentPackage: element.ContentPackage);
483 foreach (var subElement
in element.GetChildElements(
"affliction"))
486 Identifier afflictionIdentifier = subElement.GetAttributeIdentifier(
"identifier",
"");
489 DebugConsole.ThrowError($
"Error in an Attack defined in \"{parentDebugName}\" - could not find an affliction with the identifier \"{afflictionIdentifier}\".",
490 contentPackage: element.ContentPackage);
493 affliction = afflictionPrefab.Instantiate(0.0f);
505 if (affliction.
Value !=
null)
523 if (targetCharacter !=
null && !targetCharacter.
IsHuman)
531 DamageParticles(deltaTime, worldPosition);
534 var attackResult = target?.
AddDamage(attacker, worldPosition,
this, impulseDirection, deltaTime, playSound) ??
new AttackResult();
535 var conditionalEffectType = attackResult.Damage > 0.0f ?
ActionType.OnSuccess :
ActionType.OnFailure;
537 if (targetCharacter !=
null && targetCharacter.
IsDead)
548 if (additionalEffectType !=
ActionType.OnEating)
550 effect.
Apply(conditionalEffectType, deltaTime, attacker, t, worldPosition);
552 effect.
Apply(additionalEffectType, deltaTime, attacker, t, worldPosition);
556 if (additionalEffectType !=
ActionType.OnEating)
558 effect.
Apply(conditionalEffectType, deltaTime, attacker, attacker);
560 effect.
Apply(additionalEffectType, deltaTime, attacker, attacker);
562 if (targetCharacter !=
null)
566 if (additionalEffectType !=
ActionType.OnEating)
568 effect.
Apply(conditionalEffectType, deltaTime, targetCharacter, attackResult.HitLimb);
570 effect.
Apply(additionalEffectType, deltaTime, targetCharacter, attackResult.HitLimb);
574 var targets = targetCharacter.AnimController.Limbs;
575 if (additionalEffectType !=
ActionType.OnEating)
577 effect.
Apply(conditionalEffectType, deltaTime, targetCharacter, targets);
579 effect.
Apply(additionalEffectType, deltaTime, targetCharacter, targets);
582 if (target is
Entity targetEntity)
589 if (additionalEffectType !=
ActionType.OnEating)
591 effect.
Apply(conditionalEffectType, deltaTime, targetEntity, targets);
593 effect.
Apply(additionalEffectType, deltaTime, targetEntity, targets);
597 if (additionalEffectType !=
ActionType.OnEating)
607 targets.AddRange(attacker.Inventory.AllItems);
608 if (additionalEffectType !=
ActionType.OnEating)
610 effect.
Apply(conditionalEffectType, deltaTime, attacker, targets);
612 effect.
Apply(additionalEffectType, deltaTime, attacker, targets);
619 readonly List<ISerializableEntity> targets =
new List<ISerializableEntity>();
622 if (targetLimb ==
null)
637 DamageParticles(deltaTime, worldPosition);
645 if (penetrationValue.HasValue)
647 penetration += penetrationValue.Value;
650 Vector2 impulseDirection = GetImpulseDirection(targetLimb, worldPosition,
SourceItem);
651 var attackResult = targetLimb.
character.
ApplyAttack(attacker, worldPosition,
this, deltaTime, impulseDirection, playSound, targetLimb, penetration);
664 effect.
Apply(conditionalEffectType, deltaTime, attacker, attacker);
669 effect.
Apply(conditionalEffectType, deltaTime, targetLimb.character, targetLimb.character);
670 effect.
Apply(
ActionType.OnUse, deltaTime, targetLimb.character, targetLimb.character);
674 effect.
Apply(conditionalEffectType, deltaTime, targetLimb.character, targetLimb);
675 effect.
Apply(
ActionType.OnUse, deltaTime, targetLimb.character, targetLimb);
679 var targets = targetLimb.character.AnimController.Limbs;
680 effect.
Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
681 effect.
Apply(
ActionType.OnUse, deltaTime, targetLimb.character, targets);
688 effect.
Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
689 effect.
Apply(
ActionType.OnUse, deltaTime, targetLimb.character, targets);
694 targets.AddRange(attacker.Inventory.AllItems);
695 effect.
Apply(conditionalEffectType, deltaTime, attacker, targets);
703 private Vector2 GetImpulseDirection(
ISpatialEntity target, Vector2 sourceWorldPosition,
Item sourceItem)
705 Vector2 impulseDirection = Vector2.Zero;
708 impulseDirection = target.
WorldPosition - sourceWorldPosition;
717 var projectileComponent = sourceItem?.GetComponent<
Projectile>();
718 if (projectileComponent !=
null)
724 if (impulseDirection.LengthSquared() > 0.0001f)
726 impulseDirection = Vector2.Normalize(impulseDirection);
728 return impulseDirection;
790 partial
void DamageParticles(
float deltaTime, Vector2 worldPosition);
796 foreach (var context
in contexts)
void Deserialize(XElement element)
float GetVitalityDecrease(CharacterHealth characterHealth)
Affliction CreateMultiplied(float multiplier, Affliction affliction)
void Serialize(XElement element)
Character Source
Which character gave this affliction
readonly AfflictionPrefab Prefab
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...
void Deserialize(ContentXElement element, string parentDebugName)
float GetStructureDamage(float deltaTime)
AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
Attack(ContentXElement element, string parentDebugName, Item sourceItem)
float CurrentRandomCoolDown
bool IsValidTarget(Entity target)
void UpdateCoolDown(float deltaTime)
float SecondaryCoolDownTimer
bool IsValidContext(IEnumerable< AttackContext > contexts)
float RangeMultiplier
Used for multiplying all the ranges.
void Serialize(ContentXElement element)
bool EmitStructureDamageParticles
Dictionary< Identifier, SerializableProperty > SerializableProperties
bool IsValidContext(AttackContext context)
float CoolDownRandomFactor
float RequiredAngleToShoot
void SetCoolDown(bool applyRandom)
TransitionMode RootTransitionEasing
AIBehaviorAfterAttack AfterAttackSecondary
float GetLevelWallDamage(float deltaTime)
readonly List< int > ForceOnLimbIndices
Vector2 CalculateAttackPhase(TransitionMode easing=TransitionMode.Linear)
float ImpactMultiplier
Used for multiplying the physics forces.
float GetTotalDamage(bool includeStructureDamage=false)
AIBehaviorAfterAttack AfterAttack
void UpdateAttackTimer(float deltaTime, Character character)
Vector2 RootForceWorldStart
bool IsValidTarget(AttackTarget targetType)
bool FullSpeedAfterAttack
Vector2 TargetImpulseWorld
Vector2 RootForceWorldEnd
Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range=0.0f)
void ReloadAfflictions(ContentXElement element, string parentDebugName)
float AfterAttackSecondaryDelay
float DamageMultiplier
Used for multiplying all the damage.
bool CreateWallDamageProjectiles
void SetUser(Character user)
AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
Vector2 RootForceWorldMiddle
float SubmarineImpactMultiplier
List< Affliction > GetMultipliedAfflictions(float multiplier)
IEnumerable< StatusEffect > StatusEffects
readonly Dictionary< Affliction, XElement > Afflictions
float GetItemDamage(float deltaTime, float multiplier=1)
HitDetection HitDetectionType
List< PropertyConditional > Conditionals
Only affects ai decision making. All the conditionals has to be met in order to select the attack....
float SeverLimbsProbability
Attack(ContentXElement element, string parentDebugName)
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
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 ...
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)
ActionType
ActionTypes define when a StatusEffect is executed.
@ FollowThroughUntilCanAttack
@ FollowThroughWithoutObstacleAvoidance
readonly List< DamageModifier > AppliedDamageModifiers
readonly List< Affliction > Afflictions
AttackResult(float damage, List< DamageModifier > appliedDamageModifiers=null)
AttackResult(List< Affliction > afflictions, Limb hitLimb, List< DamageModifier > appliedDamageModifiers=null)