Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -42,13 +42,13 @@ namespace Barotrauma
if (wallTarget != null && !IsCoolDownRunning)
{
Vector2 wallTargetPos = wallTarget.Position;
if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.Position; }
if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.DrawPosition; }
wallTargetPos.Y = -wallTargetPos.Y;
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false);
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity}", GUIStyle.Red, Color.Black);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {SelectedTargetMemory?.Priority.FormatZeroDecimal()}, P: {SelectedTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {CurrentTargetMemory?.Priority.FormatZeroDecimal()}, P: {CurrentTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
}
/*GUIStyle.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUIStyle.Red);
@@ -73,7 +73,7 @@ namespace Barotrauma
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black);
if (State == AIState.Attack && selectedTargetingParams != null && selectedTargetingParams.AttackPattern == AttackPattern.Circle)
if (State == AIState.Attack && currentTargetingParams != null && currentTargetingParams.AttackPattern == AttackPattern.Circle)
{
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 100.0f, CirclePhase.ToString(), stateColor, Color.Black);
}
@@ -134,8 +134,8 @@ namespace Barotrauma
//GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(steeringManager.AvoidLookAheadPos.X, -steeringManager.AvoidLookAheadPos.Y), Color.Orange, width: 4);
}
}
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 4);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
}
}
}
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using FarseerPhysics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
@@ -11,7 +12,7 @@ namespace Barotrauma
{
if (Character == Character.Controlled) { return; }
if (!DebugAI) { return; }
Vector2 pos = Character.WorldPosition;
Vector2 pos = Character.DrawPosition;
pos.Y = -pos.Y;
Vector2 textOffset = new Vector2(-40, -160);
textOffset.Y -= Math.Max(ObjectiveManager.CurrentOrders.Count - 1, 0) * 20;
@@ -63,6 +64,25 @@ namespace Barotrauma
stringDrawPos += new Vector2(0, 20);
GUI.DrawString(spriteBatch, stringDrawPos, $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
if (currentObjective is AIObjectiveCombat
{
Weapon: Item weapon,
BlockedPositions: List<Vector2> blockedPositions
})
{
Vector2 weaponPos = weapon.DrawPosition;
weaponPos.Y = -weaponPos.Y;
foreach (Vector2 blockedPosition in blockedPositions)
{
Vector2 blockedPos = blockedPosition;
if (Character.Submarine != null)
{
blockedPos += Character.Submarine.DrawPosition;
}
blockedPos.Y = -blockedPos.Y;
GUI.DrawLine(spriteBatch, weaponPos, blockedPos, Color.Red);
}
}
}
Vector2 objectiveStringDrawPos = stringDrawPos + new Vector2(120, 40);
@@ -23,13 +23,13 @@ namespace Barotrauma
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
{
if (character != GameMain.Client.Character || !character.CanMove)
if (character != GameMain.Client.Character)
{
//remove states without a timestamp (there may still be ID-based states
//in the list when the controlled character switches to timestamp-based interpolation)
character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
//use simple interpolation for other players' characters and characters that can't move
//use simple interpolation for other players' characters
if (character.MemState.Count > 0)
{
CharacterStateInfo serverPos = character.MemState.Last();
@@ -93,6 +93,9 @@ namespace Barotrauma
character.AnimController.Anim = AnimController.Animation.None;
}
character.AnimController.IgnorePlatforms = character.MemState[0].IgnorePlatforms;
character.AnimController.overrideTargetMovement = character.MemState[0].TargetMovement;
Vector2 newVelocity = Collider.LinearVelocity;
Vector2 newPosition = Collider.SimPosition;
float newRotation = Collider.Rotation;
@@ -103,16 +106,17 @@ namespace Barotrauma
{
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
Collider.LinearVelocity = newVelocity;
Collider.AngularVelocity = newAngularVelocity;
}
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.CanMove && (!character.IsRagdolled || character.AnimController.IsHangingWithRope) ? 0.01f : 0.2f;
float errorTolerance =
ColliderControlsMovement && (!character.IsRagdolled || character.AnimController.IsHangingWithRope) ? 0.01f : 0.2f;
if (distSqrd > errorTolerance)
{
if (distSqrd > 10.0f || !character.CanMove)
character.AnimController.BodyInRest = false;
if (distSqrd > 10.0f)
{
Collider.TargetRotation = newRotation;
if (distSqrd > 10.0f)
@@ -126,30 +130,35 @@ namespace Barotrauma
}
}
SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
//make sure ragdoll isn't stuck at the wrong side of a platform if the movement is controlled by the ragdoll, and the ragdoll has come to rest server-side
if (!ColliderControlsMovement && newVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(newPosition); }
}
else
else if (ColliderControlsMovement)
{
Collider.TargetRotation = newRotation;
Collider.TargetPosition = newPosition;
Collider.MoveToTargetPosition(true);
}
}
//immobilized characters can't correct their position using AnimController movement
// -> we need to correct it manually
if (!character.CanMove)
{
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
float mainLimbErrorTolerance = 0.1f;
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
//don't attempt to correct the position.
if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
else
{
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
MainLimb.PullJointEnabled = true;
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, newPosition);
float mainLimbErrorTolerance = character == GameMain.Client.Character ? 0.25f : 0.1f;
MainLimb.body.LinearVelocity = newVelocity;
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
//don't attempt to correct the position.
if (mainLimbDistSqrd > mainLimbErrorTolerance)
{
MainLimb.PullJointWorldAnchorB = newPosition;
MainLimb.PullJointEnabled = true;
if (!ColliderControlsMovement && newVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(newPosition); }
}
}
}
else if (!ColliderControlsMovement)
{
//correct velocity regardless of the positional error
MainLimb.body.LinearVelocity = newVelocity;
}
}
character.MemLocalState.Clear();
}
@@ -179,12 +188,14 @@ namespace Barotrauma
}
}
if (character.MemState.Count < 1) return;
if (character.MemState.Count < 1) { return; }
overrideTargetMovement = Vector2.Zero;
overrideTargetMovement = null;
CharacterStateInfo serverPos = character.MemState.Last();
Collider.LastServerState = serverPos;
if (!character.isSynced)
{
SetPosition(serverPos.Position, lerp: false);
@@ -282,18 +293,65 @@ namespace Barotrauma
}
else if (errorMagnitude > 0.01f)
{
Collider.TargetPosition = Collider.SimPosition + positionError;
Collider.TargetRotation = Collider.Rotation + rotationError;
Collider.MoveToTargetPosition(lerp: true);
if (ColliderControlsMovement)
{
Collider.TargetPosition = Collider.SimPosition + positionError;
Collider.TargetRotation = Collider.Rotation + rotationError;
Collider.MoveToTargetPosition(lerp: true);
}
else
{
float mainLimbErrorTolerance = character == GameMain.Client.Character ? 0.25f : 0.1f;
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
//don't attempt to correct the position.
if (errorMagnitude > mainLimbErrorTolerance)
{
MainLimb.PullJointWorldAnchorB = MainLimb.SimPosition + positionError;
MainLimb.PullJointEnabled = true;
if (serverPos.LinearVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(MainLimb.SimPosition + positionError); }
}
}
}
}
}
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
if (character.MemLocalState.Count > 120) { character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120); }
character.MemState.Clear();
}
}
/// <summary>
/// Attempts to correct the ragdoll to the correct side of a platform if the server position is above the platform and some of the ragdoll's limbs below it client-side, or vice versa.
/// </summary>
private void TryPlatformCorrection(Vector2 serverPos)
{
float highestPos = limbs.Where(static l => !l.IsSevered).Max(static l => l.SimPosition.Y);
highestPos = Math.Max(serverPos.Y, highestPos);
float lowestPos = limbs.Where(static l => !l.IsSevered).Min(static l => l.SimPosition.Y);
lowestPos = Math.Min(serverPos.Y, lowestPos);
var platform = Submarine.PickBody(new Vector2(serverPos.X, highestPos), new Vector2(serverPos.X, lowestPos), collisionCategory: Physics.CollisionPlatform, allowInsideFixture: true);
if (platform == null) { return; }
int serverDir = Math.Sign(serverPos.Y - platform.Position.Y);
foreach (var limb in limbs)
{
if (limb.IsSevered) { continue; }
int limbDir = Math.Sign(limb.SimPosition.Y - platform.Position.Y);
const float Margin = 0.01f;
if (limbDir != serverDir)
{
limb.body.SetTransformIgnoreContacts(
new Vector2(
limb.SimPosition.X,
serverDir > 0 ? Math.Max(serverPos.Y + Margin + limb.body.GetMaxExtent(), limb.SimPosition.Y) : Math.Min(serverPos.Y - Margin - limb.body.GetMaxExtent(), limb.SimPosition.Y)),
limb.Rotation);
}
}
}
partial void ImpactProjSpecific(float impact, Body body)
{
@@ -563,15 +621,19 @@ namespace Barotrauma
void AdjustDepthOffset(Item item)
{
if (item?.GetComponent<Controller>() is { ControlCharacterPose: true, UserInCorrectPosition: true } controller && controller.User == character)
if (item == null) { return; }
foreach (var controller in item.GetComponents<Controller>())
{
if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
if (controller is { ControlCharacterPose: true, UserInCorrectPosition: true } && controller.User == character)
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
}
else
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
}
else
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
}
}
}
}
@@ -48,7 +48,7 @@ namespace Barotrauma
if (sound != null)
{
SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
SoundPlayer.PlaySound(sound, worldPosition);
}
}
}
@@ -9,6 +9,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
@@ -27,7 +28,8 @@ namespace Barotrauma
protected float lastRecvPositionUpdateTime;
private float hudInfoHeight = 100.0f;
private const float DefaultHudInfoHeight = 78.0f;
private float hudInfoHeight = DefaultHudInfoHeight;
private List<CharacterSound> sounds;
@@ -249,7 +251,9 @@ namespace Barotrauma
public Vector2 Position;
public Vector2 DrawPosition;
public float MoveUpAmount;
public readonly string Text;
public readonly RichString Text;
public ImmutableArray<RichTextData>? RichTextData { get; private set; }
public readonly Character Character;
public readonly Submarine Submarine;
public readonly Vector2 TextSize;
@@ -259,8 +263,10 @@ namespace Barotrauma
public SpeechBubble(Character character, float lifeTime, Color color, string text = "")
{
Text = ToolBox.WrapText(text, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text));
var richStr = RichString.Rich(text);
Text = ToolBox.WrapText(richStr.SanitizedValue, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text));
TextSize = GUIStyle.SmallFont.MeasureString(Text);
RichTextData = richStr.RichTextData;
Character = character;
Position = GetDesiredPosition();
@@ -321,7 +327,6 @@ namespace Barotrauma
/// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
if (DisableControls || GUI.InputBlockingMenuOpen)
{
foreach (Key key in keys)
@@ -329,7 +334,7 @@ namespace Barotrauma
if (key == null) { continue; }
key.Reset();
}
if (GUI.InputBlockingMenuOpen)
if (GUI.InputBlockingMenuOpen || ConversationAction.IsDialogOpen)
{
cursorPosition =
Position + PlayerInput.MouseSpeed.ClampLength(10.0f); //apply a little bit of movement to the cursor pos to prevent AFK kicking
@@ -416,6 +421,11 @@ namespace Barotrauma
UpdateLocalCursor(cam);
if (IsKeyHit(InputType.ToggleRun))
{
ToggleRun = !ToggleRun;
}
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (GUI.PauseMenuOpen)
{
@@ -471,7 +481,7 @@ namespace Barotrauma
if (!GUI.InputBlockingMenuOpen)
{
if (SelectedItem != null &&
(SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
(SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && ic.CloseByClickingOutsideGUIFrame && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)))
{
if (GameMain.Client != null)
@@ -543,7 +553,10 @@ namespace Barotrauma
{
if (attackResult.Damage <= 1.0f) { return; }
}
PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2);
if (AIState != AIState.PlayDead)
{
PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2);
}
}
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log)
@@ -588,7 +601,6 @@ namespace Barotrauma
}
}
sounds.ForEach(s => s.Sound?.Dispose());
sounds.Clear();
if (GameMain.GameSession?.CrewManager != null &&
@@ -814,9 +826,12 @@ namespace Barotrauma
PlaySound(CharacterSound.SoundType.Idle);
}
break;
case AIState.PlayDead:
case AIState.Freeze:
case AIState.Hiding:
break;
default:
var petBehavior = enemyAI.PetBehavior;
if (petBehavior != null &&
if (enemyAI.PetBehavior is PetBehavior petBehavior &&
(petBehavior.Happiness < petBehavior.UnhappyThreshold || petBehavior.Hunger > petBehavior.HungryThreshold))
{
PlaySound(CharacterSound.SoundType.Unhappy);
@@ -948,7 +963,9 @@ namespace Barotrauma
Controlled != this &&
Submarine != null &&
Controlled.Submarine == Submarine &&
GameSettings.CurrentConfig.Graphics.LosMode != LosMode.None)
GameSettings.CurrentConfig.Graphics.LosMode != LosMode.None &&
//less restrictions on name tag visibility in PvP mode (always show them if the character is visible)
GameMain.GameSession?.GameMode is not PvPMode)
{
float yPos = Controlled.AnimController.FloorY - 1.5f;
@@ -965,15 +982,16 @@ namespace Barotrauma
Vector2 pos = DrawPosition;
pos.Y += hudInfoHeight;
if (CurrentHull != null && DrawPosition.Y > CurrentHull.WorldRect.Y - 130.0f)
float paddingBelowCeiling = 30.0f;
if (CurrentHull != null && DrawPosition.Y + DefaultHudInfoHeight > CurrentHull.WorldRect.Y - paddingBelowCeiling)
{
float lowerAmount = DrawPosition.Y - (CurrentHull.WorldRect.Y - 130.0f);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f - lowerAmount, 0.1f);
float lowerAmount = (DrawPosition.Y + DefaultHudInfoHeight) - (CurrentHull.WorldRect.Y - paddingBelowCeiling);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, DefaultHudInfoHeight - lowerAmount, 0.1f);
hudInfoHeight = Math.Max(hudInfoHeight, 20.0f);
}
else
{
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f, 0.1f);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, DefaultHudInfoHeight, 0.1f);
}
pos.Y = -pos.Y;
@@ -1013,6 +1031,8 @@ namespace Barotrauma
CampaignInteractionType == CampaignMode.InteractionType.None ?
MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) :
1.0f;
//full name tag visibility in PvP mode to make it easier to tell who's an enemy
float nameTextAlpha = GameMain.GameSession?.GameMode is PvPMode ? 1.0f : hudInfoAlpha;
if (!GUI.DisableCharacterNames && hudInfoVisible &&
(controlled == null || this != controlled.FocusedCharacter || IsPet) && cam.Zoom > 0.4f)
@@ -1030,7 +1050,7 @@ namespace Barotrauma
}
Vector2 nameSize = GUIStyle.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Vector2 namePos = new Vector2(pos.X, pos.Y - 5.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Color nameColor = GetNameColor();
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
@@ -1057,7 +1077,7 @@ namespace Barotrauma
}
GUIStyle.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * nameTextAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUIStyle.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
@@ -1068,15 +1088,18 @@ namespace Barotrauma
if (petBehavior != null && !IsDead && !IsUnconscious)
{
var petStatus = petBehavior.GetCurrentStatusIndicatorType();
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null)
if (petStatus != PetBehavior.StatusIndicatorType.None)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
}
}
}
}
@@ -1100,7 +1123,7 @@ namespace Barotrauma
}
}
if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible && AIState != AIState.PlayDead && AIState != AIState.Hiding)
{
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
@@ -1175,7 +1198,7 @@ namespace Barotrauma
Vector2 bubbleSize = bubble.TextSize + Vector2.One * GUI.IntScale(15);
speechBubbleIconSliced.Draw(spriteBatch, new RectangleF(iconPos - bubbleSize / 2, bubbleSize), bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha);
}
GUI.DrawString(spriteBatch, iconPos - bubble.TextSize / 2, bubble.Text, bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha, font: GUIStyle.SmallFont);
GUI.DrawStringWithColors(spriteBatch, iconPos - bubble.TextSize / 2, bubble.Text.SanitizedValue, bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha, bubble.RichTextData, font: GUIStyle.SmallFont);
}
spriteBatch.End();
}
@@ -1233,7 +1256,7 @@ namespace Barotrauma
public Color GetNameColor()
{
CharacterTeamType team = teamID;
if (Info?.IsDisguisedAsAnother != null)
if (Info is { IsDisguisedAsAnother: true })
{
var idCard = Inventory.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent<IdCard>();
if (idCard != null)
@@ -1249,18 +1272,22 @@ namespace Barotrauma
}
}
CharacterTeamType myTeam =
Controlled?.TeamID ??
GameMain.Client?.MyClient?.TeamID ??
CharacterTeamType.Team1;
Color nameColor = GUIStyle.TextColorNormal;
if (Controlled != null && team != Controlled.TeamID)
if (TeamID == CharacterTeamType.FriendlyNPC)
{
if (TeamID == CharacterTeamType.FriendlyNPC)
{
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else
{
nameColor = GUIStyle.Red;
}
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else if (team != myTeam)
{
//opposing team is red when controlling a character
nameColor = GUIStyle.Red;
}
return nameColor;
}
@@ -1417,7 +1444,10 @@ namespace Barotrauma
partial void OnTalentGiven(TalentPrefab talentPrefab)
{
AddMessage(TextManager.Get("talentname." + talentPrefab.Identifier).Value, GUIStyle.Yellow, playSound: this == Controlled);
if (!talentPrefab.IsHiddenExtraTalent)
{
AddMessage(TextManager.Get("talentname." + talentPrefab.Identifier).Value, GUIStyle.Yellow, playSound: this == Controlled);
}
}
}
}
@@ -11,17 +11,15 @@ namespace Barotrauma
{
partial class CharacterHUD
{
const float BossHealthBarDuration = 120.0f;
abstract class BossProgressBar
abstract class ProgressBar
{
public float FadeTimer;
public readonly GUIComponent TopContainer;
public readonly GUIComponent SideContainer;
public readonly GUIProgressBar TopHealthBar;
public readonly GUIProgressBar SideHealthBar;
public readonly GUIProgressBar TopBar;
public readonly GUIProgressBar SideBar;
public abstract bool Completed { get; }
@@ -33,9 +31,9 @@ namespace Barotrauma
public abstract Color Color { get; }
public BossProgressBar(LocalizedString label)
public ProgressBar(LocalizedString label, float fadeTimer = 120.0f)
{
FadeTimer = BossHealthBarDuration;
FadeTimer = fadeTimer;
TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter)
{
@@ -43,25 +41,25 @@ namespace Barotrauma
RelativeOffset = new Vector2(0.0f, 0.01f)
}, isHorizontal: false, childAnchor: Anchor.TopCenter);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), label, textAlignment: Alignment.Center, textColor: GUIStyle.Red);
TopHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
TopBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
{
MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y)
}, barSize: 0.0f, style: "CharacterHealthBarCentered")
{
Color = GUIStyle.Red
};
CreateNumberText(TopHealthBar);
CreateNumberText(TopBar);
SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
{
MinSize = new Point(80, 60)
}, isHorizontal: false, childAnchor: Anchor.TopRight);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), label, textAlignment: Alignment.CenterRight, textColor: GUIStyle.Red);
SideHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
SideBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
{
Color = GUIStyle.Red
};
CreateNumberText(SideHealthBar);
CreateNumberText(SideBar);
TopContainer.Visible = SideContainer.Visible = false;
TopContainer.CanBeFocused = false;
@@ -88,7 +86,7 @@ namespace Barotrauma
public abstract bool IsDuplicate(object targetObject);
}
class BossHealthBar : BossProgressBar
class HealthBar : ProgressBar
{
public readonly Character Character;
@@ -104,7 +102,7 @@ namespace Barotrauma
public override string NumberToDisplay => string.Empty;
public BossHealthBar(Character character) : base(character.DisplayName)
public HealthBar(Character character) : base(character.DisplayName)
{
Character = character;
}
@@ -115,7 +113,7 @@ namespace Barotrauma
}
}
class MissionProgressBar : BossProgressBar
class MissionProgressBar : ProgressBar
{
public readonly Mission Mission;
@@ -125,13 +123,13 @@ namespace Barotrauma
public override bool Interrupted => Mission.Failed || GameMain.GameSession?.Missions == null || !GameMain.GameSession.Missions.Contains(Mission);
public override Color Color => GUIStyle.Red;
public override Color Color => Mission.Prefab.ProgressBarColor;
public override string NumberToDisplay => Mission.Prefab.ShowProgressInNumbers ?
$"{Mission.State}/{Mission.Prefab.MaxProgressState}" :
string.Empty;
public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel)
public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel, fadeTimer: float.PositiveInfinity)
{
Mission = mission;
}
@@ -150,7 +148,7 @@ namespace Barotrauma
private static readonly List<Item> brokenItems = new List<Item>();
private static float brokenItemsCheckTimer;
private static readonly List<BossProgressBar> bossProgressBars = new List<BossProgressBar>();
private static readonly List<ProgressBar> bossProgressBars = new List<ProgressBar>();
private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>();
private static LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None;
@@ -394,6 +392,7 @@ namespace Barotrauma
foreach (var target in mission.HudIconTargets)
{
if (target.Submarine != character.Submarine) { continue; }
if (target.Removed) { continue; }
float alpha = GetDistanceBasedIconAlpha(target, maxDistance: mission.Prefab.HudIconMaxDistance);
if (alpha <= 0.0f) { continue; }
GUI.DrawIndicator(spriteBatch, target.DrawPosition, cam, 100.0f, mission.Prefab.HudIcon, mission.Prefab.HudIconColor * alpha);
@@ -564,7 +563,7 @@ namespace Barotrauma
float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor);
GUI.DrawIndicator(
spriteBatch,
entity.WorldPosition,
entity.DrawPosition,
cam,
visibleRange,
style.GetDefaultSprite(),
@@ -592,7 +591,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(character.Position, item.Position) > 500f * 500f) { continue; }
var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item) { continue; }
GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range<float>(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
GUI.DrawIndicator(spriteBatch, item.DrawPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range<float>(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
}
}
@@ -752,7 +751,8 @@ namespace Barotrauma
}
textPos.X += 10.0f * GUI.Scale;
if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet)
if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet &&
character.FocusedCharacter.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior.CanPlayWith(character))
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use),
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
@@ -773,7 +773,7 @@ namespace Barotrauma
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
textPos.Y += textSize.Y;
}
if (!character.FocusedCharacter.CustomInteractHUDText.IsNullOrEmpty() && character.FocusedCharacter.AllowCustomInteract)
if (character.FocusedCharacter.ShouldShowCustomInteractText)
{
GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.CustomInteractHUDText, GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
textPos.Y += textSize.Y;
@@ -784,7 +784,7 @@ namespace Barotrauma
{
if (character == null || character.IsDead || character.Removed) { return; }
if (bossProgressBars.Any(b => b.IsDuplicate(character))) { return; }
AddBossProgressBar(new BossHealthBar(character));
AddBossProgressBar(new HealthBar(character));
}
public static void ShowMissionProgressBar(Mission mission)
@@ -803,26 +803,26 @@ namespace Barotrauma
bossProgressBars.Clear();
}
private static void RemoveBossProgressBar(BossProgressBar progressBar)
private static void RemoveBossProgressBar(ProgressBar progressBar)
{
progressBar.SideContainer.Parent?.RemoveChild(progressBar.SideContainer);
progressBar.TopContainer.Parent?.RemoveChild(progressBar.TopContainer);
bossProgressBars.Remove(progressBar);
}
private static void AddBossProgressBar(BossProgressBar progressBar)
private static void AddBossProgressBar(ProgressBar progressBar)
{
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode == EnemyHealthBarMode.HideAll)
if (healthBarMode == EnemyHealthBarMode.HideAll && progressBar is not MissionProgressBar)
{
return;
}
if (bossProgressBars.Count > 5)
{
BossProgressBar oldestHealthBar = bossProgressBars.First();
ProgressBar oldestHealthBar = bossProgressBars.First();
foreach (var bar in bossProgressBars)
{
if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
if (bar.TopBar.BarSize < oldestHealthBar.TopBar.BarSize)
{
oldestHealthBar = bar;
}
@@ -850,7 +850,7 @@ namespace Barotrauma
bossHealthBar.TopContainer.Visible = showTopBar;
bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible;
bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = bossHealthBar.State;
bossHealthBar.TopBar.BarSize = bossHealthBar.SideBar.BarSize = bossHealthBar.State;
float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f);
if (bossHealthBar.TopContainer.Visible)
@@ -862,7 +862,7 @@ namespace Barotrauma
SetColor(bossHealthBar, bossHealthBar.SideContainer, alpha);
}
static void SetColor(BossProgressBar bossHealthBar, GUIComponent container, float alpha)
static void SetColor(ProgressBar bossHealthBar, GUIComponent container, float alpha)
{
foreach (var component in container.GetAllChildren())
{
@@ -887,7 +887,7 @@ namespace Barotrauma
for (int i = bossProgressBars.Count - 1; i >= 0 ; i--)
{
var bossHealthBar = bossProgressBars[i];
if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
if (bossHealthBar.FadeTimer <= 0 || (healthBarMode == EnemyHealthBarMode.HideAll && bossHealthBar is not MissionProgressBar))
{
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
@@ -17,7 +17,8 @@ namespace Barotrauma
private static Sprite infoAreaPortraitBG;
public bool LastControlled;
public int CrewListIndex { get; set; } = -1;
public int CrewListIndex { get; set; } = int.MaxValue; //default to the bottom of the list
private Sprite disguisedPortrait;
private List<WearableSprite> disguisedAttachmentSprites;
@@ -32,6 +33,8 @@ namespace Barotrauma
private float tintHighlightThreshold;
private float tintHighlightMultiplier;
public bool ShowTalentResetPopupOnOpen = true;
public static void Init()
{
infoAreaPortraitBG = GUIStyle.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite();
@@ -208,7 +211,7 @@ namespace Barotrauma
return frame;
}
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel)
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel, bool forceNotification)
{
if (TeamID == CharacterTeamType.FriendlyNPC) { return; }
if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
@@ -226,6 +229,18 @@ namespace Barotrauma
specialIncrease ? GUIStyle.Orange : GUIStyle.Green,
playSound: Character == Character.Controlled, skillIdentifier, increase);
}
else if (forceNotification)
{
float change = newLevel - prevLevel;
if (Math.Abs(change) > 0.01f)
{
string sign = change > 0 ? "+" : "-";
Character?.AddMessage(
$"{sign}{Math.Round(change, 2)} {TextManager.Get("SkillName." + skillIdentifier).Value}",
specialIncrease ? GUIStyle.Orange : GUIStyle.Green,
playSound: Character == Character.Controlled);
}
}
}
partial void OnExperienceChanged(int prevAmount, int newAmount)
@@ -511,6 +526,17 @@ namespace Barotrauma
else
{
origin = attachment.Sprite.Origin;
if (spriteEffects.HasFlag(SpriteEffects.FlipHorizontally))
{
origin.X = attachment.Sprite.size.X - origin.X;
}
if (spriteEffects.HasFlag(SpriteEffects.FlipVertically))
{
origin.Y = attachment.Sprite.size.Y - origin.Y;
}
//the portrait's origin is forced to 0,0 (presumably for easier drawing on the UI?), see LoadHeadElement
//we need to take that into account here and draw the attachment at where the origin of the "actual" head sprite would be
drawPos += HeadSprite.Origin * scale;
}
float depth = attachment.Sprite.Depth;
if (attachment.InheritLimbDepth)
@@ -526,6 +552,8 @@ namespace Barotrauma
string newName = inc.ReadString();
string originalName = inc.ReadString();
bool renamingEnabled = inc.ReadBoolean();
BotStatus botStatus = (BotStatus)inc.ReadByte();
int salary = inc.ReadInt32();
int tagCount = inc.ReadByte();
HashSet<Identifier> tagSet = new HashSet<Identifier>();
for (int i = 0; i < tagCount; i++)
@@ -576,6 +604,8 @@ namespace Barotrauma
MinReputationToHire = (factionId, minReputationToHire),
RenamingEnabled = renamingEnabled
};
ch.BotStatus = botStatus;
ch.Salary = salary;
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.Head.SkinColor = skinColor;
ch.Head.HairColor = hairColor;
@@ -586,6 +616,8 @@ namespace Barotrauma
ch.ExperiencePoints = inc.ReadInt32();
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
ch.PermanentlyDead = inc.ReadBoolean();
ch.TalentRefundPoints = inc.ReadInt32();
ch.TalentResetCount = inc.ReadInt32();
return ch;
}
@@ -47,6 +47,7 @@ namespace Barotrauma
SelectedCharacter,
SelectedItem,
SelectedSecondaryItem,
AnimController.TargetMovement,
AnimController.Anim);
memLocalState.Add(posInfo);
@@ -56,7 +57,7 @@ namespace Barotrauma
if (IsKeyDown(InputType.Right)) newInput |= InputNetFlags.Right;
if (IsKeyDown(InputType.Up)) newInput |= InputNetFlags.Up;
if (IsKeyDown(InputType.Down)) newInput |= InputNetFlags.Down;
if (IsKeyDown(InputType.Run)) newInput |= InputNetFlags.Run;
if (IsKeyDown(InputType.Run) || ToggleRun) newInput |= InputNetFlags.Run;
if (IsKeyDown(InputType.Crouch)) newInput |= InputNetFlags.Crouch;
if (IsKeyHit(InputType.Select)) newInput |= InputNetFlags.Select; //TODO: clean up the way this input is registered
if (IsKeyHit(InputType.Deselect)) newInput |= InputNetFlags.Deselect;
@@ -68,7 +69,7 @@ namespace Barotrauma
if (IsKeyDown(InputType.Attack)) newInput |= InputNetFlags.Attack;
if (IsKeyDown(InputType.Ragdoll)) newInput |= InputNetFlags.Ragdoll;
if (AnimController.TargetDir == Direction.Left) newInput |= InputNetFlags.FacingLeft;
if (AnimController.Dir < 0) newInput |= InputNetFlags.FacingLeft;
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
relativeCursorPos.Normalize();
@@ -154,6 +155,9 @@ namespace Barotrauma
case TreatmentEventData _:
msg.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR);
break;
case ConfirmRefundEventData _:
//do nothing
break;
case CharacterStatusEventData _:
//do nothing
break;
@@ -202,12 +206,16 @@ namespace Barotrauma
keys[(int)InputType.Use].Held = useInput;
keys[(int)InputType.Use].SetState(false, useInput);
bool crouching = msg.ReadBoolean();
if (AnimController is HumanoidAnimController)
{
bool crouching = msg.ReadBoolean();
keys[(int)InputType.Crouch].Held = crouching;
keys[(int)InputType.Crouch].SetState(false, crouching);
}
else if (AnimController is FishAnimController fishAnim)
{
fishAnim.Reverse = msg.ReadBoolean();
}
bool attackInput = msg.ReadBoolean();
keys[(int)InputType.Attack].Held = attackInput;
@@ -255,17 +263,22 @@ namespace Barotrauma
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);
Vector2 targetMovement = new Vector2(
msg.ReadRangedSingle(-Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12),
msg.ReadRangedSingle(-Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12));
targetMovement = NetConfig.Quantize(targetMovement, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12);
bool fixedRotation = msg.ReadBoolean();
float? rotation = null;
float? angularVelocity = null;
if (!fixedRotation)
{
rotation = msg.ReadSingle();
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
angularVelocity = msg.ReadSingle();
}
bool ignorePlatforms = msg.ReadBoolean();
bool readStatus = msg.ReadBoolean();
if (readStatus)
{
@@ -287,7 +300,7 @@ namespace Barotrauma
{
byte happiness = msg.ReadByte();
byte hunger = msg.ReadByte();
if ((AIController as EnemyAIController)?.PetBehavior is PetBehavior petBehavior)
if (AIController is EnemyAIController { PetBehavior: PetBehavior petBehavior })
{
petBehavior.Happiness = (float)happiness / byte.MaxValue * petBehavior.MaxHappiness;
petBehavior.Hunger = (float)hunger / byte.MaxValue * petBehavior.MaxHunger;
@@ -303,13 +316,13 @@ namespace Barotrauma
msg.ReadPadBits();
int index = 0;
if (GameMain.Client.Character == this && CanMove)
if (GameMain.Client.Character == this)
{
var posInfo = new CharacterStateInfo(
pos, rotation,
networkUpdateID,
facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, selectedSecondaryItem, animation);
selectedCharacter, selectedItem, selectedSecondaryItem, targetMovement, animation, ignorePlatforms);
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
index++;
@@ -321,7 +334,7 @@ namespace Barotrauma
pos, rotation,
linearVelocity, angularVelocity,
sendingTime, facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, selectedSecondaryItem, animation);
selectedCharacter, selectedItem, selectedSecondaryItem, targetMovement, animation, ignorePlatforms);
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
index++;
@@ -371,6 +384,9 @@ namespace Barotrauma
GameMain.Client.HasSpawned = true;
GameMain.Client.Character = this;
GameMain.LightManager.LosEnabled = true;
#if DEBUG
GameMain.LightManager.LosEnabled = !GameMain.DevMode;
#endif
GameMain.LightManager.LosAlpha = 1f;
GameMain.Client.WaitForNextRoundRespawn = null;
}
@@ -393,15 +409,16 @@ namespace Barotrauma
break;
case EventType.Status:
ReadStatus(msg);
GodMode = msg.ReadBoolean();
break;
case EventType.UpdateSkills:
int skillCount = msg.ReadByte();
for (int i = 0; i < skillCount; i++)
Identifier skillIdentifier = msg.ReadIdentifier();
if (!skillIdentifier.IsEmpty)
{
Identifier skillIdentifier = msg.ReadIdentifier();
bool forceNotification = msg.ReadBoolean();
float skillLevel = msg.ReadSingle();
info?.SetSkillLevel(skillIdentifier, skillLevel);
}
info?.SetSkillLevel(skillIdentifier, skillLevel, forceNotification: forceNotification);
}
break;
case EventType.SetAttackTarget:
case EventType.ExecuteAttack:
@@ -512,7 +529,12 @@ namespace Barotrauma
break;
case EventType.UpdateExperience:
int experienceAmount = msg.ReadInt32();
info?.SetExperience(experienceAmount);
int additionalTalentPoints = msg.ReadInt32();
if (info != null)
{
info.SetExperience(experienceAmount);
info.AdditionalTalentPoints = additionalTalentPoints;
}
break;
case EventType.UpdateTalents:
ushort talentCount = msg.ReadUInt16();
@@ -527,6 +549,20 @@ namespace Barotrauma
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
break;
case EventType.UpdateTalentRefundPoints:
int refundPoints = msg.ReadInt32();
if (info != null)
{
if (refundPoints > info.TalentRefundPoints)
{
info.ShowTalentResetPopupOnOpen = true;
}
info.TalentRefundPoints = refundPoints;
}
break;
case EventType.ConfirmTalentRefund:
Info?.RefundTalents();
break;
case EventType.UpdatePermanentStats:
byte savedStatValueCount = msg.ReadByte();
StatTypes statType = (StatTypes)msg.ReadByte();
@@ -730,7 +766,7 @@ namespace Barotrauma
if (character.IsHuman && character.TeamID != CharacterTeamType.FriendlyNPC && character.TeamID != CharacterTeamType.None)
{
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos(includeReserveBench: true).FirstOrDefault(c => c.ID == info.ID);
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
if (character.isDead)
{
@@ -750,6 +786,9 @@ namespace Barotrauma
if (!character.IsDead) { Controlled = character; }
GameMain.LightManager.LosEnabled = true;
#if DEBUG
GameMain.LightManager.LosEnabled = !GameMain.DevMode;
#endif
GameMain.LightManager.LosAlpha = 1f;
GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
@@ -817,6 +856,7 @@ namespace Barotrauma
if (IsDead) { Revive(); }
CharacterHealth.ClientRead(msg);
}
byte severedLimbCount = msg.ReadByte();
for (int i = 0; i < severedLimbCount; i++)
{
@@ -180,7 +180,7 @@ namespace Barotrauma
fakeBrokenTimer -= deltaTime;
if (fakeBrokenTimer > 0.0f) { return; }
foreach (Item item in Item.ItemList)
foreach (Item item in Item.RepairableItems)
{
var repairable = item.GetComponent<Repairable>();
if (repairable == null) { continue; }
@@ -31,8 +31,7 @@ namespace Barotrauma
}
public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
private Point screenResolution;
private float uiScale, inventoryScale;
@@ -105,6 +104,12 @@ namespace Barotrauma
private GUILayoutGroup treatmentLayout;
private GUIListBox recommendedTreatmentContainer;
/// <summary>
/// Timer for updating visuals (limb tints and overlays) caused by the affliction
/// </summary>
private float updateVisualsTimer = Rand.Range(0.0f, UpdateVisualsInterval);
const float UpdateVisualsInterval = 0.5f;
private float distortTimer;
// 0-1
@@ -461,15 +466,17 @@ namespace Barotrauma
private void OnAttacked(Character attacker, AttackResult attackResult)
{
if (Math.Abs(attackResult.Damage) < 0.01f) { return; }
DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
if (ShowDamageOverlay)
{
DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
}
if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
if (healthBarPulsateTimer <= 0.0f) { healthBarPulsatePhase = 0.0f; }
healthBarPulsateTimer = 1.0f;
float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
DisplayVitalityDelay = 0.5f;
}
@@ -1036,6 +1043,12 @@ namespace Barotrauma
var affliction = kvp.Key;
affliction.Prefab.AfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
var activeEffect = affliction.GetActiveEffect();
if (activeEffect is { ThermalOverlayRange: > 0.0f })
{
StatusHUD.DrawThermalOverlay(spriteBatch, Character, Character, activeEffect.ThermalOverlayColor, activeEffect.ThermalOverlayRange, effectState: (float)Timing.TotalTimeUnpaused, showDeadCharacters: false);
}
}
float damageOverlayAlpha = DamageOverlayTimer;
@@ -1130,6 +1143,8 @@ namespace Barotrauma
if (!statusIconVisibleTime.ContainsKey(afflictionPrefab)) { statusIconVisibleTime.Add(afflictionPrefab, 0.0f); }
statusIconVisibleTime[afflictionPrefab] += deltaTime;
Color color = GetAfflictionIconColor(afflictionPrefab, affliction);
var matchingIcon =
afflictionIconContainer.GetChildByUserData(afflictionPrefab) ??
hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
@@ -1138,9 +1153,13 @@ namespace Barotrauma
matchingIcon = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: null)
{
UserData = afflictionPrefab,
ToolTip = affliction.Prefab.Name,
ToolTip = $"‖color:{color.ToStringHex()}‖{affliction.Prefab.Name}‖color:end‖",
CanBeSelected = false
};
if (affliction.Prefab.ShowDescriptionInTooltip)
{
matchingIcon.ToolTip = matchingIcon.ToolTip + "\n" + affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.Self);
}
if (affliction == pressureAffliction)
{
matchingIcon.ToolTip = TextManager.Get("PressureHUDWarning");
@@ -1149,6 +1168,8 @@ namespace Barotrauma
{
matchingIcon.ToolTip = TextManager.Get("OxygenHUDWarning");
}
matchingIcon.ToolTip = RichString.Rich(matchingIcon.ToolTip);
new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
{
CanBeFocused = false
@@ -1159,7 +1180,7 @@ namespace Barotrauma
matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
}
var image = matchingIcon.GetChild<GUIImage>();
image.Color = GetAfflictionIconColor(afflictionPrefab, affliction);
image.Color = color;
image.HoverColor = Color.Lerp(image.Color, Color.White, 0.5f);
if (affliction.DamagePerSecond > 1.0f && matchingIcon.FlashTimer <= 0.0f)
@@ -1380,7 +1401,7 @@ namespace Barotrauma
recommendedTreatmentContainer.Content.ClearChildren();
float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical");
float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel(Tags.MedicalSkill);
//key = item identifier
//float = suitability
@@ -1388,7 +1409,9 @@ namespace Barotrauma
GetSuitableTreatments(treatmentSuitability,
user: Character.Controlled,
ignoreHiddenAfflictions: true,
limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex),
checkTreatmentSuggestionThreshold: true,
checkTreatmentThreshold: false);
foreach (Identifier treatment in treatmentSuitability.Keys.ToList())
{
@@ -1949,7 +1972,6 @@ namespace Barotrauma
}
}
private bool ShouldDisplayAfflictionOnLimb(KeyValuePair<Affliction, LimbHealth> kvp, LimbHealth limbHealth)
{
if (!kvp.Key.ShouldShowIcon(Character)) { return false; }
@@ -2058,23 +2080,23 @@ namespace Barotrauma
newAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
}
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
//deactivate afflictions that weren't included in the network message
if (!newAfflictions.Any(a => kvp.Key.Prefab == a.afflictionPrefab && kvp.Value == a.limb))
if (newAfflictions.None(a => affliction.Prefab == a.afflictionPrefab && limbHealth == a.limb))
{
kvp.Key.Strength = 0.0f;
affliction.Strength = 0.0f;
}
}
foreach (var (limb, afflictionPrefab, strength) in newAfflictions)
{
Affliction existingAffliction = null;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
if (kvp.Key.Prefab == afflictionPrefab && kvp.Value == limb)
if (affliction.Prefab == afflictionPrefab && limbHealth == limb)
{
existingAffliction = kvp.Key;
existingAffliction = affliction;
break;
}
}
@@ -2123,9 +2145,8 @@ namespace Barotrauma
if (!Character.Params.Health.ApplyAfflictionColors) { return; }
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
foreach ((Affliction affliction, LimbHealth _) in afflictions)
{
var affliction = kvp.Key;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
@@ -2137,17 +2158,23 @@ namespace Barotrauma
{
foreach (Limb limb in Character.AnimController.Limbs)
{
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
limb.BurnOverlayStrength = 0.0f;
limb.DamageOverlayStrength = 0.0f;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
}
foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
if (affliction.Prefab.BurnOverlayAlpha <= 0.0f && affliction.Prefab.DamageOverlayAlpha <= 0.0f) { continue; }
float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
float damageOverlayStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
foreach (Limb limb in Character.AnimController.Limbs)
{
var affliction = kvp.Key;
float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
if (kvp.Value == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
if (limbHealth == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
{
limb.BurnOverlayStrength += burnStrength;
limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
limb.DamageOverlayStrength += damageOverlayStrength;
}
else
{
@@ -17,13 +17,16 @@ public static class InteractionLabelManager
public RectangleF TextRect { get; set; }
public RichString Text;
public readonly Vector2 OriginalItemPosition;
public bool OverlapPreventionDone;
public LabelData(Item item, RectangleF textRect, Camera drawCamera)
public LabelData(Item item, RectangleF textRect, RichString text, Camera drawCamera)
{
Item = item;
Text = text;
TextRect = textRect;
OriginalItemPosition = item.Position;
this.drawCamera = drawCamera;
@@ -106,7 +109,7 @@ public static class InteractionLabelManager
if (labels.None(l => l.Item == interactableInRange))
{
var labelData = new LabelData(interactableInRange, textRect, cam);
var labelData = new LabelData(interactableInRange, textRect, RichString.Rich(interactableInRange.Prefab.Name), cam);
labels.Add(labelData);
}
}
@@ -124,7 +127,7 @@ public static class InteractionLabelManager
private static RectangleF GetLabelRect(Item item, Camera cam)
{
// create rectangle for overlap prevention
Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(item.Name) * LabelScale;
Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(RichString.Rich(item.Prefab.Name).SanitizedValue) * LabelScale;
Vector2 interactablePosScreen = cam.WorldToScreen(item.Position);
RectangleF textRect = new RectangleF(interactablePosScreen.X, interactablePosScreen.Y, itemTextSizeScreen.X, itemTextSizeScreen.Y);
// center the rectangle on the item
@@ -320,9 +323,11 @@ public static class InteractionLabelManager
GUIStyle.InteractionLabelBackground.Draw(spriteBatch, backgroundRect, color * 0.7f);
GUIStyle.SubHeadingFont.DrawString(spriteBatch,
labelData.Item.Name,
textDrawPosScreen, color, rotation: 0, origin: Vector2.Zero, scale, spriteEffects: SpriteEffects.None, layerDepth: 0.0f,
GUIStyle.SubHeadingFont.DrawStringWithColors(spriteBatch,
labelData.Text.SanitizedValue,
textDrawPosScreen, color, rotation: 0, origin: Vector2.Zero, scale, spriteEffects: SpriteEffects.None,
layerDepth: 0.0f,
richTextData: labelData.Text.RichTextData,
forceUpperCase: ForceUpperCase.No);
}
}
@@ -1,85 +1,75 @@
using Microsoft.Xna.Framework;
using System.Linq;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class JobPrefab : PrefabWithUintIdentifier
{
public GUIButton CreateInfoFrame(out GUIComponent buttonContainer)
public GUIButton CreateInfoFrame(bool isPvP, out GUIComponent buttonContainer)
{
int width = 500, height = 400;
int windowPixelWidth = 500, windowPixelHeight = 400;
Point absoluteWindowSize = new Point((int)(windowPixelWidth * GUI.xScale), (int)(windowPixelHeight * GUI.yScale));
GUIButton frameHolder = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, frameHolder.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
GUIFrame frame = new GUIFrame(new RectTransform(new Point(width, height), frameHolder.RectTransform, Anchor.Center));
GUIFrame frame = new GUIFrame(new RectTransform(absoluteWindowSize, frameHolder.RectTransform, Anchor.Center));
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUIStyle.LargeFont)
{
CanBeFocused = false
};
var contentList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.75f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) })
{
ScrollBarVisible = true,
AutoHideScrollBar = true,
CurrentSelectMode = GUIListBox.SelectMode.None,
Padding = new Vector4(0, GUI.Scale * 10, 0, 0),
Spacing = (int)(GUI.Scale * 5)
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUIStyle.LargeFont);
var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) },
Description, font: GUIStyle.SmallFont, wrap: true);
var skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform)
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) });
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
TextManager.Get("Skills"), font: GUIStyle.LargeFont);
var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
Description, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft)
{
CanBeFocused = false,
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
TextManager.Get("Skills"), font: GUIStyle.LargeFont)
{
CanBeFocused = false
};
foreach (SkillPrefab skill in Skills)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), (int)skill.LevelRange.Start + " - " + (int)skill.LevelRange.End),
font: GUIStyle.SmallFont);
var levelRange = skill.GetLevelRange(isPvP);
string levelStr =
levelRange.End > levelRange.Start ?
(int)levelRange.Start + " - " + (int)levelRange.End :
((int)levelRange.Start).ToString();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), levelStr),
font: GUIStyle.SmallFont, wrap: true)
{
CanBeFocused = false
};
}
buttonContainer = paddedFrame;
/*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; }
var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight)
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) })
{
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
TextManager.Get("Items", "mapentitycategory.equipment"), font: GUIStyle.LargeFont);
foreach (string identifier in itemIdentifiers.Distinct())
{
if (!(MapEntityPrefab.Find(name: null, identifier: identifier) is ItemPrefab itemPrefab)) { continue; }
int count = itemIdentifiers.Count(i => i == identifier);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
" - " + (count == 1 ? itemPrefab.Name : itemPrefab.Name + " x" + count),
font: GUIStyle.SmallFont);
}*/
return frameHolder;
}
public class OutfitPreview
public IEnumerable<Sprite> GetJobOutfitSprites(CharacterTeamType team, bool isPvPMode)
{
public readonly List<(Sprite sprite, Vector2 drawOffset)> Sprites;
public Vector2 Dimensions;
public OutfitPreview()
{
Sprites = new List<(Sprite sprite, Vector2 drawOffset)>();
Dimensions = Vector2.One;
}
public void AddSprite(Sprite sprite, Vector2 drawOffset)
{
Sprites.Add((sprite, drawOffset));
}
}
public List<OutfitPreview> GetJobOutfitSprites(CharacterInfoPrefab charInfoPrefab, bool useInventoryIcon, out Vector2 maxDimensions)
{
List<OutfitPreview> outfitPreviews = new List<OutfitPreview>();
maxDimensions = Vector2.One;
var equipIdentifiers = Element.GetChildElements("ItemSet").Elements().Where(e => e.GetAttributeBool("outfit", false)).Select(e => e.GetAttributeIdentifier("identifier", ""));
var equipIdentifiers = JobItems
.SelectMany(kvp => kvp.Value)
.Where(j => j.Outfit)
.Select(j => j.GetItemIdentifier(team, isPvPMode));
List<ItemPrefab> outfitPrefabs = new List<ItemPrefab>();
foreach (var equipIdentifier in equipIdentifiers)
@@ -88,45 +78,9 @@ namespace Barotrauma
if (itemPrefab != null) { outfitPrefabs.Add(itemPrefab); }
}
if (!outfitPrefabs.Any()) { return null; }
if (!outfitPrefabs.Any()) { return Enumerable.Empty<Sprite>(); }
for (int i = 0; i < outfitPrefabs.Count; i++)
{
var outfitPreview = new OutfitPreview();
if (!ItemSets.TryGetValue(i, out var itemSetElement)) { continue; }
var previewElement = itemSetElement.GetChildElement("PreviewSprites");
if (previewElement == null || useInventoryIcon)
{
if (outfitPrefabs[i] is ItemPrefab prefab && prefab.InventoryIcon != null)
{
outfitPreview.AddSprite(prefab.InventoryIcon, Vector2.Zero);
outfitPreview.Dimensions = prefab.InventoryIcon.SourceRect.Size.ToVector2();
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
}
outfitPreviews.Add(outfitPreview);
continue;
}
var children = previewElement.Elements().ToList();
for (int n = 0; n < children.Count; n++)
{
var spriteElement = children[n];
string spriteTexture = charInfoPrefab.ReplaceVars(spriteElement.GetAttributeString("texture", ""), charInfoPrefab.Heads.First());
var sprite = new Sprite(spriteElement, file: spriteTexture);
sprite.size = new Vector2(sprite.SourceRect.Width, sprite.SourceRect.Height);
outfitPreview.AddSprite(sprite, children[n].GetAttributeVector2("offset", Vector2.Zero));
}
outfitPreview.Dimensions = previewElement.GetAttributeVector2("dims", Vector2.One);
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
outfitPreviews.Add(outfitPreview);
}
return outfitPreviews;
return outfitPrefabs.Select(p => p.InventoryIcon ?? p.Sprite);
}
}
}
@@ -137,7 +137,16 @@ namespace Barotrauma
{
get
{
var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.Exclusive && c.IsActive && c.DeformableSprite != null);
// Performance-sensitive, hence implemented without Linq.
ConditionalSprite conditionalSprite = null;
foreach (ConditionalSprite cs in ConditionalSprites)
{
if (cs.Exclusive && cs.IsActive && cs.DeformableSprite != null)
{
conditionalSprite = cs;
break;
}
}
if (conditionalSprite != null)
{
return conditionalSprite.DeformableSprite;
@@ -155,7 +164,16 @@ namespace Barotrauma
{
get
{
var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.Exclusive && c.IsActive && c.ActiveSprite != null);
// Performance-sensitive, hence implemented without Linq.
ConditionalSprite conditionalSprite = null;
foreach (ConditionalSprite cs in ConditionalSprites)
{
if (cs.Exclusive && cs.IsActive && cs.ActiveSprite != null)
{
conditionalSprite = cs;
break;
}
}
if (conditionalSprite != null)
{
return conditionalSprite.ActiveSprite;
@@ -184,12 +202,6 @@ namespace Barotrauma
public Sprite DamagedSprite { get; private set; }
public bool Hide
{
get => Params.Hide;
set => Params.Hide = value;
}
public List<ConditionalSprite> ConditionalSprites { get; private set; } = new List<ConditionalSprite>();
private Dictionary<DecorativeSprite, SpriteState> spriteAnimState = new Dictionary<DecorativeSprite, SpriteState>();
private Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
@@ -198,6 +210,7 @@ namespace Barotrauma
{
public float RotationState;
public float OffsetState;
public float ScaleState;
public Vector2 RandomOffsetMultiplier = new Vector2(Rand.Range(-1.0f, 1.0f), Rand.Range(-1.0f, 1.0f));
public float RandomRotationFactor = Rand.Range(0.0f, 1.0f);
public float RandomScaleFactor = Rand.Range(0.0f, 1.0f);
@@ -301,7 +314,16 @@ namespace Barotrauma
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath), sourceRectScale: sourceRectScale);
break;
case "conditionalsprite":
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath), sourceRectScale: sourceRectScale);
string conditionalSpritePath = string.Empty;
GetSpritePath(subElement.GetChildElement("sprite") ?? subElement.GetChildElement("deformablesprite") ?? subElement, null, ref conditionalSpritePath);
if (conditionalSpritePath.IsNullOrEmpty())
{
DebugConsole.ThrowError($"Failed to find a sprite path in the conditional sprite defined in {character.SpeciesName}, limb {type}.",
contentPackage: subElement.ContentPackage);
}
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(),
file: conditionalSpritePath,
sourceRectScale: sourceRectScale);
ConditionalSprites.Add(conditionalSprite);
if (conditionalSprite.DeformableSprite != null)
{
@@ -475,13 +497,24 @@ namespace Barotrauma
private string _damagedTexturePath;
private string GetSpritePath(ContentXElement element, SpriteParams spriteParams, ref string path)
{
if (path == null)
if (path.IsNullOrEmpty())
{
if (spriteParams != null)
{
//1. check if the variant file redefines the texture
ContentPath texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
//2. check if the base prefab defines the texture
ContentPath texturePath;
//1. check if the limb defines the texture directly
var definedTexturePath = element?.GetAttributeContentPath("texture");
if (!definedTexturePath.IsNullOrEmpty())
{
texturePath = definedTexturePath;
}
else
{
//2. check if the character file defines the texture directly
texturePath = character.Params.VariantFile?.GetRootExcludingOverride()?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
}
//3. check if the base prefab defines the texture
if (texturePath.IsNullOrEmpty() && !character.Prefab.VariantOf.IsEmpty)
{
Identifier speciesName = character.GetBaseCharacterSpeciesName();
@@ -491,7 +524,7 @@ namespace Barotrauma
texturePath = parentRagdollParams.OriginalElement?.GetAttributeContentPath("texture");
}
//3. "default case", get the texture from this character's XML
//4. "default case", get the texture from this character's XML
texturePath ??= ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath());
path = GetSpritePath(texturePath);
}
@@ -749,9 +782,29 @@ namespace Barotrauma
float herpesStrength = character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.SpaceHerpesType);
bool hideLimb = Hide ||
OtherWearables.Any(w => w.HideLimb) ||
WearingItems.Any(w => w.HideLimb);
bool hideLimb = ShouldHideLimb(this);
if (!hideLimb && Params.InheritHiding != LimbType.None)
{
if (character.AnimController.GetLimb(Params.InheritHiding) is Limb otherLimb)
{
hideLimb = ShouldHideLimb(otherLimb);
}
}
static bool ShouldHideLimb(Limb limb)
{
if (limb.Hide) { return true; }
// Performance-sensitive code -> implemented without Linq
foreach (var wearable in limb.OtherWearables)
{
if (wearable.HideLimb) { return true; }
}
foreach (var wearable in limb.WearingItems)
{
if (wearable.HideLimb) { return true; }
}
return false;
}
bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
@@ -771,11 +824,13 @@ namespace Barotrauma
{
if (ActiveDeformations.Any())
{
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size);
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, flippedHorizontally: IsFlipped, false);
deformSprite.Deform(deformation);
if (LightSource != null && LightSource.DeformableLightSprite != null)
{
deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, dir == Direction.Left);
//apparently inversing on the y-axis is only necessary for light sprites (see 345a65ca6)
//it's a mystery why this is the case, something to do with sprite flipping being handled differently in light rendering?
deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, flippedHorizontally: IsFlipped, inverseY: dir == Direction.Left);
LightSource.DeformableLightSprite.Deform(deformation);
}
}
@@ -826,7 +881,7 @@ namespace Barotrauma
var defSprite = conditionalSprite.DeformableSprite;
if (ActiveDeformations.Any())
{
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, defSprite.Size);
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, defSprite.Size, flippedHorizontally: IsFlipped);
defSprite.Deform(deformation);
}
else
@@ -883,28 +938,28 @@ namespace Barotrauma
}
depthStep += step;
}
foreach (WearableSprite wearable in OtherWearables)
if (!hideLimb)
{
if (wearable.Type == WearableType.Husk) { continue; }
if (wearableTypesToHide.Contains(wearable.Type))
foreach (WearableSprite wearable in OtherWearables)
{
if (wearable.Type == WearableType.Hair)
if (wearable.Type == WearableType.Husk) { continue; }
if (wearableTypesToHide.Contains(wearable.Type))
{
if (HairWithHatSprite != null && !hideLimb)
// Draws the short hair
if (wearable.Type == WearableType.Hair)
{
DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
depthStep += step;
continue;
if (HairWithHatSprite != null)
{
DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
depthStep += step;
}
}
}
else
{
continue;
}
DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
//if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step;
}
DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
//if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step;
}
}
foreach (WearableSprite wearable in WearingItems)
@@ -952,8 +1007,8 @@ namespace Barotrauma
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c, decorativeSprite.Sprite.Origin,
-body.Rotation + rotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: activeSprite.Depth - depthStep);
depthStep += step;
}
@@ -963,7 +1018,7 @@ namespace Barotrauma
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
colorWithoutTint * damageOverlayStrength, activeSprite.Origin,
-body.DrawRotation,
Scale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
Scale * TextureScale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
}
}