Release 1.9.7.0 - Summer Update 2025

This commit is contained in:
Regalis11
2025-06-17 16:38:11 +03:00
parent 22227f13e5
commit ea5a2bc693
297 changed files with 7344 additions and 2421 deletions
@@ -1,10 +1,8 @@
using System;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
@@ -12,7 +10,11 @@ namespace Barotrauma
{
public readonly DelayedEffect Parent;
public readonly Entity Entity;
public readonly Vector2? WorldPosition;
public Vector2? WorldPosition;
/// <summary>
/// Should the delayed effect attempt to determine the position of the effect based on the targets, or just use the position that was passed to the constructor.
/// </summary>
public bool GetPositionBasedOnTargets;
public readonly Vector2? StartPosition;
public readonly List<ISerializableEntity> Targets;
public float Delay;
@@ -62,7 +64,10 @@ namespace Barotrauma
{
foreach (var existingEffect in DelayList)
{
if (existingEffect.Parent == this && existingEffect.Targets.FirstOrDefault() == target) { return; }
if (existingEffect.Parent == this && existingEffect.Targets.FirstOrDefault() == target)
{
return;
}
}
}
if (!IsValidTarget(target)) { return; }
@@ -74,7 +79,11 @@ namespace Barotrauma
switch (delayType)
{
case DelayTypes.Timer:
DelayList.Add(new DelayedListElement(this, entity, currentTargets, delay, worldPosition ?? GetPosition(entity, currentTargets, worldPosition), startPosition: null));
var newDelayListElement = new DelayedListElement(this, entity, currentTargets, delay, worldPosition ?? GetPosition(entity, currentTargets, worldPosition), startPosition: null)
{
GetPositionBasedOnTargets = worldPosition == null
};
DelayList.Add(newDelayListElement);
break;
case DelayTypes.ReachCursor:
Projectile projectile = (entity as Item)?.GetComponent<Projectile>();
@@ -171,7 +180,16 @@ namespace Barotrauma
{
case DelayTypes.Timer:
element.Delay -= deltaTime;
if (element.Delay > 0.0f) { continue; }
if (element.Delay > 0.0f)
{
//if the delayed effect is supposed to get the position from the targets,
//keep refreshing the position until the effect runs (so e.g. a delayed effect runs at the last known position of a monster before it despawned)
if (element.GetPositionBasedOnTargets && element.Entity is { Removed: false })
{
element.WorldPosition = element.Parent.GetPosition(element.Entity, element.Parent.currentTargets);
}
continue;
}
break;
case DelayTypes.ReachCursor:
if (Vector2.Distance(element.Entity.WorldPosition, element.StartPosition.Value) < element.Delay) { continue; }
@@ -5,6 +5,7 @@ using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Steamworks;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -639,6 +640,10 @@ namespace Barotrauma
/// Should the sound(s) configured in the effect be played if the required items aren't found?
/// </summary>
private readonly bool playSoundOnRequiredItemFailure = false;
public readonly record struct SteamTimeLineEvent(string title, string description, string icon);
private readonly SteamTimeLineEvent steamTimeLineEventToTrigger;
#endif
private readonly int useItemCount;
@@ -716,6 +721,11 @@ namespace Barotrauma
/// </summary>
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.
/// </summary>
public readonly Identifier UnlockRecipe;
private Character user;
public readonly float FireSize;
@@ -926,6 +936,8 @@ namespace Barotrauma
playSoundOnRequiredItemFailure = element.GetAttributeBool("playsoundonrequireditemfailure", false);
#endif
UnlockRecipe = element.GetAttributeIdentifier(nameof(UnlockRecipe), Identifier.Empty);
List<XAttribute> propertyAttributes = new List<XAttribute>();
propertyConditionals = new List<PropertyConditional>();
foreach (XAttribute attribute in element.Attributes())
@@ -1238,6 +1250,26 @@ namespace Barotrauma
forceSayIdentifier = subElement.GetAttributeIdentifier("message", Identifier.Empty);
forceSayInRadio = subElement.GetAttributeBool("sayinradio", false);
break;
#if CLIENT
case "steamtimelineevent":
steamTimeLineEventToTrigger = new SteamTimeLineEvent(
subElement.GetAttributeString("title", string.Empty),
subElement.GetAttributeString("description", string.Empty),
subElement.GetAttributeString("icon", string.Empty));
if (steamTimeLineEventToTrigger.title.IsNullOrWhiteSpace())
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - steam timeline event has no title.", contentPackage: element.ContentPackage);
}
if (steamTimeLineEventToTrigger.description.IsNullOrWhiteSpace())
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - steam timeline event has no description.", contentPackage: element.ContentPackage);
}
if (steamTimeLineEventToTrigger.icon.IsNullOrWhiteSpace())
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - steam timeline event has no icon.", contentPackage: element.ContentPackage);
}
break;
#endif
}
}
InitProjSpecific(element, parentDebugName);
@@ -2122,6 +2154,11 @@ namespace Barotrauma
fire.Size = new Vector2(FireSize, fire.Size.Y);
}
if (isNotClient && !UnlockRecipe.IsEmpty && GameMain.GameSession is { } gameSession)
{
gameSession.UnlockRecipe(UnlockRecipe, showNotifications: true);
}
if (isNotClient && triggeredEvents != null && GameMain.GameSession?.EventManager is { } eventManager)
{
foreach (EventPrefab eventPrefab in triggeredEvents)
@@ -2489,6 +2526,12 @@ namespace Barotrauma
{
rotation = parentItemBody.TransformRotation(chosenItemSpawnInfo.RotationRad);
}
else if (parentItem != null)
{
rotation = PhysicsBody.TransformRotation(
-parentItem.RotationRad + chosenItemSpawnInfo.RotationRad,
dir: parentItem.FlippedX ? -1.0f : 1.0f);
}
break;
case ItemSpawnInfo.SpawnRotationType.Target:
if (!entity.Removed)
@@ -2556,11 +2599,17 @@ namespace Barotrauma
projectile.Shoot(user, spawnPos, spawnPos, rotation, ignoredBodies: ignoredBodies, createNetworkEvent: true, damageMultiplier: damageMultiplier);
projectile.Item.Submarine = projectile.LaunchSub = sourceEntity?.Submarine;
}
else if (newItem.body != null)
else
{
newItem.body.SetTransform(newItem.SimPosition, rotation);
Vector2 impulseDir = new Vector2(MathF.Cos(rotation), MathF.Sin(rotation));
newItem.body.ApplyLinearImpulse(impulseDir * chosenItemSpawnInfo.Impulse);
if (newItem.body != null)
{
//flipped on one axis = need to flip the rotation of the item (not if flipped on both, that's essentially double negation)
bool flip = parentItem is { FlippedX: true } != parentItem is { FlippedY: true };
newItem.body.Dir = flip ? -1 : 1;
newItem.body.SetTransform(newItem.SimPosition, flip ? rotation - MathHelper.Pi : rotation);
Vector2 impulseDir = new Vector2(MathF.Cos(rotation), MathF.Sin(rotation));
newItem.body.ApplyLinearImpulse(impulseDir * chosenItemSpawnInfo.Impulse);
}
}
}
OnItemSpawned(newItem, chosenItemSpawnInfo);