Merge remote-tracking branch 'upstream/master' into develop

This commit is contained in:
EvilFactory
2024-12-11 10:44:53 -03:00
257 changed files with 4793 additions and 1653 deletions

View File

@@ -73,7 +73,7 @@ body:
label: Version
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
options:
- v1.6.19.1 (Unto the Breach Update Hotfix 2)
- v1.7.7.0 (Winter Update)
- Other
validations:
required: true

View File

@@ -63,6 +63,12 @@ namespace Barotrauma
private float prevZoom;
public float Shake;
/// <summary>
/// Should the camera's transform matrices be automatically updated to match the screen resolution?
/// </summary>
public bool AutoUpdateToScreenResolution = true;
public Vector2 ShakePosition { get; private set; }
private float shakeTimer;
@@ -198,10 +204,13 @@ namespace Barotrauma
public void UpdateTransform(bool interpolate = true, bool updateListener = true)
{
if (GameMain.GraphicsWidth != Resolution.X ||
GameMain.GraphicsHeight != Resolution.Y)
if (AutoUpdateToScreenResolution)
{
CreateMatrices();
if (GameMain.GraphicsWidth != Resolution.X ||
GameMain.GraphicsHeight != Resolution.Y)
{
CreateMatrices();
}
}
Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position;

View File

@@ -42,7 +42,7 @@ 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);

View File

@@ -11,7 +11,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;

View File

@@ -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,28 +130,31 @@ 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;
MainLimb.body.LinearVelocity = newVelocity;
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, newPosition);
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)
{
MainLimb.PullJointWorldAnchorB = newPosition;
MainLimb.PullJointEnabled = true;
if (!ColliderControlsMovement && newVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(newPosition); }
}
else
{
MainLimb.body.LinearVelocity = newVelocity;
}
}
}
}
@@ -179,9 +186,9 @@ namespace Barotrauma
}
}
if (character.MemState.Count < 1) return;
if (character.MemState.Count < 1) { return; }
overrideTargetMovement = Vector2.Zero;
overrideTargetMovement = null;
CharacterStateInfo serverPos = character.MemState.Last();
@@ -294,6 +301,38 @@ namespace Barotrauma
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)
{

View File

@@ -250,7 +250,7 @@ namespace Barotrauma
public Vector2 Position;
public Vector2 DrawPosition;
public float MoveUpAmount;
public readonly string Text;
public readonly RichString Text;
public readonly Character Character;
public readonly Submarine Submarine;
public readonly Vector2 TextSize;
@@ -260,7 +260,7 @@ namespace Barotrauma
public SpeechBubble(Character character, float lifeTime, Color color, string text = "")
{
Text = ToolBox.WrapText(text, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text));
Text = RichString.Rich(ToolBox.WrapText(text, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text)));
TextSize = GUIStyle.SmallFont.MeasureString(Text);
Character = character;
@@ -322,7 +322,6 @@ namespace Barotrauma
/// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
if (DisableControls || GUI.InputBlockingMenuOpen)
{
foreach (Key key in keys)
@@ -417,6 +416,11 @@ namespace Barotrauma
UpdateLocalCursor(cam);
if (IsKeyHit(InputType.ToggleRun))
{
ToggleRun = !ToggleRun;
}
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (GUI.PauseMenuOpen)
{
@@ -1189,7 +1193,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.Text.RichTextData, font: GUIStyle.SmallFont);
}
spriteBatch.End();
}
@@ -1435,7 +1439,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);
}
}
}
}

View File

@@ -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;
@@ -564,7 +562,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 +590,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);
}
}
@@ -773,7 +771,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 +782,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,14 +801,14 @@ 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)
@@ -819,10 +817,10 @@ namespace Barotrauma
}
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 +848,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 +860,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())
{

View File

@@ -526,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)

View File

@@ -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();
@@ -262,6 +263,11 @@ 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;
@@ -273,6 +279,8 @@ namespace Barotrauma
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
}
bool ignorePlatforms = msg.ReadBoolean();
bool readStatus = msg.ReadBoolean();
if (readStatus)
{
@@ -294,7 +302,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;
@@ -316,7 +324,7 @@ namespace Barotrauma
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++;
@@ -328,7 +336,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++;

View File

@@ -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; }

View File

@@ -1401,7 +1401,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())
{

View File

@@ -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;
@@ -483,9 +501,20 @@ namespace Barotrauma
{
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?.Root?.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();
@@ -495,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);
}
@@ -753,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);
@@ -887,28 +936,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)
@@ -967,7 +1016,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
}
}

View File

@@ -694,7 +694,7 @@ namespace Barotrauma
if (args.Length > 1)
{
string forceThalamusArg = args[1];
if (Enum.TryParse(forceThalamusArg, out LevelData.ThalamusSpawn result))
if (Enum.TryParse(forceThalamusArg, ignoreCase: true, out LevelData.ThalamusSpawn result))
{
NewMessage($"Setting ThalamusSpawn to: {result}", color: Color.Yellow);
LevelData.ForceThalamus = result;
@@ -2540,6 +2540,18 @@ namespace Barotrauma
}));
#if DEBUG
commands.Add(new Command("unlockachievement", "unlockachievement [identifier]: Unlocks the specified achievement.", (string[] args) =>
{
if (args.Length < 1)
{
ThrowError("Please specify the achievement to unlock.");
return;
}
NewMessage($"Unlocked \"{args[0]}\".");
AchievementManager.UnlockAchievement(args[0].ToIdentifier());
}, isCheat: true));
commands.Add(new Command("deathprompt", "Shows the death prompt for testing purposes.", (string[] args) =>
{
DeathPrompt.Create(delay: 1.0f);

View File

@@ -36,7 +36,8 @@ namespace Barotrauma
{
return
lastActiveAction != null &&
lastActiveAction.ParentEvent != ParentEvent &&
!lastActiveAction.ParentEvent.IsFinished &&
lastActiveAction.ParentEvent != ParentEvent &&
Timing.TotalTime < lastActiveAction.lastActiveTime + duration;
}
@@ -101,6 +102,7 @@ namespace Barotrauma
conversationList.BarScroll = (prevSize - conversationList.Content.Rect.Height) / (conversationList.TotalSize - conversationList.Content.Rect.Height);
conversationList.ScrollToEnd(duration: 0.5f);
lastMessageBox.SetBackgroundIcon(eventSprite);
MarkMessageBoxAsLastAction(lastMessageBox);
return;
}
}
@@ -123,16 +125,7 @@ namespace Barotrauma
messageBox.AutoClose = false;
GUIStyle.Apply(messageBox.InnerFrame, "DialogBox");
if (actionInstance != null)
{
lastActiveAction = actionInstance;
actionInstance.lastActiveTime = Timing.TotalTime;
actionInstance.dialogBox = messageBox;
}
else
{
messageBox.UserData = new Pair<string, UInt16>("ConversationAction", actionId.Value);
}
MarkMessageBoxAsLastAction(messageBox);
int padding = GUI.IntScale(16);
@@ -155,6 +148,20 @@ namespace Barotrauma
};
shadow.SetAsFirstChild();
void MarkMessageBoxAsLastAction(GUIMessageBox messageBox)
{
if (actionInstance != null)
{
lastActiveAction = actionInstance;
actionInstance.lastActiveTime = Timing.TotalTime;
actionInstance.dialogBox = messageBox;
}
else
{
messageBox.UserData = new Pair<string, UInt16>("ConversationAction", actionId.Value);
}
}
static void RecalculateLastMessage(GUIListBox conversationList, bool append)
{
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)

View File

@@ -48,6 +48,7 @@ partial class EventLog
textContent,
difficultyIconCount,
icon, GUIStyle.Red,
difficultyTooltipText: null,
out GUIImage missionIcon);
if (traitorResults != null &&

View File

@@ -61,6 +61,16 @@ namespace Barotrauma
return RichString.Rich(TextManager.GetWithVariable("missionreward", "[reward]", "‖color:gui.orange‖" + rewardText + "‖end‖"));
}
public RichString GetDifficultyToolTipText()
{
// 2 skulls give +10% XP, 3 skulls +20% XP and 4 skulls give +30% XP.
float xpBonusMultiplier = CalculateDifficultyXPMultiplier();
float xpBonusPercentage = (xpBonusMultiplier - 1f) * 100f;
int bonusRounded = (int)Math.Round(xpBonusPercentage);
LocalizedString tooltipText = TextManager.GetWithVariable(tag: "missiondifficultyxpbonustooltip", varName: "[bonus]", value: bonusRounded.ToString());
return RichString.Rich(tooltipText);
}
public RichString GetReputationRewardText()
{
List<LocalizedString> reputationRewardTexts = new List<LocalizedString>();

View File

@@ -49,7 +49,8 @@ namespace Barotrauma
{
return hudIconColor ?? IconColor;
}
}
}
public Color ProgressBarColor { get; private set; }
private Sprite hudIcon;
private Color? hudIconColor;
@@ -90,6 +91,7 @@ namespace Barotrauma
}
this.portraits = portraits.ToImmutableArray();
overrideMusicOnState = overrideMusic.ToImmutableDictionary();
ProgressBarColor = element.GetAttributeColor(nameof(ProgressBarColor), GUIStyle.Blue);
}
public Identifier GetOverrideMusicType(int state)

View File

@@ -7,20 +7,7 @@ namespace Barotrauma
{
partial class ScanMission : Mission
{
public override IEnumerable<Entity> HudIconTargets
{
get
{
if (State == 0)
{
return scanTargets.Where(kvp => !kvp.Value).Select(kvp => kvp.Key);
}
else
{
return Enumerable.Empty<Entity>();
}
}
}
public override IEnumerable<Entity> HudIconTargets => scanTargets.Where(kvp => !kvp.Value).Select(kvp => kvp.Key);
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
@@ -62,7 +49,7 @@ namespace Barotrauma
ushort id = msg.ReadUInt16();
bool scanned = msg.ReadBoolean();
Entity entity = Entity.FindEntityByID(id);
if (!(entity is WayPoint wayPoint))
if (entity is not WayPoint wayPoint)
{
string errorMsg = $"Failed to find a waypoint in ScanMission.ClientReadScanTargetStatus. Entity {id} was {(entity?.ToString() ?? null)}";
DebugConsole.ThrowError(errorMsg);

View File

@@ -419,8 +419,7 @@ namespace Barotrauma
if (anyChanges) { textures[^1].SetData<uint>(currentDynamicPixelBuffer); }
}
}
// TODO: refactor this further
private void HandleNewLineAndAlignment(
string text,
in Vector2 advanceUnit,
@@ -435,23 +434,29 @@ namespace Barotrauma
out uint charIndex,
out bool shouldContinue)
{
if ((alignment.HasFlag(Alignment.CenterX) || alignment.HasFlag(Alignment.Right)) && (lineWidth < 0.0f || text[i] == '\n'))
if (lineWidth < 0.0f || text[i] == '\n')
{
int startIndex = lineWidth < 0.0f ? i : (i + 1);
lineWidth = 0.0f;
for (int j = startIndex; j < text.Length; j++)
// Use bitwise operations instead of HasFlag or HasAnyFlag to avoid boxing, as this is performance-sensitive code.
bool isHorizontallyCentered = (alignment & Alignment.CenterX) == Alignment.CenterX;
bool isAlignedToRight = (alignment & Alignment.Right) == Alignment.Right;
if (isHorizontallyCentered || isAlignedToRight)
{
if (text[j] == '\n') { break; }
uint chrIndex = text[j];
int startIndex = lineWidth < 0.0f ? i : (i + 1);
lineWidth = 0.0f;
for (int j = startIndex; j < text.Length; j++)
{
if (text[j] == '\n') { break; }
uint chrIndex = text[j];
var gd2 = GetGlyphData(chrIndex);
lineWidth += gd2.Advance;
var gd2 = GetGlyphData(chrIndex);
lineWidth += gd2.Advance;
}
currentLineOffset = -lineWidth * advanceUnit * scale.X;
if (isHorizontallyCentered) { currentLineOffset *= 0.5f; }
currentLineOffset.X = MathF.Round(currentLineOffset.X);
currentLineOffset.Y = MathF.Round(currentLineOffset.Y);
}
currentLineOffset = -lineWidth * advanceUnit * scale.X;
if (alignment.HasFlag(Alignment.CenterX)) { currentLineOffset *= 0.5f; }
currentLineOffset.X = MathF.Round(currentLineOffset.X);
currentLineOffset.Y = MathF.Round(currentLineOffset.Y);
}
if (text[i] == '\n')
{
@@ -493,7 +498,7 @@ namespace Barotrauma
int lineNum = 0;
Vector2 currentPos = position;
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2(MathF.Cos(rotation), MathF.Sin(rotation));
for (int i = 0; i < text.Length; i++)
{
HandleNewLineAndAlignment(text, advanceUnit, position, scale, alignment, i,
@@ -504,7 +509,7 @@ namespace Barotrauma
GlyphData gd = GetGlyphData(charIndex);
if (gd.TexIndex >= 0)
{
if (gd.TexIndex < 0 || gd.TexIndex >= textures.Count)
if (gd.TexIndex >= textures.Count)
{
throw new ArgumentOutOfRangeException($"Error while rendering text. Texture index was out of range. Text: {text}, char: {charIndex} index: {gd.TexIndex}, texture count: {textures.Count}");
}
@@ -542,6 +547,11 @@ namespace Barotrauma
DynamicRenderAtlas(graphicsDevice, text);
}
quadVertices[0].Color = color;
quadVertices[1].Color = color;
quadVertices[2].Color = color;
quadVertices[3].Color = color;
Vector2 currentPos = position;
for (int i = 0; i < text.Length; i++)
{
@@ -558,26 +568,33 @@ namespace Barotrauma
if (gd.TexIndex >= 0)
{
float halfCharHeight = gd.TexCoords.Height * 0.5f;
float slantStrength = 0.35f;
float topItalicOffset = italics ? ((halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f : 0.0f;
float bottomItalicOffset = italics ? ((-halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f : 0.0f;
const float slantStrength = 0.35f;
float topItalicOffset = 0.0f;
float bottomItalicOffset = 0.0f;
if (italics)
{
topItalicOffset = ((halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f;
bottomItalicOffset = ((-halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f;
}
Texture2D tex = textures[gd.TexIndex];
float left = (float)gd.TexCoords.Left / tex.Width;
float bottom = (float)gd.TexCoords.Bottom / tex.Height;
float top = (float)gd.TexCoords.Top / tex.Height;
float right = (float)gd.TexCoords.Right / tex.Width;
quadVertices[0].Position = new Vector3(currentPos + gd.DrawOffset + (bottomItalicOffset, gd.TexCoords.Height), 0.0f);
quadVertices[0].TextureCoordinate = ((float)gd.TexCoords.Left / tex.Width, (float)gd.TexCoords.Bottom / tex.Height);
quadVertices[0].Color = color;
quadVertices[0].TextureCoordinate = new Vector2(left, bottom);
quadVertices[1].Position = new Vector3(currentPos + gd.DrawOffset + (topItalicOffset, 0.0f), 0.0f);
quadVertices[1].TextureCoordinate = ((float)gd.TexCoords.Left / tex.Width, (float)gd.TexCoords.Top / tex.Height);
quadVertices[1].Color = color;
quadVertices[1].TextureCoordinate = new Vector2(left, top);
quadVertices[2].Position = new Vector3(currentPos + gd.DrawOffset + (gd.TexCoords.Width + bottomItalicOffset, gd.TexCoords.Height), 0.0f);
quadVertices[2].TextureCoordinate = ((float)gd.TexCoords.Right / tex.Width, (float)gd.TexCoords.Bottom / tex.Height);
quadVertices[2].Color = color;
quadVertices[2].TextureCoordinate = new Vector2(right, bottom);
quadVertices[3].Position = new Vector3(currentPos + gd.DrawOffset + (gd.TexCoords.Width + topItalicOffset, 0.0f), 0.0f);
quadVertices[3].TextureCoordinate = ((float)gd.TexCoords.Right / tex.Width, (float)gd.TexCoords.Top / tex.Height);
quadVertices[3].Color = color;
quadVertices[3].TextureCoordinate = new Vector2(right, top);
sb.Draw(tex, quadVertices, 0.0f);
}

View File

@@ -246,6 +246,57 @@ namespace Barotrauma
{
get { return new Vector2(Rect.Center.X, Rect.Center.Y); }
}
/// <summary>
/// Clamps the component's rect position to the specified area. Does not resize the component.
/// </summary>
/// <param name="clampArea">Area to contain the Rect of this component to</param>
public void ClampToArea(Rectangle clampArea)
{
Rectangle componentRect = Rect;
int x = componentRect.X;
int y = componentRect.Y;
// Adjust the X position
if (componentRect.Width <= clampArea.Width)
{
if (componentRect.Left < clampArea.Left)
{
x = clampArea.Left;
}
else if (componentRect.Right > clampArea.Right)
{
x = clampArea.Right - componentRect.Width;
}
}
else
{
// Component is wider than clamp area, osition it to overlap as much as possible
x = clampArea.Left - (componentRect.Width - clampArea.Width) / 2;
}
// Adjust the Y position
if (componentRect.Height <= clampArea.Height)
{
if (componentRect.Top < clampArea.Top)
{
y = clampArea.Top;
}
else if (componentRect.Bottom > clampArea.Bottom)
{
y = clampArea.Bottom - componentRect.Height;
}
}
else
{
// Component is taller than clamp area, osition it to overlap as much as possible
y = clampArea.Top - (componentRect.Height - clampArea.Height) / 2;
}
Point moveAmount = new Point(x - componentRect.X, y - componentRect.Y);
RectTransform.ScreenSpaceOffset += moveAmount;
}
protected Rectangle ClampRect(Rectangle r)
{

View File

@@ -1562,7 +1562,7 @@ namespace Barotrauma
bool locationHasDealOnItem = isSellingRelatedList ?
ActiveStore.RequestedGoods.Contains(pi.ItemPrefab) : ActiveStore.DailySpecials.Contains(pi.ItemPrefab);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform),
pi.ItemPrefab.Name, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft)
RichString.Rich(pi.ItemPrefab.Name), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false,
Shadow = locationHasDealOnItem,

View File

@@ -902,7 +902,7 @@ namespace Barotrauma
}
}
return 0;
return teamIDs.IndexOf(client.TeamID);
}
private void CreateWalletCrewFrame(Character character, GUILayoutGroup paddedFrame)
@@ -1694,6 +1694,7 @@ namespace Barotrauma
textContent,
mission.Difficulty ?? 0,
mission.Prefab.Icon, mission.Prefab.IconColor,
mission.GetDifficultyToolTipText(),
out GUIImage missionIcon);
if (missionIcon != null)
{

View File

@@ -299,7 +299,7 @@ namespace Barotrauma
}
ImmutableHashSet<TalentPrefab?> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(static e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)).ToImmutableHashSet();
if (talentsOutsideTree.Any())
if (talentsOutsideTree.Any(static t => t != null && !t.IsHiddenExtraTalent))
{
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), nameLayout.RectTransform), style: null);
@@ -324,6 +324,7 @@ namespace Barotrauma
foreach (var extraTalent in talentsOutsideTree)
{
if (extraTalent is null) { continue; }
if (extraTalent.IsHiddenExtraTalent) { continue; }
GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(extraTalent.Description.Value)),
@@ -440,7 +441,12 @@ namespace Barotrauma
private void CreateTalentResetPopup(GUIComponent parent)
{
bool hasResetTalentsBefore = character?.Info.TalentResetCount > 0;
int talentResetCount = 0;
if (character?.Info != null)
{
talentResetCount = Math.Min(character.Info.TalentResetCount, character.Info.GetCurrentLevel());
}
bool hasResetTalentsBefore = talentResetCount > 0;
var bgBlocker = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, anchor: Anchor.Center), style: "GUIBackgroundBlocker")
{
IgnoreLayoutGroups = true
@@ -455,7 +461,8 @@ namespace Barotrauma
if (hasResetTalentsBefore)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), popupLayout.RectTransform), TextManager.Get("talentresetpromptwarning"), wrap: true)
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), popupLayout.RectTransform),
TextManager.GetWithVariable("talentresetpromptwarning", "[count]", talentResetCount.ToString()), wrap: true)
{
TextColor = GUIStyle.Red
};

