Merge branch 'dev' of https://github.com/Regalis11/Barotrauma.git into unstable-tests

This commit is contained in:
Evil Factory
2022-04-08 12:52:28 -03:00
990 changed files with 44338 additions and 38589 deletions
@@ -5,8 +5,10 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -44,37 +46,43 @@ namespace Barotrauma
{
public string Name => "ai trigger";
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
[Serialize(AIState.Idle, false)]
[Serialize(AIState.Idle, IsPropertySaveable.No)]
public AIState State { get; private set; }
[Serialize(0f, false)]
[Serialize(0f, IsPropertySaveable.No)]
public float Duration { get; private set; }
[Serialize(1f, false)]
[Serialize(1f, IsPropertySaveable.No)]
public float Probability { get; private set; }
[Serialize(0f, false)]
[Serialize(0f, IsPropertySaveable.No)]
public float MinDamage { get; private set; }
[Serialize(true, false)]
[Serialize(true, IsPropertySaveable.No)]
public bool AllowToOverride { get; private set; }
[Serialize(true, false)]
[Serialize(true, IsPropertySaveable.No)]
public bool AllowToBeOverridden { get; private set; }
public bool IsTriggered { get; private set; }
public float Timer { get; private set; } = -1;
public float Timer { get; private set; }
public bool IsActive { get; private set; }
public bool IsPermanent { get; private set; }
public void Launch()
{
IsTriggered = true;
IsActive = true;
Timer = Duration;
IsPermanent = Duration <= 0;
if (!IsPermanent)
{
Timer = Duration;
}
}
public void Reset()
@@ -86,6 +94,7 @@ namespace Barotrauma
public void UpdateTimer(float deltaTime)
{
if (IsPermanent) { return; }
Timer -= deltaTime;
if (Timer < 0)
{
@@ -152,6 +161,8 @@ namespace Barotrauma
public readonly float AimSpread;
public readonly bool Equip;
public readonly float Condition;
public ItemSpawnInfo(XElement element, string parentDebugName)
{
if (element.Attribute("name") != null)
@@ -185,6 +196,8 @@ namespace Barotrauma
SpawnIfCantBeContained = element.GetAttributeBool("spawnifcantbecontained", true);
Speed = element.GetAttributeFloat("speed", 0.0f);
Condition = MathHelper.Clamp(element.GetAttributeFloat("condition", 1.0f), 0.0f, 1.0f);
Rotation = element.GetAttributeFloat("rotation", 0.0f);
Count = element.GetAttributeInt("count", 1);
Spread = element.GetAttributeFloat("spread", 0f);
@@ -206,36 +219,36 @@ namespace Barotrauma
public class AbilityStatusEffectIdentifier : AbilityObject
{
public AbilityStatusEffectIdentifier(string effectIdentifier)
public AbilityStatusEffectIdentifier(Identifier effectIdentifier)
{
EffectIdentifier = effectIdentifier;
}
public string EffectIdentifier { get; set; }
public Identifier EffectIdentifier { get; set; }
}
public class GiveTalentInfo
{
public string[] TalentIdentifiers;
public Identifier[] TalentIdentifiers;
public bool GiveRandom;
public GiveTalentInfo(XElement element, string _)
{
TalentIdentifiers = element.GetAttributeStringArray("talentidentifiers", new string[0], convertToLowerInvariant: true);
TalentIdentifiers = element.GetAttributeIdentifierArray("talentidentifiers", Array.Empty<Identifier>());
GiveRandom = element.GetAttributeBool("giverandom", false);
}
}
public class GiveSkill
{
public string SkillIdentifier;
public float Amount;
public readonly Identifier SkillIdentifier;
public readonly float Amount;
public GiveSkill(XElement element, string parentDebugName)
{
SkillIdentifier = element.GetAttributeString("skillidentifier", string.Empty);
SkillIdentifier = element.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
Amount = element.GetAttributeFloat("amount", 0);
if (SkillIdentifier == string.Empty)
if (SkillIdentifier == Identifier.Empty)
{
DebugConsole.ThrowError($"GiveSkill StatusEffect did not have a skill identifier defined in {parentDebugName}!");
}
@@ -245,24 +258,24 @@ namespace Barotrauma
public class CharacterSpawnInfo : ISerializableEntity
{
public string Name => $"Character Spawn Info ({SpeciesName})";
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
[Serialize("", false)]
public string SpeciesName { get; private set; }
[Serialize("", IsPropertySaveable.No)]
public Identifier SpeciesName { get; private set; }
[Serialize(1, false)]
[Serialize(1, IsPropertySaveable.No)]
public int Count { get; private set; }
[Serialize(0f, false)]
[Serialize(0f, IsPropertySaveable.No)]
public float Spread { get; private set; }
[Serialize("0,0", false)]
[Serialize("0,0", IsPropertySaveable.No)]
public Vector2 Offset { get; private set; }
public CharacterSpawnInfo(XElement element, string parentDebugName)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
if (string.IsNullOrEmpty(SpeciesName))
if (SpeciesName.IsEmpty)
{
DebugConsole.ThrowError($"Invalid character spawn ({Name}) in StatusEffect \"{parentDebugName}\" - identifier not found in the element \"{element}\"");
}
@@ -270,7 +283,6 @@ namespace Barotrauma
}
private readonly TargetType targetTypes;
protected HashSet<string> targetIdentifiers;
/// <summary>
/// Index of the slot the target must be in when targeting a Contained item
@@ -279,7 +291,7 @@ namespace Barotrauma
private readonly List<RelatedItem> requiredItems;
public readonly string[] propertyNames;
public readonly Identifier[] propertyNames;
public readonly object[] propertyEffects;
private readonly PropertyConditional.Comparison conditionalComparison = PropertyConditional.Comparison.Or;
@@ -324,8 +336,8 @@ namespace Barotrauma
private readonly List<AITrigger> aiTriggers;
private readonly List<EventPrefab> triggeredEvents;
private readonly string triggeredEventTargetTag = "statuseffecttarget",
triggeredEventEntityTag = "statuseffectentity";
private readonly Identifier triggeredEventTargetTag = "statuseffecttarget".ToIdentifier(),
triggeredEventEntityTag = "statuseffectentity".ToIdentifier();
private Character user;
@@ -347,15 +359,12 @@ namespace Barotrauma
/// </summary>
public readonly bool AllowWhenBroken = false;
public HashSet<string> TargetIdentifiers
{
get { return targetIdentifiers; }
}
public readonly ImmutableHashSet<Identifier> TargetIdentifiers;
/// <summary>
/// Which type of afflictions the target must receive for the StatusEffect to be applied. Only valid when the type of the effect is OnDamaged.
/// </summary>
private readonly HashSet<(string affliction, float strength)> requiredAfflictions;
private readonly HashSet<(Identifier affliction, float strength)> requiredAfflictions;
public float AfflictionMultiplier = 1.0f;
@@ -372,9 +381,9 @@ namespace Barotrauma
get { return spawnCharacters; }
}
public readonly List<(string affliction, float amount)> ReduceAffliction;
public readonly List<(Identifier AfflictionIdentifier, float ReduceAmount)> ReduceAffliction;
private readonly List<string> talentTriggers;
private readonly List<Identifier> talentTriggers;
private readonly List<int> giveExperiences;
private readonly List<GiveSkill> giveSkills;
private readonly List<string> luaHook;
@@ -407,9 +416,9 @@ namespace Barotrauma
}
}
public static StatusEffect Load(XElement element, string parentDebugName)
public static StatusEffect Load(ContentXElement element, string parentDebugName)
{
if (element.Attribute("delay") != null || element.Attribute("delaytype") != null)
if (element.GetAttribute("delay") != null || element.GetAttribute("delaytype") != null)
{
return new DelayedEffect(element, parentDebugName);
}
@@ -417,7 +426,7 @@ namespace Barotrauma
return new StatusEffect(element, parentDebugName);
}
protected StatusEffect(XElement element, string parentDebugName)
protected StatusEffect(ContentXElement element, string parentDebugName)
{
requiredItems = new List<RelatedItem>();
spawnItems = new List<ItemSpawnInfo>();
@@ -428,8 +437,8 @@ namespace Barotrauma
Afflictions = new List<Affliction>();
Explosions = new List<Explosion>();
triggeredEvents = new List<EventPrefab>();
ReduceAffliction = new List<(string affliction, float amount)>();
talentTriggers = new List<string>();
ReduceAffliction = new List<(Identifier affliction, float amount)>();
talentTriggers = new List<Identifier>();
giveExperiences = new List<int>();
giveSkills = new List<GiveSkill>();
luaHook = new List<string>();
@@ -462,7 +471,7 @@ namespace Barotrauma
string[] targetTypesStr =
element.GetAttributeStringArray("target", null) ??
element.GetAttributeStringArray("targettype", new string[0]);
element.GetAttributeStringArray("targettype", Array.Empty<string>());
foreach (string s in targetTypesStr)
{
if (!Enum.TryParse(s, true, out TargetType targetType))
@@ -502,20 +511,15 @@ namespace Barotrauma
case "targets":
case "targetidentifiers":
case "targettags":
string[] identifiers = attribute.Value.Split(',');
targetIdentifiers = new HashSet<string>();
for (int i = 0; i < identifiers.Length; i++)
{
targetIdentifiers.Add(identifiers[i].Trim().ToLowerInvariant());
}
TargetIdentifiers = attribute.Value.Split(',').ToIdentifiers().ToImmutableHashSet();
break;
case "allowedafflictions":
case "requiredafflictions":
string[] types = attribute.Value.Split(',');
requiredAfflictions ??= new HashSet<(string, float)>();
requiredAfflictions ??= new HashSet<(Identifier, float)>();
for (int i = 0; i < types.Length; i++)
{
requiredAfflictions.Add((types[i].Trim().ToLowerInvariant(), 0.0f));
requiredAfflictions.Add((types[i].Trim().ToIdentifier(), 0.0f));
}
break;
case "duration":
@@ -529,10 +533,10 @@ namespace Barotrauma
lifeTimer = lifeTime;
break;
case "eventtargettag":
triggeredEventTargetTag = attribute.Value;
triggeredEventTargetTag = attribute.Value.ToIdentifier();
break;
case "evententitytag":
triggeredEventEntityTag = attribute.Value;
triggeredEventEntityTag = attribute.Value.ToIdentifier();
break;
case "checkconditionalalways":
CheckConditionalAlways = attribute.GetAttributeBool(false);
@@ -576,19 +580,19 @@ namespace Barotrauma
int count = propertyAttributes.Count;
propertyNames = new string[count];
propertyNames = new Identifier[count];
propertyEffects = new object[count];
int n = 0;
foreach (XAttribute attribute in propertyAttributes)
{
propertyNames[n] = attribute.Name.ToString().ToLowerInvariant();
propertyNames[n] = attribute.NameAsIdentifier();
propertyEffects[n] = XMLExtensions.GetAttributeObject(attribute);
n++;
}
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -627,9 +631,9 @@ namespace Barotrauma
requiredItems.Add(newRequiredItem);
break;
case "requiredaffliction":
requiredAfflictions ??= new HashSet<(string, float)>();
string[] ids = subElement.GetAttributeStringArray("identifier", null) ?? subElement.GetAttributeStringArray("type", new string[0]);
foreach (string afflictionId in ids)
requiredAfflictions ??= new HashSet<(Identifier, float)>();
Identifier[] ids = subElement.GetAttributeIdentifierArray("identifier", null) ?? subElement.GetAttributeIdentifierArray("type", Array.Empty<Identifier>());
foreach (var afflictionId in ids)
{
requiredAfflictions.Add((
afflictionId,
@@ -647,11 +651,11 @@ namespace Barotrauma
break;
case "affliction":
AfflictionPrefab afflictionPrefab;
if (subElement.Attribute("name") != null)
if (subElement.GetAttribute("name") != null)
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers instead of names.");
string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.ToLowerInvariant() == afflictionName);
string afflictionName = subElement.GetAttributeString("name", "");
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, StringComparison.OrdinalIgnoreCase));
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
@@ -660,8 +664,8 @@ namespace Barotrauma
}
else
{
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier);
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.");
@@ -675,17 +679,16 @@ namespace Barotrauma
break;
case "reduceaffliction":
if (subElement.Attribute("name") != null)
if (subElement.GetAttribute("name") != null)
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
ReduceAffliction.Add((
subElement.GetAttributeString("name", "").ToLowerInvariant(),
subElement.GetAttributeIdentifier("name", ""),
subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount")));
}
else
{
string name = subElement.GetAttributeString("identifier", null) ?? subElement.GetAttributeString("type", null);
name = name.ToLowerInvariant();
Identifier name = subElement.GetAttributeIdentifier("identifier", subElement.GetAttributeIdentifier("type", Identifier.Empty));
if (AfflictionPrefab.List.Any(ap => ap.Identifier == name || ap.AfflictionType == name))
{
@@ -702,8 +705,8 @@ namespace Barotrauma
if (newSpawnItem.ItemPrefab != null) { spawnItems.Add(newSpawnItem); }
break;
case "triggerevent":
string identifier = subElement.GetAttributeString("identifier", null);
if (!string.IsNullOrWhiteSpace(identifier))
Identifier identifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
if (!identifier.IsEmpty)
{
EventPrefab prefab = EventSet.GetEventPrefab(identifier);
if (prefab != null)
@@ -711,15 +714,15 @@ namespace Barotrauma
triggeredEvents.Add(prefab);
}
}
foreach (XElement eventElement in subElement.Elements())
foreach (var eventElement in subElement.Elements())
{
if (!eventElement.Name.ToString().Equals("ScriptedEvent", StringComparison.OrdinalIgnoreCase)) { continue; }
triggeredEvents.Add(new EventPrefab(eventElement));
if (eventElement.NameAsIdentifier() != "ScriptedEvent") { continue; }
triggeredEvents.Add(new EventPrefab(eventElement, file: null));
}
break;
case "spawncharacter":
var newSpawnCharacter = new CharacterSpawnInfo(subElement, parentDebugName);
if (!string.IsNullOrWhiteSpace(newSpawnCharacter.SpeciesName)) { spawnCharacters.Add(newSpawnCharacter); }
if (!newSpawnCharacter.SpeciesName.IsEmpty) { spawnCharacters.Add(newSpawnCharacter); }
break;
case "givetalentinfo":
var newGiveTalentInfo = new GiveTalentInfo(subElement, parentDebugName);
@@ -729,7 +732,7 @@ namespace Barotrauma
aiTriggers.Add(new AITrigger(subElement));
break;
case "talenttrigger":
talentTriggers.Add(subElement.GetAttributeString("effectidentifier", string.Empty));
talentTriggers.Add(subElement.GetAttributeIdentifier("effectidentifier", Identifier.Empty));
break;
case "giveexperience":
giveExperiences.Add(subElement.GetAttributeInt("amount", 0));
@@ -745,7 +748,7 @@ namespace Barotrauma
InitProjSpecific(element, parentDebugName);
}
partial void InitProjSpecific(XElement element, string parentDebugName);
partial void InitProjSpecific(ContentXElement element, string parentDebugName);
public bool HasTargetType(TargetType targetType)
{
@@ -837,8 +840,8 @@ namespace Barotrauma
if (HasTargetType(TargetType.NearbyItems))
{
//optimization for powered components that can be easily fetched from Powered.PoweredList
if (targetIdentifiers?.Count == 1 &&
(targetIdentifiers.Contains("powered") || targetIdentifiers.Contains("junctionbox") || targetIdentifiers.Contains("relaycomponent")))
if (TargetIdentifiers.Count == 1 &&
(TargetIdentifiers.Contains("powered") || TargetIdentifiers.Contains("junctionbox") || TargetIdentifiers.Contains("relaycomponent")))
{
foreach (Powered powered in Powered.PoweredList)
{
@@ -1015,79 +1018,45 @@ namespace Barotrauma
}
else if (entity is Structure structure)
{
if (targetIdentifiers == null) { return true; }
if (targetIdentifiers.Contains("structure")) { return true; }
foreach (var id in targetIdentifiers)
{
if (id.Equals(structure.Prefab.Identifier, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
if (TargetIdentifiers == null) { return true; }
if (TargetIdentifiers.Contains("structure")) { return true; }
if (TargetIdentifiers.Contains(structure.Prefab.Identifier)) { return true; }
}
else if (entity is Character character)
{
return IsValidTarget(character);
}
if (targetIdentifiers == null) { return true; }
foreach (var id in targetIdentifiers)
{
if (id.Equals(entity.Name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
if (TargetIdentifiers == null) { return true; }
return TargetIdentifiers.Contains(entity.Name);
}
protected bool IsValidTarget(ItemComponent itemComponent)
{
if (OnlyInside && itemComponent.Item.CurrentHull == null) { return false; }
if (OnlyOutside && itemComponent.Item.CurrentHull != null) { return false; }
if (targetIdentifiers == null) { return true; }
if (targetIdentifiers.Contains("itemcomponent")) { return true; }
if (itemComponent.Item.HasTag(targetIdentifiers)) { return true; }
foreach (var id in targetIdentifiers)
{
if (id.Equals(itemComponent.Item.Prefab.Identifier, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
if (TargetIdentifiers == null) { return true; }
if (TargetIdentifiers.Contains("itemcomponent")) { return true; }
if (itemComponent.Item.HasTag(TargetIdentifiers)) { return true; }
return TargetIdentifiers.Contains(itemComponent.Item.Prefab.Identifier);
}
protected bool IsValidTarget(Item item)
{
if (OnlyInside && item.CurrentHull == null) { return false; }
if (OnlyOutside && item.CurrentHull != null) { return false; }
if (targetIdentifiers == null) { return true; }
if (targetIdentifiers.Contains("item")) { return true; }
if (item.HasTag(targetIdentifiers)) { return true; }
foreach (var id in targetIdentifiers)
{
if (id.Equals(item.Prefab.Identifier, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
if (TargetIdentifiers == null) { return true; }
if (TargetIdentifiers.Contains("item")) { return true; }
if (item.HasTag(TargetIdentifiers)) { return true; }
return TargetIdentifiers.Contains(item.Prefab.Identifier);
}
protected bool IsValidTarget(Character character)
{
if (OnlyInside && character.CurrentHull == null) { return false; }
if (OnlyOutside && character.CurrentHull != null) { return false; }
if (targetIdentifiers == null) { return true; }
if (targetIdentifiers.Contains("character")) { return true; }
foreach (var id in targetIdentifiers)
{
if (id.Equals(character.SpeciesName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
if (TargetIdentifiers == null) { return true; }
if (TargetIdentifiers.Contains("character")) { return true; }
return TargetIdentifiers.Contains(character.SpeciesName);
}
public void SetUser(Character user)
@@ -1134,7 +1103,7 @@ namespace Barotrauma
currentTargets.Add(target);
}
if (targetIdentifiers != null && currentTargets.Count == 0) { return; }
if (TargetIdentifiers != null && currentTargets.Count == 0) { return; }
bool hasRequiredItems = HasRequiredItems(entity);
if (!hasRequiredItems || !HasRequiredConditions(currentTargets))
@@ -1290,38 +1259,49 @@ namespace Barotrauma
{
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] is Item item) { Entity.Spawner?.AddToRemoveQueue(item); }
if (targets[i] is Item item) { Entity.Spawner?.AddItemToRemoveQueue(item); }
}
}
if (removeCharacter)
{
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] is Character character) { Entity.Spawner?.AddToRemoveQueue(character); }
var target = targets[i];
if (target is Character character)
{
Entity.Spawner?.AddEntityToRemoveQueue(character);
}
else if (target is Limb limb)
{
Entity.Spawner?.AddEntityToRemoveQueue(limb.character);
}
}
}
if (breakLimb || hideLimb)
{
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] is Character character)
var target = targets[i];
Limb targetLimb = target as Limb;
if (targetLimb == null && target is Character character)
{
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.body == sourceBody)
{
targetLimb = limb;
if (breakLimb)
{
character.TrySeverLimbJoints(limb, severLimbsProbability: 100, damage: 100, allowBeheading: true, attacker: user);
}
else
{
limb.HideAndDisable(hideLimbTimer);
}
break;
}
}
}
if (hideLimb)
{
targetLimb?.HideAndDisable(hideLimbTimer);
}
}
}
@@ -1400,7 +1380,7 @@ namespace Barotrauma
RegisterTreatmentResults(entity, limb, affliction, result);
}
}
foreach (var (affliction, amount) in ReduceAffliction)
{
Limb targetLimb = null;
@@ -1420,7 +1400,14 @@ namespace Barotrauma
if (entity is Item item && item.UseInHealthInterface) { actionType = type; }
float reduceAmount = amount * GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType);
if (targetLimb != null)
{
targetCharacter.CharacterHealth.ReduceAfflictionOnLimb(targetLimb, affliction, reduceAmount, treatmentAction: actionType);
}
else
{
targetCharacter.CharacterHealth.ReduceAfflictionOnAllLimbs(affliction, reduceAmount, treatmentAction: actionType);
}
targetCharacter.AIController?.OnHealed(healer: user, targetCharacter.Vitality - prevVitality);
if (user != null && user != targetCharacter)
{
@@ -1466,7 +1453,7 @@ namespace Barotrauma
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed)
{
foreach (string talentTrigger in talentTriggers)
foreach (Identifier talentTrigger in talentTriggers)
{
targetCharacter.CheckTalents(AbilityEffectType.OnStatusEffectIdentifier, new AbilityStatusEffectIdentifier(talentTrigger));
}
@@ -1492,13 +1479,13 @@ namespace Barotrauma
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed)
{
string skillIdentifier = giveSkill.SkillIdentifier.ToLowerInvariant() == "randomskill" ? GetRandomSkill() : giveSkill.SkillIdentifier;
Identifier skillIdentifier = giveSkill.SkillIdentifier == "randomskill" ? GetRandomSkill() : giveSkill.SkillIdentifier;
targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier, giveSkill.Amount);
string GetRandomSkill()
Identifier GetRandomSkill()
{
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom();
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandomUnsynced() ?? Identifier.Empty;
}
}
}
@@ -1508,22 +1495,22 @@ namespace Barotrauma
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter?.Info == null) { continue; }
if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
if (!TalentTree.JobTalentTrees.TryGet(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
// for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well
IEnumerable<string> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
IEnumerable<Identifier> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
{
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
IEnumerable<Identifier> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
if (viableTalents.None()) { continue; }
if (giveTalentInfo.GiveRandom)
{
targetCharacter.GiveTalent(viableTalents.GetRandom(Rand.RandSync.Unsynced), true);
targetCharacter.GiveTalent(viableTalents.GetRandomUnsynced(), true);
}
else
{
foreach (string talent in viableTalents)
foreach (Identifier talent in viableTalents)
{
targetCharacter.GiveTalent(talent, true);
}
@@ -1549,7 +1536,7 @@ namespace Barotrauma
if (ev is ScriptedEvent scriptedEvent)
{
if (!string.IsNullOrWhiteSpace(triggeredEventTargetTag))
if (!triggeredEventTargetTag.IsEmpty)
{
List<Entity> eventTargets = targets.Where(t => t is Entity).Cast<Entity>().ToList();
@@ -1559,7 +1546,7 @@ namespace Barotrauma
}
}
if (!string.IsNullOrWhiteSpace(triggeredEventEntityTag) && entity != null)
if (!triggeredEventEntityTag.IsEmpty && entity != null)
{
scriptedEvent.Targets.Add(triggeredEventEntityTag, new List<Entity> { entity });
}
@@ -1574,7 +1561,7 @@ namespace Barotrauma
var characters = new List<Character>();
for (int i = 0; i < characterSpawnInfo.Count; i++)
{
Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Unsynced) + characterSpawnInfo.Offset,
Entity.Spawner.AddCharacterToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Unsynced) + characterSpawnInfo.Offset,
onSpawn: newCharacter =>
{
if (newCharacter.AIController is EnemyAIController enemyAi &&
@@ -1595,7 +1582,7 @@ namespace Barotrauma
if (spawnItemRandomly)
{
SpawnItem(spawnItems.GetRandom(Rand.RandSync.Unsynced));
SpawnItem(spawnItems.GetRandomUnsynced());
}
else
{
@@ -1614,7 +1601,7 @@ namespace Barotrauma
switch (chosenItemSpawnInfo.SpawnPosition)
{
case ItemSpawnInfo.SpawnPositionType.This:
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem =>
Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem =>
{
Projectile projectile = newItem.GetComponent<Projectile>();
if (projectile != null && user != null && sourceBody != null && entity != null)
@@ -1704,6 +1691,7 @@ namespace Barotrauma
body.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed);
}
}
newItem.Condition = newItem.MaxCondition * chosenItemSpawnInfo.Condition;
});
break;
case ItemSpawnInfo.SpawnPositionType.ThisInventory:
@@ -1724,7 +1712,7 @@ namespace Barotrauma
}
if (inventory != null && (inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull))
{
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: item =>
Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: item =>
{
if (chosenItemSpawnInfo.Equip && entity is Character character && character.Inventory != null)
{
@@ -1736,6 +1724,7 @@ namespace Barotrauma
allowedSlots.Remove(InvSlotType.Any);
character.Inventory.TryPutItem(item, null, allowedSlots);
}
item.Condition = item.MaxCondition * chosenItemSpawnInfo.Condition;
});
}
}
@@ -1763,7 +1752,10 @@ namespace Barotrauma
Inventory containedInventory = item.GetComponent<ItemContainer>()?.Inventory;
if (containedInventory != null && (containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull))
{
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull);
Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: (Item newItem) =>
{
newItem.Condition = newItem.MaxCondition * chosenItemSpawnInfo.Condition;
});
}
break;
}
@@ -1884,7 +1876,7 @@ namespace Barotrauma
element.Parent.RegisterTreatmentResults(element.Entity, limb, affliction, result);
}
}
foreach (var (affliction, amount) in element.Parent.ReduceAffliction)
{
Limb targetLimb = null;
@@ -1904,7 +1896,14 @@ namespace Barotrauma
if (element.Entity is Item item && item.UseInHealthInterface) { actionType = element.Parent.type; }
float reduceAmount = amount * element.Parent.GetAfflictionMultiplier(element.Entity, targetCharacter, deltaTime);
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType);
if (targetLimb != null)
{
targetCharacter.CharacterHealth.ReduceAfflictionOnLimb(targetLimb, affliction, reduceAmount, treatmentAction: actionType);
}
else
{
targetCharacter.CharacterHealth.ReduceAfflictionOnAllLimbs(affliction, reduceAmount, treatmentAction: actionType);
}
if (element.User != null && element.User != targetCharacter)
{
targetCharacter.AIController?.OnHealed(healer: element.User, targetCharacter.Vitality - prevVitality);