6 using FarseerPhysics.Dynamics;
7 using Microsoft.Xna.Framework;
9 using System.Collections.Generic;
10 using System.Collections.Immutable;
12 using System.Xml.Linq;
25 public readonly List<ISerializableEntity>
Targets;
34 Targets =
new List<ISerializableEntity>(targets);
73 private static readonly ImmutableHashSet<Identifier> FieldNames;
76 FieldNames = typeof(
StatusEffect).GetFields().AsEnumerable().Select(f => f.Name.ToIdentifier()).ToImmutableHashSet();
101 NearbyCharacters = 16,
133 public enum SpawnPositionType
157 public enum SpawnRotationType
189 public readonly ItemPrefab ItemPrefab;
193 public readonly SpawnPositionType SpawnPosition;
198 public readonly
bool SpawnIfInventoryFull;
202 public readonly
bool SpawnIfNotInInventory;
206 public readonly
bool SpawnIfCantBeContained;
210 public readonly
float Impulse;
211 public readonly
float RotationRad;
215 public readonly
int Count;
219 public readonly
float Spread;
223 public readonly SpawnRotationType RotationType;
227 public readonly
float AimSpreadRad;
231 public readonly
bool Equip;
235 public readonly
float Condition;
237 public bool InheritEventTags {
get;
private set; }
239 public ItemSpawnInfo(ContentXElement element,
string parentDebugName)
241 if (element.GetAttribute(
"name") !=
null)
244 DebugConsole.ThrowError(
"Error in StatusEffect config (" + element.ToString() +
") - use item identifier instead of the name.", contentPackage: element.ContentPackage);
245 string itemPrefabName = element.GetAttributeString(
"name",
"");
246 ItemPrefab = ItemPrefab.Prefabs.Find(m => m.NameMatches(itemPrefabName, StringComparison.InvariantCultureIgnoreCase) || m.Tags.Contains(itemPrefabName));
247 if (ItemPrefab ==
null)
249 DebugConsole.ThrowError(
"Error in StatusEffect \"" + parentDebugName +
"\" - item prefab \"" + itemPrefabName +
"\" not found.", contentPackage: element.ContentPackage);
254 string itemPrefabIdentifier = element.GetAttributeString(
"identifier",
"");
255 if (
string.IsNullOrEmpty(itemPrefabIdentifier)) itemPrefabIdentifier = element.GetAttributeString(
"identifiers",
"");
256 if (
string.IsNullOrEmpty(itemPrefabIdentifier))
258 DebugConsole.ThrowError(
"Invalid item spawn in StatusEffect \"" + parentDebugName +
"\" - identifier not found in the element \"" + element.ToString() +
"\".", contentPackage: element.ContentPackage);
260 ItemPrefab = ItemPrefab.Prefabs.Find(m => m.Identifier == itemPrefabIdentifier);
261 if (ItemPrefab ==
null)
263 DebugConsole.ThrowError(
"Error in StatusEffect config - item prefab with the identifier \"" + itemPrefabIdentifier +
"\" not found.", contentPackage: element.ContentPackage);
268 SpawnIfInventoryFull = element.GetAttributeBool(nameof(SpawnIfInventoryFull),
false);
269 SpawnIfNotInInventory = element.GetAttributeBool(nameof(SpawnIfNotInInventory),
false);
270 SpawnIfCantBeContained = element.GetAttributeBool(nameof(SpawnIfCantBeContained),
true);
271 Impulse = element.GetAttributeFloat(
"impulse", element.GetAttributeFloat(
"launchimpulse", element.GetAttributeFloat(
"speed", 0.0f)));
273 Condition = MathHelper.Clamp(element.GetAttributeFloat(
"condition", 1.0f), 0.0f, 1.0f);
275 RotationRad = MathHelper.ToRadians(element.GetAttributeFloat(
"rotation", 0.0f));
276 Count = element.GetAttributeInt(
"count", 1);
277 Spread = element.GetAttributeFloat(
"spread", 0f);
278 AimSpreadRad = MathHelper.ToRadians(element.GetAttributeFloat(
"aimspread", 0f));
279 Equip = element.GetAttributeBool(
"equip",
false);
281 SpawnPosition = element.GetAttributeEnum(
"spawnposition", SpawnPositionType.This);
283 if (element.GetAttributeString(
"rotationtype",
string.Empty).Equals(
"Fixed", StringComparison.OrdinalIgnoreCase))
286 RotationType = SpawnRotationType.This;
290 RotationType = element.GetAttributeEnum(
"rotationtype", RotationRad != 0 ? SpawnRotationType.This : SpawnRotationType.Target);
292 InheritEventTags = element.GetAttributeBool(nameof(InheritEventTags),
false);
329 TalentIdentifiers = element.GetAttributeIdentifierArray(
"talentidentifiers", Array.Empty<Identifier>());
330 GiveRandom = element.GetAttributeBool(
"giverandom",
false);
371 DebugConsole.ThrowError($
"GiveSkill StatusEffect did not have a skill identifier defined in {parentDebugName}!", contentPackage: element.
ContentPackage);
381 public string Name => $
"Character Spawn Info ({SpeciesName})";
388 public int Count {
get;
private set; }
391 "Should the buffs of the character executing the effect be transferred to the spawned character?"+
392 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
396 "Should the afflictions of the character executing the effect be transferred to the spawned character?" +
397 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
401 "Should the the items from the character executing the effect be transferred to the spawned character?" +
402 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
406 "The maximum number of creatures of the given species and team that can exist in the current level before this status effect stops spawning any more.")]
410 public int Stun {
get;
private set; }
416 $
"The strength of the affliction applied on the spawned character. Only relevant if {nameof(AfflictionOnSpawn)} is defined.")]
420 "Should the player controlling the character that executes the effect gain control of the spawned character?" +
421 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
425 "Should the character that executes the effect be removed when the effect executes?" +
426 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
430 "Can be used to prevent all the characters from spawning at the exact same position if the effect spawns multiple ones.")]
431 public float Spread {
get;
private set; }
434 "Offset added to the spawn position. " +
435 "Can be used to for example spawn a character a bit up from the center of an item executing the effect.")]
436 public Vector2
Offset {
get;
private set; }
446 DebugConsole.ThrowError($
"Invalid character spawn ({Name}) in StatusEffect \"{parentDebugName}\" - identifier not found in the element \"{element}\".", contentPackage: element.
ContentPackage);
456 public string Name =>
"ai trigger";
463 [
Serialize(0f,
IsPropertySaveable.No, description:
"How long should the character stay in the specified state? If 0, the effect is permanent (unless overridden by another AITrigger).")]
466 [
Serialize(1f,
IsPropertySaveable.No, description:
"How likely is the AI to change the state when this effect executes? 1 = always, 0.5 = 50% chance, 0 = never.")]
470 "How much damage the character must receive for this AITrigger to become active? " +
471 "Checks the amount of damage the latest attack did to the character.")]
482 public float Timer {
get;
private set; }
534 private readonly List<RelatedItem> requiredItems;
536 public readonly ImmutableArray<(Identifier propertyName,
object value)>
PropertyEffects;
539 private readonly List<PropertyConditional> propertyConditionals;
540 public bool HasConditions => propertyConditionals !=
null && propertyConditionals.Any();
545 private readonly
bool setValue;
552 private readonly
bool disableDeltaTime;
557 private readonly HashSet<Identifier> tags;
565 private readonly
float lifeTime;
566 private float lifeTimer;
568 private Dictionary<Entity, float> intervalTimers;
573 private readonly
bool oneShot;
575 public static readonly List<DurationListElement>
DurationList =
new List<DurationListElement>();
601 private readonly
bool playSoundOnRequiredItemFailure =
false;
604 private readonly
int useItemCount;
606 private readonly
bool removeItem, dropContainedItems, dropItem, removeCharacter, breakLimb, hideLimb;
607 private readonly
float hideLimbTimer;
611 private readonly List<Explosion> explosions;
614 get {
return explosions ?? Enumerable.Empty<
Explosion>(); }
617 private readonly List<ItemSpawnInfo> spawnItems;
622 private readonly
bool spawnItemRandomly;
623 private readonly List<CharacterSpawnInfo> spawnCharacters;
627 private readonly List<AITrigger> aiTriggers;
634 private readonly List<EventPrefab> triggeredEvents;
640 private readonly Identifier triggeredEventTargetTag =
"statuseffecttarget".ToIdentifier();
646 private readonly Identifier triggeredEventEntityTag =
"statuseffectentity".ToIdentifier();
652 private readonly Identifier triggeredEventUserTag =
"statuseffectuser".ToIdentifier();
657 private readonly List<(Identifier eventIdentifier, Identifier tag)> eventTargetTags;
708 private readonly HashSet<(Identifier affliction,
float strength)> requiredAfflictions;
716 } =
new List<Affliction>();
723 private readonly
bool multiplyAfflictionsByMaxVitality;
730 public readonly List<(Identifier AfflictionIdentifier,
float ReduceAmount)>
ReduceAffliction =
new List<(Identifier affliction,
float amount)>();
732 private readonly List<Identifier> talentTriggers;
733 private readonly List<int> giveExperiences;
734 private readonly List<GiveSkill> giveSkills;
740 private readonly List<AnimLoadInfo> animationsToTrigger;
763 public Vector2
Offset {
get;
private set; }
767 get {
return string.Join(
",", tags); }
771 if (value ==
null)
return;
773 string[] newTags = value.Split(
',');
774 foreach (
string tag
in newTags)
776 Identifier newTag = tag.Trim().ToIdentifier();
777 if (!tags.Contains(newTag)) { tags.Add(newTag); };
796 tags =
new HashSet<Identifier>(element.
GetAttributeString(
"tags",
"").Split(
',').ToIdentifiers());
816 if (targetLimbNames !=
null)
819 foreach (
string targetLimbName
in targetLimbNames)
821 if (Enum.TryParse(targetLimbName, ignoreCase:
true, out
LimbType targetLimb)) {
targetLimbs.Add(targetLimb); }
828 string[] targetTypesStr =
831 foreach (
string s
in targetTypesStr)
833 if (!Enum.TryParse(s,
true, out
TargetType targetType))
835 DebugConsole.ThrowError($
"Invalid target type \"{s}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage);
839 targetTypes |= targetType;
842 if (targetTypes == 0)
844 string errorMessage = $
"Potential error in StatusEffect ({parentDebugName}). Target not defined, the effect might not work correctly. Use target=\"This\" if you want the effect to target the entity it's defined in. Setting \"This\" as the target.";
845 DebugConsole.AddSafeError(errorMessage);
848 var targetIdentifiers = element.GetAttributeIdentifierArray(Array.Empty<Identifier>(),
"targetnames",
"targets",
"targetidentifiers",
"targettags");
849 if (targetIdentifiers.Any())
854 triggeredEventTargetTag = element.GetAttributeIdentifier(
"eventtargettag", triggeredEventTargetTag);
855 triggeredEventEntityTag = element.GetAttributeIdentifier(
"evententitytag", triggeredEventEntityTag);
856 triggeredEventUserTag = element.GetAttributeIdentifier(
"eventusertag", triggeredEventUserTag);
857 spawnItemRandomly = element.GetAttributeBool(
"spawnitemrandomly",
false);
858 multiplyAfflictionsByMaxVitality = element.GetAttributeBool(nameof(multiplyAfflictionsByMaxVitality),
false);
860 playSoundOnRequiredItemFailure = element.GetAttributeBool(
"playsoundonrequireditemfailure",
false);
863 List<XAttribute> propertyAttributes =
new List<XAttribute>();
864 propertyConditionals =
new List<PropertyConditional>();
865 foreach (XAttribute attribute
in element.Attributes())
867 switch (attribute.Name.ToString().ToLowerInvariant())
870 if (!Enum.TryParse(attribute.Value,
true, out
type))
872 DebugConsole.ThrowError($
"Invalid action type \"{attribute.Value}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage);
879 case "targetidentifiers":
887 case "allowedafflictions":
888 case "requiredafflictions":
890 string[] types = attribute.Value.Split(
',');
891 requiredAfflictions ??=
new HashSet<(Identifier, float)>();
892 for (
int i = 0; i < types.Length; i++)
894 requiredAfflictions.Add((types[i].Trim().ToIdentifier(), 0.0f));
897 case "conditionalcomparison":
899 if (!Enum.TryParse(attribute.Value, ignoreCase:
true, out conditionalLogicalOperator))
901 DebugConsole.ThrowError($
"Invalid conditional comparison type \"{attribute.Value}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage);
905 DebugConsole.ThrowError($
"Error in StatusEffect ({parentDebugName}): sounds should be defined as child elements of the StatusEffect, not as attributes.", contentPackage: element.ContentPackage);
910 propertyAttributes.Add(attribute);
918 propertyAttributes.Add(attribute);
922 oneShot = attribute.GetAttributeBool(
false);
925 if (FieldNames.Contains(attribute.Name.ToIdentifier())) {
continue; }
926 propertyAttributes.Add(attribute);
935 propertyAttributes.RemoveAll(a => a.Name.ToString().Equals(
"tags", StringComparison.OrdinalIgnoreCase));
938 List<(Identifier propertyName,
object value)> propertyEffects =
new List<(Identifier propertyName,
object value)>();
939 foreach (XAttribute attribute
in propertyAttributes)
941 propertyEffects.Add((attribute.NameAsIdentifier(), XMLExtensions.GetAttributeObject(attribute)));
945 foreach (var subElement
in element.Elements())
947 switch (subElement.Name.ToString().ToLowerInvariant())
950 explosions ??=
new List<Explosion>();
951 explosions.Add(
new Explosion(subElement, parentDebugName));
954 FireSize = subElement.GetAttributeFloat(
"size", 10.0f);
964 case "dropcontaineditems":
965 dropContainedItems =
true;
970 case "removecharacter":
971 removeCharacter =
true;
978 hideLimbTimer = subElement.GetAttributeFloat(
"duration", 0);
981 case "requireditems":
982 requiredItems ??=
new List<RelatedItem>();
984 if (newRequiredItem ==
null)
986 DebugConsole.ThrowError(
"Error in StatusEffect config - requires an item with no identifiers.", contentPackage: element.ContentPackage);
989 requiredItems.Add(newRequiredItem);
991 case "requiredaffliction":
992 requiredAfflictions ??=
new HashSet<(Identifier, float)>();
993 Identifier[] ids = subElement.GetAttributeIdentifierArray(
"identifier",
null) ?? subElement.GetAttributeIdentifierArray(
"type", Array.Empty<Identifier>());
994 foreach (var afflictionId
in ids)
996 requiredAfflictions.Add((
998 subElement.GetAttributeFloat(
"minstrength", 0.0f)));
1006 if (subElement.GetAttribute(
"name") !=
null)
1008 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - define afflictions using identifiers instead of names.", contentPackage: element.ContentPackage);
1009 string afflictionName = subElement.GetAttributeString(
"name",
"");
1010 afflictionPrefab =
AfflictionPrefab.
List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, StringComparison.OrdinalIgnoreCase));
1011 if (afflictionPrefab ==
null)
1013 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - Affliction prefab \"" + afflictionName +
"\" not found.", contentPackage: element.ContentPackage);
1019 Identifier afflictionIdentifier = subElement.GetAttributeIdentifier(
"identifier",
"");
1020 afflictionPrefab =
AfflictionPrefab.
List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier);
1021 if (afflictionPrefab ==
null)
1023 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - Affliction prefab with the identifier \"" + afflictionIdentifier +
"\" not found.", contentPackage: element.ContentPackage);
1028 Affliction afflictionInstance = afflictionPrefab.
Instantiate(subElement.GetAttributeFloat(1.0f,
"amount", nameof(afflictionInstance.
Strength)));
1031 afflictionInstance.
Probability = subElement.GetAttributeFloat(1.0f, nameof(afflictionInstance.
Probability));
1035 case "reduceaffliction":
1036 if (subElement.GetAttribute(
"name") !=
null)
1038 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - define afflictions using identifiers or types instead of names.", contentPackage: element.ContentPackage);
1040 subElement.GetAttributeIdentifier(
"name",
""),
1041 subElement.GetAttributeFloat(1.0f,
"amount",
"strength",
"reduceamount")));
1045 Identifier name = subElement.GetAttributeIdentifier(
"identifier", subElement.GetAttributeIdentifier(
"type", Identifier.Empty));
1049 ReduceAffliction.Add((name, subElement.GetAttributeFloat(1.0f,
"amount",
"strength",
"reduceamount")));
1053 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - Affliction prefab with the identifier or type \"" + name +
"\" not found.", contentPackage: element.ContentPackage);
1058 var newSpawnItem =
new ItemSpawnInfo(subElement, parentDebugName);
1059 if (newSpawnItem.ItemPrefab !=
null)
1061 spawnItems ??=
new List<ItemSpawnInfo>();
1062 spawnItems.Add(newSpawnItem);
1065 case "triggerevent":
1066 triggeredEvents ??=
new List<EventPrefab>();
1067 Identifier identifier = subElement.GetAttributeIdentifier(
"identifier", Identifier.Empty);
1068 if (!identifier.IsEmpty)
1073 triggeredEvents.Add(prefab);
1076 foreach (var eventElement
in subElement.Elements())
1078 if (eventElement.NameAsIdentifier() !=
"ScriptedEvent") {
continue; }
1079 triggeredEvents.Add(
new EventPrefab(eventElement, file:
null));
1082 case "spawncharacter":
1084 if (!newSpawnCharacter.SpeciesName.IsEmpty)
1086 spawnCharacters ??=
new List<CharacterSpawnInfo>();
1087 spawnCharacters.Add(newSpawnCharacter);
1090 case "givetalentinfo":
1091 var newGiveTalentInfo =
new GiveTalentInfo(subElement, parentDebugName);
1092 if (newGiveTalentInfo.TalentIdentifiers.Any())
1099 aiTriggers ??=
new List<AITrigger>();
1100 aiTriggers.Add(
new AITrigger(subElement));
1102 case "talenttrigger":
1103 talentTriggers ??=
new List<Identifier>();
1104 talentTriggers.Add(subElement.GetAttributeIdentifier(
"effectidentifier", Identifier.Empty));
1107 eventTargetTags ??=
new List<(Identifier eventIdentifier, Identifier tag)>();
1108 eventTargetTags.Add(
1109 (subElement.GetAttributeIdentifier(
"eventidentifier", Identifier.Empty),
1110 subElement.GetAttributeIdentifier(
"tag", Identifier.Empty)));
1112 case "giveexperience":
1113 giveExperiences ??=
new List<int>();
1114 giveExperiences.Add(subElement.GetAttributeInt(
"amount", 0));
1117 giveSkills ??=
new List<GiveSkill>();
1118 giveSkills.Add(
new GiveSkill(subElement, parentDebugName));
1122 luaHook ??=
new List<(string, ContentXElement)>();
1123 luaHook.Add((subElement.GetAttributeString(
"name",
""), subElement));
1125 case "triggeranimation":
1127 string fileName = subElement.GetAttributeString(
"filename", def:
null) ?? subElement.GetAttributeString(
"file", def:
null);
1128 Either<string, ContentPath> file = fileName !=
null ? fileName.ToLowerInvariant() : subElement.GetAttributeContentPath(
"path");
1129 if (!file.TryGet(out
string _))
1131 if (!file.TryGet(out
ContentPath _) || (file.TryGet(out
ContentPath contentPath) && contentPath.IsNullOrWhiteSpace()))
1133 DebugConsole.ThrowError($
"Error in a <TriggerAnimation> element of {subElement.ParseContentPathFromUri()}: neither path nor filename defined!",
1134 contentPackage: subElement.ContentPackage);
1138 float priority = subElement.GetAttributeFloat(
"priority", def: 0f);
1139 Identifier[] expectedSpeciesNames = subElement.GetAttributeIdentifierArray(
"expectedspecies", Array.Empty<Identifier>());
1140 animationsToTrigger ??=
new List<AnimLoadInfo>();
1141 animationsToTrigger.Add(
new AnimLoadInfo(animType, file, priority, expectedSpeciesNames.ToImmutableArray()));
1146 InitProjSpecific(element, parentDebugName);
1149 partial
void InitProjSpecific(
ContentXElement element,
string parentDebugName);
1153 return (targetTypes & targetType) != 0;
1160 if (propertyName ==
"condition" && value.GetType() == typeof(
float))
1162 return (
float)value < 0.0f || (setValue && (float)value <= 0.0f);
1172 if (propertyName ==
"condition" && value.GetType() == typeof(
float))
1174 return (
float)value > 0.0f || (setValue && (float)value > 0.0f);
1188 return itemPrefab.
Tags.Any(t => propertyConditionals.Any(pc => pc.TargetTagMatchesTagCondition(t)));
1194 if (requiredAfflictions ==
null) {
return true; }
1195 if (attackResult.
Afflictions ==
null) {
return false; }
1196 if (attackResult.
Afflictions.None(a => requiredAfflictions.Any(a2 => a.Strength >= a2.strength && (a.Identifier == a2.affliction || a.Prefab.AfflictionType == a2.affliction))))
1205 if (entity ==
null || requiredItems ==
null) {
return true; }
1206 foreach (
RelatedItem requiredItem
in requiredItems)
1208 if (entity is
Item item)
1222 if (
Range <= 0.0f) {
return; }
1243 if (targets.Contains(powered)) {
continue; }
1265 float xDiff = Math.Abs(e.
WorldPosition.X - worldPosition.X);
1266 if (xDiff >
Range) {
return false; }
1267 float yDiff = Math.Abs(e.
WorldPosition.Y - worldPosition.Y);
1268 if (yDiff >
Range) {
return false; }
1269 if (xDiff * xDiff + yDiff * yDiff <
Range *
Range)
1282 private delegate
bool ShouldShortCircuit(
bool condition, out
bool valueToReturn);
1287 private static bool ShouldShortCircuitLogicalOrOperator(
bool condition, out
bool valueToReturn)
1289 valueToReturn =
true;
1296 private static bool ShouldShortCircuitLogicalAndOperator(
bool condition, out
bool valueToReturn)
1298 valueToReturn =
false;
1302 private bool HasRequiredConditions(IReadOnlyList<ISerializableEntity> targets, IReadOnlyList<PropertyConditional> conditionals,
bool targetingContainer =
false)
1304 if (conditionals.Count == 0) {
return true; }
1305 if (targets.Count == 0 && requiredItems !=
null && requiredItems.All(ri => ri.MatchOnEmpty)) {
return true; }
1307 (ShouldShortCircuit, bool) shortCircuitMethodPair = conditionalLogicalOperator
switch
1309 PropertyConditional.LogicalOperatorType.Or => (ShouldShortCircuitLogicalOrOperator,
false),
1310 PropertyConditional.LogicalOperatorType.And => (ShouldShortCircuitLogicalAndOperator,
true),
1311 _ =>
throw new NotImplementedException()
1313 var (shouldShortCircuit, didNotShortCircuit) = shortCircuitMethodPair;
1315 for (
int i = 0; i < conditionals.Count; i++)
1319 var pc = conditionals[i];
1320 if (!pc.TargetContainer || targetingContainer)
1322 if (shouldShortCircuit(AnyTargetMatches(targets, pc.TargetItemComponent, pc), out valueToReturn)) {
return valueToReturn; }
1326 var target = FindTargetItemOrComponent(targets);
1328 if (targetItem?.ParentInventory ==
null)
1332 bool comparisonIsNeq = pc.ComparisonOperator == PropertyConditional.ComparisonOperatorType.NotEquals;
1333 if (shouldShortCircuit(comparisonIsNeq, out valueToReturn))
1335 return valueToReturn;
1339 var owner = targetItem.ParentInventory.Owner;
1340 if (pc.TargetGrandParent && owner is Item ownerItem)
1342 owner = ownerItem.ParentInventory?.Owner;
1344 if (owner is Item container)
1346 if (pc.Type == PropertyConditional.ConditionType.HasTag)
1349 if (shouldShortCircuit(pc.Matches(container), out valueToReturn)) {
return valueToReturn; }
1353 if (shouldShortCircuit(AnyTargetMatches(container.AllPropertyObjects, pc.TargetItemComponent, pc), out valueToReturn)) {
return valueToReturn; }
1356 if (owner is Character character && shouldShortCircuit(pc.Matches(character), out valueToReturn)) {
return valueToReturn; }
1358 return didNotShortCircuit;
1360 static bool AnyTargetMatches(IReadOnlyList<ISerializableEntity> targets,
string targetItemComponentName, PropertyConditional conditional)
1362 for (
int i = 0; i < targets.Count; i++)
1364 if (!
string.IsNullOrEmpty(targetItemComponentName))
1366 if (!(targets[i] is
ItemComponent ic) || ic.Name != targetItemComponentName) {
continue; }
1368 if (conditional.Matches(targets[i]))
1376 static ISerializableEntity FindTargetItemOrComponent(IReadOnlyList<ISerializableEntity> targets)
1378 for (
int i = 0; i < targets.Count; i++)
1380 if (targets[i] is Item || targets[i] is
ItemComponent) {
return targets[i]; }
1388 if (entity is
Item item)
1449 affliction.
Source = user;
1453 private static readonly List<Entity> intervalsToRemove =
new List<Entity>();
1457 if (
Interval > 0.0f && entity !=
null && intervalTimers !=
null)
1459 if (intervalTimers.ContainsKey(entity))
1461 intervalTimers[entity] -= deltaTime;
1462 if (intervalTimers[entity] > 0.0f) {
return true; }
1464 intervalsToRemove.Clear();
1465 intervalsToRemove.AddRange(intervalTimers.Keys.Where(e => e.Removed));
1466 foreach (var toRemove
in intervalsToRemove)
1468 intervalTimers.Remove(toRemove);
1485 if (existingEffect !=
null)
1498 protected readonly List<ISerializableEntity>
currentTargets =
new List<ISerializableEntity>();
1499 public virtual void Apply(
ActionType type,
float deltaTime,
Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition =
null)
1502 if (this.type !=
type) {
return; }
1518 if (!hasRequiredItems && playSoundOnRequiredItemFailure)
1520 PlaySound(entity, GetHull(entity), GetPosition(entity, targets, worldPosition));
1530 if (existingEffect !=
null)
1545 hull = character.AnimController.CurrentHull;
1547 else if (entity is Item item)
1549 hull = item.CurrentHull;
1554 private Vector2 GetPosition(Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition =
null)
1556 Vector2 position = worldPosition ?? (entity ==
null || entity.Removed ? Vector2.Zero : entity.WorldPosition);
1557 if (worldPosition ==
null)
1559 if (entity is Character character && !character.Removed &&
targetLimbs !=
null)
1563 Limb limb = character.AnimController.GetLimb(targetLimbType);
1564 if (limb !=
null && !limb.Removed)
1573 for (
int i = 0; i < targets.Count; i++)
1575 if (targets[i] is Item targetItem)
1577 position = targetItem.WorldPosition;
1584 for (
int i = 0; i < targets.Count; i++)
1586 if (targets[i] is Limb targetLimb && !targetLimb.Removed)
1588 position = targetLimb.WorldPosition;
1599 protected void Apply(
float deltaTime,
Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition =
null)
1604 lifeTimer -= deltaTime;
1605 if (lifeTimer <= 0) {
return; }
1610 if (entity is
Item item)
1612 var result =
GameMain.
LuaCs.
Hook.
Call<
bool?>(
"statusEffect.apply." + item.Prefab.Identifier,
this, deltaTime, entity, targets, worldPosition);
1614 if (result !=
null && result.Value) {
return; }
1619 var result =
GameMain.
LuaCs.
Hook.
Call<
bool?>(
"statusEffect.apply." + character.SpeciesName,
this, deltaTime, entity, targets, worldPosition);
1621 if (result !=
null && result.Value) {
return; }
1625 if (luaHook !=
null)
1629 var result =
GameMain.
LuaCs.
Hook.
Call<
bool?>(hookName,
this, deltaTime, entity, targets, worldPosition, element);
1631 if (result !=
null && result.Value) {
return; }
1637 Hull hull = GetHull(entity);
1638 Vector2 position = GetPosition(entity, targets, worldPosition);
1639 if (useItemCount > 0)
1642 Limb useTargetLimb =
null;
1643 for (
int i = 0; i < targets.Count; i++)
1647 useTargetCharacter = character;
1652 useTargetLimb = limb;
1653 useTargetCharacter ??= limb.character;
1657 for (
int i = 0; i < targets.Count; i++)
1659 if (targets[i] is not
Item item) {
continue; }
1660 for (
int j = 0; j < useItemCount; j++)
1662 if (item.Removed) {
continue; }
1663 item.Use(deltaTime, user:
null, useTargetLimb, useTargetCharacter);
1670 for (
int i = 0; i < targets.Count; i++)
1672 if (targets[i] is
Item item)
1674 item.Drop(dropper:
null);
1678 if (dropContainedItems)
1680 for (
int i = 0; i < targets.Count; i++)
1682 if (targets[i] is
Item item)
1684 foreach (var itemContainer
in item.GetComponents<
ItemContainer>())
1686 foreach (var containedItem
in itemContainer.Inventory.AllItemsMod)
1688 containedItem.Drop(dropper:
null);
1694 foreach (var containedItem
in character.Inventory.AllItemsMod)
1696 containedItem.Drop(dropper:
null);
1703 for (
int i = 0; i < targets.Count; i++)
1708 if (removeCharacter)
1710 for (
int i = 0; i < targets.Count; i++)
1712 var target = targets[i];
1717 else if (target is
Limb limb)
1723 if (breakLimb || hideLimb)
1725 for (
int i = 0; i < targets.Count; i++)
1727 var target = targets[i];
1729 if (targetLimb ==
null && target is
Character character)
1731 foreach (
Limb limb
in character.AnimController.Limbs)
1740 if (targetLimb !=
null)
1744 targetLimb.
character.
TrySeverLimbJoints(targetLimb, severLimbsProbability: 1, damage: -1, allowBeheading:
true, ignoreSeveranceProbabilityModifier:
true, attacker: user);
1760 for (
int i = 0; i < targets.Count; i++)
1762 var target = targets[i];
1763 if (target?.SerializableProperties ==
null) {
continue; }
1764 if (target is
Entity targetEntity)
1766 if (targetEntity.Removed) {
continue; }
1768 else if (target is
Limb limb)
1770 if (limb.Removed) {
continue; }
1771 position = limb.WorldPosition +
Offset;
1775 if (!target.SerializableProperties.TryGetValue(propertyName, out
SerializableProperty property))
1779 ApplyToProperty(target, property, value, deltaTime);
1784 if (explosions !=
null)
1786 foreach (
Explosion explosion
in explosions)
1788 explosion.
Explode(position, damageSource: entity, attacker: user);
1794 for (
int i = 0; i < targets.Count; i++)
1796 var target = targets[i];
1799 if (target ==
null) {
continue; }
1805 if (character.Removed) {
continue; }
1806 newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime, multiplyAfflictionsByMaxVitality);
1807 character.LastDamageSource = entity;
1808 foreach (
Limb limb
in character.AnimController.Limbs)
1810 if (limb.
Removed) {
continue; }
1813 AttackResult result = limb.
character.
DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attackImpulse: Vector2.Zero, attacker: affliction.
Source, allowStacking: !setValue);
1815 RegisterTreatmentResults(user, entity as
Item, limb, affliction, result);
1820 else if (target is
Limb limb)
1825 newAffliction = GetMultipliedAffliction(affliction, entity, limb.
character, deltaTime, multiplyAfflictionsByMaxVitality);
1826 AttackResult result = limb.
character.
DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attackImpulse: Vector2.Zero, attacker: affliction.
Source, allowStacking: !setValue);
1828 RegisterTreatmentResults(user, entity as
Item, limb, affliction, result);
1834 Limb targetLimb =
null;
1838 targetCharacter = character;
1843 targetCharacter = limb.character;
1845 if (targetCharacter !=
null && !targetCharacter.
Removed)
1849 float reduceAmount = amount * GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
1850 float prevVitality = targetCharacter.
Vitality;
1851 if (targetLimb !=
null)
1859 if (!targetCharacter.
IsDead)
1861 float healthChange = targetCharacter.
Vitality - prevVitality;
1867 GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, user, -healthChange, 0.0f);
1874 if (aiTriggers !=
null)
1877 if (targetCharacter ==
null)
1879 if (target is
Limb targetLimb && !targetLimb.
Removed)
1881 targetCharacter = targetLimb.character;
1886 targetCharacter ??= entityCharacter;
1887 if (targetCharacter !=
null && !targetCharacter.
Removed && !targetCharacter.
IsPlayer)
1891 foreach (
AITrigger trigger
in aiTriggers)
1893 if (Rand.Value(Rand.RandSync.Unsynced) > trigger.
Probability) {
continue; }
1894 if (entityCharacter != targetCharacter)
1898 if (hitLimb != targetLimb) {
continue; }
1902 enemyAI.LaunchTrigger(trigger);
1909 if (talentTriggers !=
null)
1911 Character targetCharacter = CharacterFromTarget(target);
1912 if (targetCharacter !=
null && !targetCharacter.
Removed)
1914 foreach (Identifier talentTrigger
in talentTriggers)
1921 TryTriggerAnimation(target, entity);
1926 if (giveExperiences !=
null)
1928 foreach (
int giveExperience
in giveExperiences)
1930 Character targetCharacter = CharacterFromTarget(target);
1931 if (targetCharacter !=
null && !targetCharacter.
Removed)
1938 if (giveSkills !=
null)
1940 Character targetCharacter = CharacterFromTarget(target);
1941 if (targetCharacter is { Removed:
false })
1943 foreach (
GiveSkill giveSkill
in giveSkills)
1957 Identifier GetRandomSkill()
1959 return targetCharacter.
Info?.
Job?.
GetSkills().GetRandomUnsynced()?.Identifier ?? Identifier.Empty;
1967 Character targetCharacter = CharacterFromTarget(target);
1968 if (targetCharacter?.Info ==
null) {
continue; }
1969 if (!TalentTree.JobTalentTrees.TryGet(targetCharacter.
Info.
Job.
Prefab.
Identifier, out TalentTree characterTalentTree)) {
continue; }
1976 IEnumerable<Identifier> viableTalents = giveTalentInfo.
TalentIdentifiers.Where(
id => !targetCharacter.Info.UnlockedTalents.Contains(
id) && !characterTalentTree.AllTalentIdentifiers.Contains(
id));
1977 if (viableTalents.None()) {
continue; }
1978 targetCharacter.GiveTalent(viableTalents.GetRandomUnsynced(),
true);
1984 if (targetCharacter.Info.UnlockedTalents.Contains(
id) || characterTalentTree.AllTalentIdentifiers.Contains(
id)) {
continue; }
1985 targetCharacter.GiveTalent(
id,
true);
1991 if (eventTargetTags !=
null)
1993 foreach ((Identifier eventId, Identifier tag) in eventTargetTags)
1997 targets.Where(t => t is
Entity).ForEach(t => ev.AddTarget(tag, (
Entity)t));
2004 if (
FireSize > 0.0f && entity !=
null)
2006 var fire =
new FireSource(position, hull, sourceCharacter: user);
2007 fire.Size =
new Vector2(
FireSize, fire.Size.Y);
2012 foreach (
EventPrefab eventPrefab
in triggeredEvents)
2015 if (ev ==
null) {
continue; }
2016 eventManager.QueuedEvents.Enqueue(ev);
2019 if (!triggeredEventTargetTag.IsEmpty)
2021 IEnumerable<ISerializableEntity> eventTargets = targets.Where(t => t is
Entity);
2022 if (eventTargets.Any())
2024 scriptedEvent.Targets.Add(triggeredEventTargetTag, eventTargets.Cast<
Entity>().ToList());
2027 if (!triggeredEventEntityTag.IsEmpty && entity !=
null)
2029 scriptedEvent.Targets.Add(triggeredEventEntityTag,
new List<Entity> { entity });
2031 if (!triggeredEventUserTag.IsEmpty && user !=
null)
2033 scriptedEvent.Targets.Add(triggeredEventUserTag,
new List<Entity> { user });
2039 if (isNotClient && entity !=
null &&
Entity.
Spawner !=
null)
2041 if (spawnCharacters !=
null)
2045 var characters =
new List<Character>();
2046 for (
int i = 0; i < characterSpawnInfo.
Count; i++)
2049 onSpawn: newCharacter =>
2051 if (characterSpawnInfo.TotalMaxCount > 0)
2053 if (Character.CharacterList.Count(c => c.SpeciesName == characterSpawnInfo.SpeciesName && c.TeamID == newCharacter.TeamID) > characterSpawnInfo.TotalMaxCount)
2055 Entity.Spawner?.AddEntityToRemoveQueue(newCharacter);
2061 entity is
Item item &&
2064 enemyAi.PetBehavior.Owner = inv.Owner as Character;
2066 characters.Add(newCharacter);
2067 if (characters.Count == characterSpawnInfo.
Count)
2069 SwarmBehavior.CreateSwarm(characters.Cast<AICharacter>());
2073 if (!AfflictionPrefab.Prefabs.TryGet(characterSpawnInfo.AfflictionOnSpawn, out AfflictionPrefab afflictionPrefab))
2075 DebugConsole.NewMessage($
"Could not apply an affliction to the spawned character(s). No affliction with the identifier \"{characterSpawnInfo.AfflictionOnSpawn}\" found.", Color.Red);
2078 newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(characterSpawnInfo.
AfflictionStrength));
2080 if (characterSpawnInfo.
Stun > 0)
2082 newCharacter.SetStun(characterSpawnInfo.Stun);
2084 foreach (var target in targets)
2086 if (target is not
Character character) {
continue; }
2087 if (characterSpawnInfo.
TransferInventory && character.Inventory !=
null && newCharacter.Inventory !=
null)
2089 if (character.Inventory.Capacity != newCharacter.Inventory.Capacity) { return; }
2090 for (
int i = 0; i < character.Inventory.Capacity && i < newCharacter.Inventory.Capacity; i++)
2092 character.Inventory.GetItemsAt(i).ForEachMod(item => newCharacter.Inventory.TryPutItem(item, i, allowSwapping:
true, allowCombine:
false, user:
null));
2097 foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions())
2099 if (affliction.Prefab.IsBuff)
2101 if (!characterSpawnInfo.TransferBuffs) { continue; }
2105 if (!characterSpawnInfo.TransferAfflictions) { continue; }
2109 float afflictionStrength = affliction.Strength * (newCharacter.MaxVitality / 100.0f);
2110 newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, affliction.Prefab.Instantiate(afflictionStrength));
2113 if (i == characterSpawnInfo.
Count)
2115 if (characterSpawnInfo.TransferControl)
2118 if (Character.Controlled == target)
2120 Character.Controlled = newCharacter;
2123 foreach (Client c in GameMain.Server.ConnectedClients)
2125 if (c.Character != target) { continue; }
2126 GameMain.Server.SetClientCharacter(c, newCharacter);
2133 if (characterSpawnInfo.InheritEventTags)
2135 foreach (var activeEvent in GameMain.GameSession.EventManager.ActiveEvents)
2137 if (activeEvent is ScriptedEvent scriptedEvent)
2139 scriptedEvent.InheritTags(entity, newCharacter);
2148 if (spawnItems !=
null && spawnItems.Count > 0)
2150 if (spawnItemRandomly)
2152 if (spawnItems.Count > 0)
2154 var randomSpawn = spawnItems.GetRandomUnsynced();
2155 for (
int i = 0; i < randomSpawn.Count; i++)
2157 ProcessItemSpawnInfo(randomSpawn);
2163 foreach (ItemSpawnInfo itemSpawnInfo
in spawnItems)
2165 for (
int i = 0; i < itemSpawnInfo.Count; i++)
2167 ProcessItemSpawnInfo(itemSpawnInfo);
2172 void ProcessItemSpawnInfo(ItemSpawnInfo spawnInfo)
2174 if (spawnInfo.SpawnPosition == ItemSpawnInfo.SpawnPositionType.Target)
2176 foreach (var target
in targets)
2178 if (target is Entity targetEntity)
2180 SpawnItem(spawnInfo, entity, sourceBody, position, targetEntity);
2186 SpawnItem(spawnInfo, entity, sourceBody, position, targetEntity:
null);
2192 ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound:
true);
2198 if (Interval > 0.0f && entity !=
null)
2200 intervalTimers ??=
new Dictionary<Entity, float>();
2201 intervalTimers[entity] = Interval;
2205 private static Character CharacterFromTarget(ISerializableEntity target)
2208 if (targetCharacter ==
null)
2210 if (target is Limb targetLimb && !targetLimb.Removed)
2212 targetCharacter = targetLimb.character;
2215 return targetCharacter;
2218 void SpawnItem(ItemSpawnInfo chosenItemSpawnInfo, Entity entity, PhysicsBody sourceBody, Vector2 position, Entity targetEntity)
2221 PhysicsBody parentItemBody = parentItem?.body;
2222 if (user ==
null && parentItem !=
null)
2228 if (chosenItemSpawnInfo.SpawnPosition == ItemSpawnInfo.SpawnPositionType.Target && targetEntity !=
null)
2230 entity = targetEntity;
2231 position = entity.WorldPosition;
2232 if (entity is Item it)
2235 (entity as
Item)?.body ??
2236 (entity as Character)?.AnimController.Collider;
2240 switch (chosenItemSpawnInfo.SpawnPosition)
2242 case ItemSpawnInfo.SpawnPositionType.This:
2243 case ItemSpawnInfo.SpawnPositionType.Target:
2244 Entity.Spawner?.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem =>
2249 var rope = newItem.GetComponent<
Rope>();
2250 if (rope !=
null && sourceBody !=
null && sourceBody.UserData is Limb sourceLimb)
2252 rope.
Attach(sourceLimb, newItem);
2254 newItem.CreateServerEvent(rope);
2257 float spread = Rand.Range(-chosenItemSpawnInfo.AimSpreadRad, chosenItemSpawnInfo.AimSpreadRad);
2258 float rotation = chosenItemSpawnInfo.RotationRad;
2260 if (sourceBody !=
null)
2262 worldPos = sourceBody.Position;
2263 if (user?.Submarine !=
null)
2265 worldPos += user.Submarine.Position;
2270 worldPos = entity.WorldPosition;
2272 switch (chosenItemSpawnInfo.RotationType)
2274 case ItemSpawnInfo.SpawnRotationType.None:
2275 rotation = chosenItemSpawnInfo.RotationRad;
2277 case ItemSpawnInfo.SpawnRotationType.This:
2278 if (sourceBody !=
null)
2280 rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.RotationRad);
2282 else if (parentItemBody !=
null)
2284 rotation = parentItemBody.TransformRotation(chosenItemSpawnInfo.RotationRad);
2287 case ItemSpawnInfo.SpawnRotationType.Target:
2288 rotation = MathUtils.VectorToAngle(entity.WorldPosition - worldPos);
2290 case ItemSpawnInfo.SpawnRotationType.Limb:
2291 if (sourceBody !=
null)
2293 rotation = sourceBody.TransformedRotation;
2296 case ItemSpawnInfo.SpawnRotationType.Collider:
2297 if (parentItemBody !=
null)
2299 rotation = parentItemBody.TransformedRotation;
2301 else if (user !=
null)
2303 rotation = user.AnimController.Collider.Rotation + MathHelper.PiOver2;
2306 case ItemSpawnInfo.SpawnRotationType.MainLimb:
2309 rotation = user.AnimController.MainLimb.body.TransformedRotation;
2312 case ItemSpawnInfo.SpawnRotationType.Random:
2313 if (projectile !=
null)
2315 DebugConsole.LogError(
"Random rotation is not supported for Projectiles.");
2319 rotation = Rand.Range(0f, MathHelper.TwoPi, Rand.RandSync.Unsynced);
2323 throw new NotImplementedException(
"Item spawn rotation type not implemented: " + chosenItemSpawnInfo.RotationType);
2327 rotation += chosenItemSpawnInfo.RotationRad * user.AnimController.Dir;
2330 if (projectile !=
null)
2332 var sourceEntity = (sourceBody?.UserData as ISpatialEntity) ?? entity;
2333 Vector2 spawnPos = sourceEntity.SimPosition;
2334 List<Body> ignoredBodies =
null;
2337 ignoredBodies = user?.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList();
2339 projectile.
Shoot(user, spawnPos, spawnPos, rotation,
2340 ignoredBodies: ignoredBodies, createNetworkEvent:
true);
2343 else if (newItem.body !=
null)
2345 newItem.body.SetTransform(newItem.SimPosition, rotation);
2346 Vector2 impulseDir =
new Vector2(MathF.Cos(rotation), MathF.Sin(rotation));
2347 newItem.body.ApplyLinearImpulse(impulseDir * chosenItemSpawnInfo.Impulse);
2350 OnItemSpawned(newItem, chosenItemSpawnInfo);
2353 case ItemSpawnInfo.SpawnPositionType.ThisInventory:
2355 Inventory inventory =
null;
2356 if (entity is Character character && character.Inventory !=
null)
2358 inventory = character.Inventory;
2360 else if (entity is Item item)
2364 if (itemContainer.
CanBeContained(chosenItemSpawnInfo.ItemPrefab))
2370 if (!chosenItemSpawnInfo.SpawnIfCantBeContained && inventory ==
null)
2375 if (inventory !=
null && (inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull))
2377 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: item =>
2379 if (chosenItemSpawnInfo.Equip && entity is Character character && character.Inventory !=
null)
2382 List<InvSlotType> allowedSlots =
2383 item.GetComponents<
Pickable>().Count() > 1 ?
2387 character.Inventory.TryPutItem(item,
null, allowedSlots);
2389 OnItemSpawned(item, chosenItemSpawnInfo);
2394 case ItemSpawnInfo.SpawnPositionType.SameInventory:
2396 Inventory inventory =
null;
2397 if (entity is Character character)
2401 else if (entity is Item item)
2403 inventory = item.ParentInventory;
2405 if (inventory !=
null)
2407 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: (Item newItem) =>
2409 OnItemSpawned(newItem, chosenItemSpawnInfo);
2412 else if (chosenItemSpawnInfo.SpawnIfNotInInventory)
2414 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position, onSpawned: (Item newItem) =>
2416 OnItemSpawned(newItem, chosenItemSpawnInfo);
2421 case ItemSpawnInfo.SpawnPositionType.ContainedInventory:
2423 Inventory thisInventory =
null;
2424 if (entity is Character character)
2426 thisInventory = character.Inventory;
2428 else if (entity is Item item)
2431 thisInventory = itemContainer?.
Inventory;
2432 if (!chosenItemSpawnInfo.SpawnIfCantBeContained && !itemContainer.CanBeContained(chosenItemSpawnInfo.ItemPrefab))
2437 if (thisInventory !=
null)
2439 foreach (Item item
in thisInventory.AllItems)
2441 Inventory containedInventory = item.GetComponent<
ItemContainer>()?.Inventory;
2442 if (containedInventory !=
null && (containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull))
2444 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: (Item newItem) =>
2446 OnItemSpawned(newItem, chosenItemSpawnInfo);
2455 void OnItemSpawned(Item newItem, ItemSpawnInfo itemSpawnInfo)
2457 newItem.Condition = newItem.MaxCondition * itemSpawnInfo.Condition;
2458 if (itemSpawnInfo.InheritEventTags)
2460 foreach (var activeEvent
in GameMain.GameSession.EventManager.ActiveEvents)
2462 if (activeEvent is ScriptedEvent scriptedEvent)
2464 scriptedEvent.InheritTags(entity, newItem);
2471 private void TryTriggerAnimation(ISerializableEntity target, Entity entity)
2473 if (animationsToTrigger ==
null) {
return; }
2475 if ((CharacterFromTarget(target) ?? entity as Character) is Character targetCharacter)
2477 foreach (AnimLoadInfo animLoadInfo
in animationsToTrigger)
2479 if (failedAnimations !=
null && failedAnimations.Contains((targetCharacter, animLoadInfo))) {
continue; }
2480 if (!targetCharacter.AnimController.TryLoadTemporaryAnimation(animLoadInfo, throwErrors: animLoadInfo.ExpectedSpeciesNames.Contains(targetCharacter.SpeciesName)))
2482 failedAnimations ??=
new HashSet<(Character, AnimLoadInfo)>();
2483 failedAnimations.Add((targetCharacter, animLoadInfo));
2489 partial
void ApplyProjSpecific(
float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Hull currentHull, Vector2 worldPosition,
bool playSound);
2491 private void ApplyToProperty(ISerializableEntity target, SerializableProperty property,
object value,
float deltaTime)
2493 if (disableDeltaTime || setValue) { deltaTime = 1.0f; }
2494 if (value is
int || value is
float)
2496 float propertyValueF =
property.GetFloatValue(target);
2497 if (property.PropertyType == typeof(
float))
2499 float floatValue = value is
float single ? single : (int)value;
2500 floatValue *= deltaTime;
2503 floatValue += propertyValueF;
2505 property.TrySetValue(target, floatValue);
2508 else if (property.PropertyType == typeof(
int))
2510 int intValue = (int)(value is
float single ? single * deltaTime : (
int)value * deltaTime);
2513 intValue += (int)propertyValueF;
2515 property.TrySetValue(target, intValue);
2519 else if (value is
bool propertyValueBool)
2521 property.TrySetValue(target, propertyValueBool);
2524 property.TrySetValue(target, value);
2529 UpdateAllProjSpecific(deltaTime);
2532 for (
int i = DurationList.Count - 1; i >= 0; i--)
2538 DurationList.RemoveAt(i);
2542 element.
Targets.RemoveAll(t =>
2544 (t is
Limb limb && (limb.character ==
null || limb.character.Removed)));
2545 if (element.
Targets.Count == 0)
2547 DurationList.RemoveAt(i);
2553 if (target?.SerializableProperties !=
null)
2555 foreach (var (propertyName, value) in element.Parent.PropertyEffects)
2561 element.Parent.ApplyToProperty(target, property, value, CoroutineManager.DeltaTime);
2565 foreach (
Affliction affliction
in element.Parent.Afflictions)
2570 if (character.Removed) {
continue; }
2571 newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, character, deltaTime, element.Parent.multiplyAfflictionsByMaxVitality);
2572 var result = character.AddDamage(character.WorldPosition, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attacker: element.User);
2573 element.Parent.RegisterTreatmentResults(element.Parent.user, element.Entity as
Item, result.HitLimb, affliction, result);
2575 else if (target is
Limb limb)
2577 if (limb.character.Removed || limb.Removed) {
continue; }
2578 newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime, element.Parent.multiplyAfflictionsByMaxVitality);
2579 var result = limb.character.DamageLimb(limb.WorldPosition, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attackImpulse: Vector2.Zero, attacker: element.User);
2580 element.Parent.RegisterTreatmentResults(element.Parent.user, element.Entity as
Item, limb, affliction, result);
2584 foreach ((Identifier affliction,
float amount) in element.Parent.ReduceAffliction)
2586 Limb targetLimb =
null;
2590 targetCharacter = character;
2592 else if (target is
Limb limb)
2595 targetCharacter = limb.character;
2597 if (targetCharacter !=
null && !targetCharacter.
Removed)
2601 float reduceAmount = amount * element.Parent.GetAfflictionMultiplier(element.Entity, targetCharacter, deltaTime);
2602 float prevVitality = targetCharacter.
Vitality;
2603 if (targetLimb !=
null)
2611 if (!targetCharacter.
IsDead)
2613 float healthChange = targetCharacter.
Vitality - prevVitality;
2615 if (element.User !=
null)
2619 GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, element.User, -healthChange, 0.0f);
2626 element.Parent.TryTriggerAnimation(target, element.Entity);
2629 element.Parent.ApplyProjSpecific(deltaTime,
2632 element.Parent.GetHull(element.Entity),
2633 element.Parent.GetPosition(element.Entity, element.Targets),
2634 playSound: element.Timer >= element.Duration);
2636 element.Timer -= deltaTime;
2638 if (element.Timer > 0.0f) {
continue; }
2639 DurationList.Remove(element);
2643 private float GetAfflictionMultiplier(
Entity entity,
Character targetCharacter,
float deltaTime)
2645 float afflictionMultiplier = !setValue && !disableDeltaTime ? deltaTime : 1.0f;
2646 if (entity is
Item sourceItem)
2648 if (sourceItem.HasTag(
Barotrauma.Tags.MedicalItem))
2650 afflictionMultiplier *= 1 + targetCharacter.
GetStatValue(
StatTypes.MedicalItemEffectivenessMultiplier);
2651 if (user is not
null)
2653 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.MedicalItemApplyingMultiplier);
2656 else if (sourceItem.HasTag(AfflictionPrefab.PoisonType) && user is not
null)
2658 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.PoisonMultiplier);
2661 return afflictionMultiplier * AfflictionMultiplier;
2664 private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter,
float deltaTime,
bool multiplyByMaxVitality)
2666 float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
2667 if (multiplyByMaxVitality)
2669 afflictionMultiplier *= targetCharacter.MaxVitality / 100f;
2671 if (user is not
null)
2673 if (affliction.Prefab.IsBuff)
2675 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.BuffItemApplyingMultiplier);
2677 else if (affliction.Prefab.Identifier ==
"organdamage" && targetCharacter.CharacterHealth.GetActiveAfflictionTags().Any(t => t ==
"poisoned"))
2679 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.PoisonMultiplier);
2682 if (!MathUtils.NearlyEqual(afflictionMultiplier, 1.0f))
2684 return affliction.CreateMultiplied(afflictionMultiplier, affliction);
2689 private void RegisterTreatmentResults(Character user, Item item, Limb limb, Affliction affliction, AttackResult result)
2691 if (item ==
null) {
return; }
2692 if (!item.UseInHealthInterface) {
return; }
2693 if (limb ==
null) {
return; }
2694 foreach (Affliction limbAffliction
in limb.character.CharacterHealth.GetAllAfflictions())
2696 if (result.Afflictions !=
null && result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) &&
2697 (!affliction.Prefab.LimbSpecific || limb.character.CharacterHealth.GetAfflictionLimb(affliction) == limb))
2701 limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
2702 limb.character.TryAdjustHealerSkill(user, affliction: affliction);
2706 limbAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
2707 limb.character.TryAdjustHealerSkill(user, affliction: affliction);
2713 static partial
void UpdateAllProjSpecific(
float deltaTime);
2717 CoroutineManager.StopCoroutines(
"statuseffect");
2719 DurationList.Clear();
2724 if (tags.Contains(tag)) {
return; }
2730 if (tag ==
null) {
return true; }
2731 return tags.Contains(tag) || tags.Contains(tag);
virtual void OnHealed(Character healer, float healAmount)
bool MultiplyByMaxVitality
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 IEnumerable< AfflictionPrefab > List
readonly bool LimbSpecific
If set to true, the affliction affects individual limbs. Otherwise, it affects the whole character.
void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction=null, Character attacker=null)
void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction=null, Character attacker=null)
CharacterHealth CharacterHealth
void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
virtual AIController AIController
void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, bool ignoreSeveranceProbabilityModifier=false, Character attacker=null)
float GetStatValue(StatTypes statType, bool includeSaved=true)
static readonly List< Character > CharacterList
CharacterInventory Inventory
void TryAdjustHealerSkill(Character healer, float healthChange=0, Affliction affliction=null)
AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable< Affliction > afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker=null, float damageMultiplier=1, bool allowStacking=true, float penetration=0f, bool shouldImplode=false)
void ApplySkillGain(Identifier skillIdentifier, float baseGain, bool gainedFromAbility=false, float maxGain=2f)
Increases the characters skill at a rate proportional to their current skill. If you want to increase...
void IncreaseSkillLevel(Identifier skillIdentifier, float increase, bool gainedFromAbility=false)
Increase the skill by a specific amount. Talents may affect the actual, final skill increase.
void GiveExperience(int amount)
static readonly Identifier HumanSpeciesName
string? GetAttributeString(string key, string? def)
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
Vector2 GetAttributeVector2(string key, in Vector2 def)
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
string?[] GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant=false)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
static void Update(float deltaTime)
static readonly List< DelayedListElement > DelayList
void Reset(float duration, Character newUser)
readonly List< ISerializableEntity > Targets
DurationListElement(StatusEffect parentEffect, Entity parentEntity, IEnumerable< ISerializableEntity > targets, float duration, Character user)
readonly StatusEffect Parent
static EntitySpawner Spawner
virtual Vector2 WorldPosition
void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, Action< Character > onSpawn=null)
void AddItemToRemoveQueue(Item item)
void AddEntityToRemoveQueue(Entity entity)
IEnumerable< Event > ActiveEvents
Event CreateInstance(int seed)
Event sets are sets of random events that occur within a level (most commonly, monster spawns and scr...
static EventPrefab GetEventPrefab(Identifier identifier)
Explosions are area of effect attacks that can damage characters, items and structures.
void Explode(Vector2 worldPosition, Entity damageSource, Character attacker=null)
static GameSession?? GameSession
static NetworkMember NetworkMember
readonly EventManager EventManager
Inventory(Entity owner, int capacity, int slotsPerRow=5)
Inventory ParentInventory
bool UseInHealthInterface
IReadOnlyList< ISerializableEntity > AllPropertyObjects
bool HasTag(Identifier tag)
static readonly List< Item > ItemList
override ImmutableHashSet< Identifier > Tags
The base class for components holding the different functionalities of the item
readonly ItemInventory Inventory
bool CanBeContained(Item item)
List< InvSlotType > AllowedSlots
static IEnumerable< Powered > PoweredList
void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List< Body > ignoredBodies, bool createNetworkEvent, float damageMultiplier=1f, float launchImpulseModifier=0f)
void Attach(ISpatialEntity source, Item target)
IEnumerable< Skill > GetSkills()
readonly Character character
void HideAndDisable(float duration=0, bool ignoreCollisions=true)
object Call(string name, params object[] args)
readonly Identifier Identifier
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)
Can be used to trigger a behavior change of some kind on an AI character. Only applicable for enemy c...
void UpdateTimer(float deltaTime)
Dictionary< Identifier, SerializableProperty > SerializableProperties
AITrigger(XElement element)
Can be used by AbilityConditionStatusEffectIdentifier to check whether some specific StatusEffect is ...
Identifier EffectIdentifier
AbilityStatusEffectIdentifier(Identifier effectIdentifier)
Defines characters spawned by the effect, and where and how they're spawned.
Dictionary< Identifier, SerializableProperty > SerializableProperties
bool RemovePreviousCharacter
CharacterSpawnInfo(ContentXElement element, string parentDebugName)
Identifier AfflictionOnSpawn
Increases a character's skills when the effect executes. Only valid if the target is a character or a...
readonly bool TriggerTalents
Should the talents that trigger when the character gains skills be triggered by the effect?
readonly float Amount
How much to increase the skill.
readonly bool Proportional
Should the amount be inversely proportional to the current skill level? Meaning, the higher the skill...
readonly bool UseDeltaTime
Should the amount be multiplied by delta time? Useful if you want to give a skill increase per frame.
GiveSkill(ContentXElement element, string parentDebugName)
readonly Identifier SkillIdentifier
The identifier of the skill to increase.
Unlocks a talent, or multiple talents when the effect executes. Only valid if the target is a charact...
GiveTalentInfo(XElement element, string _)
Identifier[] TalentIdentifiers
The identifier(s) of the talents that should be unlocked.
bool GiveRandom
If true and there's multiple identifiers defined, a random one will be chosen instead of unlocking al...
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
Vector2 Offset
An offset added to the position of the effect is executed at. Only relevant if the effect does someth...
void Apply(float deltaTime, Entity entity, IReadOnlyList< ISerializableEntity > targets, Vector2? worldPosition=null)
IEnumerable< CharacterSpawnInfo >?? SpawnCharacters
virtual bool HasRequiredItems(Entity entity)
readonly bool OnlyOutside
If enabled, this effect can only execute outside hulls.
bool ReducesItemCondition()
StatusEffect(ContentXElement element, string parentDebugName)
bool IsValidTarget(ISerializableEntity entity)
bool HasTargetType(TargetType targetType)
readonly bool Stackable
Only valid if the effect has a duration or delay. Can the effect be applied on the same target(s) if ...
bool IsValidTarget(ItemComponent itemComponent)
bool IsValidTarget(Character character)
static void UpdateAll(float deltaTime)
readonly float SeverLimbsProbability
The probability of severing a limb damaged by this status effect. Only valid when targeting character...
static readonly List< DurationListElement > DurationList
void SetUser(Character user)
IEnumerable< Explosion >?? Explosions
bool MatchesTagConditionals(ItemPrefab itemPrefab)
bool ShouldWaitForInterval(Entity entity, float deltaTime)
readonly bool AllowWhenBroken
Can the StatusEffect be applied when the item applying it is broken?
float AfflictionMultiplier
static StatusEffect Load(ContentXElement element, string parentDebugName)
readonly List< ISerializableEntity > currentTargets
readonly string TargetItemComponent
If set to the name of one of the target's ItemComponents, the effect is only applied on that componen...
readonly List< GiveTalentInfo > giveTalentInfos
void AddNearbyTargets(Vector2 worldPosition, List< ISerializableEntity > targets)
readonly bool OnlyWhenDamagedByPlayer
If enabled, the effect only executes when the entity receives damage from a player character (a chara...
readonly record struct AnimLoadInfo(AnimationType Type, Either< string, ContentPath > File, float Priority, ImmutableArray< Identifier > ExpectedSpeciesNames)
bool HasRequiredAfflictions(AttackResult attackResult)
float Range
How close to the entity executing the effect the targets must be. Only applicable if targeting Nearby...
readonly ImmutableArray<(Identifier propertyName, object value)> PropertyEffects
readonly float Interval
The interval at which the effect is executed. The difference between delay and interval is that effec...
readonly List<(Identifier AfflictionIdentifier, float ReduceAmount)> ReduceAffliction
bool IsValidTarget(Item item)
int TargetSlot
Index of the slot the target must be in. Only valid when targeting a Contained item.
bool HasRequiredConditions(IReadOnlyList< ISerializableEntity > targets)
bool IncreasesItemCondition()
List< Affliction > Afflictions
virtual void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList< ISerializableEntity > targets, Vector2? worldPosition=null)
readonly LimbType[] targetLimbs
Which types of limbs this effect can target? Only valid when targeting characters or limbs.
readonly bool CheckConditionalAlways
Only applicable for StatusEffects with a duration or delay. Should the conditional checks only be don...
readonly float Duration
How long the effect runs (in seconds). Note that if Stackable is true, there can be multiple instance...
bool HasTag(Identifier tag)
void AddTag(Identifier tag)
readonly ImmutableHashSet< Identifier > TargetIdentifiers
Identifier(s), tag(s) or species name(s) of the entity the effect can target. Null if there's no iden...
virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition=null)
readonly bool OnlyInside
If enabled, this effect can only execute inside a hull.
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
Dictionary< Identifier, SerializableProperty > SerializableProperties
ActionType
ActionTypes define when a StatusEffect is executed.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
readonly List< Affliction > Afflictions