View File

@@ -316,6 +316,8 @@ namespace Barotrauma
GameSettings.SetCurrentConfig(config);
}
int display = GameSettings.CurrentConfig.Graphics.Display;
GraphicsWidth = GameSettings.CurrentConfig.Graphics.Width;
GraphicsHeight = GameSettings.CurrentConfig.Graphics.Height;
@@ -343,7 +345,7 @@ namespace Barotrauma
GraphicsDeviceManager.PreferredBackBufferFormat = SurfaceFormat.Color;
GraphicsDeviceManager.PreferMultiSampling = false;
GraphicsDeviceManager.SynchronizeWithVerticalRetrace = GameSettings.CurrentConfig.Graphics.VSync;
SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode);
SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode, display);
defaultViewport = new Viewport(0, 0, GraphicsWidth, GraphicsHeight);
@@ -356,8 +358,17 @@ namespace Barotrauma
ResolutionChanged?.Invoke();
}
public void SetWindowMode(WindowMode windowMode)
public void SetWindowMode(WindowMode windowMode, int display)
{
// We can't move the monitor while the window is fullscreen because of a restriction in SDL2, so as a workaround we switch to windowed mode first
var prevDisplayMode = WindowMode;
if (Window.TargetDisplay != display && prevDisplayMode != WindowMode.Windowed)
{
GraphicsDeviceManager.IsFullScreen = false;
GraphicsDeviceManager.ApplyChanges();
}
Window.TargetDisplay = display;
WindowMode = windowMode;
GraphicsDeviceManager.HardwareModeSwitch = windowMode != WindowMode.BorderlessWindowed;
GraphicsDeviceManager.IsFullScreen = windowMode == WindowMode.Fullscreen || windowMode == WindowMode.BorderlessWindowed;

View File

@@ -1345,6 +1345,7 @@ namespace Barotrauma
{
if (ConversationAction.IsDialogOpen) { return; }
if (!AllowCharacterSwitch) { return; }
if (character == null || character.Removed) { return; }
//make the previously selected character wait in place for some time
//(so they don't immediately start idling and walking away from their station)
var aiController = Character.Controlled?.AIController;
@@ -1373,7 +1374,7 @@ namespace Barotrauma
if (index > lastIndex) { index = 0; }
if (index < 0) { index = lastIndex; }
if ((crewList.Content.GetChild(index)?.UserData as Character)?.IsOnPlayerTeam ?? false)
if (crewList.Content.GetChild(index)?.UserData is Character { IsOnPlayerTeam: true, Removed: false })
{
return index;
}
@@ -1668,7 +1669,7 @@ namespace Barotrauma
{
crewArea.Visible = characters.Count > 0 && CharacterHealth.OpenHealthWindow == null;
var myTeam = Character.Controlled?.TeamID ?? GameMain.Client?.MyClient?.TeamID;
CharacterTeamType myTeam = Character.Controlled?.TeamID ?? GameMain.Client?.MyClient?.TeamID ?? CharacterTeamType.Team1;
if (GameMain.GameSession?.GameMode is PvPMode)
{
var team1Text = crewArea.GetChildByUserData(CharacterTeamType.Team1);
@@ -1690,7 +1691,7 @@ namespace Barotrauma
continue;
}
characterComponent.Visible = Character.Controlled == null || myTeam == character.TeamID;
characterComponent.Visible = myTeam == character.TeamID;
if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null &&
(character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
{
@@ -2055,7 +2056,7 @@ namespace Barotrauma
// Character context works differently to others as we still use the "basic" command interface,
// but the order will be automatically assigned to this character
isContextual = forceContextual;
if (entityContext is Character character)
if (entityContext is Character { Info: not null } character)
{
characterContext = character;
itemContext = null;
@@ -2670,9 +2671,9 @@ namespace Barotrauma
bool IsNonDuplicateOrder(Order order) => IsNonDuplicateOrderPrefab(order.Prefab, order.Option);
bool IsNonDuplicateOrderPrefab(OrderPrefab orderPrefab, Identifier option = default)
{
return characterContext == null || (option.IsEmpty ?
return characterContext == null || (characterContext.CurrentOrders != null && (option.IsEmpty ?
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier) :
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi.Option == option));
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi?.Option == option)));
}
void AddOrderNodeWithIdentifier(string identifier)
{
@@ -3045,7 +3046,7 @@ namespace Barotrauma
{
optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
}
var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o != null &&
var colorMultiplier = characters.Any(c => c.CurrentOrders != null && c.CurrentOrders.Any(o => o != null &&
o.Identifier == userData.Order.Identifier &&
o.TargetEntity == userData.Order.TargetEntity)) ? 0.5f : 1f;
CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier, tooltip: item.Name);

View File

@@ -251,6 +251,7 @@ namespace Barotrauma
textContent,
mission.Difficulty ?? 0,
mission.Prefab.Icon, mission.Prefab.IconColor,
mission.GetDifficultyToolTipText(),
out GUIImage missionIcon);
if (selectedMissions.Contains(mission))
@@ -442,13 +443,14 @@ namespace Barotrauma
textContent,
difficultyIconCount: 0,
icon, GUIStyle.Red,
difficultyTooltipText: null,
out GUIImage missionIcon);
UpdateMissionStateIcon(traitorResults.VotedCorrectTraitor, missionIcon, iconAnimDelay);
return content;
}
public static GUIComponent CreateMissionEntry(GUIComponent parent, LocalizedString header, List<LocalizedString> textContent, int difficultyIconCount,
Sprite icon, Color iconColor, out GUIImage missionIcon)
Sprite icon, Color iconColor, RichString difficultyTooltipText, out GUIImage missionIcon)
{
int spacing = GUI.IntScale(5);
@@ -499,7 +501,8 @@ namespace Barotrauma
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Point(missionTextGroup.Rect.Width, defaultLineHeight), parent: missionTextGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1
AbsoluteSpacing = 1,
CanBeFocused = true
};
difficultyIndicatorGroup.RectTransform.MinSize = new Point(0, defaultLineHeight);
var difficultyColor = Mission.GetDifficultyColor(difficultyIconCount);
@@ -507,8 +510,8 @@ namespace Barotrauma
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), "DifficultyIndicator", scaleToFit: true)
{
CanBeFocused = false,
Color = difficultyColor
Color = difficultyColor,
ToolTip = difficultyTooltipText
};
}
}

View File

@@ -184,6 +184,13 @@ namespace Barotrauma.Items.Components
{
shakePos = Vector2.Zero;
}
if (Character.Controlled is Character character && character.FocusedItem == item)
{
if ((IsFullyOpen || IsFullyClosed) && MathF.Abs(openState - lastOpenState) > 0)
{
CharacterHUD.RecreateHudTexts = true;
}
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)

View File

@@ -14,6 +14,7 @@ namespace Barotrauma.Items.Components
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
{
bool mergedMaterialTainted = false;
if (!materialName.IsNullOrEmpty() && item.ContainedItems.Count() > 0)
{
LocalizedString mergedMaterialName = materialName;
@@ -22,11 +23,15 @@ namespace Barotrauma.Items.Components
var containedMaterial = containedItem.GetComponent<GeneticMaterial>();
if (containedMaterial == null) { continue; }
mergedMaterialName += ", " + containedMaterial.materialName;
if (containedMaterial.Tainted)
{
mergedMaterialTainted = true;
}
}
name = name.Replace(materialName, mergedMaterialName);
}
if (Tainted)
if (Tainted || mergedMaterialTainted)
{
name = TextManager.GetWithVariable("entityname.taintedgeneticmaterial", "[geneticmaterialname]", name);
}

View File

@@ -71,13 +71,13 @@ namespace Barotrauma.Items.Components
}
public override bool ValidateEventData(NetEntityEvent.IData data)
=> TryExtractEventData<EventData>(data, out _);
=> TryExtractEventData<AttachEventData>(data, out _);
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (!attachable || body == null) { return; }
var eventData = ExtractEventData<EventData>(extraData);
var eventData = ExtractEventData<AttachEventData>(extraData);
Vector2 attachPos = eventData.AttachPos;
msg.WriteSingle(attachPos.X);
@@ -94,7 +94,9 @@ namespace Barotrauma.Items.Components
bool shouldBeAttached = msg.ReadBoolean();
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
UInt16 submarineID = msg.ReadUInt16();
UInt16 attacherID = msg.ReadUInt16();
Submarine sub = Entity.FindEntityByID(submarineID) as Submarine;
Character attacher = Entity.FindEntityByID(attacherID) as Character;
if (shouldBeAttached)
{
@@ -104,6 +106,8 @@ namespace Barotrauma.Items.Components
item.SetTransform(simPosition, 0.0f);
item.Submarine = sub;
AttachToWall();
PlaySound(ActionType.OnUse, attacher);
ApplyStatusEffects(ActionType.OnUse, (float)Timing.Step, character: attacher, user: attacher);
}
}
else

View File

@@ -62,6 +62,10 @@ namespace Barotrauma.Items.Components
private readonly Dictionary<ActionType, List<ItemSound>> sounds;
private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes;
/// <summary>
/// Starts the timer for delayed client-side corrections (<see cref="StartDelayedCorrection(IReadMessage, float, bool)"/>) - in other words,
/// the client will not attempt to read server updates for this component until the timer elapses.
/// </summary>
protected float correctionTimer;
public float IsActiveTimer;
@@ -760,7 +764,11 @@ namespace Barotrauma.Items.Components
/// </summary>
protected virtual void CreateGUI() { }
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
/// <summary>
/// Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
/// Useful in cases where we a client is constantly adjusting some value, and we don't want state updates from the server to interfere with it
/// (e.g. setting the value back to what a client just set it to, when the client has already modified the value further).
/// </summary>
protected void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
{
if (delayedCorrectionCoroutine != null) { CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); }

View File

@@ -372,17 +372,17 @@ namespace Barotrauma.Items.Components
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
}
private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
private static RichString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
{
if (fabricationRecipe == null) { return ""; }
if (fabricationRecipe.Amount > 1)
{
return TextManager.GetWithVariables("fabricationrecipenamewithamount",
("[name]", fabricationRecipe.DisplayName), ("[amount]", fabricationRecipe.Amount.ToString()));
("[name]", RichString.Rich(fabricationRecipe.DisplayName)), ("[amount]", fabricationRecipe.Amount.ToString()));
}
else
{
return fabricationRecipe.DisplayName;
return RichString.Rich(fabricationRecipe.DisplayName);
}
}

View File

@@ -1978,25 +1978,6 @@ namespace Barotrauma.Items.Components
2, GUIStyle.SmallFont);
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
sonarBlip?.Remove();
pingCircle?.Remove();
directionalPingCircle?.Remove();
screenOverlay?.Remove();
screenBackground?.Remove();
lineSprite?.Remove();
foreach (var t in targetIcons.Values)
{
t.Item1.Remove();
}
targetIcons.Clear();
MineralClusters = null;
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.WriteBoolean(currentMode == Mode.Active);

View File

@@ -50,7 +50,7 @@ namespace Barotrauma.Items.Components
Hull hull = Entity.FindEntityByID(hullID) as Hull;
item.Submarine = submarine;
item.CurrentHull = hull;
item.body.SetTransform(simPosition, item.body.Rotation);
item.body.SetTransformIgnoreContacts(simPosition, item.body.Rotation);
switch (targetType)
{

View File

@@ -12,7 +12,7 @@ namespace Barotrauma.Items.Components
private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
private GUILayoutGroup uiElementContainer;
private bool readingNetworkEvent;
private bool suppressNetworkEvents;
private GUIComponent insufficientPowerWarning;
@@ -70,7 +70,7 @@ namespace Barotrauma.Items.Components
}
else
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
};
@@ -100,13 +100,10 @@ namespace Barotrauma.Items.Components
ValueStep = numberInputStep,
OnValueChanged = (ni) =>
{
if (GameMain.Client == null)
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
if (!suppressNetworkEvents && GameMain.Client != null)
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
}
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
}
};
@@ -126,13 +123,10 @@ namespace Barotrauma.Items.Components
ValueStep = numberInputStep,
OnValueChanged = (ni) =>
{
if (GameMain.Client == null)
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
if (!suppressNetworkEvents && GameMain.Client != null)
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
}
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
}
};
@@ -162,13 +156,10 @@ namespace Barotrauma.Items.Components
};
tickBox.OnSelected += (tBox) =>
{
if (GameMain.Client == null)
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
if (!suppressNetworkEvents && GameMain.Client != null)
{
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
}
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
return true;
};
@@ -191,8 +182,10 @@ namespace Barotrauma.Items.Components
{
ButtonClicked(btnElement);
}
else if (!readingNetworkEvent)
else if (!suppressNetworkEvents && GameMain.Client != null)
{
//don't use CreateClientEventWithCorrectionDelay here, because buttons have no state,
//which means we don't need to worry about server updates interfering with client-side changes to the values in the interface
item.CreateClientEvent(this, new EventData(btnElement));
}
return true;
@@ -215,6 +208,12 @@ namespace Barotrauma.Items.Components
Visible = false
};
}
void CreateClientEventWithCorrectionDelay()
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
}
public override void CreateEditingHUD(SerializableEntityEditor editor)
@@ -268,7 +267,7 @@ namespace Barotrauma.Items.Components
if (visible)
{
visibleElementCount++;
if (element.GetValueInterval > 0.0f)
if (element.GetValueInterval > 0.0f && correctionTimer <= 0.0f)
{
element.GetValueTimer -= deltaTime;
if (element.GetValueTimer <= 0.0f)
@@ -330,7 +329,7 @@ namespace Barotrauma.Items.Components
LocalizedString CreateLabelText(int elementIndex)
{
var label = customInterfaceElementList[elementIndex].Label;
string label = customInterfaceElementList[elementIndex].Label;
return string.IsNullOrWhiteSpace(label) ?
TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
TextManager.Get(label).Fallback(label);
@@ -373,6 +372,9 @@ namespace Barotrauma.Items.Components
private void UpdateSignalProjSpecific(GUIComponent uiElement)
{
if (uiElement.UserData is not CustomInterfaceElement element) { return; }
suppressNetworkEvents = true;
string signal = element.Signal;
if (uiElement is GUITextBox tb)
{
@@ -384,14 +386,22 @@ namespace Barotrauma.Items.Components
{
if (ni.InputType == NumberType.Int)
{
int.TryParse(signal, out int value);
ni.IntValue = value;
if (int.TryParse(signal, out int value))
{
ni.IntValue = value;
}
else if (float.TryParse(signal, out float floatValue))
{
ni.IntValue = (int)MathF.Round(floatValue);
}
}
}
else if (uiElement is GUITickBox tickBox)
{
tickBox.Selected = signal.Equals("true", StringComparison.OrdinalIgnoreCase);
}
suppressNetworkEvents = false;
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
@@ -429,38 +439,62 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
readingNetworkEvent = true;
int msgStartPos = msg.BitPosition;
suppressNetworkEvents = true;
try
{
string[] stringValues = new string[customInterfaceElementList.Count];
bool[] boolValues = new bool[customInterfaceElementList.Count];
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
switch (element.InputType)
{
case CustomInterfaceElement.InputTypeOption.Number:
case CustomInterfaceElement.InputTypeOption.Text:
stringValues[i] = msg.ReadString();
break;
case CustomInterfaceElement.InputTypeOption.TickBox:
case CustomInterfaceElement.InputTypeOption.Button:
boolValues[i] = msg.ReadBoolean();
break;
}
}
if (correctionTimer > 0.0f)
{
int msgLength = msg.BitPosition - msgStartPos;
msg.BitPosition = msgStartPos;
StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime);
return;
}
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
switch (element.InputType)
{
case CustomInterfaceElement.InputTypeOption.Number:
string newValue = msg.ReadString();
switch (element.NumberType)
{
case NumberType.Int when int.TryParse(newValue, out int value):
case NumberType.Int when int.TryParse(stringValues[i], out int value):
ValueChanged(element, value);
break;
case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
case NumberType.Float when TryParseFloatInvariantCulture(stringValues[i], out float value):
ValueChanged(element, value);
break;
}
break;
case CustomInterfaceElement.InputTypeOption.Text:
string newTextValue = msg.ReadString();
TextChanged(element, newTextValue);
TextChanged(element, stringValues[i]);
break;
case CustomInterfaceElement.InputTypeOption.TickBox:
bool tickBoxState = msg.ReadBoolean();
bool tickBoxState = boolValues[i];
((GUITickBox)uiElements[i]).Selected = tickBoxState;
TickBoxToggled(element, tickBoxState);
break;
case CustomInterfaceElement.InputTypeOption.Button:
bool buttonState = msg.ReadBoolean();
if (buttonState)
if (boolValues[i])
{
ButtonClicked(element);
}
@@ -472,7 +506,7 @@ namespace Barotrauma.Items.Components
}
finally
{
readingNetworkEvent = false;
suppressNetworkEvents = false;
}
}
}

View File

@@ -1,6 +1,7 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -21,13 +22,16 @@ namespace Barotrauma.Items.Components
private GUIListBox historyBox;
private GUITextBlock fillerBlock;
private GUITextBox inputBox;
private GUILayoutGroup layoutGroup;
private bool shouldSelectInputBox;
private readonly List<GUIComponent> inputElements = new List<GUIComponent>();
partial void InitProjSpecific(XElement element)
{
float marginMultiplier = element.GetAttributeFloat("marginmultiplier", 1.0f);
var layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin.Multiply(marginMultiplier), GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset.Multiply(marginMultiplier) })
layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin.Multiply(marginMultiplier), GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset.Multiply(marginMultiplier) })
{
ChildAnchor = Anchor.TopCenter,
RelativeSpacing = 0.02f,
@@ -39,43 +43,53 @@ namespace Barotrauma.Items.Components
AutoHideScrollBar = this.AutoHideScrollbar
};
if (!Readonly)
inputElements.Add(CreateFillerBlock());
inputElements.Add(new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine"));
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
{
CreateFillerBlock();
new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine");
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
MaxTextLength = MaxMessageLength,
OverflowClip = true,
OnEnterPressed = (GUITextBox textBox, string text) =>
{
MaxTextLength = MaxMessageLength,
OverflowClip = true,
OnEnterPressed = (GUITextBox textBox, string text) =>
if (GameMain.NetworkMember == null)
{
if (GameMain.NetworkMember == null)
{
SendOutput(text);
}
else
{
item.CreateClientEvent(this, new ClientEventData(text));
}
textBox.Text = string.Empty;
return true;
SendOutput(text);
}
};
}
else
{
item.CreateClientEvent(this, new ClientEventData(text));
}
textBox.Text = string.Empty;
return true;
}
};
inputElements.Add(inputBox);
RefreshInputElements();
}
layoutGroup.Recalculate();
/// <summary>
/// Refreshes the visibility of the input box and the layout of the UI depending on whether the terminal is readonly or not.
/// </summary>
private void RefreshInputElements()
{
foreach (var inputElement in inputElements)
{
inputElement.Visible = !_readonly;
inputElement.IgnoreLayoutGroups = !inputElement.Visible;
}
layoutGroup?.Recalculate();
}
// Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox
// This could be removed if GUIListBox supported aligning its children
public void CreateFillerBlock()
public GUIComponent CreateFillerBlock()
{
fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty)
{
CanBeFocused = false
};
return fillerBlock;
}
private void SendOutput(string input)

