v1.12.6.2 (Spring Update 2026)

This commit is contained in:
Regalis11
2026-04-09 15:10:07 +03:00
parent c8657caefa
commit a4607dffad
197 changed files with 5586 additions and 3461 deletions
@@ -187,6 +187,11 @@ namespace Barotrauma
set => Params.Health.DoesBleed = value;
}
/// <summary>
/// Can this character be contained inside a controller?
/// </summary>
public bool IsContainable { get; set; }
public readonly Dictionary<Identifier, SerializableProperty> Properties;
public Dictionary<Identifier, SerializableProperty> SerializableProperties
{
@@ -683,6 +688,11 @@ namespace Barotrauma
get { return AnimController.Mass; }
}
/// <summary>
/// The position the character was at when we previously set the transforms of the items in the character's inventory.
/// </summary>
private Vector2 lastInventoryItemSetTransformPosition;
public CharacterInventory Inventory { get; private set; }
/// <summary>
@@ -788,7 +798,24 @@ namespace Barotrauma
set
{
if (value == selectedCharacter) { return; }
if (selectedCharacter != null) { selectedCharacter.selectedBy = null; }
//deselect the currently selected character
if (selectedCharacter != null)
{
selectedCharacter.selectedBy = null;
//check if some other character has selected the currently selected character too,
//and set selectedBy to that other character (otherwise the currently selected character would be unaware they're still being dragged by someone)
foreach (var otherCharacter in CharacterList)
{
if (otherCharacter != this && otherCharacter.selectedCharacter == selectedCharacter)
{
selectedCharacter.selectedBy = otherCharacter;
break;
}
}
}
CharacterHUD.RecreateHudTextsIfControlling(this);
selectedCharacter = value;
if (selectedCharacter != null) { selectedCharacter.selectedBy = this; }
#if CLIENT
@@ -1642,8 +1669,10 @@ namespace Barotrauma
AnimController.FindHull(setInWater: true);
if (AnimController.CurrentHull != null) { Submarine = AnimController.CurrentHull.Submarine; }
IsContainable = prefab.ConfigElement.GetAttributeBool(nameof(IsContainable), def: Mass <= 30.0f);
CharacterList.Add(this);
Enabled = GameMain.NetworkMember == null;
if (info != null)
@@ -2268,6 +2297,12 @@ namespace Barotrauma
}
}
// Try to detach from the controller if we are currently attached to something that is dangerous for our character
if (aiControlled && Stun <= 0f && !IsKnockedDownOrRagdolled && !LockHands && ShouldAvoidStayingAttachedToController())
{
SelectedItem = null;
}
if (GameMain.NetworkMember != null)
{
if (GameMain.NetworkMember.IsServer)
@@ -2316,7 +2351,7 @@ namespace Barotrauma
{
attackCoolDown -= deltaTime;
}
else if (IsKeyDown(InputType.Attack))
else if (IsKeyDown(InputType.Attack) && !IsAttachedToController())
{
//normally the attack target, where to aim the attack and such is handled by EnemyAIController,
//but in the case of player-controlled monsters, we handle it here
@@ -2843,14 +2878,14 @@ namespace Barotrauma
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen) { hidden = false; }
#endif
if (!CanInteract || hidden || !item.IsInteractable(this)) { return false; }
Controller controller = item.GetComponent<Controller>();
if (controller != null && IsAnySelectedItem(item) && controller.IsAttachedUser(this))
{
return true;
}
if (!CanInteract || hidden || !item.IsInteractable(this)) { return false; }
if (item.ParentInventory != null)
{
return CanAccessInventory(item.ParentInventory);
@@ -2972,7 +3007,9 @@ namespace Barotrauma
}
}
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger)
//note that the distance to item should be set to 0 above if the character is within the item's bounding box
bool closeEnoughToIgnoreVisibilityCheck = distanceToItem <= 0.1f;
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger && !closeEnoughToIgnoreVisibilityCheck)
{
var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true);
bool itemCenterVisible = CheckBody(body, item);
@@ -3001,7 +3038,6 @@ namespace Barotrauma
{
return itemCenterVisible;
}
}
return true;
@@ -3091,7 +3127,11 @@ namespace Barotrauma
if (!CanInteract)
{
SelectedItem = SelectedSecondaryItem = null;
if (!IsAttachedToController())
{
SelectedItem = null;
}
SelectedSecondaryItem = null;
focusedItem = null;
if (!AllowInput)
{
@@ -3110,8 +3150,16 @@ namespace Barotrauma
{
if (!PlayerInput.PrimaryMouseButtonHeld() || Barotrauma.Inventory.DraggingItemToWorld)
{
FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null;
if (FocusedCharacter != null && !CanSeeTarget(FocusedCharacter)) { FocusedCharacter = null; }
//don't allow focusing on anyone when the health window is open (avoids accidentally selecting someone when closing the window)
if (CharacterHealth.OpenHealthWindow != null)
{
FocusedCharacter = null;
}
else
{
FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null;
if (FocusedCharacter != null && !CanSeeTarget(FocusedCharacter)) { FocusedCharacter = null; }
}
float aimAssist = GameSettings.CurrentConfig.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f);
if (HeldItems.Any(it => it?.GetComponent<Wire>()?.IsActive ?? false))
{
@@ -3435,7 +3483,7 @@ namespace Barotrauma
obstructVisionAmount = Math.Max(obstructVisionAmount - deltaTime, 0.0f);
if (Inventory != null)
if (Inventory != null && Vector2.DistanceSquared(lastInventoryItemSetTransformPosition, Position) > 0.1f)
{
//do not check for duplicates: this is code is called very frequently, and duplicates don't matter here,
//so it's better just to avoid the relatively expensive duplicate check
@@ -3444,6 +3492,7 @@ namespace Barotrauma
if (item.body == null || item.body.Enabled) { continue; }
item.SetTransform(SimPosition, 0.0f, forceSubmarine: Submarine);
}
lastInventoryItemSetTransformPosition = Position;
}
HideFace = false;
@@ -3570,7 +3619,7 @@ namespace Barotrauma
{
wasRagdolled = IsRagdolled;
IsRagdolled = IsKeyDown(InputType.Ragdoll);
if (IsRagdolled && IsBot && GameMain.NetworkMember is not { IsClient: true })
if (IsRagdolled && !IsPlayer && GameMain.NetworkMember is not { IsClient: true })
{
ClearInput(InputType.Ragdoll);
}
@@ -3622,7 +3671,19 @@ namespace Barotrauma
AnimController.IgnorePlatforms = true;
}
AnimController.ResetPullJoints();
SelectedItem = SelectedSecondaryItem = null;
// Prevent us from detaching from the controller if we are attached to it OR detach if we
// manually ragdoll, in this case it should be similar to us deselecting the controller
if (!IsAttachedToController() ||
(IsKeyDown(InputType.Ragdoll)
// Let only the server do this check since the Ragdoll input for other clients is set to be held
// for stunned characters even if a character isn't manually ragdolling
&& (GameMain.NetworkMember == null || GameMain.NetworkMember is { IsServer: true } )))
{
SelectedItem = null;
}
SelectedSecondaryItem = null;
SelectedCharacter = null;
return;
}
@@ -3651,6 +3712,13 @@ namespace Barotrauma
bool MustDeselect(Item item)
{
if (item == null) { return false; }
// Prevent creatures from deselecting the controller if they are attached to it
if (IsAIControlled && !CanInteract && IsAttachedToController())
{
return false;
}
if (!CanInteractWith(item)) { return true; }
bool hasSelectableComponent = false;
foreach (var component in item.Components)
@@ -4376,6 +4444,41 @@ namespace Barotrauma
}
}
public void ForceSay(LocalizedString messageToSay, bool sayInRadio, bool removeQuotes = false, float delay = 0.0f)
{
if (messageToSay.IsNullOrEmpty() || SpeechImpediment >= 100.0f || IsDead)
{
return;
}
if (removeQuotes)
{
messageToSay = new TrimLString(messageToSay,
TrimLString.Mode.Both, ['"', '”', '“', ' ']);
}
ChatMessageType messageType = ChatMessageType.Default;
bool canUseRadio = ChatMessage.CanUseRadio(this, out WifiComponent radio);
if (canUseRadio && sayInRadio)
{
messageType = ChatMessageType.Radio;
}
CoroutineManager.Invoke(() =>
{
#if SERVER
GameMain.Server?.SendChatMessage(messageToSay.Value, messageType, senderClient: null, this);
#elif CLIENT
// no need to create the message when playing as a client, the server will send it to us
if (GameMain.Client == null)
{
AIChatMessage message = new AIChatMessage(messageToSay.Value, messageType);
SendSinglePlayerMessage(message, canUseRadio, radio);
}
#endif
}, delay);
}
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
{
CharacterHealth.SetAllDamage(damageAmount, bleedingDamageAmount, burnDamageAmount);
@@ -4760,6 +4863,10 @@ namespace Barotrauma
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
if (Screen.Selected != GameMain.GameScreen) { return; }
//don't allow stunning for less than one frame
//fixes monsters/enemies that take some minuscule amount of stun from a weapon still being noticeable affected by the stun,
//because even a one-frame stun briefly disables the animations and makes the character stop
if (newStun < Timing.Step && Stun <= 0.0f) { return; }
if (GodMode)
{
CharacterHealth.Stun = 0;
@@ -4787,7 +4894,12 @@ namespace Barotrauma
CharacterHealth.Stun = newStun;
if (newStun > 0.0f)
{
SelectedItem = SelectedSecondaryItem = null;
if (!IsAttachedToController())
{
SelectedItem = null;
}
SelectedSecondaryItem = null;
if (SelectedCharacter != null) { DeselectCharacter(); }
}
HealthUpdateInterval = 0.0f;
@@ -4976,6 +5088,37 @@ namespace Barotrauma
}
}
public bool IsAttachedToController()
{
if (SelectedItem == null) { return false; }
var controller = SelectedItem.GetComponent<Controller>();
if (controller == null) { return false; }
return controller.IsAttachedUser(this);
}
public bool ShouldAvoidStayingAttachedToController()
{
if (!IsAttachedToController()) { return false; }
var deconstructor = SelectedItem.GetComponent<Deconstructor>();
if (deconstructor != null)
{
return true;
}
// Character is being carried by an enemy!
if (IsHuman &&
SelectedItem.GetRootInventoryOwner() is Character carryingCharacter &&
TeamID != carryingCharacter.TeamID)
{
return true;
}
return false;
}
public void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage = false, bool log = true)
{
if (IsDead || CharacterHealth.Unkillable || GodMode || Removed) { return; }
@@ -5113,7 +5256,7 @@ namespace Barotrauma
AnimController.movement = Vector2.Zero;
AnimController.TargetMovement = Vector2.Zero;
if (!LockHands)
if (!LockHands && causeOfDeath != CauseOfDeathType.Disconnected)
{
foreach (Item heldItem in HeldItems.ToList())
{