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,
129 LinkedEntities = 2048
137 public enum SpawnPositionType
161 public enum SpawnRotationType
193 public readonly ItemPrefab ItemPrefab;
197 public readonly SpawnPositionType SpawnPosition;
202 public readonly
bool SpawnIfInventoryFull;
206 public readonly
bool SpawnIfNotInInventory;
210 public readonly
bool SpawnIfCantBeContained;
214 public readonly
float Impulse;
215 public readonly
float RotationRad;
219 public readonly
int MinCount;
223 public readonly
int MaxCount;
227 public readonly
float Probability;
231 public readonly
float Spread;
235 public readonly SpawnRotationType RotationType;
239 public readonly
float AimSpreadRad;
243 public readonly
bool Equip;
247 public readonly
float Condition;
249 public bool InheritEventTags {
get;
private set; }
251 public ItemSpawnInfo(ContentXElement element,
string parentDebugName)
253 if (element.GetAttribute(
"name") !=
null)
256 DebugConsole.ThrowError(
"Error in StatusEffect config (" + element.ToString() +
") - use item identifier instead of the name.", contentPackage: element.ContentPackage);
257 string itemPrefabName = element.GetAttributeString(
"name",
"");
258 ItemPrefab = ItemPrefab.Prefabs.Find(m => m.NameMatches(itemPrefabName, StringComparison.InvariantCultureIgnoreCase) || m.Tags.Contains(itemPrefabName));
259 if (ItemPrefab ==
null)
261 DebugConsole.ThrowError(
"Error in StatusEffect \"" + parentDebugName +
"\" - item prefab \"" + itemPrefabName +
"\" not found.", contentPackage: element.ContentPackage);
266 string itemPrefabIdentifier = element.GetAttributeString(
"identifier",
"");
267 if (
string.IsNullOrEmpty(itemPrefabIdentifier)) itemPrefabIdentifier = element.GetAttributeString(
"identifiers",
"");
268 if (
string.IsNullOrEmpty(itemPrefabIdentifier))
270 DebugConsole.ThrowError(
"Invalid item spawn in StatusEffect \"" + parentDebugName +
"\" - identifier not found in the element \"" + element.ToString() +
"\".", contentPackage: element.ContentPackage);
272 ItemPrefab = ItemPrefab.Prefabs.Find(m => m.Identifier == itemPrefabIdentifier);
273 if (ItemPrefab ==
null)
275 DebugConsole.ThrowError(
"Error in StatusEffect config - item prefab with the identifier \"" + itemPrefabIdentifier +
"\" not found.", contentPackage: element.ContentPackage);
280 SpawnIfInventoryFull = element.GetAttributeBool(nameof(SpawnIfInventoryFull),
false);
281 SpawnIfNotInInventory = element.GetAttributeBool(nameof(SpawnIfNotInInventory),
false);
282 SpawnIfCantBeContained = element.GetAttributeBool(nameof(SpawnIfCantBeContained),
true);
283 Impulse = element.GetAttributeFloat(
"impulse", element.GetAttributeFloat(
"launchimpulse", element.GetAttributeFloat(
"speed", 0.0f)));
285 Condition = MathHelper.Clamp(element.GetAttributeFloat(
"condition", 1.0f), 0.0f, 1.0f);
287 RotationRad = MathHelper.ToRadians(element.GetAttributeFloat(
"rotation", 0.0f));
289 int fixedCount = element.GetAttributeInt(
"count", 1);
290 MinCount = element.GetAttributeInt(nameof(MinCount), fixedCount);
291 MaxCount = element.GetAttributeInt(nameof(MaxCount), fixedCount);
292 if (MinCount > MaxCount)
294 DebugConsole.AddWarning($
"Potential error in a StatusEffect {parentDebugName}: mincount is larger than maxcount.");
296 Probability = element.GetAttributeFloat(nameof(Probability), 1.0f);
298 Spread = element.GetAttributeFloat(
"spread", 0f);
299 AimSpreadRad = MathHelper.ToRadians(element.GetAttributeFloat(
"aimspread", 0f));
300 Equip = element.GetAttributeBool(
"equip",
false);
302 SpawnPosition = element.GetAttributeEnum(
"spawnposition", SpawnPositionType.This);
304 if (element.GetAttributeString(
"rotationtype",
string.Empty).Equals(
"Fixed", StringComparison.OrdinalIgnoreCase))
307 RotationType = SpawnRotationType.This;
311 RotationType = element.GetAttributeEnum(
"rotationtype", RotationRad != 0 ? SpawnRotationType.This : SpawnRotationType.Target);
313 InheritEventTags = element.GetAttributeBool(nameof(InheritEventTags),
false);
316 public int GetCount(Rand.RandSync randSync)
318 return Rand.Range(MinCount, MaxCount + 1, randSync);
355 TalentIdentifiers = element.GetAttributeIdentifierArray(
"talentidentifiers", Array.Empty<Identifier>());
356 GiveRandom = element.GetAttributeBool(
"giverandom",
false);
403 DebugConsole.ThrowError($
"GiveSkill StatusEffect did not have a skill identifier defined in {parentDebugName}!", contentPackage: element.
ContentPackage);
413 public string Name => $
"Character Spawn Info ({SpeciesName})";
420 public int Count {
get;
private set; }
423 "Should the buffs of the character executing the effect be transferred to the spawned character?"+
424 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
428 "Should the afflictions of the character executing the effect be transferred to the spawned character?" +
429 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
433 "Should the the items from the character executing the effect be transferred to the spawned character?" +
434 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
438 "The maximum number of creatures of the given species and team that can exist per team in the current level before this status effect stops spawning any more.")]
442 public int Stun {
get;
private set; }
448 $
"The strength of the affliction applied on the spawned character. Only relevant if {nameof(AfflictionOnSpawn)} is defined.")]
452 "Should the player controlling the character that executes the effect gain control of the spawned character?" +
453 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
457 "Should the character that executes the effect be removed when the effect executes?" +
458 " Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
462 "Can be used to prevent all the characters from spawning at the exact same position if the effect spawns multiple ones.")]
463 public float Spread {
get;
private set; }
466 "Offset added to the spawn position. " +
467 "Can be used to for example spawn a character a bit up from the center of an item executing the effect.")]
468 public Vector2
Offset {
get;
private set; }
473 [
Serialize(
false,
IsPropertySaveable.No, description:
"Should the character team be inherited from the entity that owns the status effect?")]
481 DebugConsole.ThrowError($
"Invalid character spawn ({Name}) in StatusEffect \"{parentDebugName}\" - identifier not found in the element \"{element}\".", contentPackage: element.
ContentPackage);
491 public string Name =>
"ai trigger";
498 [
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).")]
501 [
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.")]
505 "How much damage the character must receive for this AITrigger to become active? " +
506 "Checks the amount of damage the latest attack did to the character.")]
517 public float Timer {
get;
private set; }
569 private readonly List<RelatedItem> requiredItems;
571 public readonly ImmutableArray<(Identifier propertyName,
object value)>
PropertyEffects;
574 private readonly List<PropertyConditional> propertyConditionals;
575 public bool HasConditions => propertyConditionals !=
null && propertyConditionals.Any();
580 private readonly
bool setValue;
587 private readonly
bool disableDeltaTime;
592 private readonly HashSet<Identifier> tags;
600 private readonly
float lifeTime;
601 private float lifeTimer;
603 private Dictionary<Entity, float> intervalTimers;
608 private readonly
bool oneShot;
610 public static readonly List<DurationListElement>
DurationList =
new List<DurationListElement>();
636 private readonly
bool playSoundOnRequiredItemFailure =
false;
639 private readonly
int useItemCount;
641 private readonly
bool removeItem, dropContainedItems, dropItem, removeCharacter, breakLimb, hideLimb;
642 private readonly
float hideLimbTimer;
647 private readonly Identifier containerForItemsOnCharacterRemoval;
651 private readonly List<Explosion> explosions;
654 get {
return explosions ?? Enumerable.Empty<
Explosion>(); }
657 private readonly List<ItemSpawnInfo> spawnItems;
662 private readonly
bool spawnItemRandomly;
663 private readonly List<CharacterSpawnInfo> spawnCharacters;
672 private readonly List<AITrigger> aiTriggers;
679 private readonly List<EventPrefab> triggeredEvents;
685 private readonly Identifier triggeredEventTargetTag =
"statuseffecttarget".ToIdentifier();
691 private readonly Identifier triggeredEventEntityTag =
"statuseffectentity".ToIdentifier();
697 private readonly Identifier triggeredEventUserTag =
"statuseffectuser".ToIdentifier();
702 private readonly List<(Identifier eventIdentifier, Identifier tag)> eventTargetTags;
753 private readonly HashSet<(Identifier affliction,
float strength)> requiredAfflictions;
761 } =
new List<Affliction>();
768 private readonly
bool multiplyAfflictionsByMaxVitality;
775 public readonly List<(Identifier AfflictionIdentifier,
float ReduceAmount)>
ReduceAffliction =
new List<(Identifier affliction,
float amount)>();
777 private readonly List<Identifier> talentTriggers;
778 private readonly List<int> giveExperiences;
779 private readonly List<GiveSkill> giveSkills;
785 private readonly List<AnimLoadInfo> animationsToTrigger;
808 public Vector2
Offset {
get;
private set; }
812 get {
return string.Join(
",", tags); }
816 if (value ==
null)
return;
818 string[] newTags = value.Split(
',');
819 foreach (
string tag
in newTags)
821 Identifier newTag = tag.Trim().ToIdentifier();
822 if (!tags.Contains(newTag)) { tags.Add(newTag); };
861 if (targetLimbNames !=
null)
864 foreach (
string targetLimbName
in targetLimbNames)
866 if (Enum.TryParse(targetLimbName, ignoreCase:
true, out
LimbType targetLimb)) {
targetLimbs.Add(targetLimb); }
873 string[] targetTypesStr =
876 foreach (
string s
in targetTypesStr)
878 if (!Enum.TryParse(s,
true, out
TargetType targetType))
880 DebugConsole.ThrowError($
"Invalid target type \"{s}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage);
884 targetTypes |= targetType;
887 if (targetTypes == 0)
889 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.";
890 DebugConsole.AddSafeError(errorMessage);
893 var targetIdentifiers = element.GetAttributeIdentifierArray(Array.Empty<Identifier>(),
"targetnames",
"targets",
"targetidentifiers",
"targettags");
894 if (targetIdentifiers.Any())
899 triggeredEventTargetTag = element.GetAttributeIdentifier(
"eventtargettag", triggeredEventTargetTag);
900 triggeredEventEntityTag = element.GetAttributeIdentifier(
"evententitytag", triggeredEventEntityTag);
901 triggeredEventUserTag = element.GetAttributeIdentifier(
"eventusertag", triggeredEventUserTag);
902 spawnItemRandomly = element.GetAttributeBool(
"spawnitemrandomly",
false);
903 multiplyAfflictionsByMaxVitality = element.GetAttributeBool(nameof(multiplyAfflictionsByMaxVitality),
false);
905 playSoundOnRequiredItemFailure = element.GetAttributeBool(
"playsoundonrequireditemfailure",
false);
908 List<XAttribute> propertyAttributes =
new List<XAttribute>();
909 propertyConditionals =
new List<PropertyConditional>();
910 foreach (XAttribute attribute
in element.Attributes())
912 switch (attribute.Name.ToString().ToLowerInvariant())
915 if (!Enum.TryParse(attribute.Value,
true, out
type))
917 DebugConsole.ThrowError($
"Invalid action type \"{attribute.Value}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage);
924 case "targetidentifiers":
932 case "allowedafflictions":
933 case "requiredafflictions":
935 string[] types = attribute.Value.Split(
',');
936 requiredAfflictions ??=
new HashSet<(Identifier, float)>();
937 for (
int i = 0; i < types.Length; i++)
939 requiredAfflictions.Add((types[i].Trim().ToIdentifier(), 0.0f));
942 case "conditionalcomparison":
944 if (!Enum.TryParse(attribute.Value, ignoreCase:
true, out conditionalLogicalOperator))
946 DebugConsole.ThrowError($
"Invalid conditional comparison type \"{attribute.Value}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage);
950 DebugConsole.ThrowError($
"Error in StatusEffect ({parentDebugName}): sounds should be defined as child elements of the StatusEffect, not as attributes.", contentPackage: element.ContentPackage);
955 propertyAttributes.Add(attribute);
963 propertyAttributes.Add(attribute);
967 oneShot = attribute.GetAttributeBool(
false);
970 if (FieldNames.Contains(attribute.Name.ToIdentifier())) {
continue; }
971 propertyAttributes.Add(attribute);
980 propertyAttributes.RemoveAll(a => a.Name.ToString().Equals(
"tags", StringComparison.OrdinalIgnoreCase));
983 List<(Identifier propertyName,
object value)> propertyEffects =
new List<(Identifier propertyName,
object value)>();
984 foreach (XAttribute attribute
in propertyAttributes)
986 propertyEffects.Add((attribute.NameAsIdentifier(), XMLExtensions.GetAttributeObject(attribute)));
990 foreach (var subElement
in element.Elements())
992 switch (subElement.Name.ToString().ToLowerInvariant())
995 explosions ??=
new List<Explosion>();
996 explosions.Add(
new Explosion(subElement, parentDebugName));
999 FireSize = subElement.GetAttributeFloat(
"size", 10.0f);
1009 case "dropcontaineditems":
1010 dropContainedItems =
true;
1015 case "removecharacter":
1016 removeCharacter =
true;
1017 containerForItemsOnCharacterRemoval = subElement.GetAttributeIdentifier(
"moveitemstocontainer", Identifier.Empty);
1024 hideLimbTimer = subElement.GetAttributeFloat(
"duration", 0);
1026 case "requireditem":
1027 case "requireditems":
1028 requiredItems ??=
new List<RelatedItem>();
1030 if (newRequiredItem ==
null)
1032 DebugConsole.ThrowError(
"Error in StatusEffect config - requires an item with no identifiers.", contentPackage: element.ContentPackage);
1035 requiredItems.Add(newRequiredItem);
1037 case "requiredafflictions":
1038 case "requiredaffliction":
1039 requiredAfflictions ??=
new HashSet<(Identifier, float)>();
1040 Identifier[] ids = subElement.GetAttributeIdentifierArray(
"identifier",
null) ?? subElement.GetAttributeIdentifierArray(
"type", Array.Empty<Identifier>());
1041 foreach (var afflictionId
in ids)
1043 requiredAfflictions.Add((
1045 subElement.GetAttributeFloat(
"minstrength", 0.0f)));
1053 if (subElement.GetAttribute(
"name") !=
null)
1055 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - define afflictions using identifiers instead of names.", contentPackage: element.ContentPackage);
1056 string afflictionName = subElement.GetAttributeString(
"name",
"");
1057 afflictionPrefab =
AfflictionPrefab.
List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, StringComparison.OrdinalIgnoreCase));
1058 if (afflictionPrefab ==
null)
1060 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - Affliction prefab \"" + afflictionName +
"\" not found.", contentPackage: element.ContentPackage);
1066 Identifier afflictionIdentifier = subElement.GetAttributeIdentifier(
"identifier",
"");
1067 afflictionPrefab =
AfflictionPrefab.
List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier);
1068 if (afflictionPrefab ==
null)
1070 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - Affliction prefab with the identifier \"" + afflictionIdentifier +
"\" not found.", contentPackage: element.ContentPackage);
1075 Affliction afflictionInstance = afflictionPrefab.
Instantiate(subElement.GetAttributeFloat(1.0f,
"amount", nameof(afflictionInstance.
Strength)));
1078 afflictionInstance.
Probability = subElement.GetAttributeFloat(1.0f, nameof(afflictionInstance.
Probability));
1082 case "reduceaffliction":
1083 if (subElement.GetAttribute(
"name") !=
null)
1085 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - define afflictions using identifiers or types instead of names.", contentPackage: element.ContentPackage);
1087 subElement.GetAttributeIdentifier(
"name",
""),
1088 subElement.GetAttributeFloat(1.0f,
"amount",
"strength",
"reduceamount")));
1092 Identifier name = subElement.GetAttributeIdentifier(
"identifier", subElement.GetAttributeIdentifier(
"type", Identifier.Empty));
1096 ReduceAffliction.Add((name, subElement.GetAttributeFloat(1.0f,
"amount",
"strength",
"reduceamount")));
1100 DebugConsole.ThrowError(
"Error in StatusEffect (" + parentDebugName +
") - Affliction prefab with the identifier or type \"" + name +
"\" not found.", contentPackage: element.ContentPackage);
1105 var newSpawnItem =
new ItemSpawnInfo(subElement, parentDebugName);
1106 if (newSpawnItem.ItemPrefab !=
null)
1108 spawnItems ??=
new List<ItemSpawnInfo>();
1109 spawnItems.Add(newSpawnItem);
1112 case "triggerevent":
1113 triggeredEvents ??=
new List<EventPrefab>();
1114 Identifier identifier = subElement.GetAttributeIdentifier(
"identifier", Identifier.Empty);
1115 if (!identifier.IsEmpty)
1120 triggeredEvents.Add(prefab);
1123 foreach (var eventElement
in subElement.Elements())
1125 if (eventElement.NameAsIdentifier() !=
"ScriptedEvent") {
continue; }
1126 triggeredEvents.Add(
new EventPrefab(eventElement, file:
null));
1129 case "spawncharacter":
1131 if (!newSpawnCharacter.SpeciesName.IsEmpty)
1133 spawnCharacters ??=
new List<CharacterSpawnInfo>();
1134 spawnCharacters.Add(newSpawnCharacter);
1137 case "givetalentinfo":
1138 var newGiveTalentInfo =
new GiveTalentInfo(subElement, parentDebugName);
1139 if (newGiveTalentInfo.TalentIdentifiers.Any())
1145 case "refundtalents":
1149 aiTriggers ??=
new List<AITrigger>();
1150 aiTriggers.Add(
new AITrigger(subElement));
1152 case "talenttrigger":
1153 talentTriggers ??=
new List<Identifier>();
1154 talentTriggers.Add(subElement.GetAttributeIdentifier(
"effectidentifier", Identifier.Empty));
1157 eventTargetTags ??=
new List<(Identifier eventIdentifier, Identifier tag)>();
1158 eventTargetTags.Add(
1159 (subElement.GetAttributeIdentifier(
"eventidentifier", Identifier.Empty),
1160 subElement.GetAttributeIdentifier(
"tag", Identifier.Empty)));
1162 case "giveexperience":
1163 giveExperiences ??=
new List<int>();
1164 giveExperiences.Add(subElement.GetAttributeInt(
"amount", 0));
1167 giveSkills ??=
new List<GiveSkill>();
1168 giveSkills.Add(
new GiveSkill(subElement, parentDebugName));
1172 luaHook ??=
new List<(string, ContentXElement)>();
1173 luaHook.Add((subElement.GetAttributeString(
"name",
""), subElement));
1175 case "triggeranimation":
1177 string fileName = subElement.GetAttributeString(
"filename", def:
null) ?? subElement.GetAttributeString(
"file", def:
null);
1178 Either<string, ContentPath> file = fileName !=
null ? fileName.ToLowerInvariant() : subElement.GetAttributeContentPath(
"path");
1179 if (!file.TryGet(out
string _))
1181 if (!file.TryGet(out
ContentPath _) || (file.TryGet(out
ContentPath contentPath) && contentPath.IsNullOrWhiteSpace()))
1183 DebugConsole.ThrowError($
"Error in a <TriggerAnimation> element of {subElement.ParseContentPathFromUri()}: neither path nor filename defined!",
1184 contentPackage: subElement.ContentPackage);
1188 float priority = subElement.GetAttributeFloat(
"priority", def: 0f);
1189 Identifier[] expectedSpeciesNames = subElement.GetAttributeIdentifierArray(
"expectedspecies", Array.Empty<Identifier>());
1190 animationsToTrigger ??=
new List<AnimLoadInfo>();
1191 animationsToTrigger.Add(
new AnimLoadInfo(animType, file, priority, expectedSpeciesNames.ToImmutableArray()));
1196 InitProjSpecific(element, parentDebugName);
1199 partial
void InitProjSpecific(
ContentXElement element,
string parentDebugName);
1203 return (targetTypes & targetType) != 0;
1210 if (ChangesItemCondition(propertyName, value, out
float conditionValue))
1212 return conditionValue < 0.0f || (setValue && conditionValue <= 0.0f);
1222 if (ChangesItemCondition(propertyName, value, out
float conditionValue))
1224 return conditionValue > 0.0f || (setValue && conditionValue > 0.0f);
1230 private bool ChangesItemCondition(Identifier propertyName,
object value, out
float conditionValue)
1232 if (propertyName ==
"condition")
1244 conditionValue = 0.0f;
1256 return itemPrefab.
Tags.Any(t => propertyConditionals.Any(pc => pc.TargetTagMatchesTagCondition(t)));
1262 if (requiredAfflictions ==
null) {
return true; }
1263 if (attackResult.
Afflictions ==
null) {
return false; }
1264 if (attackResult.
Afflictions.None(a => requiredAfflictions.Any(a2 => a.Strength >= a2.strength && (a.Identifier == a2.affliction || a.Prefab.AfflictionType == a2.affliction))))
1273 if (entity ==
null || requiredItems ==
null) {
return true; }
1274 foreach (
RelatedItem requiredItem
in requiredItems)
1276 if (entity is
Item item)
1290 if (
Range <= 0.0f) {
return; }
1311 if (targets.Contains(powered)) {
continue; }
1333 float xDiff = Math.Abs(e.
WorldPosition.X - worldPosition.X);
1334 if (xDiff >
Range) {
return false; }
1335 float yDiff = Math.Abs(e.
WorldPosition.Y - worldPosition.Y);
1336 if (yDiff >
Range) {
return false; }
1337 if (xDiff * xDiff + yDiff * yDiff <
Range *
Range)
1350 private delegate
bool ShouldShortCircuit(
bool condition, out
bool valueToReturn);
1355 private static bool ShouldShortCircuitLogicalOrOperator(
bool condition, out
bool valueToReturn)
1357 valueToReturn =
true;
1364 private static bool ShouldShortCircuitLogicalAndOperator(
bool condition, out
bool valueToReturn)
1366 valueToReturn =
false;
1370 private bool HasRequiredConditions(IReadOnlyList<ISerializableEntity> targets, IReadOnlyList<PropertyConditional> conditionals,
bool targetingContainer =
false)
1372 if (conditionals.Count == 0) {
return true; }
1373 if (targets.Count == 0 && requiredItems !=
null && requiredItems.All(ri => ri.MatchOnEmpty)) {
return true; }
1375 (ShouldShortCircuit, bool) shortCircuitMethodPair = conditionalLogicalOperator
switch
1377 PropertyConditional.LogicalOperatorType.Or => (ShouldShortCircuitLogicalOrOperator,
false),
1378 PropertyConditional.LogicalOperatorType.And => (ShouldShortCircuitLogicalAndOperator,
true),
1379 _ =>
throw new NotImplementedException()
1381 var (shouldShortCircuit, didNotShortCircuit) = shortCircuitMethodPair;
1383 for (
int i = 0; i < conditionals.Count; i++)
1387 var pc = conditionals[i];
1388 if (!pc.TargetContainer || targetingContainer)
1390 if (shouldShortCircuit(AnyTargetMatches(targets, pc.TargetItemComponent, pc), out valueToReturn)) {
return valueToReturn; }
1394 var target = FindTargetItemOrComponent(targets);
1396 if (targetItem?.ParentInventory ==
null)
1400 bool comparisonIsNeq = pc.ComparisonOperator == PropertyConditional.ComparisonOperatorType.NotEquals;
1401 if (shouldShortCircuit(comparisonIsNeq, out valueToReturn))
1403 return valueToReturn;
1407 var owner = targetItem.ParentInventory.Owner;
1408 if (pc.TargetGrandParent && owner is
Item ownerItem)
1410 owner = ownerItem.ParentInventory?.Owner;
1412 if (owner is
Item container)
1414 if (pc.Type == PropertyConditional.ConditionType.HasTag)
1417 if (shouldShortCircuit(pc.Matches(container), out valueToReturn)) {
return valueToReturn; }
1421 if (shouldShortCircuit(AnyTargetMatches(container.AllPropertyObjects, pc.TargetItemComponent, pc), out valueToReturn)) {
return valueToReturn; }
1424 if (owner is Character character && shouldShortCircuit(pc.Matches(character), out valueToReturn)) {
return valueToReturn; }
1426 return didNotShortCircuit;
1428 static bool AnyTargetMatches(IReadOnlyList<ISerializableEntity> targets,
string targetItemComponentName, PropertyConditional conditional)
1430 for (
int i = 0; i < targets.Count; i++)
1432 if (!
string.IsNullOrEmpty(targetItemComponentName))
1434 if (!(targets[i] is
ItemComponent ic) || ic.Name != targetItemComponentName) {
continue; }
1436 if (conditional.Matches(targets[i]))
1444 static ISerializableEntity FindTargetItemOrComponent(IReadOnlyList<ISerializableEntity> targets)
1446 for (
int i = 0; i < targets.Count; i++)
1456 if (entity is
Item item)
1517 affliction.
Source = user;
1521 private static readonly List<Entity> intervalsToRemove =
new List<Entity>();
1525 if (
Interval > 0.0f && entity !=
null && intervalTimers !=
null)
1527 if (intervalTimers.ContainsKey(entity))
1529 intervalTimers[entity] -= deltaTime;
1530 if (intervalTimers[entity] > 0.0f) {
return true; }
1532 intervalsToRemove.Clear();
1533 intervalsToRemove.AddRange(intervalTimers.Keys.Where(e => e.Removed));
1534 foreach (var toRemove
in intervalsToRemove)
1536 intervalTimers.Remove(toRemove);
1553 if (existingEffect !=
null)
1566 protected readonly List<ISerializableEntity>
currentTargets =
new List<ISerializableEntity>();
1567 public virtual void Apply(
ActionType type,
float deltaTime,
Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition =
null)
1570 if (this.type !=
type) {
return; }
1586 if (!hasRequiredItems && playSoundOnRequiredItemFailure)
1588 PlaySound(entity, GetHull(entity), GetPosition(entity, targets, worldPosition));
1598 if (existingEffect !=
null)
1613 hull = character.AnimController.CurrentHull;
1615 else if (entity is
Item item)
1617 hull = item.CurrentHull;
1622 private Vector2 GetPosition(Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition =
null)
1624 Vector2 position = worldPosition ?? (entity ==
null || entity.Removed ? Vector2.Zero : entity.WorldPosition);
1625 if (worldPosition ==
null)
1627 if (entity is Character character && !character.Removed &&
targetLimbs !=
null)
1631 Limb limb = character.AnimController.GetLimb(targetLimbType);
1632 if (limb !=
null && !limb.Removed)
1641 for (
int i = 0; i < targets.Count; i++)
1643 if (targets[i] is
Item targetItem)
1645 position = targetItem.WorldPosition;
1652 for (
int i = 0; i < targets.Count; i++)
1654 if (targets[i] is Limb targetLimb && !targetLimb.Removed)
1656 position = targetLimb.WorldPosition;
1667 protected void Apply(
float deltaTime,
Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition =
null)
1672 lifeTimer -= deltaTime;
1673 if (lifeTimer <= 0) {
return; }
1678 if (entity is
Item item)
1680 var result =
GameMain.
LuaCs.
Hook.
Call<
bool?>(
"statusEffect.apply." + item.Prefab.Identifier,
this, deltaTime, entity, targets, worldPosition);
1682 if (result !=
null && result.Value) {
return; }
1687 var result =
GameMain.
LuaCs.
Hook.
Call<
bool?>(
"statusEffect.apply." + character.SpeciesName,
this, deltaTime, entity, targets, worldPosition);
1689 if (result !=
null && result.Value) {
return; }
1693 if (luaHook !=
null)
1697 var result =
GameMain.
LuaCs.
Hook.
Call<
bool?>(hookName,
this, deltaTime, entity, targets, worldPosition, element);
1699 if (result !=
null && result.Value) {
return; }
1705 Hull hull = GetHull(entity);
1706 Vector2 position = GetPosition(entity, targets, worldPosition);
1707 if (useItemCount > 0)
1710 Limb useTargetLimb =
null;
1711 for (
int i = 0; i < targets.Count; i++)
1715 useTargetCharacter = character;
1720 useTargetLimb = limb;
1721 useTargetCharacter ??= limb.character;
1725 for (
int i = 0; i < targets.Count; i++)
1727 if (targets[i] is not
Item item) {
continue; }
1728 for (
int j = 0; j < useItemCount; j++)
1730 if (item.Removed) {
continue; }
1731 item.Use(deltaTime, user:
null, useTargetLimb, useTargetCharacter);
1738 for (
int i = 0; i < targets.Count; i++)
1740 if (targets[i] is
Item item)
1742 item.Drop(dropper:
null);
1746 if (dropContainedItems)
1748 for (
int i = 0; i < targets.Count; i++)
1750 if (targets[i] is
Item item)
1752 foreach (var itemContainer
in item.GetComponents<
ItemContainer>())
1754 foreach (var containedItem
in itemContainer.Inventory.AllItemsMod)
1756 containedItem.Drop(dropper:
null);
1762 foreach (var containedItem
in character.Inventory.AllItemsMod)
1764 containedItem.Drop(dropper:
null);
1771 for (
int i = 0; i < targets.Count; i++)
1776 if (removeCharacter)
1778 for (
int i = 0; i < targets.Count; i++)
1780 Character targetCharacter = GetCharacterFromTarget(targets[i]);
1781 if (targetCharacter !=
null) { RemoveCharacter(targetCharacter); }
1784 if (breakLimb || hideLimb)
1786 for (
int i = 0; i < targets.Count; i++)
1788 var target = targets[i];
1790 if (targetLimb ==
null && target is
Character character)
1792 foreach (
Limb limb
in character.AnimController.Limbs)
1801 if (targetLimb !=
null)
1805 targetLimb.
character.
TrySeverLimbJoints(targetLimb, severLimbsProbability: 1, damage: -1, allowBeheading:
true, ignoreSeveranceProbabilityModifier:
true, attacker: user);
1821 for (
int i = 0; i < targets.Count; i++)
1823 var target = targets[i];
1824 if (target?.SerializableProperties ==
null) {
continue; }
1825 if (target is
Entity targetEntity)
1827 if (targetEntity.Removed) {
continue; }
1829 else if (target is
Limb limb)
1831 if (limb.Removed) {
continue; }
1832 position = limb.WorldPosition +
Offset;
1836 if (!target.SerializableProperties.TryGetValue(propertyName, out
SerializableProperty property))
1840 ApplyToProperty(target, property, value, deltaTime);
1845 if (explosions !=
null)
1847 foreach (
Explosion explosion
in explosions)
1849 explosion.
Explode(position, damageSource: entity, attacker: user);
1855 for (
int i = 0; i < targets.Count; i++)
1857 var target = targets[i];
1860 if (target ==
null) {
continue; }
1866 if (character.Removed) {
continue; }
1867 newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime, multiplyAfflictionsByMaxVitality);
1868 character.LastDamageSource = entity;
1869 foreach (
Limb limb
in character.AnimController.Limbs)
1871 if (limb.
Removed) {
continue; }
1874 AttackResult result = limb.
character.
DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attackImpulse: Vector2.Zero, attacker: affliction.
Source, allowStacking: !setValue);
1876 RegisterTreatmentResults(user, entity as
Item, limb, affliction, result);
1881 else if (target is
Limb limb)
1886 newAffliction = GetMultipliedAffliction(affliction, entity, limb.
character, deltaTime, multiplyAfflictionsByMaxVitality);
1887 AttackResult result = limb.
character.
DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attackImpulse: Vector2.Zero, attacker: affliction.
Source, allowStacking: !setValue);
1889 RegisterTreatmentResults(user, entity as
Item, limb, affliction, result);
1895 Limb targetLimb =
null;
1899 targetCharacter = character;
1904 targetCharacter = limb.character;
1906 if (targetCharacter !=
null && !targetCharacter.
Removed)
1910 float reduceAmount = amount * GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
1911 float prevVitality = targetCharacter.
Vitality;
1912 if (targetLimb !=
null)
1920 if (!targetCharacter.
IsDead)
1922 float healthChange = targetCharacter.
Vitality - prevVitality;
1928 GameMain.
Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, user, -healthChange, 0.0f);
1935 if (aiTriggers !=
null)
1938 if (targetCharacter ==
null)
1940 if (target is
Limb targetLimb && !targetLimb.
Removed)
1942 targetCharacter = targetLimb.character;
1947 targetCharacter ??= entityCharacter;
1948 if (targetCharacter !=
null && !targetCharacter.
Removed && !targetCharacter.
IsPlayer)
1952 foreach (
AITrigger trigger
in aiTriggers)
1954 if (Rand.Value(Rand.RandSync.Unsynced) > trigger.
Probability) {
continue; }
1955 if (entityCharacter != targetCharacter)
1959 if (hitLimb != targetLimb) {
continue; }
1963 enemyAI.LaunchTrigger(trigger);
1970 if (talentTriggers !=
null)
1972 Character targetCharacter = GetCharacterFromTarget(target);
1973 if (targetCharacter !=
null && !targetCharacter.
Removed)
1975 foreach (Identifier talentTrigger
in talentTriggers)
1982 TryTriggerAnimation(target, entity);
1987 if (giveExperiences !=
null)
1989 foreach (
int giveExperience
in giveExperiences)
1991 Character targetCharacter = GetCharacterFromTarget(target);
1992 if (targetCharacter !=
null && !targetCharacter.
Removed)
1999 if (giveSkills !=
null)
2001 Character targetCharacter = GetCharacterFromTarget(target);
2002 if (targetCharacter is { Removed:
false })
2004 foreach (
GiveSkill giveSkill
in giveSkills)
2018 Identifier GetRandomSkill()
2020 return targetCharacter.
Info?.
Job?.
GetSkills().GetRandomUnsynced()?.Identifier ?? Identifier.Empty;
2028 if (GetCharacterFromTarget(target) is { Removed:
false } c)
2030 c.Info?.AddRefundPoints(1);
2036 Character targetCharacter = GetCharacterFromTarget(target);
2037 if (targetCharacter?.Info ==
null) {
continue; }
2038 if (!TalentTree.JobTalentTrees.TryGet(targetCharacter.
Info.
Job.
Prefab.
Identifier, out TalentTree characterTalentTree)) {
continue; }
2045 IEnumerable<Identifier> viableTalents = giveTalentInfo.
TalentIdentifiers.Where(
id => !targetCharacter.Info.UnlockedTalents.Contains(
id) && !characterTalentTree.AllTalentIdentifiers.Contains(
id));
2046 if (viableTalents.None()) {
continue; }
2047 targetCharacter.GiveTalent(viableTalents.GetRandomUnsynced(),
true);
2053 if (targetCharacter.Info.UnlockedTalents.Contains(
id) || characterTalentTree.AllTalentIdentifiers.Contains(
id)) {
continue; }
2054 targetCharacter.GiveTalent(
id,
true);
2060 if (eventTargetTags !=
null)
2062 foreach ((Identifier eventId, Identifier tag) in eventTargetTags)
2066 targets.Where(t => t is
Entity).ForEach(t => ev.AddTarget(tag, (
Entity)t));
2073 if (
FireSize > 0.0f && entity !=
null)
2075 var fire =
new FireSource(position, hull, sourceCharacter: user);
2076 fire.Size =
new Vector2(
FireSize, fire.Size.Y);
2081 foreach (
EventPrefab eventPrefab
in triggeredEvents)
2084 if (ev ==
null) {
continue; }
2085 eventManager.QueuedEvents.Enqueue(ev);
2088 if (!triggeredEventTargetTag.IsEmpty)
2090 IEnumerable<ISerializableEntity> eventTargets = targets.Where(t => t is
Entity);
2091 if (eventTargets.Any())
2093 scriptedEvent.Targets.Add(triggeredEventTargetTag, eventTargets.Cast<
Entity>().ToList());
2096 if (!triggeredEventEntityTag.IsEmpty && entity !=
null)
2098 scriptedEvent.Targets.Add(triggeredEventEntityTag,
new List<Entity> { entity });
2100 if (!triggeredEventUserTag.IsEmpty && user !=
null)
2102 scriptedEvent.Targets.Add(triggeredEventUserTag,
new List<Entity> { user });
2108 if (isNotClient && entity !=
null &&
Entity.
Spawner !=
null)
2110 if (spawnCharacters !=
null)
2114 var characters =
new List<Character>();
2115 for (
int i = 0; i < characterSpawnInfo.
Count; i++)
2118 onSpawn: newCharacter =>
2120 if (characterSpawnInfo.InheritTeam)
2122 newCharacter.TeamID = entity switch
2124 Character c => c.TeamID,
2125 Item it => it.GetRootInventoryOwner() is Character owner ? owner.TeamID : it.Submarine?.TeamID ?? newCharacter.TeamID,
2126 MapEntity e => e.Submarine?.TeamID ?? newCharacter.TeamID,
2127 _ => newCharacter.TeamID
2132 if (Character.CharacterList.Count(c => c.SpeciesName == characterSpawnInfo.SpeciesName && c.TeamID == newCharacter.TeamID) > characterSpawnInfo.TotalMaxCount)
2134 Entity.Spawner?.AddEntityToRemoveQueue(newCharacter);
2140 entity is
Item item &&
2143 enemyAi.PetBehavior.Owner = inv.Owner as Character;
2145 characters.Add(newCharacter);
2146 if (characters.Count == characterSpawnInfo.
Count)
2148 SwarmBehavior.CreateSwarm(characters.Cast<AICharacter>());
2152 if (!AfflictionPrefab.Prefabs.TryGet(characterSpawnInfo.AfflictionOnSpawn, out AfflictionPrefab afflictionPrefab))
2154 DebugConsole.NewMessage($
"Could not apply an affliction to the spawned character(s). No affliction with the identifier \"{characterSpawnInfo.AfflictionOnSpawn}\" found.", Color.Red);
2157 newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(characterSpawnInfo.
AfflictionStrength));
2159 if (characterSpawnInfo.
Stun > 0)
2161 newCharacter.SetStun(characterSpawnInfo.Stun);
2163 foreach (var target in targets)
2165 if (target is not
Character character) {
continue; }
2166 if (characterSpawnInfo.
TransferInventory && character.Inventory !=
null && newCharacter.Inventory !=
null)
2168 if (character.Inventory.Capacity != newCharacter.Inventory.Capacity) { return; }
2169 for (
int i = 0; i < character.Inventory.Capacity && i < newCharacter.Inventory.Capacity; i++)
2171 character.Inventory.GetItemsAt(i).ForEachMod(item => newCharacter.Inventory.TryPutItem(item, i, allowSwapping:
true, allowCombine:
false, user:
null));
2176 foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions())
2178 if (affliction.Prefab.IsBuff)
2180 if (!characterSpawnInfo.TransferBuffs) { continue; }
2184 if (!characterSpawnInfo.TransferAfflictions) { continue; }
2188 float afflictionStrength = affliction.Strength * (newCharacter.MaxVitality / 100.0f);
2189 newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, affliction.Prefab.Instantiate(afflictionStrength));
2192 if (i == characterSpawnInfo.
Count)
2194 if (characterSpawnInfo.TransferControl)
2197 if (Character.Controlled == target)
2199 Character.Controlled = newCharacter;
2202 foreach (Client c in GameMain.Server.ConnectedClients)
2204 if (c.Character != target) { continue; }
2205 GameMain.Server.SetClientCharacter(c, newCharacter);
2212 if (characterSpawnInfo.InheritEventTags)
2214 foreach (var activeEvent in GameMain.GameSession.EventManager.ActiveEvents)
2216 if (activeEvent is ScriptedEvent scriptedEvent)
2218 scriptedEvent.InheritTags(entity, newCharacter);
2227 if (spawnItems !=
null && spawnItems.Count > 0)
2229 if (spawnItemRandomly)
2231 if (spawnItems.Count > 0)
2233 var randomSpawn = spawnItems.GetRandomUnsynced();
2234 int count = randomSpawn.GetCount(Rand.RandSync.Unsynced);
2235 if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) < randomSpawn.Probability)
2237 for (
int i = 0; i < count; i++)
2239 ProcessItemSpawnInfo(randomSpawn);
2246 foreach (ItemSpawnInfo itemSpawnInfo
in spawnItems)
2248 int count = itemSpawnInfo.GetCount(Rand.RandSync.Unsynced);
2249 if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) < itemSpawnInfo.Probability)
2251 for (
int i = 0; i < count; i++)
2253 ProcessItemSpawnInfo(itemSpawnInfo);
2259 void ProcessItemSpawnInfo(ItemSpawnInfo spawnInfo)
2261 if (spawnInfo.SpawnPosition == ItemSpawnInfo.SpawnPositionType.Target)
2263 foreach (var target
in targets)
2265 if (target is Entity targetEntity)
2267 SpawnItem(spawnInfo, entity, sourceBody, position, targetEntity);
2273 SpawnItem(spawnInfo, entity, sourceBody, position, targetEntity:
null);
2279 ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound:
true);
2285 if (Interval > 0.0f && entity !=
null)
2287 intervalTimers ??=
new Dictionary<Entity, float>();
2288 intervalTimers[entity] = Interval;
2291 private static Character GetCharacterFromTarget(ISerializableEntity target)
2294 if (targetCharacter ==
null)
2296 if (target is Limb targetLimb && !targetLimb.Removed)
2298 targetCharacter = targetLimb.character;
2301 return targetCharacter;
2304 private void RemoveCharacter(Character character)
2306 if (containerForItemsOnCharacterRemoval != Identifier.Empty)
2308 ItemPrefab containerPrefab =
2309 ItemPrefab.Prefabs.Find(me => me.Tags.Contains(containerForItemsOnCharacterRemoval)) ??
2310 MapEntityPrefab.FindByIdentifier(containerForItemsOnCharacterRemoval) as ItemPrefab;
2312 if (containerPrefab ==
null)
2314 DebugConsole.ThrowError($
"Could not spawn a container for a removed character's items. No item found with the identifier or tag \"{containerForItemsOnCharacterRemoval}\"");
2318 Entity.Spawner?.AddItemToSpawnQueue(containerPrefab, character.WorldPosition, onSpawned: OnItemContainerSpawned);
2321 void OnItemContainerSpawned(
Item item)
2323 if (character.Inventory ==
null) {
return; }
2325 item.UpdateTransform();
2326 item.AddTag(
"name:" + character.Name);
2327 if (character.Info?.Job is { } job) { item.AddTag($
"job:{job.Name}"); }
2330 List<Item> inventoryItems =
new List<Item>(character.Inventory.AllItemsMod);
2331 foreach (
Item inventoryItem
in inventoryItems)
2333 if (!itemContainer.Inventory.TryPutItem(inventoryItem, user:
null, createNetworkEvent:
true))
2336 inventoryItem.Drop(dropper: character, createNetworkEvent:
true);
2341 Entity.Spawner?.AddEntityToRemoveQueue(character);
2344 void SpawnItem(ItemSpawnInfo chosenItemSpawnInfo, Entity entity, PhysicsBody sourceBody, Vector2 position, Entity targetEntity)
2347 PhysicsBody parentItemBody = parentItem?.body;
2348 if (user ==
null && parentItem !=
null)
2354 if (chosenItemSpawnInfo.SpawnPosition == ItemSpawnInfo.SpawnPositionType.Target && targetEntity !=
null)
2356 entity = targetEntity;
2357 position = entity.WorldPosition;
2358 if (entity is
Item it)
2361 (entity as
Item)?.body ??
2362 (entity as Character)?.AnimController.Collider;
2366 switch (chosenItemSpawnInfo.SpawnPosition)
2368 case ItemSpawnInfo.SpawnPositionType.This:
2369 case ItemSpawnInfo.SpawnPositionType.Target:
2370 Entity.Spawner?.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem =>
2375 var rope = newItem.GetComponent<
Rope>();
2376 if (rope !=
null && sourceBody !=
null && sourceBody.UserData is Limb sourceLimb)
2378 rope.
Attach(sourceLimb, newItem);
2380 newItem.CreateServerEvent(rope);
2383 float spread = Rand.Range(-chosenItemSpawnInfo.AimSpreadRad, chosenItemSpawnInfo.AimSpreadRad);
2384 float rotation = chosenItemSpawnInfo.RotationRad;
2386 if (sourceBody !=
null)
2388 worldPos = sourceBody.Position;
2389 if (user?.Submarine !=
null)
2391 worldPos += user.Submarine.Position;
2396 worldPos = entity.WorldPosition;
2398 switch (chosenItemSpawnInfo.RotationType)
2400 case ItemSpawnInfo.SpawnRotationType.None:
2401 rotation = chosenItemSpawnInfo.RotationRad;
2403 case ItemSpawnInfo.SpawnRotationType.This:
2404 if (sourceBody !=
null)
2406 rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.RotationRad);
2408 else if (parentItemBody !=
null)
2410 rotation = parentItemBody.TransformRotation(chosenItemSpawnInfo.RotationRad);
2413 case ItemSpawnInfo.SpawnRotationType.Target:
2414 rotation = MathUtils.VectorToAngle(entity.WorldPosition - worldPos);
2416 case ItemSpawnInfo.SpawnRotationType.Limb:
2417 if (sourceBody !=
null)
2419 rotation = sourceBody.TransformedRotation;
2422 case ItemSpawnInfo.SpawnRotationType.Collider:
2423 if (parentItemBody !=
null)
2425 rotation = parentItemBody.TransformedRotation;
2427 else if (user !=
null)
2429 rotation = user.AnimController.Collider.Rotation + MathHelper.PiOver2;
2432 case ItemSpawnInfo.SpawnRotationType.MainLimb:
2435 rotation = user.AnimController.MainLimb.body.TransformedRotation;
2438 case ItemSpawnInfo.SpawnRotationType.Random:
2439 if (projectile !=
null)
2441 DebugConsole.LogError(
"Random rotation is not supported for Projectiles.");
2445 rotation = Rand.Range(0f, MathHelper.TwoPi, Rand.RandSync.Unsynced);
2449 throw new NotImplementedException(
"Item spawn rotation type not implemented: " + chosenItemSpawnInfo.RotationType);
2453 rotation += chosenItemSpawnInfo.RotationRad * user.AnimController.Dir;
2456 if (projectile !=
null)
2458 var sourceEntity = (sourceBody?.UserData as ISpatialEntity) ?? entity;
2459 Vector2 spawnPos = sourceEntity.SimPosition;
2460 List<Body> ignoredBodies =
null;
2463 ignoredBodies = user?.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList();
2466 float damageMultiplier = 1f;
2468 if (sourceEntity is Limb attackLimb)
2470 damageMultiplier = attackLimb.attack?.DamageMultiplier ?? 1.0f;
2473 projectile.
Shoot(user, spawnPos, spawnPos, rotation,
2474 ignoredBodies: ignoredBodies, createNetworkEvent:
true, damageMultiplier: damageMultiplier);
2477 else if (newItem.body !=
null)
2479 newItem.body.SetTransform(newItem.SimPosition, rotation);
2480 Vector2 impulseDir =
new Vector2(MathF.Cos(rotation), MathF.Sin(rotation));
2481 newItem.body.ApplyLinearImpulse(impulseDir * chosenItemSpawnInfo.Impulse);
2484 OnItemSpawned(newItem, chosenItemSpawnInfo);
2487 case ItemSpawnInfo.SpawnPositionType.ThisInventory:
2489 Inventory inventory =
null;
2490 if (entity is Character character && character.Inventory !=
null)
2492 inventory = character.Inventory;
2494 else if (entity is
Item item)
2498 if (itemContainer.
CanBeContained(chosenItemSpawnInfo.ItemPrefab))
2504 if (!chosenItemSpawnInfo.SpawnIfCantBeContained && inventory ==
null)
2509 if (inventory !=
null && (inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull))
2511 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: item =>
2513 if (chosenItemSpawnInfo.Equip && entity is Character character && character.Inventory !=
null)
2516 List<InvSlotType> allowedSlots =
2517 item.GetComponents<
Pickable>().Count() > 1 ?
2521 character.Inventory.TryPutItem(item,
null, allowedSlots);
2523 OnItemSpawned(item, chosenItemSpawnInfo);
2528 case ItemSpawnInfo.SpawnPositionType.SameInventory:
2530 Inventory inventory =
null;
2531 if (entity is Character character)
2535 else if (entity is
Item item)
2537 inventory = item.ParentInventory;
2539 if (inventory !=
null)
2541 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: (
Item newItem) =>
2543 OnItemSpawned(newItem, chosenItemSpawnInfo);
2546 else if (chosenItemSpawnInfo.SpawnIfNotInInventory)
2548 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position, onSpawned: (
Item newItem) =>
2550 OnItemSpawned(newItem, chosenItemSpawnInfo);
2555 case ItemSpawnInfo.SpawnPositionType.ContainedInventory:
2557 Inventory thisInventory =
null;
2558 if (entity is Character character)
2560 thisInventory = character.Inventory;
2562 else if (entity is
Item item)
2565 thisInventory = itemContainer?.
Inventory;
2566 if (!chosenItemSpawnInfo.SpawnIfCantBeContained && !itemContainer.CanBeContained(chosenItemSpawnInfo.ItemPrefab))
2571 if (thisInventory !=
null)
2573 foreach (
Item item
in thisInventory.AllItems)
2575 Inventory containedInventory = item.GetComponent<
ItemContainer>()?.Inventory;
2576 if (containedInventory !=
null && (containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull))
2578 Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: (
Item newItem) =>
2580 OnItemSpawned(newItem, chosenItemSpawnInfo);
2589 void OnItemSpawned(
Item newItem, ItemSpawnInfo itemSpawnInfo)
2591 newItem.Condition = newItem.MaxCondition * itemSpawnInfo.Condition;
2592 if (itemSpawnInfo.InheritEventTags)
2594 foreach (var activeEvent
in GameMain.GameSession.EventManager.ActiveEvents)
2596 if (activeEvent is ScriptedEvent scriptedEvent)
2598 scriptedEvent.InheritTags(entity, newItem);
2605 private void TryTriggerAnimation(ISerializableEntity target, Entity entity)
2607 if (animationsToTrigger ==
null) {
return; }
2609 if ((GetCharacterFromTarget(target) ?? entity as Character) is Character targetCharacter)
2611 foreach (AnimLoadInfo animLoadInfo
in animationsToTrigger)
2613 if (failedAnimations !=
null && failedAnimations.Contains((targetCharacter, animLoadInfo))) {
continue; }
2614 if (!targetCharacter.AnimController.TryLoadTemporaryAnimation(animLoadInfo, throwErrors: animLoadInfo.ExpectedSpeciesNames.Contains(targetCharacter.SpeciesName)))
2616 failedAnimations ??=
new HashSet<(Character, AnimLoadInfo)>();
2617 failedAnimations.Add((targetCharacter, animLoadInfo));
2623 partial
void ApplyProjSpecific(
float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Hull currentHull, Vector2 worldPosition,
bool playSound);
2625 private void ApplyToProperty(ISerializableEntity target, SerializableProperty property,
object value,
float deltaTime)
2627 if (disableDeltaTime || setValue) { deltaTime = 1.0f; }
2628 if (value is
int || value is
float)
2630 float propertyValueF =
property.GetFloatValue(target);
2631 if (property.PropertyType == typeof(
float))
2633 float floatValue = value is
float single ? single : (int)value;
2634 floatValue *= deltaTime;
2637 floatValue += propertyValueF;
2639 property.TrySetValue(target, floatValue);
2642 else if (property.PropertyType == typeof(
int))
2644 int intValue = (int)(value is
float single ? single * deltaTime : (
int)value * deltaTime);
2647 intValue += (int)propertyValueF;
2649 property.TrySetValue(target, intValue);
2653 else if (value is
bool propertyValueBool)
2655 property.TrySetValue(target, propertyValueBool);
2658 property.TrySetValue(target, value);
2663 UpdateAllProjSpecific(deltaTime);
2666 for (
int i = DurationList.Count - 1; i >= 0; i--)
2672 DurationList.RemoveAt(i);
2676 element.
Targets.RemoveAll(t =>
2678 (t is
Limb limb && (limb.character ==
null || limb.character.Removed)));
2679 if (element.
Targets.Count == 0)
2681 DurationList.RemoveAt(i);
2687 if (target?.SerializableProperties !=
null)
2689 foreach (var (propertyName, value) in element.Parent.PropertyEffects)
2695 element.Parent.ApplyToProperty(target, property, value, CoroutineManager.DeltaTime);
2699 foreach (
Affliction affliction
in element.Parent.Afflictions)
2704 if (character.Removed) {
continue; }
2705 newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, character, deltaTime, element.Parent.multiplyAfflictionsByMaxVitality);
2706 var result = character.AddDamage(character.WorldPosition, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attacker: element.User);
2707 element.Parent.RegisterTreatmentResults(element.Parent.user, element.Entity as
Item, result.HitLimb, affliction, result);
2709 else if (target is
Limb limb)
2711 if (limb.character.Removed || limb.Removed) {
continue; }
2712 newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime, element.Parent.multiplyAfflictionsByMaxVitality);
2713 var result = limb.character.DamageLimb(limb.WorldPosition, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound:
false, attackImpulse: Vector2.Zero, attacker: element.User);
2714 element.Parent.RegisterTreatmentResults(element.Parent.user, element.Entity as
Item, limb, affliction, result);
2718 foreach ((Identifier affliction,
float amount) in element.Parent.ReduceAffliction)
2720 Limb targetLimb =
null;
2724 targetCharacter = character;
2726 else if (target is
Limb limb)
2729 targetCharacter = limb.character;
2731 if (targetCharacter !=
null && !targetCharacter.
Removed)
2734 if (element.Entity is
Item item && item.UseInHealthInterface) { actionType = element.Parent.type; }
2735 float reduceAmount = amount * element.Parent.GetAfflictionMultiplier(element.Entity, targetCharacter, deltaTime);
2736 float prevVitality = targetCharacter.
Vitality;
2737 if (targetLimb !=
null)
2745 if (!targetCharacter.
IsDead)
2747 float healthChange = targetCharacter.
Vitality - prevVitality;
2749 if (element.User !=
null)
2753 GameMain.
Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, element.User, -healthChange, 0.0f);
2760 element.Parent.TryTriggerAnimation(target, element.Entity);
2763 element.Parent.ApplyProjSpecific(deltaTime,
2766 element.Parent.GetHull(element.Entity),
2767 element.Parent.GetPosition(element.Entity, element.Targets),
2768 playSound: element.Timer >= element.Duration);
2770 element.Timer -= deltaTime;
2772 if (element.Timer > 0.0f) {
continue; }
2773 DurationList.Remove(element);
2777 private float GetAfflictionMultiplier(
Entity entity,
Character targetCharacter,
float deltaTime)
2779 float afflictionMultiplier = !setValue && !disableDeltaTime ? deltaTime : 1.0f;
2780 if (entity is
Item sourceItem)
2782 if (sourceItem.HasTag(
Barotrauma.Tags.MedicalItem))
2784 afflictionMultiplier *= 1 + targetCharacter.
GetStatValue(
StatTypes.MedicalItemEffectivenessMultiplier);
2785 if (user is not
null)
2787 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.MedicalItemApplyingMultiplier);
2790 else if (sourceItem.HasTag(AfflictionPrefab.PoisonType) && user is not
null)
2792 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.PoisonMultiplier);
2795 return afflictionMultiplier * AfflictionMultiplier;
2798 private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter,
float deltaTime,
bool multiplyByMaxVitality)
2800 float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
2801 if (multiplyByMaxVitality)
2803 afflictionMultiplier *= targetCharacter.MaxVitality / 100f;
2805 if (user is not
null)
2807 if (affliction.Prefab.IsBuff)
2809 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.BuffItemApplyingMultiplier);
2811 else if (affliction.Prefab.Identifier ==
"organdamage" && targetCharacter.CharacterHealth.GetActiveAfflictionTags().Any(t => t ==
"poisoned"))
2813 afflictionMultiplier *= 1 + user.GetStatValue(
StatTypes.PoisonMultiplier);
2816 if (!MathUtils.NearlyEqual(afflictionMultiplier, 1.0f))
2818 return affliction.CreateMultiplied(afflictionMultiplier, affliction);
2823 private void RegisterTreatmentResults(Character user,
Item item, Limb limb, Affliction affliction, AttackResult result)
2825 if (item ==
null) {
return; }
2826 if (!item.UseInHealthInterface) {
return; }
2827 if (limb ==
null) {
return; }
2828 foreach (Affliction limbAffliction
in limb.character.CharacterHealth.GetAllAfflictions())
2830 if (result.Afflictions !=
null && result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) &&
2831 (!affliction.Prefab.LimbSpecific || limb.character.CharacterHealth.GetAfflictionLimb(affliction) == limb))
2835 limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
2836 limb.character.TryAdjustHealerSkill(user, affliction: affliction);
2840 limbAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
2841 limb.character.TryAdjustHealerSkill(user, affliction: affliction);
2847 static partial
void UpdateAllProjSpecific(
float deltaTime);
2851 CoroutineManager.StopCoroutines(
"statuseffect");
2853 DurationList.Clear();
2858 if (tags.Contains(tag)) {
return; }
2864 if (tag ==
null) {
return true; }
2865 return 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
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, bool ignoreDamageOverlay=false, bool recalculateVitality=true)
CharacterInventory Inventory
void TryAdjustHealerSkill(Character healer, float healthChange=0, Affliction affliction=null)
void IncreaseSkillLevel(Identifier skillIdentifier, float increase, bool gainedFromAbility=false, bool forceNotification=false)
Increase the skill by a specific amount. Talents may affect the actual, final skill increase.
void ApplySkillGain(Identifier skillIdentifier, float baseGain, bool gainedFromAbility=false, float maxGain=2f, bool forceNotification=false)
Increases the characters skill at a rate proportional to their current skill. If you want to increase...
void GiveExperience(int amount)
static readonly Identifier HumanSpeciesName
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
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)
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 NetworkMember NetworkMember
static GameSession GameSession
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 AlwayShowNotification
Should the skill increase popup be always shown regardless of how much the skill increases?...
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)
readonly bool refundTalents
If enabled, the effect removes all talents from the target and refunds the talent points.
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.
@ Character
Characters only
readonly List< Affliction > Afflictions