View File

@@ -199,6 +199,8 @@ namespace Barotrauma.Items.Components
return;
}
if (Width * wireSprite.size.Y * Screen.Selected.Cam.Zoom < 1.0f) { return; }
Vector2 drawOffset = GetDrawOffset() + offset;
float baseDepth = UseSpriteDepth ? item.SpriteDepth : wireSprite.Depth;

View File

@@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components
}
else
{
if (!target.CustomInteractHUDText.IsNullOrEmpty() && target.AllowCustomInteract)
if (target.ShouldShowCustomInteractText)
{
texts.Add(target.CustomInteractHUDText);
textColors.Add(GUIStyle.Green);

View File

@@ -1552,10 +1552,19 @@ namespace Barotrauma
debugInitialHudPositions.Clear();
foreach (ItemComponent ic in activeHUDs)
{
if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) { continue; }
if (!ignoreLocking && ic.LockGuiFramePosition) { continue; }
//if the frame covers nearly all of the screen, don't trying to prevent overlaps because it'd fail anyway
if (ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f) { continue; }
if (ic.GuiFrame == null || ic.GetLinkUIToComponent() != null) { continue; }
bool nearlyCoversScreen = ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f &&
ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f;
// when we are not using overlap prevention, we still need to clamp the frame to the screen area to
// prevent frames becoming inaccessible outside the screen for example after a resolution change
if (ic.AllowUIOverlap || (!ignoreLocking && ic.LockGuiFramePosition) || nearlyCoversScreen)
{
ic.GuiFrame.ClampToArea(new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight));
continue;
}
ic.GuiFrame.RectTransform.ScreenSpaceOffset = ic.GuiFrameOffset;
elementsToMove.Add(ic.GuiFrame);
debugInitialHudPositions.Add(ic.GuiFrame.Rect);
@@ -2281,7 +2290,13 @@ namespace Barotrauma
if (!components.Contains(ic)) { return; }
var eventData = new ComponentStateEventData(ic, extraData);
if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false"); }
if (!ic.ValidateEventData(eventData)) {
string errorMsg =
$"Client-side component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false. " +
$"Data: {extraData?.GetType().ToString() ?? "null"}";
GameAnalyticsManager.AddErrorEventOnce($"Item.CreateClientEvent:ValidateEventData:{Prefab.Identifier}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
GameMain.Client.CreateEntityEvent(this, eventData);
}
@@ -2487,12 +2502,18 @@ namespace Barotrauma
if (inventory != null)
{
if (inventorySlotIndex >= 0 && inventorySlotIndex < 255 &&
inventory.TryPutItem(item, inventorySlotIndex, false, false, null, false))
if (inventorySlotIndex is >= 0 and < 255)
{
return item;
if (!inventory.TryPutItem(item, inventorySlotIndex, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false, ignoreCondition: true) &&
inventory.IsSlotEmpty(inventorySlotIndex))
{
//If the item won't go nicely, force it to the slot. If the server says the item is in the slot, it should go in the slot.
//May happen e.g. when a character is configured to spawn with an item that won't normally go in its inventory slots.
inventory.ForceToSlot(item, index: inventorySlotIndex);
return item;
}
}
inventory.TryPutItem(item, null, item.AllowedSlots, false);
inventory.TryPutItem(item, user: null, allowedSlots: item.AllowedSlots, createNetworkEvent: false);
}
return item;

View File

@@ -96,9 +96,13 @@ namespace Barotrauma
new Vector2(Math.Sign(targetHull.Rect.Center.X - rect.Center.X), 0.0f)
: new Vector2(0.0f, Math.Sign((rect.Y - rect.Height / 2.0f) - (targetHull.Rect.Y - targetHull.Rect.Height / 2.0f)));
Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2));
Vector2 arrowPos = new Vector2(WorldRect.Center.X, WorldRect.Y - WorldRect.Height / 2);
if (Submarine != null)
{
arrowPos += (Submarine.DrawPosition - Submarine.Position);
}
arrowPos.Y = -arrowPos.Y;
arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2));
bool invalidDir = false;
if (dir == Vector2.Zero)
{

View File

@@ -404,8 +404,8 @@ namespace Barotrauma
//GUI.DrawRectangle(spriteBatch, new Rectangle((int)fs.LastExtinguishPos.X, (int)-fs.LastExtinguishPos.Y, 5,5), Color.Yellow, true);
}
GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f);
float worldSurface = surface + Submarine.DrawPosition.Y;
GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -worldSurface), new Vector2(drawRect.Right, -worldSurface), Color.Cyan * 0.5f);
for (int i = 0; i < waveY.Length - 1; i++)
{
GUI.DrawLine(spriteBatch,
@@ -578,8 +578,7 @@ namespace Barotrauma
corners[4] = new Vector3(x, bottom, 0.0f);
//bottom right
corners[5] = new Vector3(x + width, bottom, 0.0f);
Vector2[] uvCoords = new Vector2[4];
for (int n = 0; n < 4; n++)
{
uvCoords[n] = Vector2.Transform(new Vector2(corners[n].X, -corners[n].Y), transform);

View File

@@ -9,26 +9,56 @@ namespace Barotrauma
{
static partial class CaveGenerator
{
public static List<VertexPositionTexture> GenerateWallVertices(List<Vector2[]> triangles, LevelGenerationParams generationParams, float zCoord)
public static List<VertexPositionColor> GenerateWallVertices(List<Vector2[]> triangles, Color color, float zCoord)
{
var vertices = new List<VertexPositionTexture>();
var vertices = new List<VertexPositionColor>();
for (int i = 0; i < triangles.Count; i++)
{
foreach (Vector2 vertex in triangles[i])
{
Vector2 uvCoords = vertex / generationParams.WallTextureSize;
vertices.Add(new VertexPositionTexture(new Vector3(vertex, zCoord), uvCoords));
vertices.Add(new VertexPositionColor(new Vector3(vertex, zCoord), color));
}
}
return vertices;
}
public static List<VertexPositionTexture> GenerateWallEdgeVertices(List<VoronoiCell> cells, Level level, float zCoord)
/// <summary>
/// Generates texture coordinates for the vertices based on their positions
/// </summary>
public static VertexPositionColorTexture[] ConvertToTextured(VertexPositionColor[] verts, float textureSize)
{
float outWardThickness = level.GenerationParams.WallEdgeExpandOutwardsAmount;
VertexPositionColorTexture[] texturedVerts = new VertexPositionColorTexture[verts.Length];
for (int i = 0; i < verts.Length; i++)
{
VertexPositionColor vertex = verts[i];
texturedVerts[i] = new VertexPositionColorTexture(vertex.Position, vertex.Color, textureCoordinate: Vector2.Zero);
}
GenerateTextureCoordinates(texturedVerts, textureSize);
return texturedVerts;
}
List<VertexPositionTexture> vertices = new List<VertexPositionTexture>();
/// <summary>
/// Generates texture coordinates for the vertices based on their positions
/// </summary>
public static void GenerateTextureCoordinates(VertexPositionColorTexture[] verts, float textureSize)
{
for (int i = 0; i < verts.Length; i++)
{
VertexPositionColorTexture vertex = verts[i];
Vector2 uvCoords = new Vector2(vertex.Position.X, vertex.Position.Y) / textureSize;
verts[i] = new VertexPositionColorTexture(verts[i].Position, verts[i].Color, uvCoords);
}
}
public static List<VertexPositionColorTexture> GenerateWallEdgeVertices(
List<VoronoiCell> cells,
float expandOutwards, float expandInwards,
Color outerColor, Color innerColor,
Level level, float zCoord, bool preventExpandThroughCell = false)
{
float outWardThickness = expandOutwards;
List<VertexPositionColorTexture> vertices = new List<VertexPositionColorTexture>();
foreach (VoronoiCell cell in cells)
{
Vector2 minVert = cell.Edges[0].Point1;
@@ -49,7 +79,10 @@ namespace Barotrauma
{
if (!edge.IsSolid) { continue; }
GraphEdge leftEdge = cell.Edges.Find(e => e != edge && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2)));
//the left-side edge on this same cell
GraphEdge myLeftEdge = cell.Edges.Find(e => e != edge && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2)));
//the left-side edge on either this cell, or the adjacent one if this is attached to another cell
GraphEdge leftEdge = myLeftEdge;
var leftAdjacentCell = leftEdge?.AdjacentCell(cell);
if (leftAdjacentCell != null)
{
@@ -57,7 +90,10 @@ namespace Barotrauma
if (adjEdge != null) { leftEdge = adjEdge; }
}
GraphEdge rightEdge = cell.Edges.Find(e => e != edge && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2)));
//the right-side edge on this same cell
GraphEdge myRightEdge = cell.Edges.Find(e => e != edge && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2)));
//the right-side edge on either this cell, or the adjacent one if this is attached to another cell
GraphEdge rightEdge = myRightEdge;
var rightAdjacentCell = rightEdge?.AdjacentCell(cell);
if (rightAdjacentCell != null)
{
@@ -67,18 +103,25 @@ namespace Barotrauma
Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero;
float inwardThickness1 = level.GenerationParams.WallEdgeExpandInwardsAmount;
float inwardThickness2 = level.GenerationParams.WallEdgeExpandInwardsAmount;
float inwardThickness1 = Math.Min(expandInwards, edge.Length);
float inwardThickness2 = inwardThickness1;
if (leftEdge != null && !leftEdge.IsSolid)
{
//the left-side edge is non-solid (an edge between two cells, not an actual solid wall edge)
// -> expand in the direction of that edge
leftNormal = edge.Point1.NearlyEquals(leftEdge.Point1) ?
Vector2.Normalize(leftEdge.Point2 - leftEdge.Point1) :
Vector2.Normalize(leftEdge.Point1 - leftEdge.Point2);
//maximum expansion is half of the size of the edge (otherwise the expansions from different sides of the edge could overlap or even extend "through" the cell)
inwardThickness1 = Math.Min(inwardThickness1, leftEdge.Length / 2);
}
else if (leftEdge != null)
{
//use the average of this edge's and the adjacent edge's normals
leftNormal = -Vector2.Normalize(edge.GetNormal(cell) + leftEdge.GetNormal(leftAdjacentCell ?? cell));
if (!MathUtils.IsValid(leftNormal)) { leftNormal = -edge.GetNormal(cell); }
//maximum expansion is the length of the adjacent edge (more expansion causes the textures to distort)
inwardThickness1 = Math.Min(Math.Min(inwardThickness1, leftEdge.Length), myLeftEdge.Length);
}
else
{
@@ -109,11 +152,13 @@ namespace Barotrauma
rightNormal = edge.Point2.NearlyEquals(rightEdge.Point1) ?
Vector2.Normalize(rightEdge.Point2 - rightEdge.Point1) :
Vector2.Normalize(rightEdge.Point1 - rightEdge.Point2);
inwardThickness2 = Math.Min(inwardThickness2, rightEdge.Length / 2);
}
else if (rightEdge != null)
{
rightNormal = -Vector2.Normalize(edge.GetNormal(cell) + rightEdge.GetNormal(rightAdjacentCell ?? cell));
if (!MathUtils.IsValid(rightNormal)) { rightNormal = -edge.GetNormal(cell); }
inwardThickness2 = Math.Min(Math.Min(inwardThickness2, rightEdge.Length), myRightEdge.Length);
}
else
{
@@ -150,10 +195,51 @@ namespace Barotrauma
point1UV = point1UV / MathHelper.TwoPi * textureRepeatCount;
point2UV = point2UV / MathHelper.TwoPi * textureRepeatCount;
//if calculating the UVs based on polar coordinates would result in stretching (using less than 10% of the texture for a wall the size of the texture)
//just calculate the UVs based on the length of the wall
//(this will mean the textures don't align at point2, but it doesn't seem that noticeable)
if ((point2UV - point1UV) * level.GenerationParams.WallEdgeTextureWidth < edge.Length * 0.1f)
{
point2UV = point1UV + edge.Length / 2 / level.GenerationParams.WallEdgeTextureWidth;
}
//"extruding" inwards, need to make sure we don't make the edge poke through the cell from the other side
if (preventExpandThroughCell)
{
foreach (GraphEdge otherEdge in cell.Edges)
{
if (otherEdge == edge || Vector2.Dot(otherEdge.GetNormal(cell), edge.GetNormal(cell)) > 0) { continue; }
if (otherEdge != leftEdge)
{
inwardThickness1 = ClampThickness(otherEdge, edge.Point1, leftNormal, inwardThickness1);
}
if (otherEdge != rightEdge)
{
inwardThickness2 = ClampThickness(otherEdge, edge.Point2, rightNormal, inwardThickness2);
}
}
static float ClampThickness(GraphEdge otherEdge, Vector2 thisPoint, Vector2 thisEdgeNormal, float currThickness)
{
if (MathUtils.GetLineIntersection(
thisPoint, thisPoint + thisEdgeNormal * currThickness,
otherEdge.Point1, otherEdge.Point2, areLinesInfinite: false, out Vector2 intersection1))
{
return Math.Min(currThickness, Vector2.Distance(thisPoint, intersection1));
}
return currThickness;
}
}
//there needs to be some minimum amount of inward thickness,
//if the edge texture doesn't extend inside at all you can see through between the edge texture and the solid part of the cell
inwardThickness1 = Math.Max(inwardThickness1, Math.Min(100.0f, expandInwards));
inwardThickness2 = Math.Max(inwardThickness2, Math.Min(100.0f, expandInwards));
for (int i = 0; i < 2; i++)
{
Vector2[] verts = new Vector2[3];
VertexPositionTexture[] vertPos = new VertexPositionTexture[3];
VertexPositionColorTexture[] vertPos = new VertexPositionColorTexture[3];
if (i == 0)
{
@@ -161,9 +247,9 @@ namespace Barotrauma
verts[1] = edge.Point2 - rightNormal * outWardThickness;
verts[2] = edge.Point1 + leftNormal * inwardThickness1;
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 0.0f));
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point1UV, 1.0f));
vertPos[0] = new VertexPositionColorTexture(new Vector3(verts[0], zCoord), outerColor, new Vector2(point1UV, 0.0f));
vertPos[1] = new VertexPositionColorTexture(new Vector3(verts[1], zCoord), outerColor, new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionColorTexture(new Vector3(verts[2], zCoord), innerColor, new Vector2(point1UV, 1.0f));
}
else
{
@@ -172,9 +258,9 @@ namespace Barotrauma
verts[1] = edge.Point2 - rightNormal * outWardThickness;
verts[2] = edge.Point2 + rightNormal * inwardThickness2;
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 1.0f));
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point2UV, 1.0f));
vertPos[0] = new VertexPositionColorTexture(new Vector3(verts[0], zCoord), innerColor, new Vector2(point1UV, 1.0f));
vertPos[1] = new VertexPositionColorTexture(new Vector3(verts[1], zCoord), outerColor, new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionColorTexture(new Vector3(verts[2], zCoord), innerColor, new Vector2(point2UV, 1.0f));
}
vertices.AddRange(vertPos);
}

