Release 1.10.5.0 - Autumn Update 2025

This commit is contained in:
Regalis11
2025-09-17 13:44:21 +03:00
parent d13836ce87
commit caa0326cf8
120 changed files with 2584 additions and 635 deletions
@@ -420,7 +420,7 @@ namespace Barotrauma
[Serialize(1, IsPropertySaveable.No, description: "How many characters to spawn.")]
public int Count { get; private set; }
[Serialize(false, IsPropertySaveable.No, description:
[Serialize(false, IsPropertySaveable.No, description:
"Should the buffs of the character executing the effect be transferred to the spawned character?"+
" Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
public bool TransferBuffs { get; private set; }
@@ -445,11 +445,11 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.No, description: "An affliction to apply on the spawned character.")]
public Identifier AfflictionOnSpawn { get; private set; }
[Serialize(1, IsPropertySaveable.No, description:
[Serialize(1, IsPropertySaveable.No, description:
$"The strength of the affliction applied on the spawned character. Only relevant if {nameof(AfflictionOnSpawn)} is defined.")]
public int AfflictionStrength { get; private set; }
[Serialize(false, IsPropertySaveable.No, description:
[Serialize(false, IsPropertySaveable.No, description:
"Should the player controlling the character that executes the effect gain control of the spawned character?" +
" Useful for effects that \"transform\" a character to something else by deleting the character and spawning a new one on its place.")]
public bool TransferControl { get; private set; }
@@ -470,7 +470,7 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.No)]
public bool InheritEventTags { get; private set; }
[Serialize(false, IsPropertySaveable.No, description: "Should the character team be inherited from the entity that owns the status effect?")]
public bool InheritTeam { get; private set; }
@@ -722,9 +722,9 @@ namespace Barotrauma
private readonly List<(Identifier eventIdentifier, Identifier tag)> eventTargetTags;
/// <summary>
/// Can be used to make the effect unlock a fabrication recipe globally for the entire crew.
/// Can be used to make the effect unlock a fabrication recipe (or multiple recipes separated by a comma) globally for the entire crew.
/// </summary>
public readonly Identifier UnlockRecipe;
public readonly ImmutableHashSet<Identifier> UnlockRecipes;
private Character user;
@@ -740,6 +740,8 @@ namespace Barotrauma
/// </summary>
public readonly float SeverLimbsProbability;
private readonly Vector2 randomCondition;
public PhysicsBody sourceBody;
/// <summary>
@@ -802,11 +804,11 @@ namespace Barotrauma
private readonly List<Identifier> talentTriggers;
private readonly List<int> giveExperiences;
private readonly List<GiveSkill> giveSkills;
private HashSet<(Character targetCharacter, AnimLoadInfo anim)> failedAnimations;
public readonly record struct AnimLoadInfo(AnimationType Type, Either<string, ContentPath> File, float Priority, ImmutableArray<Identifier> ExpectedSpeciesNames);
private readonly List<AnimLoadInfo> animationsToTrigger;
/// <summary>
/// How long the effect runs (in seconds). Note that if <see cref="Stackable"/> is true,
/// there can be multiple instances of the effect running at a time.
@@ -903,9 +905,10 @@ namespace Barotrauma
}
SeverLimbsProbability = MathHelper.Clamp(element.GetAttributeFloat(0.0f, "severlimbs", "severlimbsprobability"), 0.0f, 1.0f);
randomCondition = element.GetAttributeVector2("randomcondition", Vector2.Zero);
string[] targetTypesStr =
element.GetAttributeStringArray("target", null) ??
string[] targetTypesStr =
element.GetAttributeStringArray("target", null) ??
element.GetAttributeStringArray("targettype", Array.Empty<string>());
foreach (string s in targetTypesStr)
{
@@ -939,7 +942,10 @@ namespace Barotrauma
playSoundOnRequiredItemFailure = element.GetAttributeBool("playsoundonrequireditemfailure", false);
#endif
UnlockRecipe = element.GetAttributeIdentifier(nameof(UnlockRecipe), Identifier.Empty);
UnlockRecipes =
element.GetAttributeIdentifierImmutableHashSet(nameof(UnlockRecipes),
//backwards compatibility
def: element.GetAttributeIdentifierImmutableHashSet("UnlockRecipe", ImmutableHashSet<Identifier>.Empty));
List<XAttribute> propertyAttributes = new List<XAttribute>();
propertyConditionals = new List<PropertyConditional>();
@@ -1014,7 +1020,7 @@ namespace Barotrauma
DebugConsole.AddWarning(
$"StatusEffect tags defined using the attribute 'tags' in StatusEffect ({parentDebugName}). "+
"Please use the attribute 'statuseffecttags' or 'settags' instead to make it more explicit whether the 'tags' attribute means the status effect's tags, or tags the effect is supposed to set. " +
"The game now assumes it means the status effect's tags.",
"The game now assumes it means the status effect's tags.",
contentPackage: element.ContentPackage);
#endif
}
@@ -1038,7 +1044,7 @@ namespace Barotrauma
//if the status effect has a duration, assume tags mean this status effect's tags and leave item tags untouched.
propertyAttributes.RemoveAll(a => a.Name.ToString().Equals("tags", StringComparison.OrdinalIgnoreCase));
}
List<(Identifier propertyName, object value)> propertyEffects = new List<(Identifier propertyName, object value)>();
foreach (XAttribute attribute in propertyAttributes)
{
@@ -1074,7 +1080,7 @@ namespace Barotrauma
dropItem = true;
break;
case "removecharacter":
removeCharacter = true;
removeCharacter = true;
containerForItemsOnCharacterRemoval = subElement.GetAttributeIdentifier("moveitemstocontainer", Identifier.Empty);
break;
case "breaklimb":
@@ -1132,7 +1138,7 @@ namespace Barotrauma
continue;
}
}
Affliction afflictionInstance = afflictionPrefab.Instantiate(subElement.GetAttributeFloat(1.0f, "amount", nameof(afflictionInstance.Strength)));
// Deserializing the object normally might cause some unexpected side effects. At least it clamps the strength of the affliction, which we don't want here.
// Could probably be solved by using the NonClampedStrength or by bypassing the clamping, but ran out of time and played it safe here.
@@ -1166,10 +1172,10 @@ namespace Barotrauma
break;
case "spawnitem":
var newSpawnItem = new ItemSpawnInfo(subElement, parentDebugName);
if (newSpawnItem.ItemPrefab != null)
if (newSpawnItem.ItemPrefab != null)
{
spawnItems ??= new List<ItemSpawnInfo>();
spawnItems.Add(newSpawnItem);
spawnItems.Add(newSpawnItem);
}
break;
case "triggerevent":
@@ -1247,7 +1253,7 @@ namespace Barotrauma
Identifier[] expectedSpeciesNames = subElement.GetAttributeIdentifierArray("expectedspecies", Array.Empty<Identifier>());
animationsToTrigger ??= new List<AnimLoadInfo>();
animationsToTrigger.Add(new AnimLoadInfo(animType, file, priority, expectedSpeciesNames.ToImmutableArray()));
break;
case "forcesay":
forceSayIdentifier = subElement.GetAttributeIdentifier("message", Identifier.Empty);
@@ -1294,7 +1300,7 @@ namespace Barotrauma
return conditionValue < 0.0f || (setValue && conditionValue <= 0.0f);
}
}
return false;
return randomCondition.X < 0f || randomCondition.Y < 0f;
}
public bool IncreasesItemCondition()
@@ -1306,7 +1312,7 @@ namespace Barotrauma
return conditionValue > 0.0f || (setValue && conditionValue > 0.0f);
}
}
return false;
return randomCondition.X > 0f || randomCondition.Y > 0f;
}
private bool ChangesItemCondition(Identifier propertyName, object value, out float conditionValue)
@@ -1383,7 +1389,7 @@ namespace Barotrauma
if (HasTargetType(TargetType.NearbyItems))
{
//optimization for powered components that can be easily fetched from Powered.PoweredList
if (TargetIdentifiers != null &&
if (TargetIdentifiers != null &&
TargetIdentifiers.Count == 1 &&
(TargetIdentifiers.Contains("powered") || TargetIdentifiers.Contains("junctionbox") || TargetIdentifiers.Contains("relaycomponent")))
{
@@ -1491,8 +1497,8 @@ namespace Barotrauma
{
owner = ownerItem.ParentInventory?.Owner;
}
if (owner is Item container)
{
if (owner is Item container)
{
if (pc.Type == PropertyConditional.ConditionType.HasTag)
{
//if we're checking for tags, just check the Item object, not the ItemComponents
@@ -1500,8 +1506,8 @@ namespace Barotrauma
}
else
{
if (shouldShortCircuit(AnyTargetMatches(container.AllPropertyObjects, pc.TargetItemComponent, pc), out valueToReturn)) { return valueToReturn; }
}
if (shouldShortCircuit(AnyTargetMatches(container.AllPropertyObjects, pc.TargetItemComponent, pc), out valueToReturn)) { return valueToReturn; }
}
}
if (owner is Character character && shouldShortCircuit(pc.Matches(character), out valueToReturn)) { return valueToReturn; }
}
@@ -1673,7 +1679,7 @@ namespace Barotrauma
PlaySound(entity, GetHull(entity), GetPosition(entity, targets, worldPosition));
}
#endif
return;
return;
}
if (Duration > 0.0f && !Stackable)
@@ -1743,7 +1749,7 @@ namespace Barotrauma
}
}
}
}
position += Offset;
position += Rand.Vector(Rand.Range(0.0f, RandomOffset));
@@ -1784,7 +1790,7 @@ namespace Barotrauma
}
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] is not Item item) { continue; }
if (targets[i] is not Item item) { continue; }
for (int j = 0; j < useItemCount; j++)
{
if (item.Removed) { continue; }
@@ -1807,8 +1813,8 @@ namespace Barotrauma
{
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] is Item item)
{
if (targets[i] is Item item)
{
foreach (var itemContainer in item.GetComponents<ItemContainer>())
{
foreach (var containedItem in itemContainer.Inventory.AllItemsMod)
@@ -1885,6 +1891,11 @@ namespace Barotrauma
if (target is Entity targetEntity)
{
if (targetEntity.Removed) { continue; }
if (targetEntity is Item targetItem && randomCondition != Vector2.Zero)
{
float newCondition = Rand.Range(randomCondition.X, randomCondition.Y);
targetItem.Condition = GetModifiedValue(targetItem.Condition, newCondition, deltaTime);
}
}
else if (target is Limb limb)
{
@@ -1893,10 +1904,7 @@ namespace Barotrauma
}
foreach (var (propertyName, value) in PropertyEffects)
{
if (!target.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property))
{
continue;
}
if (!target.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; }
ApplyToProperty(target, property, value, deltaTime);
}
}
@@ -2034,10 +2042,10 @@ namespace Barotrauma
}
}
}
TryTriggerAnimation(target, entity);
if (!forceSayIdentifier.IsEmpty)
if (!forceSayIdentifier.IsEmpty)
{
LocalizedString messageToSay = TextManager.Get(forceSayIdentifier).Fallback(forceSayIdentifier.Value);
@@ -2121,7 +2129,7 @@ namespace Barotrauma
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
{
if (giveTalentInfo.GiveRandom)
{
{
// 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<Identifier> viableTalents = giveTalentInfo.TalentIdentifiers.Where(id => !targetCharacter.Info.UnlockedTalents.Contains(id) && !characterTalentTree.AllTalentIdentifiers.Contains(id));
if (viableTalents.None()) { continue; }
@@ -2142,8 +2150,8 @@ namespace Barotrauma
{
foreach ((Identifier eventId, Identifier tag) in eventTargetTags)
{
if (GameMain.GameSession.EventManager.ActiveEvents.FirstOrDefault(e => e.Prefab.Identifier == eventId) is ScriptedEvent ev)
{
if (GameMain.GameSession.EventManager.ActiveEvents.FirstOrDefault(e => e.Prefab.Identifier == eventId) is ScriptedEvent ev)
{
targets.Where(t => t is Entity).ForEach(t => ev.AddTarget(tag, (Entity)t));
}
}
@@ -2157,9 +2165,13 @@ namespace Barotrauma
fire.Size = new Vector2(FireSize, fire.Size.Y);
}
if (isNotClient && !UnlockRecipe.IsEmpty && GameMain.GameSession is { } gameSession)
if (isNotClient && UnlockRecipes.Any() && GameMain.GameSession is { } gameSession)
{
gameSession.UnlockRecipe(UnlockRecipe, showNotifications: true);
foreach (var unlockRecipe in UnlockRecipes)
{
Character targetCharacter = user ?? targets.Select(GetCharacterFromTarget).NotNull().FirstOrDefault();
gameSession.UnlockRecipe(targetCharacter?.TeamID ?? CharacterTeamType.Team1, unlockRecipe, showNotifications: true);
}
}
if (isNotClient && triggeredEvents != null && GameMain.GameSession?.EventManager is { } eventManager)
@@ -2168,7 +2180,7 @@ namespace Barotrauma
{
Event ev = eventPrefab.CreateInstance(eventManager.RandomSeed);
if (ev == null) { continue; }
eventManager.QueuedEvents.Enqueue(ev);
eventManager.QueuedEvents.Enqueue(ev);
if (ev is ScriptedEvent scriptedEvent)
{
if (!triggeredEventTargetTag.IsEmpty)
@@ -2198,7 +2210,7 @@ namespace Barotrauma
foreach (CharacterSpawnInfo characterSpawnInfo in spawnCharacters)
{
var characters = new List<Character>();
CharacterTeamType? inheritedTeam = null;
if (characterSpawnInfo.InheritTeam)
{
@@ -2211,16 +2223,16 @@ namespace Barotrauma
_ => null
// Default to Team1, when we can't deduce the team (for example when spawning outside the sub AND character inventory).
} ?? (isPvP ? CharacterTeamType.None : CharacterTeamType.Team1);
CharacterTeamType? GetTeamFromSubmarine(MapEntity e)
{
if (e.Submarine == null) { return null; }
// Don't allow team FriendlyNPC in outposts, because if you buy a spawner item (such as husk container) from the store and choose to get it immediately, it will be spawned in the outpost.
return !isPvP && e.Submarine.Info.IsOutpost && e.Submarine.TeamID == CharacterTeamType.FriendlyNPC ?
return !isPvP && e.Submarine.Info.IsOutpost && e.Submarine.TeamID == CharacterTeamType.FriendlyNPC ?
CharacterTeamType.Team1 : e.Submarine.TeamID;
}
}
for (int i = 0; i < characterSpawnInfo.Count; i++)
{
Entity.Spawner.AddCharacterToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Unsynced) + characterSpawnInfo.Offset,
@@ -2311,11 +2323,11 @@ namespace Barotrauma
Character.Controlled = newCharacter;
}
#elif SERVER
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.Character != target) { continue; }
GameMain.Server.SetClientCharacter(c, newCharacter);
}
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.Character != target) { continue; }
GameMain.Server.SetClientCharacter(c, newCharacter);
}
#endif
}
if (characterSpawnInfo.RemovePreviousCharacter) { Entity.Spawner?.AddEntityToRemoveQueue(character); }
@@ -2477,7 +2489,7 @@ namespace Barotrauma
position = entity.WorldPosition;
if (entity is Item it)
{
sourceBody ??=
sourceBody ??=
(entity as Item)?.body ??
(entity as Character)?.AnimController.Collider;
}
@@ -2532,7 +2544,7 @@ namespace Barotrauma
else if (parentItem != null)
{
rotation = PhysicsBody.TransformRotation(
-parentItem.RotationRad + chosenItemSpawnInfo.RotationRad,
-parentItem.RotationRad + chosenItemSpawnInfo.RotationRad,
dir: parentItem.FlippedX ? -1.0f : 1.0f);
}
break;
@@ -2758,29 +2770,17 @@ namespace Barotrauma
private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime)
{
if (disableDeltaTime || setValue) { deltaTime = 1.0f; }
if (value is int || value is float)
if (value is int or float)
{
float propertyValueF = property.GetFloatValue(target);
float newValue = GetModifiedValue(property.GetFloatValue(target), Convert.ToSingle(value), deltaTime);
if (property.PropertyType == typeof(float))
{
float floatValue = value is float single ? single : (int)value;
floatValue *= deltaTime;
if (!setValue)
{
floatValue += propertyValueF;
}
property.TrySetValue(target, floatValue);
property.TrySetValue(target, newValue);
return;
}
else if (property.PropertyType == typeof(int))
{
int intValue = (int)(value is float single ? single * deltaTime : (int)value * deltaTime);
if (!setValue)
{
intValue += (int)propertyValueF;
}
property.TrySetValue(target, intValue);
property.TrySetValue(target, (int)newValue);
return;
}
}
@@ -2792,6 +2792,13 @@ namespace Barotrauma
property.TrySetValue(target, value);
}
private float GetModifiedValue(float currValue, float newValue, float deltaTime)
{
if (setValue) { return newValue; }
if (disableDeltaTime) { deltaTime = 1f; }
return currValue + newValue * deltaTime;
}
public static void UpdateAll(float deltaTime)
{
UpdateAllProjSpecific(deltaTime);
@@ -2848,7 +2855,7 @@ namespace Barotrauma
element.Parent.RegisterTreatmentResults(element.Parent.user, element.Entity as Item, limb, affliction, result);
}
}
foreach ((Identifier affliction, float amount) in element.Parent.ReduceAffliction)
{
Limb targetLimb = null;
@@ -2894,10 +2901,10 @@ namespace Barotrauma
element.Parent.TryTriggerAnimation(target, element.Entity);
}
element.Parent.ApplyProjSpecific(deltaTime,
element.Entity,
element.Targets,
element.Parent.GetHull(element.Entity),
element.Parent.ApplyProjSpecific(deltaTime,
element.Entity,
element.Targets,
element.Parent.GetHull(element.Entity),
element.Parent.GetPosition(element.Entity, element.Targets),
playSound: element.Timer >= element.Duration);
@@ -2970,7 +2977,7 @@ namespace Barotrauma
if (limb == null) { return; }
foreach (Affliction limbAffliction in limb.character.CharacterHealth.GetAllAfflictions())
{
if (result.Afflictions != null &&
if (result.Afflictions != null &&
/* "affliction" is the affliction directly defined in the status effect (e.g. "5 internal damage (per second / per frame / however the effect is defined to run)"),
* "result" is how much we actually applied of that affliction right now (taking into account the elapsed time, resistances and such) */
result.Afflictions.FirstOrDefault(a => a.Prefab == limbAffliction.Prefab) is Affliction resultAffliction &&