View File

@@ -10,9 +10,11 @@ namespace Barotrauma
{
partial class LevelObjectManager
{
private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>();
private readonly List<LevelObject> visibleObjectsMid = new List<LevelObject>();
private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>();
// Pre-initialized to the max size, so that we don't have to resize the lists at runtime. TODO: Could the capacity (of some collections?) be lower?
private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>(MaxVisibleObjects);
private readonly List<LevelObject> visibleObjectsMid = new List<LevelObject>(MaxVisibleObjects);
private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>(MaxVisibleObjects);
private readonly HashSet<LevelObject> allVisibleObjects = new HashSet<LevelObject>(MaxVisibleObjects);
private double NextRefreshTime;
@@ -47,9 +49,25 @@ namespace Barotrauma
}
}
public IEnumerable<LevelObject> GetVisibleObjects()
/// <summary>
/// Returns all visible objects, but not in order, because internally uses a HashSet.
/// </summary>
public IEnumerable<LevelObject> GetAllVisibleObjects()
{
return visibleObjectsBack.Union(visibleObjectsMid).Union(visibleObjectsFront);
allVisibleObjects.Clear();
foreach (LevelObject obj in visibleObjectsBack)
{
allVisibleObjects.Add(obj);
}
foreach (LevelObject obj in visibleObjectsMid)
{
allVisibleObjects.Add(obj);
}
foreach (LevelObject obj in visibleObjectsFront)
{
allVisibleObjects.Add(obj);
}
return allVisibleObjects;
}
/// <summary>

View File

@@ -11,10 +11,25 @@ namespace Barotrauma
{
class LevelWallVertexBuffer : IDisposable
{
public VertexBuffer WallEdgeBuffer, WallBuffer;
/// <summary>
/// Buffer for the vertices of the "actual" wall texture.
/// </summary>
public VertexBuffer WallBuffer;
/// <summary>
/// Buffer for the vertices of the repeating edge texture drawn at the edges of the walls.
/// </summary>
public VertexBuffer WallEdgeBuffer;
/// <summary>
/// Buffer for the vertices of the inner, non-textured black part of the wall.
/// </summary>
public VertexBuffer WallInnerBuffer;
public readonly Texture2D WallTexture, EdgeTexture;
private VertexPositionColorTexture[] wallVertices;
private VertexPositionColorTexture[] wallEdgeVertices;
private VertexPositionColor[] wallInnerVertices;
public bool IsDisposed
{
@@ -22,7 +37,7 @@ namespace Barotrauma
private set;
}
public LevelWallVertexBuffer(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
public LevelWallVertexBuffer(VertexPositionColorTexture[] wallVertices, VertexPositionColorTexture[] wallEdgeVertices, VertexPositionColor[] wallInnerVertices, Texture2D wallTexture, Texture2D edgeTexture)
{
if (wallVertices.Length == 0)
{
@@ -32,32 +47,41 @@ namespace Barotrauma
{
throw new ArgumentException("Failed to instantiate a LevelWallVertexBuffer (no wall edge vertices).");
}
this.wallVertices = LevelRenderer.GetColoredVertices(wallVertices, color);
this.wallVertices = wallVertices;
WallBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, wallVertices.Length, BufferUsage.WriteOnly);
WallBuffer.SetData(this.wallVertices);
WallTexture = wallTexture;
this.wallEdgeVertices = LevelRenderer.GetColoredVertices(wallEdgeVertices, color);
this.wallEdgeVertices = wallEdgeVertices;
WallEdgeBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, wallEdgeVertices.Length, BufferUsage.WriteOnly);
WallEdgeBuffer.SetData(this.wallEdgeVertices);
EdgeTexture = edgeTexture;
if (wallInnerVertices != null)
{
this.wallInnerVertices = wallInnerVertices;
WallInnerBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColor.VertexDeclaration, wallInnerVertices.Length, BufferUsage.WriteOnly);
WallInnerBuffer.SetData(this.wallInnerVertices);
}
}
public void Append(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Color color)
public void Append(VertexPositionColorTexture[] newWallVertices, VertexPositionColorTexture[] newWallEdgeVertices, VertexPositionColor[] newWallInnerVertices)
{
WallBuffer.Dispose();
WallBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, this.wallVertices.Length + wallVertices.Length, BufferUsage.WriteOnly);
int originalWallVertexCount = this.wallVertices.Length;
Array.Resize(ref this.wallVertices, originalWallVertexCount + wallVertices.Length);
Array.Copy(LevelRenderer.GetColoredVertices(wallVertices, color), 0, this.wallVertices, originalWallVertexCount, wallVertices.Length);
WallBuffer.SetData(this.wallVertices);
WallBuffer = Append(WallBuffer, ref wallVertices, newWallVertices, VertexPositionColorTexture.VertexDeclaration);
WallEdgeBuffer = Append(WallEdgeBuffer, ref wallEdgeVertices, newWallEdgeVertices, VertexPositionColorTexture.VertexDeclaration);
WallInnerBuffer = Append(WallInnerBuffer, ref wallInnerVertices, newWallInnerVertices, VertexPositionColor.VertexDeclaration);
WallEdgeBuffer.Dispose();
WallEdgeBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, this.wallEdgeVertices.Length + wallEdgeVertices.Length, BufferUsage.WriteOnly);
int originalWallEdgeVertexCount = this.wallEdgeVertices.Length;
Array.Resize(ref this.wallEdgeVertices, originalWallEdgeVertexCount + wallEdgeVertices.Length);
Array.Copy(LevelRenderer.GetColoredVertices(wallEdgeVertices, color), 0, this.wallEdgeVertices, originalWallEdgeVertexCount, wallEdgeVertices.Length);
WallEdgeBuffer.SetData(this.wallEdgeVertices);
static VertexBuffer Append<T>(VertexBuffer buffer, ref T[] currentVertices, T[] newVertices, VertexDeclaration vertexDeclaration) where T : struct, IVertexType
{
buffer?.Dispose();
int originalVertexCount = currentVertices.Length;
int newBufferSize = originalVertexCount + newVertices.Length;
buffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, vertexDeclaration, newBufferSize, BufferUsage.WriteOnly);
Array.Resize(ref currentVertices, newBufferSize);
Array.Copy(newVertices, 0, currentVertices, originalVertexCount, newVertices.Length);
buffer.SetData(currentVertices);
return buffer;
}
}
public void Dispose()
@@ -70,7 +94,7 @@ namespace Barotrauma
class LevelRenderer : IDisposable
{
private static BasicEffect wallEdgeEffect, wallCenterEffect;
private static BasicEffect wallEdgeEffect, wallCenterEffect, wallInnerEffect;
private Vector2 waterParticleOffset;
private Vector2 waterParticleVelocity;
@@ -129,7 +153,16 @@ namespace Barotrauma
};
wallCenterEffect.CurrentTechnique = wallCenterEffect.Techniques["BasicEffect_Texture"];
}
if (wallInnerEffect == null)
{
wallInnerEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true,
TextureEnabled = false
};
wallInnerEffect.CurrentTechnique = wallInnerEffect.Techniques["BasicEffect_Texture"];
}
this.level = level;
}
@@ -184,7 +217,7 @@ namespace Barotrauma
//calculate the sum of the forces of nearby level triggers
//and use it to move the water texture and water distortion effect
Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
foreach (LevelObject levelObject in level.LevelObjectManager.GetVisibleObjects())
foreach (LevelObject levelObject in level.LevelObjectManager.GetAllVisibleObjects())
{
if (levelObject.Triggers == null) { continue; }
//use the largest water flow velocity of all the triggers
@@ -225,16 +258,16 @@ namespace Barotrauma
return verts;
}
public void SetVertices(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
public void SetVertices(VertexPositionColorTexture[] wallVertices, VertexPositionColorTexture[] wallEdgeVertices, VertexPositionColor[] wallInnerVertices, Texture2D wallTexture, Texture2D edgeTexture)
{
var existingBuffer = vertexBuffers.Find(vb => vb.WallTexture == wallTexture && vb.EdgeTexture == edgeTexture);
if (existingBuffer != null)
{
existingBuffer.Append(wallVertices, wallEdgeVertices,color);
existingBuffer.Append(wallVertices, wallEdgeVertices, wallInnerVertices);
}
else
{
vertexBuffers.Add(new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallTexture, edgeTexture, color));
vertexBuffers.Add(new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallInnerVertices, wallTexture, edgeTexture));
}
}
@@ -497,15 +530,16 @@ namespace Barotrauma
}
}
wallEdgeEffect.Alpha = 1.0f;
wallCenterEffect.Alpha = 1.0f;
wallCenterEffect.World = transformMatrix;
wallEdgeEffect.World = transformMatrix;
wallEdgeEffect.Alpha = wallInnerEffect.Alpha = wallCenterEffect.Alpha = 1.0f;
wallCenterEffect.World = wallInnerEffect.World = wallEdgeEffect.World = transformMatrix;
//render static walls
foreach (var vertexBuffer in vertexBuffers)
{
wallInnerEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(vertexBuffer.WallInnerBuffer);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(vertexBuffer.WallInnerBuffer.VertexCount / 3.0f));
wallCenterEffect.Texture = vertexBuffer.WallTexture;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(vertexBuffer.WallBuffer);

View File

@@ -1,10 +1,7 @@
using Barotrauma.Extensions;
using FarseerPhysics;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -26,22 +23,29 @@ namespace Barotrauma
Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(Body.Position), 0.0f));
}
public void SetWallVertices(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
public void SetWallVertices(
VertexPositionColorTexture[] wallVertices, VertexPositionColorTexture[] wallEdgeVertices,
Texture2D wallTexture, Texture2D edgeTexture)
{
if (VertexBuffer != null && !VertexBuffer.IsDisposed) { VertexBuffer.Dispose(); }
VertexBuffer = new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallTexture, edgeTexture, color);
VertexBuffer = new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallInnerVertices: null, wallTexture, edgeTexture);
}
public void GenerateVertices()
{
float zCoord = this is DestructibleLevelWall ? Rand.Range(0.9f, 1.0f) : 0.9f;
List<VertexPositionTexture> wallVertices = CaveGenerator.GenerateWallVertices(triangles, level.GenerationParams, zCoord);
var nonTexturedWallVerts =
CaveGenerator.GenerateWallVertices(triangles, color, zCoord: 0.9f).ToArray();
var wallVerts = CaveGenerator.ConvertToTextured(nonTexturedWallVerts, level.GenerationParams.WallTextureSize);
SetWallVertices(
wallVertices.ToArray(),
CaveGenerator.GenerateWallEdgeVertices(Cells, level, zCoord).ToArray(),
wallVertices: wallVerts,
wallEdgeVertices: CaveGenerator.GenerateWallEdgeVertices(Cells,
level.GenerationParams.WallEdgeExpandOutwardsAmount, level.GenerationParams.WallEdgeExpandInwardsAmount,
outerColor: color, innerColor: color,
level, zCoord)
.ToArray(),
level.GenerationParams.WallSprite.Texture,
level.GenerationParams.WallEdgeSprite.Texture,
color);
level.GenerationParams.WallEdgeSprite.Texture);
}
public bool IsVisible(Rectangle worldView)

View File

@@ -443,10 +443,10 @@ namespace Barotrauma.Lights
public bool Intersects(Rectangle rect)
{
if (!Enabled) return false;
if (!Enabled) { return false; }
Rectangle transformedBounds = BoundingBox;
if (ParentEntity != null && ParentEntity.Submarine != null)
if (ParentEntity is { Submarine: not null })
{
transformedBounds.X += (int)ParentEntity.Submarine.Position.X;
transformedBounds.Y += (int)ParentEntity.Submarine.Position.Y;

View File

@@ -243,6 +243,7 @@ namespace Barotrauma.Lights
}
}
/// <param name="backgroundObstructor">A render target that contains the structures that should obstruct lights in the background. If not given, damageable walls and hulls are rendered to obstruct the background lights.</param>
public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{
if (!LightingEnabled) { return; }
@@ -411,11 +412,21 @@ namespace Barotrauma.Lights
}
spriteBatch.End();
GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
spriteBatch.End();
if (backgroundObstructor != null)
{
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: Matrix.Identity, effect: GameMain.GameScreen.DamageEffect);
spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0,
(int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black);
spriteBatch.End();
}
else
{
GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
spriteBatch.End();
}
graphics.BlendState = BlendState.Additive;
@@ -680,11 +691,14 @@ namespace Barotrauma.Lights
}
return visibleHulls;
}
private static readonly List<VertexPositionColor> ShadowVertices = new List<VertexPositionColor>(500);
private static readonly List<VertexPositionTexture> PenumbraVertices = new List<VertexPositionTexture>(500);
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
{
if ((!LosEnabled || LosMode == LosMode.None) && ObstructVisionAmount <= 0.0f) { return; }
if (ViewTarget == null) return;
if (ViewTarget == null) { return; }
graphics.SetRenderTarget(LosTexture);
@@ -767,11 +781,11 @@ namespace Barotrauma.Lights
if (convexHulls != null)
{
List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
ShadowVertices.Clear();
PenumbraVertices.Clear();
foreach (ConvexHull convexHull in convexHulls)
{
if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
if (!convexHull.Intersects(camView)) { continue; }
Vector2 relativeViewPos = pos;
if (convexHull.ParentEntity?.Submarine != null)
@@ -783,26 +797,26 @@ namespace Barotrauma.Lights
for (int i = 0; i < convexHull.ShadowVertexCount; i++)
{
shadowVerts.Add(convexHull.ShadowVertices[i]);
ShadowVertices.Add(convexHull.ShadowVertices[i]);
}
for (int i = 0; i < convexHull.PenumbraVertexCount; i++)
{
penumbraVerts.Add(convexHull.PenumbraVertices[i]);
PenumbraVertices.Add(convexHull.PenumbraVertices[i]);
}
}
if (shadowVerts.Count > 0)
if (ShadowVertices.Count > 0)
{
ConvexHull.shadowEffect.World = shadowTransform;
ConvexHull.shadowEffect.CurrentTechnique.Passes[0].Apply();
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, shadowVerts.ToArray(), 0, shadowVerts.Count / 3, VertexPositionColor.VertexDeclaration);
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, ShadowVertices.ToArray(), 0, ShadowVertices.Count / 3, VertexPositionColor.VertexDeclaration);
if (penumbraVerts.Count > 0)
if (PenumbraVertices.Count > 0)
{
ConvexHull.penumbraEffect.World = shadowTransform;
ConvexHull.penumbraEffect.CurrentTechnique.Passes[0].Apply();
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVerts.ToArray(), 0, penumbraVerts.Count / 3, VertexPositionTexture.VertexDeclaration);
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, PenumbraVertices.ToArray(), 0, PenumbraVertices.Count / 3, VertexPositionTexture.VertexDeclaration);
}
}
}

View File

@@ -592,7 +592,17 @@ namespace Barotrauma.Lights
private void CheckHullsInRange(Submarine sub)
{
//find the list of convexhulls that belong to the sub
ConvexHullList chList = convexHullsInRange.FirstOrDefault(chList => chList.Submarine == sub);
// Performance-sensitive code, hence implemented without Linq.
ConvexHullList chList = null;
foreach (var chl in convexHullsInRange)
{
if (chl.Submarine == sub)
{
chList = chl;
break;
}
}
//not found -> create one
if (chList == null)

View File

@@ -451,7 +451,7 @@ namespace Barotrauma
MathUtils.PositiveModulo(-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width * TextureScale.X * Scale),
MathUtils.PositiveModulo(-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height * TextureScale.Y * Scale));
float rotationRad = rotationForSprite(this.rotationRad, Prefab.BackgroundSprite);
float rotationRad = GetRotationForSprite(this.rotationRad, Prefab.BackgroundSprite);
Prefab.BackgroundSprite.DrawTiled(
spriteBatch,
@@ -492,7 +492,7 @@ namespace Barotrauma
advanceY = advanceY.FlipX();
}
float sectionSpriteRotationRad = rotationForSprite(this.rotationRad, Prefab.Sprite);
float sectionSpriteRotationRad = GetRotationForSprite(this.rotationRad, Prefab.Sprite);
for (int i = 0; i < Sections.Length; i++)
{
@@ -501,11 +501,17 @@ namespace Barotrauma
{
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth);
if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor)
if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.05f)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.BackToFront,
BlendState.NonPremultiplied, SamplerState.LinearWrap,
null, null,
damageEffect,
Screen.Selected.Cam.Transform);
damageEffect.Parameters["aCutoff"].SetValue(newCutoff);
damageEffect.Parameters["cCutoff"].SetValue(newCutoff * 1.2f);
damageEffect.Parameters["inColor"].SetValue(color.ToVector4());
damageEffect.CurrentTechnique.Passes[0].Apply();
@@ -570,9 +576,13 @@ namespace Barotrauma
}
}
static float rotationForSprite(float rotationRad, Sprite sprite)
static float GetRotationForSprite(float rotationRad, Sprite sprite)
{
if (sprite.effects.HasFlag(SpriteEffects.FlipHorizontally) != sprite.effects.HasFlag(SpriteEffects.FlipVertically))
// Use bitwise operations instead of HasFlag to avoid boxing, as this is performance-sensitive code.
bool flipHorizontally = (sprite.effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally;
bool flipVertically = (sprite.effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically;
if (flipHorizontally != flipVertically)
{
rotationRad = -rotationRad;
}
@@ -604,6 +614,10 @@ namespace Barotrauma
if (GetSection(i).damage > 0)
{
var textPos = SectionPosition(i, true);
if (Submarine != null)
{
textPos += (Submarine.DrawPosition - Submarine.Position);
}
textPos.Y = -textPos.Y;
GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / MaxHealth) * 100f) + "%", Color.Yellow);
}

View File

@@ -134,7 +134,7 @@ namespace Barotrauma
foreach (Submarine sub in Loaded)
{
Rectangle worldBorders = sub.Borders;
worldBorders.Location += sub.WorldPosition.ToPoint();
worldBorders.Location += (sub.DrawPosition + sub.HiddenSubPosition).ToPoint();
worldBorders.Y = -worldBorders.Y;
GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
@@ -161,38 +161,38 @@ namespace Barotrauma
public static float DamageEffectCutoff;
public static Color DamageEffectColor;
private static readonly List<Structure> depthSortedDamageable = new List<Structure>();
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
depthSortedDamageable.Clear();
//insertion sort according to draw depth
foreach (MapEntity e in entitiesToRender)
if (!editing && visibleEntities != null)
{
if (e is Structure structure && structure.DrawDamageEffect)
foreach (MapEntity e in visibleEntities)
{
if (predicate != null)
if (e is Structure structure && structure.DrawDamageEffect)
{
if (!predicate(e)) { continue; }
if (predicate != null)
{
if (!predicate(structure)) { continue; }
}
structure.DrawDamage(spriteBatch, damageEffect, editing);
}
float drawDepth = structure.GetDrawDepth();
int i = 0;
while (i < depthSortedDamageable.Count)
}
}
else
{
foreach (Structure structure in Structure.WallList)
{
if (structure.DrawDamageEffect)
{
float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth();
if (otherDrawDepth < drawDepth) { break; }
i++;
if (predicate != null)
{
if (!predicate(structure)) { continue; }
}
structure.DrawDamage(spriteBatch, damageEffect, editing);
}
depthSortedDamageable.Insert(i, structure);
}
}
foreach (Structure s in depthSortedDamageable)
{
s.DrawDamage(spriteBatch, damageEffect, editing);
}
if (damageEffect != null)
{
damageEffect.Parameters["aCutoff"].SetValue(0.0f);
@@ -506,6 +506,16 @@ namespace Barotrauma
warnings.Add(SubEditorScreen.WarningType.WaterInHulls);
Hull.ShowHulls = true;
}
if (Info.IsWreck)
{
Point vanillaBrainSize = new Point(204, 204);
if (WreckAI.GetPotentialBrainRooms(this, WreckAIConfig.GetRandom(), minSize: vanillaBrainSize).None())
{
errorMsgs.Add(TextManager.Get("NoSuitableBrainRoomsWarning").Value);
warnings.Add(SubEditorScreen.WarningType.NoSuitableBrainRooms);
}
}
if (!IsWarningSuppressed(SubEditorScreen.WarningType.NotEnoughContainers))
{

View File

@@ -46,6 +46,10 @@ namespace Barotrauma
}
if (IsHighlighted || IsHighlighted) { clr = Color.Lerp(clr, Color.White, 0.8f); }
if (Stairs is { Removed: true }) { Stairs = null; }
if (Ladders is { Item.Removed: true }) { Ladders = null; }
if (ConnectedGap is { Removed: true }) { ConnectedGap = null; }
int iconSize = spawnType == SpawnType.Path ? WaypointSize : SpawnPointSize;
if (ConnectedDoor != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path)
{

View File

@@ -519,10 +519,12 @@ namespace Barotrauma.Networking
{
string errorMsg = "Error while reading a message from server. ";
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
AppendExceptionInfo(ref errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
AppendExceptionInfo(ref errorMsg, out Entity causingEntity, e);
string targetSite = e.TargetSite?.ToString() ?? "unknown";
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + targetSite, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError(errorMsg, contentPackage: causingEntity?.ContentPackage);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", targetSite)))
{
DisplayInLoadingScreens = true
};
@@ -664,7 +666,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string errorMsg = "Error while reading an ingame update message from server.";
AppendExceptionInfo(ref errorMsg, e);
AppendExceptionInfo(ref errorMsg, out Entity causingEntity, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw;
}
@@ -988,13 +990,15 @@ namespace Barotrauma.Networking
{
if (Level.Loaded.EqualityCheckValues[stage] != levelEqualityCheckValues[stage])
{
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" +
" (client value " + stage + ": " + Level.Loaded.EqualityCheckValues[stage].ToString("X") +
", server value " + stage + ": " + levelEqualityCheckValues[stage].ToString("X") +
", level value count: " + levelEqualityCheckValues.Count +
", seed: " + Level.Loaded.Seed +
", sub: " + (Submarine.MainSub == null ? "null" : (Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")")) +
", mirrored: " + Level.Loaded.Mirrored + "). Round init status: " + roundInitStatus + "." + campaignErrorInfo;
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server, " +
$"(client value {stage}:{Level.Loaded.EqualityCheckValues[stage].ToString("X")}, " +
$"server value {stage}: {levelEqualityCheckValues[stage].ToString("X")}, " +
$"level value count: {levelEqualityCheckValues.Count}, " +
$"seed: {Level.Loaded.Seed}, " +
$"missions: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"sub: {(Submarine.MainSub == null ? "null" : (Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation))}, " +
$"mirrored: {Level.Loaded.Mirrored}). Round init status: {roundInitStatus}." +
campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
@@ -1472,6 +1476,7 @@ namespace Barotrauma.Networking
{
string levelSeed = inc.ReadString();
float levelDifficulty = inc.ReadSingle();
Identifier biomeId = inc.ReadIdentifier();
string subName = inc.ReadString();
string subHash = inc.ReadString();
string shuttleName = inc.ReadString();
@@ -1559,7 +1564,7 @@ namespace Barotrauma.Networking
var selectedEnemySub = hasEnemySub && GameMain.NetLobbyScreen.SelectedEnemySub is { } enemySub ? Option.Some(enemySub) : Option.None;
GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, selectedEnemySub, gameMode, missionPrefabs: selectedMissions);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, levelGenerationParams: null, forceBiome: ServerSettings.Biome);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, levelGenerationParams: null, forceBiome: biomeId);
}
else
{
@@ -2077,16 +2082,15 @@ namespace Barotrauma.Networking
UInt16 updateID = inc.ReadUInt16();
UInt16 settingsLen = inc.ReadUInt16();
byte[] settingsData = inc.ReadBytes(settingsLen);
bool isInitialUpdate = inc.ReadBoolean();
DebugConsole.Log($"Received {(isInitialUpdate ? "initial" : string.Empty)} lobby update ID: {updateID}, last ID: {GameMain.NetLobbyScreen.LastUpdateID}.");
if (isInitialUpdate)
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("Received initial lobby update, ID: " + updateID + ", last ID: " + GameMain.NetLobbyScreen.LastUpdateID, Color.Gray);
}
{
ReadInitialUpdate(inc);
initialUpdateReceived = true;
}
@@ -2365,7 +2369,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
throw new Exception(
$"Exception thrown while reading segment {segment.Identifier} at position {segment.Pointer}." +
$"Exception thrown while reading a message of the type \"{segment.Identifier}\" at position {segment.Pointer}." +
(prevSegments.Any() ? $" Previous segments: {string.Join(", ", prevSegments)}" : ""),
ex);
});
@@ -3767,16 +3771,24 @@ namespace Barotrauma.Networking
eventErrorWritten = true;
}
private static void AppendExceptionInfo(ref string errorMsg, Exception e)
private static void AppendExceptionInfo(ref string errorMsg, out Entity causingEntity, Exception e)
{
if (!errorMsg.EndsWith("\n")) { errorMsg += "\n"; }
Exception innerMostException = e.GetInnermost();
causingEntity = GetCausingEntity(e);
if (causingEntity != null)
{
errorMsg += "Entity: " + causingEntity + "\n";
}
errorMsg += e.Message + "\n";
var innermostException = e.GetInnermost();
if (innermostException != e)
if (innerMostException != e)
{
// If available, only append the stacktrace of the innermost exception,
// because that's the most important one to fix
errorMsg += "Inner exception: " + innermostException.Message + "\n" + innermostException.StackTrace.CleanupStackTrace();
errorMsg += "Inner exception: " + innerMostException.Message + "\n" + innerMostException.StackTrace.CleanupStackTrace();
}
else
{
@@ -3784,6 +3796,24 @@ namespace Barotrauma.Networking
}
}
/// <summary>
/// Checks if the exception or any of its inner exceptions are EntityEventExceptions, and returns the entity that caused the innermost EntityEventException.
/// </summary>
private static Entity GetCausingEntity(Exception e)
{
Entity causingEntity = null;
Exception currentException = e;
while (currentException != null)
{
if (currentException is EntityEventException entityEventException)
{
causingEntity = entityEventException.Entity;
}
currentException = currentException.InnerException;
}
return causingEntity;
}
#if DEBUG
public void ForceTimeOut()
{

View File

@@ -154,13 +154,25 @@ namespace Barotrauma.Networking
//16 = entity ID, 8 = msg length
if (msg.BitPosition + 16 + 8 > msg.LengthBits)
{
string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
UInt16 potentialEntityId = Entity.NullEntityID;
try
{
potentialEntityId = msg.ReadUInt16();
}
catch
{
//failed to read the ID, do nothing (we would've just used it for the error message)
}
Entity targetEntity = Entity.FindEntityByID(potentialEntityId);
string errorMsg = $"Error while reading a message from the server (entity: {targetEntity?.ToString() ?? "unknown"}).";
errorMsg += $" Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
errorMsg += "\nPrevious entities:";
for (int j = tempEntityList.Count - 1; j >= 0; j--)
{
errorMsg += "\n" + (tempEntityList[j] == null ? "NULL" : tempEntityList[j].ToString());
}
DebugConsole.ThrowError(errorMsg);
DebugConsole.ThrowError(errorMsg, contentPackage: targetEntity?.ContentPackage);
return false;
}
@@ -172,7 +184,7 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
Microsoft.Xna.Framework.Color.Orange);
Color.Orange);
}
tempEntityList.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
@@ -187,7 +199,7 @@ namespace Barotrauma.Networking
//skip the event if we've already received it or if the entity isn't found
if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
{
if (thisEventID != (UInt16) (lastReceivedID + 1))
if (thisEventID != (UInt16)(lastReceivedID + 1))
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
@@ -195,7 +207,7 @@ namespace Barotrauma.Networking
"Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")",
NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1))
? GUIStyle.Red
: Microsoft.Xna.Framework.Color.Yellow);
: Color.Yellow);
}
}
else if (entity == null)
@@ -215,12 +227,18 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")",
Microsoft.Xna.Framework.Color.Green);
Color.Green);
}
lastReceivedID++;
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
try
{
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
}
catch (Exception exception)
{
throw new EntityEventException("Failed to read event." , entity as Entity, exception);
}
if (msg.BitPosition != msgPosition + msgLength * 8)
{
var prevEntity = tempEntityList.Count >= 2 ? tempEntityList[tempEntityList.Count - 2] : null;
@@ -231,7 +249,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
throw new EntityEventException(errorMsg, entity as Entity);
}
}
}

View File

@@ -74,7 +74,16 @@ sealed class SteamConnectSocket : P2PSocket
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
var connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
ConnectionManager connectionManager;
try
{
connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
}
catch (ArgumentException e)
{
DebugConsole.ThrowError("Failed to connect via SteamP2P. Are you logged in to Steam, is the same Steam account already connected to the server?", e);
return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket));
}
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);

View File

@@ -202,6 +202,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Capture device has been disconnected. You can select another available device in the settings.");
Disconnected = true;
TryRefreshDevice();
break;
}
}
@@ -401,5 +402,65 @@ namespace Barotrauma.Networking
captureThread = null;
if (captureDevice != IntPtr.Zero) { Alc.CaptureCloseDevice(captureDevice); }
}
public static void TryRefreshDevice()
{
DebugConsole.NewMessage("Refreshing audio capture device");
List<string> deviceList = Alc.GetStringList(IntPtr.Zero, Alc.CaptureDeviceSpecifier).ToList();
int alcError = Alc.GetError(IntPtr.Zero);
if (alcError != Alc.NoError)
{
DebugConsole.ThrowError("Failed to list available audio input devices: " + alcError.ToString());
return;
}
if (deviceList.Any())
{
string device;
if (deviceList.Find(n => n.Equals(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, StringComparison.OrdinalIgnoreCase))
is string availablePreviousDevice)
{
DebugConsole.NewMessage($" Previous device choice available: {availablePreviousDevice}");
device = availablePreviousDevice;
}
else
{
device = Alc.GetString(IntPtr.Zero, Alc.CaptureDefaultDeviceSpecifier);
DebugConsole.NewMessage($" Reverting to default device: {device}");
}
if (string.IsNullOrEmpty(device))
{
device = deviceList[0];
DebugConsole.NewMessage($" No default device found, resorting to first available device: {device}");
}
// Save the new device choice and generate a new voice capture instance with it
var currentConfig = GameSettings.CurrentConfig;
currentConfig.Audio.VoiceCaptureDevice = device;
GameSettings.SetCurrentConfig(currentConfig);
if (Instance is VoipCapture currentCaptureInstance)
{
currentCaptureInstance.Dispose();
}
Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice);
}
// Didn't end up with any capture device, so let's disable voice capture for now
if (Instance == null)
{
DebugConsole.NewMessage($" No devices found, disabling");
var currentConfig = GameSettings.CurrentConfig;
currentConfig.Audio.VoiceSetting = VoiceMode.Disabled;
GameSettings.SetCurrentConfig(currentConfig);
}
if (GUI.SettingsMenuOpen)
{
SettingsMenu.Instance?.CreateAudioAndVCTab(true);
}
}
}
}

View File

@@ -10,6 +10,7 @@ namespace Barotrauma.Particles
{
public static readonly PrefabCollection<ParticlePrefab> Prefabs = new PrefabCollection<ParticlePrefab>();
[Flags]
public enum DrawTargetType { Air = 1, Water = 2, Both = 3 }
public readonly List<Sprite> Sprites;

View File

@@ -64,12 +64,12 @@ namespace Barotrauma
if (drawOffset != Vector2.Zero)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(FarseerBody.Position);
if (Submarine != null) pos += Submarine.DrawPosition;
if (Submarine != null) { pos += Submarine.DrawPosition; }
GUI.DrawLine(spriteBatch,
new Vector2(pos.X, -pos.Y),
new Vector2(DrawPosition.X, -DrawPosition.Y),
Color.Cyan, 0, 5);
Color.Purple * 0.75f, 0, 5);
}
if (IsValidShape(Radius, Height, Width))
{

View File

@@ -145,6 +145,7 @@ namespace Barotrauma
public SettingValue<float> OxygenMultiplier;
public SettingValue<float> FuelMultiplier;
public SettingValue<float> MissionRewardMultiplier;
public SettingValue<float> ExperienceRewardMultiplier;
public SettingValue<float> ShopPriceMultiplier;
public SettingValue<float> ShipyardPriceMultiplier;
public SettingValue<float> RepairFailMultiplier;
@@ -167,6 +168,7 @@ namespace Barotrauma
OxygenMultiplier = OxygenMultiplier.GetValue(),
FuelMultiplier = FuelMultiplier.GetValue(),
MissionRewardMultiplier = MissionRewardMultiplier.GetValue(),
ExperienceRewardMultiplier = ExperienceRewardMultiplier.GetValue(),
ShopPriceMultiplier = ShopPriceMultiplier.GetValue(),
ShipyardPriceMultiplier = ShipyardPriceMultiplier.GetValue(),
RepairFailMultiplier = RepairFailMultiplier.GetValue(),
@@ -344,6 +346,19 @@ namespace Barotrauma
verticalSize,
OnValuesChanged);
// Experience reward multiplier
CampaignSettings.MultiplierSettings experienceMultiplierSettings = CampaignSettings.GetMultiplierSettings("ExperienceRewardMultiplier");
SettingValue<float> experienceMultiplier = CreateGUIFloatInputCarousel(
settingsList.Content,
TextManager.Get("campaignoption.experiencerewardmultiplier"),
TextManager.Get("campaignoption.experiencerewardmultiplier.tooltip"),
prevSettings.ExperienceRewardMultiplier,
valueStep: experienceMultiplierSettings.Step,
minValue: experienceMultiplierSettings.Min,
maxValue: experienceMultiplierSettings.Max,
verticalSize,
OnValuesChanged);
// Shop buying prices multiplier
CampaignSettings.MultiplierSettings shopPriceMultiplierSettings = CampaignSettings.GetMultiplierSettings("ShopPriceMultiplier");
SettingValue<float> shopPriceMultiplier = CreateGUIFloatInputCarousel(
@@ -501,6 +516,7 @@ namespace Barotrauma
oxygenMultiplier.SetValue(settings.OxygenMultiplier);
fuelMultiplier.SetValue(settings.FuelMultiplier);
rewardMultiplier.SetValue(settings.MissionRewardMultiplier);
experienceMultiplier.SetValue(settings.ExperienceRewardMultiplier);
shopPriceMultiplier.SetValue(settings.ShopPriceMultiplier);
shipyardPriceMultiplier.SetValue(settings.ShipyardPriceMultiplier);
repairFailMultiplier.SetValue(settings.RepairFailMultiplier);
@@ -530,6 +546,7 @@ namespace Barotrauma
OxygenMultiplier = oxygenMultiplier,
FuelMultiplier = fuelMultiplier,
MissionRewardMultiplier = rewardMultiplier,
ExperienceRewardMultiplier = experienceMultiplier,
ShopPriceMultiplier = shopPriceMultiplier,
ShipyardPriceMultiplier = shipyardPriceMultiplier,
RepairFailMultiplier = repairFailMultiplier,

View File

@@ -236,10 +236,36 @@ namespace Barotrauma
{
if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
LoadGame?.Invoke(saveInfo.FilePath, backupIndex: Option.None);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
if (saveInfo.RespawnMode != RespawnMode.None && saveInfo.RespawnMode != GameMain.NetworkMember?.ServerSettings?.RespawnMode)
{
var msgBox = new GUIMessageBox(TextManager.Get("Warning"),
TextManager.GetWithVariables("RespawnModeMismatch",
("[currentrespawnmode]", TextManager.Get($"respawnmode.{GameMain.NetworkMember?.ServerSettings?.RespawnMode}")),
("[savedrespawnmode]", TextManager.Get($"respawnmode.{saveInfo.RespawnMode}"))),
new LocalizedString[] { TextManager.Get("RespawnModeMismatch.GoBack"), TextManager.Get("RespawnModeMismatch.LoadAnyway") });
msgBox.Buttons[0].OnClicked = (button, obj) =>
{
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked = (button, obj) =>
{
msgBox.Close();
LoadSaveGame();
return true;
};
return false;
}
LoadSaveGame();
return true;
void LoadSaveGame()
{
LoadGame?.Invoke(saveInfo.FilePath, backupIndex: Option.None);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
}
},
Enabled = false
};

View File

@@ -443,20 +443,22 @@ namespace Barotrauma
GUILayoutGroup difficultyIndicatorGroup = null;
if (mission.Difficulty.HasValue)
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.9f), missionName.RectTransform, anchor: Anchor.CenterRight) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = 1,
UserData = "difficulty"
UserData = "difficulty",
};
difficultyIndicatorGroup.SetAsFirstChild();
var difficultyColor = mission.GetDifficultyColor();
for (int i = 0; i < mission.Difficulty; i++)
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
new GUIImage(new RectTransform(Vector2.One * 0.9f, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
{
Color = difficultyColor,
SelectedColor = difficultyColor,
HoverColor = difficultyColor
HoverColor = difficultyColor,
ToolTip = mission.GetDifficultyToolTipText()
};
}
}

View File

@@ -1771,7 +1771,7 @@ namespace Barotrauma.CharacterEditor
// Ragdoll
RagdollParams.ClearCache();
string ragdollPath = RagdollParams.GetDefaultFile(name, contentPackage);
string ragdollPath = RagdollParams.GetDefaultFile(name);
RagdollParams ragdollParams = isHumanoid
? RagdollParams.CreateDefault<HumanRagdollParams>(ragdollPath, name, ragdoll)
: RagdollParams.CreateDefault<FishRagdollParams>(ragdollPath, name, ragdoll);

View File

@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Graphics;
using System;
using System.Diagnostics;
using System.Linq;
using System.Transactions;
namespace Barotrauma
{
@@ -15,6 +16,8 @@ namespace Barotrauma
private RenderTarget2D renderTargetWater;
private RenderTarget2D renderTargetFinal;
private RenderTarget2D renderTargetDamageable;
public readonly Effect DamageEffect;
private readonly Texture2D damageStencil;
private readonly Texture2D distortTexture;
@@ -65,6 +68,7 @@ namespace Barotrauma
renderTargetBackground = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
renderTargetWater = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
renderTargetFinal = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight, false, SurfaceFormat.Color, DepthFormat.None);
renderTargetDamageable = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight, false, SurfaceFormat.Color, DepthFormat.None);
}
public override void AddToGUIUpdateList()
@@ -303,7 +307,7 @@ namespace Barotrauma
//Draw background structures and wall background sprites
//(= the background texture that's revealed when a wall is destroyed) into the background render target
//These will be visible through the LOS effect.
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
Submarine.DrawBack(spriteBatch, false, e => e is Structure s && (e.SpriteDepth >= 0.9f || s.Prefab.BackgroundSprite != null) && !IsFromOutpostDrawnBehindSubs(e));
Submarine.DrawPaintedColors(spriteBatch, false);
spriteBatch.End();
@@ -312,8 +316,19 @@ namespace Barotrauma
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:BackStructures", sw.ElapsedTicks);
sw.Restart();
graphics.SetRenderTarget(renderTargetDamageable);
graphics.Clear(Color.Transparent);
DamageEffect.CurrentTechnique = DamageEffect.Techniques["StencilShader"];
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.LinearWrap, effect: DamageEffect, transformMatrix: cam.Transform);
Submarine.DrawDamageable(spriteBatch, DamageEffect, false);
spriteBatch.End();
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:FrontDamageable", sw.ElapsedTicks);
sw.Restart();
graphics.SetRenderTarget(null);
GameMain.LightManager.RenderLightMap(graphics, spriteBatch, cam, renderTarget);
GameMain.LightManager.RenderLightMap(graphics, spriteBatch, cam, renderTargetDamageable);
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:Lighting", sw.ElapsedTicks);
@@ -331,24 +346,24 @@ namespace Barotrauma
Level.Loaded.DrawBack(graphics, spriteBatch, cam);
}
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
Submarine.DrawBack(spriteBatch, false, e => e is Structure s && (e.SpriteDepth >= 0.9f || s.Prefab.BackgroundSprite != null) && IsFromOutpostDrawnBehindSubs(e));
spriteBatch.End();
//draw alpha blended particles that are in water and behind subs
#if LINUX || OSX
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
#else
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
#endif
GameMain.ParticleManager.Draw(spriteBatch, true, false, Particles.ParticleBlendState.AlphaBlend);
spriteBatch.End();
//draw additive particles that are in water and behind subs
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
GameMain.ParticleManager.Draw(spriteBatch, true, false, Particles.ParticleBlendState.Additive);
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.None);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None);
spriteBatch.Draw(renderTarget, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
spriteBatch.End();
@@ -367,8 +382,8 @@ namespace Barotrauma
GraphicsQuad.Render();
//Draw the rest of the structures, characters and front structures
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
Submarine.DrawBack(spriteBatch, false, e => !(e is Structure) || e.SpriteDepth < 0.9f);
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
Submarine.DrawBack(spriteBatch, false, e => e is not Structure || e.SpriteDepth < 0.9f);
DrawCharacters(deformed: false, firstPass: true);
spriteBatch.End();
@@ -376,7 +391,7 @@ namespace Barotrauma
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:BackCharactersItems", sw.ElapsedTicks);
sw.Restart();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
DrawCharacters(deformed: true, firstPass: true);
DrawCharacters(deformed: true, firstPass: false);
DrawCharacters(deformed: false, firstPass: false);
@@ -421,19 +436,19 @@ namespace Barotrauma
GraphicsQuad.Render();
//draw alpha blended particles that are inside a sub
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.DepthRead, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.DepthRead, transformMatrix: cam.Transform);
GameMain.ParticleManager.Draw(spriteBatch, true, true, Particles.ParticleBlendState.AlphaBlend);
spriteBatch.End();
graphics.SetRenderTarget(renderTarget);
//draw alpha blended particles that are not in water
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.DepthRead, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.DepthRead, transformMatrix: cam.Transform);
GameMain.ParticleManager.Draw(spriteBatch, false, null, Particles.ParticleBlendState.AlphaBlend);
spriteBatch.End();
//draw additive particles that are not in water
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
GameMain.ParticleManager.Draw(spriteBatch, false, null, Particles.ParticleBlendState.Additive);
spriteBatch.End();
@@ -450,20 +465,10 @@ namespace Barotrauma
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:FrontParticles", sw.ElapsedTicks);
sw.Restart();
DamageEffect.CurrentTechnique = DamageEffect.Techniques["StencilShader"];
spriteBatch.Begin(SpriteSortMode.Immediate,
BlendState.NonPremultiplied, SamplerState.LinearWrap,
null, null,
DamageEffect,
cam.Transform);
Submarine.DrawDamageable(spriteBatch, DamageEffect, false);
spriteBatch.End();
GraphicsQuad.UseBasicEffect(renderTargetDamageable);
GraphicsQuad.Render();
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:FrontDamageable", sw.ElapsedTicks);
sw.Restart();
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
Submarine.DrawFront(spriteBatch, false, null);
spriteBatch.End();
@@ -472,7 +477,7 @@ namespace Barotrauma
sw.Restart();
//draw additive particles that are inside a sub
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, DepthStencilState.Default, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, depthStencilState: DepthStencilState.Default, transformMatrix: cam.Transform);
GameMain.ParticleManager.Draw(spriteBatch, true, true, Particles.ParticleBlendState.Additive);
foreach (var discharger in Items.Components.ElectricalDischarger.List)
{
@@ -488,7 +493,7 @@ namespace Barotrauma
GraphicsQuad.Render();
}
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, depthStencilState: DepthStencilState.None, transformMatrix: cam.Transform);
foreach (Character c in Character.CharacterList)
{
c.DrawFront(spriteBatch, cam);

View File

@@ -363,7 +363,7 @@ namespace Barotrauma
s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) &&
!nonPlayerFiles.Any(f => f.Path == s.FilePath));
GameSession gameSession = new GameSession(subInfo, Option.None, CampaignDataPath.Empty, GameModePreset.TestMode, CampaignSettings.Empty, null);
gameSession.StartRound(Level.Loaded.LevelData);
gameSession.StartRound(Level.Loaded.LevelData, mirrorLevel.Selected);
(gameSession.GameMode as TestGameMode).OnRoundEnd = () =>
{
GameMain.LevelEditorScreen.Select();
@@ -560,9 +560,13 @@ namespace Barotrauma
new Vector2(relWidth, relWidth * ((float)levelObjectList.Content.Rect.Width / levelObjectList.Content.Rect.Height)),
levelObjectList.Content.RectTransform) { MinSize = new Point(0, 60) }, style: "ListBoxElementSquare")
{
UserData = levelObjPrefab
UserData = levelObjPrefab,
ToolTip = levelObjPrefab.Name
};
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null)
{
CanBeFocused = false
};
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform, Anchor.BottomCenter),
text: ToolBox.LimitString(levelObjPrefab.Name, GUIStyle.SmallFont, paddedFrame.Rect.Width), textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
@@ -876,9 +880,22 @@ namespace Barotrauma
{
var levelObj = levelObjFrame.UserData as LevelObjectPrefab;
float commonness = levelObj.GetCommonness(levelData);
levelObjFrame.Color = commonness > 0.0f ? GUIStyle.Green * 0.4f : Color.Transparent;
levelObjFrame.SelectedColor = commonness > 0.0f ? GUIStyle.Green * 0.6f : Color.White * 0.5f;
levelObjFrame.HoverColor = commonness > 0.0f ? GUIStyle.Green * 0.7f : Color.White * 0.6f;
Color color = GUIStyle.Green;
if (commonness > 0.0f && levelData?.GenerationParams != null)
{
if (levelObj.MinSurfaceWidth > levelData.GenerationParams.CellSubdivisionLength &&
levelObj.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.Wall))
{
color = Color.Orange;
levelObjFrame.ToolTip = $"Potential issue: the level walls in \"{levelData.GenerationParams.Identifier}\" are set to be subdivided every {levelData.GenerationParams.CellSubdivisionLength} pixels, but the level object requires wall segments of at least {levelObj.MinSurfaceWidth} px. The object may be rarer than intended (or fail to spawn at all) in the level.";
}
}
levelObjFrame.Color = commonness > 0.0f ? color * 0.4f : Color.Transparent;
levelObjFrame.SelectedColor = commonness > 0.0f ? color * 0.6f : Color.White * 0.5f;
levelObjFrame.HoverColor = commonness > 0.0f ? color * 0.7f : Color.White * 0.6f;
levelObjFrame.GetAnyChild<GUIImage>().Color = commonness > 0.0f ? Color.White : Color.DarkGray;
if (commonness <= 0.0f)

View File

@@ -790,12 +790,16 @@ namespace Barotrauma
var winScoreContainer = CreateLabeledSlider(gameModeSettingsContent, headerTag: string.Empty, valueLabelTag: string.Empty, tooltipTag: "ServerSettingsWinScorePvPTooltip",
out var winScorePvPSlider, out var winScorePvPSliderLabel);
winScorePvPSlider.Range = new Vector2(1, 1000);
winScorePvPSlider.StepValue = 1;
winScorePvPSlider.Range = new Vector2(10, 1000);
winScorePvPSlider.StepValue = 10;
winScorePvPSlider.OnMoved = (scrollBar, _) =>
{
if (scrollBar.UserData is not GUITextBlock text) { return false; }
text.Text = TextManager.GetWithVariable("ServerSettingsWinScoreValuePvP", "[value]", ((int)Math.Round(scrollBar.BarScrollValue, digits: 0)).ToString());
return true;
};
winScorePvPSlider.OnReleased = (scrollBar, _) =>
{
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
return true;
};
@@ -931,7 +935,7 @@ namespace Barotrauma
//do this before adding the contents, otherwise they get disabled too (and we just want to disable the dropdown itself)
clientDisabledElements.AddRange(biomeHolder.GetAllChildren());
biomeDropdown.AddItem(TextManager.Get("random"), "Random".ToIdentifier());
foreach (var biome in Biome.Prefabs)
foreach (var biome in Biome.Prefabs.OrderBy(b => b.MinDifficulty))
{
if (biome.IsEndBiome) { continue; }
biomeDropdown.AddItem(biome.DisplayName, biome.Identifier);
@@ -1195,7 +1199,7 @@ namespace Barotrauma
var respawnModeHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
respawnModeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.0f), respawnModeHolder.RectTransform), TextManager.Get("RespawnMode"), wrap: true);
respawnModeSelection = new GUISelectionCarousel<RespawnMode>(new RectTransform(new Vector2(0.6f, 1.0f), respawnModeHolder.RectTransform));
foreach (var respawnMode in Enum.GetValues(typeof(RespawnMode)).Cast<RespawnMode>())
foreach (var respawnMode in Enum.GetValues(typeof(RespawnMode)).Cast<RespawnMode>().Where(rm => rm != RespawnMode.None))
{
respawnModeSelection.AddElement(respawnMode, TextManager.Get($"respawnmode.{respawnMode}"), TextManager.Get($"respawnmode.{respawnMode}.tooltip"));
}
@@ -3005,10 +3009,13 @@ namespace Barotrauma
}
}
outpostDropdown.ListBox.Select(prevSelected);
GameMain.Client.ServerSettings.AssignGUIComponent(nameof(ServerSettings.SelectedOutpostName), outpostDropdown);
}
else
{
outpostDropdown.Parent.Visible = false;
//remove assignment, we shouldn't try selecting the outpost when there's none to select
GameMain.Client.ServerSettings.AssignGUIComponent(nameof(ServerSettings.SelectedOutpostName), null);
}
outpostDropdownUpToDate = true;
}

View File

@@ -1003,6 +1003,8 @@ namespace Barotrauma
private bool ShouldShowServer(ServerInfo serverInfo)
{
if (serverInfo == null) { return false; }
#if !DEBUG
//never show newer versions
//(ignore revision number, it doesn't affect compatibility)

View File

@@ -56,7 +56,8 @@ namespace Barotrauma
WaterInHulls,
LowOxygenOutputWarning,
TooLargeForEndGame,
NotEnoughContainers
NotEnoughContainers,
NoSuitableBrainRooms
}
public static Vector2 MouseDragStart = Vector2.Zero;
@@ -1308,7 +1309,7 @@ namespace Barotrauma
}
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform, Anchor.BottomCenter),
text: name, textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
text: RichString.Rich(name), textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
{
CanBeFocused = false
};
@@ -1320,7 +1321,7 @@ namespace Barotrauma
textBlock.Text = frame.ToolTip = ep.Identifier.Value;
textBlock.TextColor = GUIStyle.Red;
}
textBlock.Text = ToolBox.LimitString(textBlock.Text, textBlock.Font, textBlock.Rect.Width);
textBlock.Text = ToolBox.LimitString(textBlock.Text.SanitizedString, textBlock.Font, textBlock.Rect.Width);
if (ep.Category == MapEntityCategory.ItemAssembly
&& ep.ContentPackage?.Files.Length == 1
@@ -5794,7 +5795,7 @@ namespace Barotrauma
foreach (LightComponent lightComponent in item.GetComponents<LightComponent>())
{
lightComponent.Light.Color =
(item.body == null || item.body.Enabled || item.ParentInventory is ItemInventory { Container.HideItems: true }) &&
(item.body == null || item.body.Enabled || item.ParentInventory is ItemInventory { Container.HideItems: false }) &&
/*the light is only visible when worn -> can't be visible in the editor*/
lightComponent.Parent is not Wearable ?
lightComponent.LightColor :

View File

@@ -107,6 +107,11 @@ namespace Barotrauma
public void SelectTab(Tab tab)
{
if (tab == Tab.AudioAndVC && CurrentDeviceMismatchesDisplayed())
{
CreateAudioAndVCTab(refresh: true);
}
CurrentTab = tab;
SwitchContent(tabContents[tab].Content);
tabber.Children.ForEach(c =>
@@ -134,6 +139,11 @@ namespace Barotrauma
private GUIFrame CreateNewContentFrame(Tab tab)
{
if (tabContents.TryGetValue(tab, out (GUIButton Button, GUIFrame Content) tabContent))
{
return tabContent.Content;
}
var content = new GUIFrame(new RectTransform(Vector2.One * 0.95f, contentFrame.RectTransform, Anchor.Center, Pivot.Center), style: null);
AddButtonToTabber(tab, content);
return content;
@@ -180,7 +190,7 @@ namespace Barotrauma
private static GUIDropDown Dropdown<T>(GUILayoutGroup parent, Func<T, LocalizedString> textFunc, Func<T, LocalizedString>? tooltipFunc, IReadOnlyList<T> values, T currentValue, Action<T> setter)
{
var dropdown = new GUIDropDown(NewItemRectT(parent));
var dropdown = new GUIDropDown(NewItemRectT(parent), elementCount: values.Count);
values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v, toolTip: tooltipFunc?.Invoke(v) ?? null));
int childIndex = values.IndexOf(currentValue);
dropdown.Select(childIndex);
@@ -269,6 +279,11 @@ namespace Barotrauma
DropdownEnum(left, (m) => TextManager.Get($"{m}"), null, unsavedConfig.Graphics.DisplayMode, v => unsavedConfig.Graphics.DisplayMode = v);
Spacer(left);
var displayLabel = Label(left, TextManager.Get("TargetDisplay"), GUIStyle.SubHeadingFont);
displayLabel.ToolTip = TextManager.Get("TargetDisplay.Tooltip");
Dropdown(left, m => TextManager.GetWithVariables(m == 0 ? "PrimaryDisplayFormat" : "SecondaryDisplayFormat", ("[num]", m.ToString()), ("[name]", Display.GetDisplayName(m))), null, Enumerable.Range(0, Display.GetNumberOfDisplays()).ToArray(), unsavedConfig.Graphics.Display, v => unsavedConfig.Graphics.Display = v);
Spacer(left);
Tickbox(left, TextManager.Get("EnableVSync"), TextManager.Get("EnableVSyncTooltip"), unsavedConfig.Graphics.VSync, v => unsavedConfig.Graphics.VSync = v);
Tickbox(left, TextManager.Get("EnableTextureCompression"), TextManager.Get("EnableTextureCompressionTooltip"), unsavedConfig.Graphics.CompressTextures, v => unsavedConfig.Graphics.CompressTextures = v);
Spacer(right);
@@ -347,17 +362,45 @@ namespace Barotrauma
current = list[0];
}
}
private void CreateAudioAndVCTab()
private static bool IsCurrentDevice(string savedDeviceName, int deviceType)
{
try
{
string currentDevice = Alc.GetString(IntPtr.Zero, deviceType);
if (string.IsNullOrEmpty(savedDeviceName) || string.IsNullOrEmpty(currentDevice))
{
return false;
}
return currentDevice.Equals(savedDeviceName, StringComparison.OrdinalIgnoreCase);
}
catch (Exception ex)
{
Console.WriteLine($"Error checking output device name: {ex.Message}");
return false;
}
}
private static bool CurrentDeviceMismatchesDisplayed()
{
return !IsCurrentDevice(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, Alc.CaptureDefaultDeviceSpecifier) ||
!IsCurrentDevice(GameSettings.CurrentConfig.Audio.AudioOutputDevice, Alc.DefaultDeviceSpecifier);
}
public void CreateAudioAndVCTab(bool refresh = false)
{
if (GameMain.Client == null
&& VoipCapture.Instance == null)
&& (refresh || VoipCapture.Instance == null))
{
string currDevice = unsavedConfig.Audio.VoiceCaptureDevice;
GetAudioDevices(Alc.CaptureDeviceSpecifier, Alc.CaptureDefaultDeviceSpecifier, out var deviceList, ref currDevice);
if (deviceList.Any())
{
if (VoipCapture.Instance is VoipCapture currentCaptureInstance)
{
currentCaptureInstance.Dispose();
}
VoipCapture.Create(unsavedConfig.Audio.VoiceCaptureDevice);
}
if (VoipCapture.Instance == null)
@@ -367,7 +410,10 @@ namespace Barotrauma
}
GUIFrame content = CreateNewContentFrame(Tab.AudioAndVC);
if (refresh)
{
content.ClearChildren();
}
var (audio, voiceChat) = CreateSidebars(content, split: true);
static void audioDeviceElement(
@@ -401,6 +447,15 @@ namespace Barotrauma
string currentOutputDevice = unsavedConfig.Audio.AudioOutputDevice;
audioDeviceElement(audio, v => unsavedConfig.Audio.AudioOutputDevice = v, Alc.OutputDevicesSpecifier, Alc.DefaultDeviceSpecifier, ref currentOutputDevice);
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), audio.RectTransform), text: TextManager.Get("RefreshAudioDevices"), style: "GUIButtonSmall")
{
ToolTip = TextManager.Get("RefreshAudioDevicesToolTip"),
OnClicked = (btn, obj) =>
{
CreateAudioAndVCTab(refresh: true);
return true;
}
};
Spacer(audio);
Label(audio, TextManager.Get("SoundVolume"), GUIStyle.SubHeadingFont);
@@ -443,6 +498,15 @@ namespace Barotrauma
string currentInputDevice = unsavedConfig.Audio.VoiceCaptureDevice;
audioDeviceElement(voiceChat, v => unsavedConfig.Audio.VoiceCaptureDevice = v, Alc.CaptureDeviceSpecifier, Alc.CaptureDefaultDeviceSpecifier, ref currentInputDevice);
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), voiceChat.RectTransform), text: TextManager.Get("RefreshAudioDevices"), style: "GUIButtonSmall")
{
ToolTip = TextManager.Get("RefreshAudioDevicesToolTip"),
OnClicked = (btn, obj) =>
{
CreateAudioAndVCTab(refresh: true);
return true;
}
};
Spacer(voiceChat);
Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont);

View File

@@ -258,11 +258,11 @@ namespace OpenAL
public static void GetInteger(IntPtr device, int param, out int data)
{
int[] dataArr = new int[1];
GCHandle handle = GCHandle.Alloc(dataArr,GCHandleType.Pinned);
data = 0; // (Optimization: let's pin an integer on the stack instead of an array on the heap, which previously allocated almost a GB of memory)
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
GetIntegerv(device, param, 1, handle.AddrOfPinnedObject());
data = Marshal.ReadInt32(handle.AddrOfPinnedObject());
handle.Free();
data = dataArr[0];
}
#endregion

View File

@@ -670,6 +670,7 @@ namespace Barotrauma.Sounds
DebugConsole.ThrowError("Playback device has been disconnected. You can select another available device in the settings.");
SetAudioOutputDevice("<disconnected>");
Disconnected = true;
TryRefreshDevice();
return;
}
}
@@ -885,5 +886,52 @@ namespace Barotrauma.Sounds
throw new Exception("Failed to close ALC device!");
}
}
public static void TryRefreshDevice()
{
DebugConsole.NewMessage("Refreshing audio playback device");
List<string> deviceList = Alc.GetStringList(IntPtr.Zero, Alc.OutputDevicesSpecifier).ToList();
int alcError = Alc.GetError(IntPtr.Zero);
if (alcError != Alc.NoError)
{
DebugConsole.ThrowError("Failed to list available audio playback devices: " + alcError.ToString());
return;
}
if (deviceList.Any())
{
string device;
if (deviceList.Find(n => n.Equals(GameSettings.CurrentConfig.Audio.AudioOutputDevice, StringComparison.OrdinalIgnoreCase))
is string availablePreviousDevice)
{
DebugConsole.NewMessage($" Previous device choice available: {availablePreviousDevice}");
device = availablePreviousDevice;
}
else
{
device = Alc.GetString(IntPtr.Zero, Alc.DefaultDeviceSpecifier);
DebugConsole.NewMessage($" Reverting to default device: {device}");
}
if (string.IsNullOrEmpty(device))
{
device = deviceList[0];
DebugConsole.NewMessage($" No default device found, resorting to first available device: {device}");
}
// Save the new device choice and initialize it
var currentConfig = GameSettings.CurrentConfig;
currentConfig.Audio.AudioOutputDevice = device;
GameSettings.SetCurrentConfig(currentConfig);
GameMain.SoundManager.InitializeAlcDevice(device);
}
if (GUI.SettingsMenuOpen)
{
SettingsMenu.Instance?.CreateAudioAndVCTab(true);
}
}
}
}

View File

@@ -48,6 +48,8 @@ namespace Barotrauma
private static bool IsFiltered(ServerInfo info, SpamServerFilterType type, string value)
{
if (info == null) { return true; }
string desc = info.ServerMessage,
name = info.ServerName;

View File

@@ -298,24 +298,27 @@ namespace Barotrauma
/// Last version of the game that had broken handling of sprites that were scaled, flipped and offset
/// </summary>
public static readonly Version LastBrokenTiledSpriteGameVersion = new Version(major: 1, minor: 2, build: 7, revision: 0);
public void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, float rotation = 0f, Vector2? origin = null, Color? color = null, Vector2? startOffset = null, Vector2? textureScale = null, float? depth = null)
{
DrawTiled(spriteBatch, position, targetSize, effects, rotation, origin, color, startOffset, textureScale, depth);
}
public void DrawTiled(ISpriteBatch spriteBatch,
Vector2 position,
Vector2 targetSize,
SpriteEffects spriteEffects,
float rotation = 0f,
Vector2? origin = null,
Color? color = null,
Vector2? startOffset = null,
Vector2? textureScale = null,
float? depth = null,
SpriteEffects? spriteEffects = null)
float? depth = null)
{
if (Texture == null) { return; }
spriteEffects ??= effects;
bool flipHorizontal = spriteEffects.Value.HasFlag(SpriteEffects.FlipHorizontally);
bool flipVertical = spriteEffects.Value.HasFlag(SpriteEffects.FlipVertically);
bool flipHorizontal = (spriteEffects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally; // optimized from spriteEffects.HasFlag(SpriteEffects.FlipHorizontally)
bool flipVertical = (spriteEffects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically; // optimized from spriteEffects.HasFlag(SpriteEffects.FlipVertically)
float addedRotation = rotation + this.rotation;
if (flipHorizontal != flipVertical) { addedRotation = -addedRotation; }
@@ -354,7 +357,7 @@ namespace Barotrauma
rotation: addedRotation,
origin: Vector2.Zero,
scale: scale,
effects: spriteEffects.Value,
effects: spriteEffects,
layerDepth: depth ?? this.depth);
}

View File

@@ -777,8 +777,7 @@ namespace Barotrauma.Steam
{
GUIMessageBox msgBox = new(TextManager.Get("DeleteMods"), TextManager.GetWithVariables("DeleteModsConfirm", ("[amount]", selectedMods.Length.ToString())),
new LocalizedString[] { TextManager.Get("Confirm"), TextManager.Get("Cancel")}, textAlignment: Alignment.TopCenter);
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += (_, _) =>
msgBox.Buttons[0].OnClicked += (_, _) =>
{
foreach (ContentPackage mod in selectedMods)
{
@@ -788,6 +787,7 @@ namespace Barotrauma.Steam
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
});

View File

@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace Barotrauma
@@ -16,4 +17,22 @@ namespace Barotrauma
return -CompareCCW.Compare(a.WorldPos, b.WorldPos, center);
}
}
public class CompareVertexPositionColorCCW : IComparer<VertexPositionColor>
{
private Vector2 center;
public CompareVertexPositionColorCCW(Vector2 center)
{
this.center = center;
}
public int Compare(VertexPositionColor a, VertexPositionColor b)
{
return -CompareCW.Compare(new Vector2(a.Position.X, a.Position.Y), new Vector2(b.Position.X, b.Position.Y), center);
}
public static int Compare(VertexPositionColor a, VertexPositionColor b, Vector2 center)
{
return -CompareCW.Compare(new Vector2(a.Position.X, a.Position.Y), new Vector2(b.Position.X, b.Position.Y), center);
}
}
}

View File

@@ -76,11 +76,14 @@ namespace Barotrauma
float zoom = (float)texWidth / (float)boundingBox.Width;
int texHeight = (int)(zoom * boundingBox.Height);
Camera cam = new Camera();
Camera cam = new Camera()
{
AutoUpdateToScreenResolution = false,
MaxZoom = zoom,
MinZoom = zoom * 0.5f,
Zoom = zoom
};
cam.SetResolution(new Point(texWidth, texHeight));
cam.MaxZoom = zoom;
cam.MinZoom = zoom * 0.5f;
cam.Zoom = zoom;
cam.Position = boundingBox.Center.ToVector2();
cam.UpdateTransform(false);

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.6.19.1</Version>
<Version>1.7.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -55,7 +55,7 @@
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'">
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" Exclude="..\BarotraumaShared\Data\Saves\*.save;..\BarotraumaShared\ModLists\*.xml" />
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" Exclude="..\BarotraumaShared\Data\Saves\*.save;..\BarotraumaShared\ModLists\*.xml;..\BarotraumaShared\LocalMods\[DebugOnlyTest]*\**" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" />

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.6.19.1</Version>
<Version>1.7.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -58,7 +58,7 @@
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'">
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" Exclude="..\BarotraumaShared\Data\Saves\*.save;..\BarotraumaShared\ModLists\*.xml" />
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" Exclude="..\BarotraumaShared\Data\Saves\*.save;..\BarotraumaShared\ModLists\*.xml;..\BarotraumaShared\LocalMods\[DebugOnlyTest]*\**" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" />

View File

@@ -1,4 +1,4 @@
Texture2D xTexture;
sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; };
@@ -7,8 +7,6 @@ sampler StencilSampler = sampler_state { Texture = <xStencil>; };
float4 solidColor;
float4 inColor;
float aCutoff;
float aMultiplier;
@@ -33,7 +31,7 @@ float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord
float4 solidColorStencil(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = xTexture.Sample(TextureSampler, texCoord) * inColor;
float4 c = xTexture.Sample(TextureSampler, texCoord) * color;
float4 stencilColor = xStencil.Sample(StencilSampler, texCoord);
float aDiff = stencilColor.a - aCutoff;

View File

@@ -1,4 +1,4 @@
Texture xTexture;
sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; };
@@ -7,8 +7,6 @@ sampler StencilSampler = sampler_state { Texture = <xStencil>; };
float4 solidColor;
float4 inColor;
float aCutoff;
float aMultiplier;
@@ -17,7 +15,7 @@ float cMultiplier;
float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = tex2D(TextureSampler, texCoord) * inColor;
float4 c = tex2D(TextureSampler, texCoord) * color;
float4 stencilColor = tex2D(StencilSampler, texCoord);
float aDiff = stencilColor.a - aCutoff;
@@ -33,7 +31,7 @@ float4 main(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord
float4 solidColorStencil(float4 position : POSITION0, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c = tex2D(TextureSampler, texCoord) * inColor;
float4 c = tex2D(TextureSampler, texCoord) * color;
float4 stencilColor = tex2D(StencilSampler, texCoord);
float aDiff = stencilColor.a - aCutoff;

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.6.19.1</Version>
<Version>1.7.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
@@ -63,7 +63,7 @@
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'">
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" Exclude="..\BarotraumaShared\Data\Saves\*.save;..\BarotraumaShared\ModLists\*.xml" />
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" Exclude="..\BarotraumaShared\Data\Saves\*.save;..\BarotraumaShared\ModLists\*.xml;..\BarotraumaShared\LocalMods\[DebugOnlyTest]*\**" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" />

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.6.19.1</Version>
<Version>1.7.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.6.19.1</Version>
<Version>1.7.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -1,6 +1,7 @@
using Barotrauma.Networking;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma
{
@@ -88,5 +89,16 @@ namespace Barotrauma
{
GameServer.Log($"{GameServer.CharacterLogName(this)} has gained the talent '{talentPrefab.DisplayName}'", ServerLog.MessageType.Talent);
}
private void SyncInGameEditables(Item item)
{
foreach (ItemComponent itemComponent in item.Components)
{
foreach (var serializableProperty in SerializableProperty.GetProperties<InGameEditable>(itemComponent))
{
GameMain.Server.CreateEntityEvent(item, new Item.ChangePropertyEventData(serializableProperty, itemComponent));
}
}
}
}
}

View File

@@ -431,21 +431,33 @@ namespace Barotrauma
tempBuffer.WriteSingle(SimPosition.Y);
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
AnimController.Collider.LinearVelocity = new Vector2(
MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel),
MathHelper.Clamp(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel));
NetConfig.Quantize(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12),
NetConfig.Quantize(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12));
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12);
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12);
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled;
AnimController.TargetMovement = new Vector2(
NetConfig.Quantize(AnimController.TargetMovement.X, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12),
NetConfig.Quantize(AnimController.TargetMovement.Y, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12));
tempBuffer.WriteRangedSingle(AnimController.TargetMovement.X, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12);
tempBuffer.WriteRangedSingle(AnimController.TargetMovement.Y, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12);
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation;
tempBuffer.WriteBoolean(fixedRotation);
if (!fixedRotation)
{
tempBuffer.WriteSingle(AnimController.Collider.Rotation);
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8);
AnimController.Collider.AngularVelocity =
AnimController.Collider.PhysEnabled ?
0.0f :
NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8);
tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8);
}
tempBuffer.WriteBoolean(AnimController.IgnorePlatforms);
bool writeStatus = healthUpdateTimer <= 0.0f;
tempBuffer.WriteBoolean(writeStatus);
if (writeStatus)

View File

@@ -0,0 +1,70 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Limb : ISerializableEntity, ISpatialEntity
{
/// <summary>
/// An invisible "ghost body" used for doing lag compensation server side by allowing clients' shots to hit bodies at the positions where
/// they "used to be" back when the client fired a weapon.
/// </summary>
public PhysicsBody LagCompensatedBody { get; private set; }
/// <summary>
/// A queue of past positions of the limb.
/// </summary>
public Queue<PosInfo> MemState { get; } = new Queue<PosInfo>();
partial void InitProjSpecific(ContentXElement element)
{
LagCompensatedBody = new PhysicsBody(Params, findNewContacts: false)
{
BodyType = FarseerPhysics.BodyType.Static,
CollisionCategories = Physics.CollisionLagCompensationBody,
CollidesWith = Physics.CollisionNone,
UserData = this
};
}
partial void UpdateProjSpecific(float deltaTime)
{
if (GameMain.Server == null) { return; }
MemState.Enqueue(new PosInfo(body.SimPosition, body.Rotation, body.LinearVelocity, body.AngularVelocity, (float)Timing.TotalTime));
//clear old states
while (
MemState.Any() &&
MemState.Peek().Timestamp < Timing.TotalTime - GameMain.Server.ServerSettings.MaxLagCompensationSeconds)
{
MemState.Dequeue();
}
}
public static void SetLagCompensatedBodyPositions(Client client)
{
if (GameMain.Server == null) { return; }
//convert from milliseconds to seconds, assume latency is symmetrical (time from client to server is half of the roundtrip time / ping)
float latency = client.Ping / 1000.0f / 2;
float time = (float)Timing.TotalTime - MathUtils.Min(latency, GameMain.Server.ServerSettings.MaxLagCompensationSeconds);
foreach (var character in Character.CharacterList)
{
foreach (var limb in character.AnimController.Limbs)
{
if (limb.body.Enabled == false || limb.IgnoreCollisions) { continue; }
var matchingState = limb.MemState.FirstOrDefault(l => l.Timestamp <= time);
if (matchingState == null) { continue; }
limb.LagCompensatedBody.SetTransformIgnoreContacts(matchingState.Position, matchingState.Rotation ?? 0.0f);
}
}
}
partial void RemoveProjSpecific()
{
LagCompensatedBody.Remove();
}
}
}

View File

@@ -2765,6 +2765,11 @@ namespace Barotrauma
{
GameMain.Server.CreateEntityEvent(wall);
}
foreach (Hull hull in Hull.HullList)
{
if (hull.IdFreed) { continue; }
hull.CreateStatusEvent();
}
}));
commands.Add(new Command("stallfiletransfers", "stallfiletransfers [seconds]: A debug command that makes all file transfers take at least the specified duration.", (string[] args) =>
{

View File

@@ -1,4 +1,5 @@
using Barotrauma.Networking;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -60,20 +61,35 @@ namespace Barotrauma
private bool IsBlockedByAnotherConversation(IEnumerable<Entity> targets, float duration)
{
foreach (Entity e in targets)
if (targets == null || targets.None())
{
if (e is not Character character || !character.IsRemotePlayer) { continue; }
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
if (targetClient != null)
//if the action doesn't target anyone in specific, it's shown to every client
foreach (var client in GameMain.Server.ConnectedClients)
{
if (lastActiveAction.ContainsKey(targetClient) &&
lastActiveAction[targetClient].ParentEvent != ParentEvent &&
Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + duration)
{
return true;
}
if (IsBlockedByAnotherConversation(client, duration)) { return true; }
}
}
else
{
foreach (Entity e in targets)
{
if (e is not Character character || !character.IsRemotePlayer) { continue; }
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
if (targetClient != null && IsBlockedByAnotherConversation(targetClient, duration)) { return true; }
}
}
return false;
}
private bool IsBlockedByAnotherConversation(Client targetClient, float duration)
{
if (lastActiveAction.ContainsKey(targetClient) &&
!lastActiveAction[targetClient].ParentEvent.IsFinished &&
lastActiveAction[targetClient].ParentEvent != ParentEvent &&
Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + duration)
{
return true;
}
return false;
}
@@ -91,6 +107,7 @@ namespace Barotrauma
{
targetClients.Add(targetClient);
lastActiveAction[targetClient] = this;
lastActiveTime = Timing.TotalTime;
ServerWrite(speaker, targetClient, interrupt);
}
}
@@ -105,6 +122,7 @@ namespace Barotrauma
{
targetClients.Add(c);
lastActiveAction[c] = this;
lastActiveTime = Timing.TotalTime;
ServerWrite(speaker, c, interrupt);
}
}

View File

@@ -1,6 +1,7 @@
#nullable enable
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
@@ -193,6 +194,26 @@ namespace Barotrauma
}
}
public void AddToScore(CharacterTeamType team, int amount)
{
if (!HasWinScore) { return; }
int index;
switch (team)
{
case CharacterTeamType.Team1:
index = 0;
break;
case CharacterTeamType.Team2:
index = 1;
break;
default:
DebugConsole.AddSafeError($"Attempted to increase the score of an invalid team ({team}).");
return;
}
Scores[index] = MathHelper.Clamp(Scores[index] + amount, 0, WinScore);
GameMain.Server?.UpdateMissionState(this);
}
private void AddKill(Character character)
{
kills.Add(new KillCount(character, character.CauseOfDeath?.Killer));

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Barotrauma.Networking;
namespace Barotrauma
@@ -12,9 +13,9 @@ namespace Barotrauma
{
item.WriteSpawnData(msg,
item.ID,
parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID,
parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0,
inventorySlotIndices.ContainsKey(item) ? inventorySlotIndices[item] : -1);
parentInventoryIDs.GetValueOrDefault(item, Entity.NullEntityID),
parentItemContainerIndices.GetValueOrDefault(item, (byte)0),
inventorySlotIndices.GetValueOrDefault(item, -1));
}
ServerWriteScanTargetStatus(msg);
}
@@ -30,7 +31,7 @@ namespace Barotrauma
msg.WriteByte((byte)scanTargets.Count);
foreach (var kvp in scanTargets)
{
msg.WriteUInt16(kvp.Key != null ? kvp.Key.ID : Entity.NullEntityID);
msg.WriteUInt16(kvp.Key?.ID ?? Entity.NullEntityID);
msg.WriteBoolean(kvp.Value);
}
}

View File

@@ -1,5 +1,6 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma.Items.Components
{
@@ -13,10 +14,18 @@ namespace Barotrauma.Items.Components
msg.WriteBoolean(writeAttachData);
if (!writeAttachData) { return; }
UInt16 attacherId = Entity.NullEntityID;
if (TryExtractEventData(extraData, out AttachEventData attachEventData) &&
attachEventData.Attacher != null)
{
attacherId = attachEventData.Attacher.ID;
}
msg.WriteBoolean(Attached);
msg.WriteSingle(body.SimPosition.X);
msg.WriteSingle(body.SimPosition.Y);
msg.WriteUInt16(item.Submarine?.ID ?? Entity.NullEntityID);
msg.WriteUInt16(attacherId);
}
public void ServerEventRead(IReadMessage msg, Client c)
@@ -34,7 +43,7 @@ namespace Barotrauma.Items.Components
AttachToWall();
OnUsed.Invoke(new ItemUseInfo(item, c.Character));
item.CreateServerEvent(this);
item.CreateServerEvent(this, new AttachEventData(simPosition, c.Character));
c.Character.Inventory?.CreateNetworkEvent();
GameServer.Log(GameServer.CharacterLogName(c.Character) + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);

View File

@@ -424,7 +424,14 @@ namespace Barotrauma
if (!components.Contains(ic)) { return; }
var eventData = new ComponentStateEventData(ic, extraData);
if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false."); }
if (!ic.ValidateEventData(eventData))
{
string errorMsg =
$"Server-side component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false. " +
$"Data: {extraData?.GetType().ToString() ?? "null"}";
GameAnalyticsManager.AddErrorEventOnce($"Item.CreateServerEvent:ValidateEventData:{Prefab.Identifier}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
GameMain.Server.CreateEntityEvent(this, eventData);
}
@@ -435,10 +442,12 @@ namespace Barotrauma
foreach (ItemComponent ic in components)
{
if (!(ic is IServerSerializable)) { continue; }
var eventData = new ComponentStateEventData(ic, ic.ServerGetEventData());
if (!ic.ValidateEventData(eventData)) { continue; }
GameMain.Server.CreateEntityEvent(this, eventData);
if (ic is not IServerSerializable) { continue; }
var eventData = ic.ServerGetEventData();
if (eventData == null) { continue; }
var componentData = new ComponentStateEventData(ic, eventData);
if (!ic.ValidateEventData(componentData)) { continue; }
GameMain.Server.CreateEntityEvent(this, componentData);
}
}
#endif

View File

@@ -76,6 +76,12 @@ namespace Barotrauma
}
}
public void CreateStatusEvent()
{
GameMain.NetworkMember?.CreateEntityEvent(this, new StatusEventData());
}
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed hull event: expected {nameof(Hull)}.{nameof(IEventData)}"); }

View File

@@ -22,6 +22,8 @@ namespace Barotrauma.Networking
public UInt16 LastRecvLobbyUpdate
= NetIdUtils.GetIdOlderThan(GameMain.NetLobbyScreen.LastUpdateID);
public bool InitialLobbyUpdateSent;
public UInt16 LastSentChatMsgID = 0; //last msg this client said
public UInt16 LastRecvChatMsgID = 0; //last msg this client knows about
@@ -166,8 +168,8 @@ namespace Barotrauma.Networking
LastSentChatMsgID = 0;
LastRecvChatMsgID = ChatMessage.LastID;
LastRecvLobbyUpdate = 0;
LastRecvLobbyUpdate = NetIdUtils.GetIdOlderThan(GameMain.NetLobbyScreen.LastUpdateID);
InitialLobbyUpdateSent = false;
LastRecvEntityEventID = 0;
UnreceivedEntityEventCount = 0;

View File

@@ -1298,11 +1298,8 @@ namespace Barotrauma.Networking
//check if midround syncing is needed due to missed unique events
if (!midroundSyncingDone) { entityEventManager.InitClientMidRoundSync(c); }
MissionAction.NotifyMissionsUnlockedThisRound(c);
if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign)
{
mpCampaign.SendCrewState();
}
else if (GameMain.GameSession.GameMode is PvPMode)
if (GameMain.GameSession.GameMode is PvPMode)
{
if (c.TeamID == CharacterTeamType.None)
{
@@ -1311,6 +1308,10 @@ namespace Barotrauma.Networking
}
else
{
if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign)
{
mpCampaign.SendCrewState();
}
//everyone's in team 1 in non-pvp game modes
c.TeamID = CharacterTeamType.Team1;
}
@@ -2251,12 +2252,13 @@ namespace Barotrauma.Networking
outmsg.WriteUInt16((UInt16)settingsBuf.LengthBytes);
outmsg.WriteBytes(settingsBuf.Buffer, 0, settingsBuf.LengthBytes);
outmsg.WriteBoolean(c.LastRecvLobbyUpdate < 1);
if (c.LastRecvLobbyUpdate < 1)
outmsg.WriteBoolean(!c.InitialLobbyUpdateSent);
if (!c.InitialLobbyUpdateSent)
{
isInitialUpdate = true;
initialUpdateBytes = outmsg.LengthBytes;
ClientWriteInitial(c, outmsg);
c.InitialLobbyUpdateSent = true;
initialUpdateBytes = outmsg.LengthBytes - initialUpdateBytes;
}
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.Name);
@@ -3135,6 +3137,7 @@ namespace Barotrauma.Networking
{
msg.WriteString(levelSeed);
msg.WriteSingle(ServerSettings.SelectedLevelDifficulty);
msg.WriteIdentifier(ServerSettings.Biome == "Random".ToIdentifier() ? Identifier.Empty : ServerSettings.Biome);
msg.WriteString(gameSession.SubmarineInfo.Name);
msg.WriteString(gameSession.SubmarineInfo.MD5Hash.StringRepresentation);
var selectedShuttle = GameStarted && RespawnManager != null && RespawnManager.UsingShuttle ?
@@ -3788,13 +3791,13 @@ namespace Barotrauma.Networking
}
else //msg sent by an AI character
{
senderName = senderCharacter.Name;
senderName = senderCharacter.DisplayName;
}
}
else //msg sent by a client
{
senderCharacter = senderClient.Character;
senderName = senderCharacter == null ? senderClient.Name : senderCharacter.Name;
senderName = senderCharacter == null ? senderClient.Name : senderCharacter.DisplayName;
if (type == ChatMessageType.Private)
{
if (senderCharacter != null && !senderCharacter.IsDead || targetClient.Character != null && !targetClient.Character.IsDead)

View File

@@ -151,11 +151,11 @@ namespace Barotrauma
//increase the strength of the herpes affliction in steps instead of linearly
//otherwise clients could determine their exact karma value from the strength
float herpesStrength = 0.0f;
if (client.Karma < 20)
if (client.Karma < HerpesThreshold * 0.5f)
herpesStrength = 100.0f;
else if (client.Karma < 30)
else if (client.Karma < HerpesThreshold * 0.75f)
herpesStrength = 60.0f;
else if (client.Karma < 40.0f)
else if (client.Karma < HerpesThreshold)
herpesStrength = 30.0f;
var existingAffliction = client.Character.CharacterHealth.GetAffliction<AfflictionSpaceHerpes>(AfflictionPrefab.SpaceHerpesType);

View File

@@ -229,7 +229,9 @@ namespace Barotrauma.Networking
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
{
lastWarningTime = Timing.TotalTime;
GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage);
string warningMsg = $"WARNING: ServerEntityEventManager is lagging behind! Last sent id: {lastSentToAnyone}, latest create id: {ID}";
warningMsg += "\n" + GetHighEventCountsWarning(events, maxEventsToList: 3);
GameServer.Log(warningMsg, ServerLog.MessageType.ServerMessage);
events.ForEach(e => e.ResetCreateTime());
//TODO: reset clients if this happens, maybe do it if a majority are behind rather than all of them?
}
@@ -323,30 +325,20 @@ namespace Barotrauma.Networking
}
//too many events for one packet
//(normal right after a round has just started, don't show a warning if it's been less than 10 seconds)
if (eventsToSync.Count > 200 && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 10.0)
//(normal right after a round has just started, don't show a warning if it's been less than 30 seconds)
if (eventsToSync.Count > 200 && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 30.0)
{
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync && Timing.TotalTime > lastEventCountHighWarning + 2.0)
{
Color color = eventsToSync.Count > 500 ? Color.Red : Color.Orange;
if (eventsToSync.Count < 300) { color = Color.Yellow; }
string warningMsg = "WARNING: event count very high: " + eventsToSync.Count;
var sortedEvents = eventsToSync.GroupBy(e => e.Entity.ToString())
.Select(e => new { Value = e.Key, Count = e.Count() })
.OrderByDescending(e => e.Count);
int count = 1;
foreach (var sortedEvent in sortedEvents)
{
warningMsg += "\n" + count + ". " + (sortedEvent.Value?.ToString() ?? "null") + " x" + sortedEvent.Count;
count++;
if (count > 3) { break; }
}
warningMsg += "\n" + GetHighEventCountsWarning(eventsToSync, maxEventsToList: 3);
if (GameSettings.CurrentConfig.VerboseLogging)
{
GameServer.Log(warningMsg, ServerLog.MessageType.Error);
}
server.SendConsoleMessage(warningMsg, client, color);
DebugConsole.NewMessage(warningMsg, color);
lastEventCountHighWarning = Timing.TotalTime;
}
@@ -373,6 +365,31 @@ namespace Barotrauma.Networking
}
}
private string GetHighEventCountsWarning(IEnumerable<NetEntityEvent> events, int maxEventsToList)
{
string warningMsg = string.Empty;
var sortedEvents = events.GroupBy(e => e.Entity.ToString())
.Select(e => new { Value = e.First(), Count = e.Count() })
.OrderByDescending(e => e.Count);
int count = 1;
foreach (var sortedEvent in sortedEvents)
{
Entity targetEntity = sortedEvent.Value.Entity;
if (!warningMsg.IsNullOrEmpty()) { warningMsg += "\n"; }
warningMsg += count + ". " + (targetEntity?.ToString() ?? "null") + " x" + sortedEvent.Count;
if (targetEntity != null && targetEntity.ContentPackage != ContentPackageManager.VanillaCorePackage)
{
warningMsg += $" (content package: {targetEntity.ContentPackage.Name})";
}
count++;
if (count > maxEventsToList) { break; }
}
return warningMsg;
}
/// <summary>
/// Returns a list of events that should be sent to the client from the eventList
/// </summary>

View File

@@ -457,7 +457,7 @@ namespace Barotrauma.Networking
{
if (pendingClient.AccountInfo.AccountId != packet.AccountId)
{
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
rejectClient();
}
return;
}
@@ -513,10 +513,16 @@ namespace Barotrauma.Networking
pendingClient.AuthSessionStarted = true;
TaskPool.Add($"{nameof(LidgrenServerPeer)}.ProcessAuth", authenticator.VerifyTicket(authTicket), t =>
{
if (!t.TryGetResult(out AccountInfo accountInfo)
|| accountInfo.IsNone)
if (!t.TryGetResult(out AccountInfo accountInfo) || accountInfo.IsNone)
{
rejectClient();
if (GameMain.Server.ServerSettings.RequireAuthentication)
{
rejectClient();
}
else
{
acceptClient(new AccountInfo(new UnauthenticatedAccountId(packet.Name)));
}
return;
}

View File

@@ -74,7 +74,7 @@ namespace Barotrauma.Networking
{
var property = netProperties[key];
property.SyncValue();
if (NetIdUtils.IdMoreRecent(property.LastUpdateID, c.LastRecvLobbyUpdate))
if (NetIdUtils.IdMoreRecent(property.LastUpdateID, c.LastRecvLobbyUpdate) || !c.InitialLobbyUpdateSent)
{
outMsg.WriteUInt32(key);
netProperties[key].Write(outMsg);

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.6.19.1</Version>
<Version>1.7.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<CampaignSettingPresets>
<CampaignSettingDefinitions>
<StartingBalanceAmount high="10000" medium="8500" low="6000" />
@@ -20,6 +20,7 @@
OxygenMultiplier="1.2"
FuelMultiplier="1.2"
MissionRewardMultiplier="1.0"
ExperienceRewardMultiplier="1.0"
ShopPriceMultiplier="0.9"
ShipyardPriceMultiplier="0.9"
RepairFailMultiplier="1.0"
@@ -38,6 +39,7 @@
OxygenMultiplier="1.0"
FuelMultiplier="1.0"
MissionRewardMultiplier="1.0"
ExperienceRewardMultiplier="1.0"
ShopPriceMultiplier="1.0"
ShipyardPriceMultiplier="1.0"
RepairFailMultiplier="1.0"
@@ -56,6 +58,7 @@
OxygenMultiplier="0.7"
FuelMultiplier="0.9"
MissionRewardMultiplier="1.0"
ExperienceRewardMultiplier="1.0"
ShopPriceMultiplier="1.5"
ShipyardPriceMultiplier="1.5"
RepairFailMultiplier="2.0"
@@ -74,6 +77,7 @@
OxygenMultiplier="0.4"
FuelMultiplier="0.8"
MissionRewardMultiplier="0.8"
ExperienceRewardMultiplier="0.8"
ShopPriceMultiplier="2.0"
ShipyardPriceMultiplier="2.0"
RepairFailMultiplier="5.0"

Some files were not shown because too many files have changed in this diff Show More