Merge branch 'dev'

# Conflicts:
#	Barotrauma/BarotraumaClient/LinuxClient.csproj
#	Barotrauma/BarotraumaClient/MacClient.csproj
#	Barotrauma/BarotraumaClient/WindowsClient.csproj
#	Barotrauma/BarotraumaServer/LinuxServer.csproj
#	Barotrauma/BarotraumaServer/MacServer.csproj
#	Barotrauma/BarotraumaServer/WindowsServer.csproj
#	Barotrauma/BarotraumaShared/changelog.txt
This commit is contained in:
Regalis11
2022-06-15 19:09:38 +03:00
408 changed files with 11140 additions and 5797 deletions
@@ -6,7 +6,7 @@ using System;
namespace Barotrauma namespace Barotrauma
{ {
public class Camera public class Camera : IDisposable
{ {
public static bool FollowSub = true; public static bool FollowSub = true;
@@ -147,15 +147,19 @@ namespace Barotrauma
position = Vector2.Zero; position = Vector2.Zero;
CreateMatrices(); CreateMatrices();
// TODO: Needs to unregister if ever destroy cameras. // TODO: this has the potential to cause a resource leak
// by sneakily creating a reference to cameras that we might
// fail to release.
GameMain.Instance.ResolutionChanged += CreateMatrices; GameMain.Instance.ResolutionChanged += CreateMatrices;
UpdateTransform(false); UpdateTransform(false);
} }
~Camera() private bool disposed = false;
public void Dispose()
{ {
GameMain.Instance.ResolutionChanged -= CreateMatrices; if (!disposed) { GameMain.Instance.ResolutionChanged -= CreateMatrices; }
disposed = true;
} }
public Vector2 TargetPos { get; set; } public Vector2 TargetPos { get; set; }
@@ -400,7 +400,7 @@ namespace Barotrauma
partial void UpdateControlled(float deltaTime, Camera cam) partial void UpdateControlled(float deltaTime, Camera cam)
{ {
if (controlled != this) return; if (controlled != this) { return; }
ControlLocalPlayer(deltaTime, cam); ControlLocalPlayer(deltaTime, cam);
@@ -3,7 +3,6 @@ using Barotrauma.Items.Components;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -266,7 +266,7 @@ namespace Barotrauma
disguisedSkinColor = idCard.StoredOwnerAppearance.SkinColor; disguisedSkinColor = idCard.StoredOwnerAppearance.SkinColor;
} }
partial void LoadAttachmentSprites(bool omitJob) partial void LoadAttachmentSprites()
{ {
if (attachmentSprites == null) if (attachmentSprites == null)
{ {
@@ -280,14 +280,6 @@ namespace Barotrauma
Head.BeardElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Beard))); Head.BeardElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Beard)));
Head.MoustacheElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Moustache))); Head.MoustacheElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Moustache)));
Head.HairElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Hair))); Head.HairElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Hair)));
if (omitJob)
{
JobPrefab.NoJobElement?.GetChildElement("PortraitClothing")?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.JobIndicator)));
}
else
{
Job?.Prefab.ClothingElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.JobIndicator)));
}
} }
// Doesn't work if the head's source rect does not start at 0,0. // Doesn't work if the head's source rect does not start at 0,0.
@@ -988,11 +980,6 @@ namespace Barotrauma
HeadSelectionList = null; HeadSelectionList = null;
} }
} }
~AppearanceCustomizationMenu()
{
Dispose();
}
} }
} }
} }
@@ -551,6 +551,7 @@ namespace Barotrauma
{ {
bool hasOwner = inc.ReadBoolean(); bool hasOwner = inc.ReadBoolean();
int ownerId = hasOwner ? inc.ReadByte() : -1; int ownerId = hasOwner ? inc.ReadByte() : -1;
float humanPrefabHealthMultiplier = inc.ReadSingle();
int balance = inc.ReadInt32(); int balance = inc.ReadInt32();
int rewardDistribution = inc.ReadRangedInteger(0, 100); int rewardDistribution = inc.ReadRangedInteger(0, 100);
byte teamID = inc.ReadByte(); byte teamID = inc.ReadByte();
@@ -573,6 +574,7 @@ namespace Barotrauma
{ {
character.MerchantIdentifier = inc.ReadIdentifier(); character.MerchantIdentifier = inc.ReadIdentifier();
} }
character.HumanPrefabHealthMultiplier = humanPrefabHealthMultiplier;
character.Wallet.Balance = balance; character.Wallet.Balance = balance;
character.Wallet.RewardDistribution = rewardDistribution; character.Wallet.RewardDistribution = rewardDistribution;
if (character.CampaignInteractionType != CampaignMode.InteractionType.None) if (character.CampaignInteractionType != CampaignMode.InteractionType.None)
@@ -649,6 +651,8 @@ namespace Barotrauma
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f; GameMain.LightManager.LosAlpha = 1f;
GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
character.memInput.Clear(); character.memInput.Clear();
character.memState.Clear(); character.memState.Clear();
character.memLocalState.Clear(); character.memLocalState.Clear();
@@ -6,7 +6,6 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -1345,6 +1344,7 @@ namespace Barotrauma
{ {
UserData = item, UserData = item,
DisabledColor = Color.White * 0.1f, DisabledColor = Color.White * 0.1f,
PlaySoundOnSelect = false,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!(userdata is ItemPrefab itemPrefab)) { return false; } if (!(userdata is ItemPrefab itemPrefab)) { return false; }
@@ -1352,6 +1352,7 @@ namespace Barotrauma
if (item == null) { return false; } if (item == null) { return false; }
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex); Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex);
item.ApplyTreatment(Character.Controlled, Character, targetLimb); item.ApplyTreatment(Character.Controlled, Character, targetLimb);
SoundPlayer.PlayUISound(GUISoundType.Select);
return true; return true;
} }
}; };
@@ -2008,8 +2009,27 @@ namespace Barotrauma
DisplayedVitality = Vitality; DisplayedVitality = Vitality;
} }
partial void UpdateSkinTint()
{
if (!Character.IsVisible) { return; }
FaceTint = DefaultFaceTint;
BodyTint = Color.TransparentBlack;
if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; }
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{
var affliction = kvp.Key;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
}
}
partial void UpdateLimbAfflictionOverlays() partial void UpdateLimbAfflictionOverlays()
{ {
if (!Character.IsVisible) { return; }
foreach (Limb limb in Character.AnimController.Limbs) foreach (Limb limb in Character.AnimController.Limbs)
{ {
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; } if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
@@ -58,21 +58,19 @@ namespace Barotrauma
public class OutfitPreview public class OutfitPreview
{ {
/// <summary> public readonly List<(Sprite sprite, Vector2 drawOffset)> Sprites;
/// Pair.First = sprite, Pair.Second = draw offset
/// </summary>
public readonly List<Pair<Sprite, Vector2>> Sprites;
public Vector2 Dimensions; public Vector2 Dimensions;
public OutfitPreview() public OutfitPreview()
{ {
Sprites = new List<Pair<Sprite, Vector2>>(); Sprites = new List<(Sprite sprite, Vector2 drawOffset)>();
Dimensions = Vector2.One; Dimensions = Vector2.One;
} }
public void AddSprite(Sprite sprite, Vector2 drawOffset) public void AddSprite(Sprite sprite, Vector2 drawOffset)
{ {
Sprites.Add(new Pair<Sprite, Vector2>(sprite, drawOffset)); Sprites.Add((sprite, drawOffset));
} }
} }
@@ -224,14 +224,14 @@ namespace Barotrauma
public float DamageOverlayStrength public float DamageOverlayStrength
{ {
get { return damageOverlayStrength; } get { return damageOverlayStrength; }
set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
} }
private float burnOverLayStrength; private float burnOverLayStrength;
public float BurnOverlayStrength public float BurnOverlayStrength
{ {
get { return burnOverLayStrength; } get { return burnOverLayStrength; }
set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
} }
public string HitSoundTag => Params?.Sound?.Tag; public string HitSoundTag => Params?.Sound?.Tag;
@@ -279,7 +279,7 @@ namespace Barotrauma
for (int i = 0; i < Params.decorativeSpriteParams.Count; i++) for (int i = 0; i < Params.decorativeSpriteParams.Count; i++)
{ {
var param = Params.decorativeSpriteParams[i]; var param = Params.decorativeSpriteParams[i];
var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param)); var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param, ref _texturePath));
DecorativeSprites.Add(decorativeSprite); DecorativeSprites.Add(decorativeSprite);
int groupID = decorativeSprite.RandomGroupID; int groupID = decorativeSprite.RandomGroupID;
if (!DecorativeSpriteGroups.ContainsKey(groupID)) if (!DecorativeSpriteGroups.ContainsKey(groupID))
@@ -295,13 +295,13 @@ namespace Barotrauma
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "sprite": case "sprite":
Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams)); Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams, ref _texturePath));
break; break;
case "damagedsprite": case "damagedsprite":
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams)); DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath));
break; break;
case "conditionalsprite": case "conditionalsprite":
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null)); var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath));
ConditionalSprites.Add(conditionalSprite); ConditionalSprites.Add(conditionalSprite);
if (conditionalSprite.DeformableSprite != null) if (conditionalSprite.DeformableSprite != null)
{ {
@@ -311,7 +311,7 @@ namespace Barotrauma
} }
break; break;
case "deformablesprite": case "deformablesprite":
_deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams)); _deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams, ref _texturePath));
var deformations = CreateDeformations(subElement); var deformations = CreateDeformations(subElement);
Deformations.AddRange(deformations); Deformations.AddRange(deformations);
NonConditionalDeformations.AddRange(deformations); NonConditionalDeformations.AddRange(deformations);
@@ -435,33 +435,33 @@ namespace Barotrauma
{ {
Sprite.Remove(); Sprite.Remove();
var source = Sprite.SourceElement; var source = Sprite.SourceElement;
Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams)); Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams, ref _texturePath));
} }
if (_deformSprite != null) if (_deformSprite != null)
{ {
_deformSprite.Remove(); _deformSprite.Remove();
var source = _deformSprite.Sprite.SourceElement; var source = _deformSprite.Sprite.SourceElement;
_deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams)); _deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams, ref _texturePath));
} }
if (DamagedSprite != null) if (DamagedSprite != null)
{ {
DamagedSprite.Remove(); DamagedSprite.Remove();
var source = DamagedSprite.SourceElement; var source = DamagedSprite.SourceElement;
DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams)); DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams, ref _damagedTexturePath));
} }
for (int i = 0; i < ConditionalSprites.Count; i++) for (int i = 0; i < ConditionalSprites.Count; i++)
{ {
var conditionalSprite = ConditionalSprites[i]; var conditionalSprite = ConditionalSprites[i];
var source = conditionalSprite.ActiveSprite.SourceElement; var source = conditionalSprite.ActiveSprite.SourceElement;
conditionalSprite.Remove(); conditionalSprite.Remove();
ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null)); ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null, ref _texturePath));
} }
for (int i = 0; i < DecorativeSprites.Count; i++) for (int i = 0; i < DecorativeSprites.Count; i++)
{ {
var decorativeSprite = DecorativeSprites[i]; var decorativeSprite = DecorativeSprites[i];
decorativeSprite.Remove(); decorativeSprite.Remove();
var source = decorativeSprite.Sprite.SourceElement; var source = decorativeSprite.Sprite.SourceElement;
DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i])); DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i], ref _texturePath));
} }
} }
@@ -472,16 +472,17 @@ namespace Barotrauma
} }
private string _texturePath; private string _texturePath;
private string GetSpritePath(ContentXElement element, SpriteParams spriteParams) private string _damagedTexturePath;
private string GetSpritePath(ContentXElement element, SpriteParams spriteParams, ref string path)
{ {
if (_texturePath == null) if (path == null)
{ {
if (spriteParams != null) if (spriteParams != null)
{ {
ContentPath texturePath = ContentPath texturePath =
character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage) character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage)
?? ContentPath.FromRaw(character.Prefab.ContentPackage, spriteParams.GetTexturePath()); ?? ContentPath.FromRaw(character.Prefab.ContentPackage, spriteParams.GetTexturePath());
_texturePath = GetSpritePath(texturePath); path = GetSpritePath(texturePath);
} }
else else
{ {
@@ -489,10 +490,10 @@ namespace Barotrauma
texturePath = texturePath.IsNullOrWhiteSpace() texturePath = texturePath.IsNullOrWhiteSpace()
? ContentPath.FromRaw(character.Prefab.ContentPackage, ragdoll.RagdollParams.Texture) ? ContentPath.FromRaw(character.Prefab.ContentPackage, ragdoll.RagdollParams.Texture)
: texturePath; : texturePath;
_texturePath = GetSpritePath(texturePath); path = GetSpritePath(texturePath);
} }
} }
return _texturePath; return path;
} }
/// <summary> /// <summary>
@@ -625,12 +626,7 @@ namespace Barotrauma
{ {
if (!body.Enabled) { return; } if (!body.Enabled) { return; }
if (!IsDead) if (IsDead)
{
DamageOverlayStrength -= deltaTime;
BurnOverlayStrength -= deltaTime;
}
else
{ {
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams != null && spriteParams.DeadColorTime > 0 && deadTimer < spriteParams.DeadColorTime) if (spriteParams != null && spriteParams.DeadColorTime > 0 && deadTimer < spriteParams.DeadColorTime)
@@ -688,7 +684,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false) public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
{ {
float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f; float brightness = Math.Max(1.0f - burnOverLayStrength, 0.2f);
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; } if (spriteParams == null) { return; }
@@ -831,32 +827,6 @@ namespace Barotrauma
{ {
LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically; LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
} }
if (damageOverlayStrength > 0.0f && DamagedSprite != null && !hideLimb)
{
DamagedSprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin,
-body.DrawRotation,
Scale, spriteEffect, activeSprite.Depth - (depthStep * 90));
}
foreach (var decorativeSprite in DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f);
if (deadTimer > 0)
{
c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer));
}
c = overrideColor ?? c;
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: activeSprite.Depth - (depthStep * 100));
}
float step = depthStep; float step = depthStep;
WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables); WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables);
if (Params.MirrorHorizontally) if (Params.MirrorHorizontally)
@@ -925,6 +895,36 @@ namespace Barotrauma
//if there are multiple sprites on this limb, make the successive ones be drawn in front //if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step; depthStep += step;
} }
if (!Hide && onlyDrawable == null)
{
foreach (var decorativeSprite in DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f);
if (deadTimer > 0)
{
c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer));
}
c = overrideColor ?? c;
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: activeSprite.Depth - depthStep);
depthStep += step;
}
if (damageOverlayStrength > 0.0f && DamagedSprite != null)
{
DamagedSprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
color * 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
}
}
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
@@ -108,6 +108,15 @@ namespace Barotrauma
} }
} }
public void RemoveFile(File file)
{
if (HasFile(file))
{
files.Remove(file);
DiscardHashAndInstallTime();
}
}
public void DiscardHashAndInstallTime() public void DiscardHashAndInstallTime()
{ {
ExpectedHash = null; ExpectedHash = null;
@@ -144,7 +153,7 @@ namespace Barotrauma
=> rootElement.Add(new XAttribute(name, value.ToString() ?? "")); => rootElement.Add(new XAttribute(name, value.ToString() ?? ""));
addRootAttribute("name", Name); addRootAttribute("name", Name);
addRootAttribute("modversion", ModVersion); if (!ModVersion.IsNullOrEmpty()) { addRootAttribute("modversion", ModVersion); }
addRootAttribute("corepackage", IsCore); addRootAttribute("corepackage", IsCore);
if (SteamWorkshopId != 0) { addRootAttribute("steamworkshopid", SteamWorkshopId); } if (SteamWorkshopId != 0) { addRootAttribute("steamworkshopid", SteamWorkshopId); }
addRootAttribute("gameversion", GameMain.Version); addRootAttribute("gameversion", GameMain.Version);
@@ -0,0 +1,133 @@
#nullable enable
using System;
using System.Linq;
using Barotrauma.Steam;
using Barotrauma.IO;
namespace Barotrauma
{
public static class ModMerger
{
public static void AskMerge(ContentPackage[] mods)
{
ErrorIfNonLocal(mods);
var msgBox = new GUIMessageBox(TextManager.Get("MergeModsHeader"), "", relativeSize: (0.5f, 0.8f),
buttons: new LocalizedString[] { TextManager.Get("ConfirmModMerge"), TextManager.Get("Cancel") });
msgBox.Buttons[1].OnClicked = msgBox.Close;
var desc = new GUITextBlock(new RectTransform((1.0f, 0.1f), msgBox.Content.RectTransform), TextManager.Get("MergeModsDesc"));
var modsList = new GUIListBox(new RectTransform((1.0f, 0.5f), msgBox.Content.RectTransform))
{
OnSelected = (component, o) => false,
HoverCursor = CursorState.Default
};
foreach (var mod in mods)
{
new GUITextBlock(new RectTransform((1.0f, 0.11f), modsList.Content.RectTransform), mod.Name)
{
CanBeFocused = false
};
}
var footer = new GUITextBlock(new RectTransform((1.0f, 0.1f), msgBox.Content.RectTransform), TextManager.Get("MergeModsFooter"));
var resultName = new GUITextBox(new RectTransform((1.0f, 0.1f), msgBox.Content.RectTransform))
{
Text = (mods.Count(m => m.Files.Length > 1)==1)
? mods.First(m => m.Files.Length > 1).Name
: ""
};
void flashText()
{
resultName!.Select();
resultName.Flash(GUIStyle.Red);
}
msgBox.Buttons[0].OnClicked = (button, o) =>
{
if (string.IsNullOrEmpty(resultName.Text))
{
flashText();
return false;
}
string targetDir = $"{ContentPackage.LocalModsDir}/{resultName.Text}";
bool dirMatches(ContentPackage mod)
=> mod.Dir.CleanUpPathCrossPlatform(correctFilenameCase: false)
.Equals(targetDir, StringComparison.OrdinalIgnoreCase);
if (ContentPackageManager.LocalPackages.Any(dirMatches)
&& !mods.Any(dirMatches))
{
flashText();
return false;
}
MergeMods(mods, resultName.Text);
msgBox.Close();
return false;
};
}
private static void MergeMods(ContentPackage[] mods, string resultName)
{
ModProject resultProject = new ModProject
{
Name = resultName
};
string targetDir = $"{ContentPackage.LocalModsDir}/{resultName}";
Directory.CreateDirectory(targetDir);
foreach (var mod in mods)
{
foreach (var file in Directory.GetFiles(mod.Dir, "*", System.IO.SearchOption.AllDirectories)
.Select(f => f.CleanUpPathCrossPlatform(correctFilenameCase: false)))
{
if (Path.GetFileName(file).Equals(ContentPackage.FileListFileName, StringComparison.OrdinalIgnoreCase)) { continue; }
string targetFilePath = file[mod.Dir.Length..];
if (targetFilePath.StartsWith("/") || targetFilePath.StartsWith("\\"))
{
targetFilePath = targetFilePath[1..];
}
targetFilePath = Path.Combine(targetDir, targetFilePath).CleanUpPathCrossPlatform(correctFilenameCase: false);
//DebugConsole.NewMessage(targetFilePath);
Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath)!);
File.Copy(file, targetFilePath, overwrite: true);
var oldFileInProject = resultProject.Files.FirstOrDefault(f
=> f.Path.Equals(targetFilePath, StringComparison.OrdinalIgnoreCase));
if (oldFileInProject != null)
{
resultProject.RemoveFile(oldFileInProject);
}
var fileInMod = mod.Files.Find(f => f.Path == file);
if (fileInMod != null)
{
var newFileInProject = ModProject.File.FromPath(targetFilePath, fileInMod.GetType());
resultProject.AddFile(newFileInProject);
}
}
}
resultProject.Save(Path.Combine(targetDir, ContentPackage.FileListFileName));
foreach (var mod in mods)
{
Directory.Delete(mod.Dir);
}
(SettingsMenu.Instance!.WorkshopMenu as MutableWorkshopMenu)!.PopulateInstalledModLists(forceRefreshEnabled: true, refreshDisabled: true);
}
private static void ErrorIfNonLocal(ContentPackage[] mods)
{
var nonLocal = mods.Where(m => !ContentPackageManager.LocalPackages.Contains(m)).ToArray();
if (nonLocal.Any())
{
throw new Exception($"{string.Join(", ", nonLocal.Select(m => m.Name))} are not local mods");
}
}
}
}
@@ -1,7 +1,6 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -9,9 +8,7 @@ using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Steam; using Barotrauma.Steam;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Directory = Barotrauma.IO.Directory; using Barotrauma.IO;
using File = Barotrauma.IO.File;
using Path = Barotrauma.IO.Path;
namespace Barotrauma.Transition namespace Barotrauma.Transition
{ {
@@ -258,13 +255,13 @@ namespace Barotrauma.Transition
{ {
string[] getFiles(string path, string pattern) string[] getFiles(string path, string pattern)
=> Directory.Exists(path) => Directory.Exists(path)
? Directory.GetFiles(path, pattern, SearchOption.TopDirectoryOnly) ? Directory.GetFiles(path, pattern, System.IO.SearchOption.TopDirectoryOnly)
: Array.Empty<string>(); : Array.Empty<string>();
subs = getFiles(oldSubsPath, "*.sub"); subs = getFiles(oldSubsPath, "*.sub");
itemAssemblies = getFiles(oldItemAssembliesPath, "*.xml"); itemAssemblies = getFiles(oldItemAssembliesPath, "*.xml");
string[] allOldMods = Directory.GetDirectories(oldModsPath, "*", SearchOption.TopDirectoryOnly); string[] allOldMods = Directory.GetDirectories(oldModsPath, "*", System.IO.SearchOption.TopDirectoryOnly);
var publishedItems = await SteamManager.Workshop.GetPublishedItems(); var publishedItems = await SteamManager.Workshop.GetPublishedItems();
foreach (var modDir in allOldMods) foreach (var modDir in allOldMods)
@@ -359,7 +356,7 @@ namespace Barotrauma.Transition
else else
{ {
//copying a mod: we have a neat method for that! //copying a mod: we have a neat method for that!
await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath); await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath, SteamManager.Workshop.ShouldCorrectPaths.Yes);
return null; return null;
} }
@@ -125,7 +125,7 @@ namespace Barotrauma
{ {
if (isOpen) if (isOpen)
{ {
frame.AddToGUIUpdateList(); frame.AddToGUIUpdateList(order: 1);
} }
} }
@@ -172,6 +172,7 @@ namespace Barotrauma
isOpen = false; isOpen = false;
GUI.ForceMouseOn(null); GUI.ForceMouseOn(null);
textBox.Deselect(); textBox.Deselect();
SoundPlayer.PlayUISound(GUISoundType.Select);
} }
if (isOpen) if (isOpen)
@@ -209,7 +210,7 @@ namespace Barotrauma
isOpen = !isOpen; isOpen = !isOpen;
if (isOpen) if (isOpen)
{ {
textBox.Select(); textBox.Select(ignoreSelectSound: true);
AddToGUIUpdateList(); AddToGUIUpdateList();
} }
else else
@@ -217,6 +218,7 @@ namespace Barotrauma
GUI.ForceMouseOn(null); GUI.ForceMouseOn(null);
textBox.Deselect(); textBox.Deselect();
} }
SoundPlayer.PlayUISound(GUISoundType.Select);
} }
private static bool IsCommandPermitted(string command, GameClient client) private static bool IsCommandPermitted(string command, GameClient client)
@@ -1714,9 +1716,47 @@ namespace Barotrauma
//check missing mission texts //check missing mission texts
foreach (var missionPrefab in MissionPrefab.Prefabs) foreach (var missionPrefab in MissionPrefab.Prefabs)
{ {
Identifier missionId = (missionPrefab.ConfigElement.GetAttribute("textidentifier") == null ? missionPrefab.Identifier : missionPrefab.ConfigElement.GetAttributeIdentifier("textidentifier", Identifier.Empty)); Identifier missionId = missionPrefab.ConfigElement.GetAttribute("textidentifier") == null ?
addIfMissing($"missionname.{missionId}".ToIdentifier(), language); missionPrefab.Identifier :
addIfMissing($"missiondescription.{missionId}".ToIdentifier(), language); missionPrefab.ConfigElement.GetAttributeIdentifier("textidentifier", Identifier.Empty);
if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("name", Identifier.Empty)))
{
addIfMissing($"missionname.{missionId}".ToIdentifier(), language);
}
if (missionPrefab.Type == MissionType.Combat)
{
addIfMissing($"MissionDescriptionNeutral.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionDescription1.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionDescription2.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionTeam1.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionTeam2.{missionId}".ToIdentifier(), language);
}
else
{
if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("description", Identifier.Empty)))
{
addIfMissing($"missiondescription.{missionId}".ToIdentifier(), language);
}
if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("successmessage", Identifier.Empty)))
{
addIfMissing($"missionsuccess.{missionId}".ToIdentifier(), language);
}
//only check failure message if there's something defined in the xml (otherwise we just use the generic "missionfailed" text)
if (missionPrefab.ConfigElement.GetAttribute("failuremessage") != null &&
!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("failuremessage", Identifier.Empty)))
{
addIfMissing($"missionfailure.{missionId}".ToIdentifier(), language);
}
}
for (int i = 0; i<missionPrefab.Messages.Length; i++)
{
if (missionPrefab.Messages[i].IsNullOrWhiteSpace())
{
addIfMissing($"MissionMessage{i}.{missionId}".ToIdentifier(), language);
}
}
} }
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent)))) foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
@@ -2503,7 +2543,7 @@ namespace Barotrauma
var entity = MapEntity.mapEntityList[i] as ISerializableEntity; var entity = MapEntity.mapEntityList[i] as ISerializableEntity;
if (entity != null) if (entity != null)
{ {
List<Pair<object, SerializableProperty>> allProperties = new List<Pair<object, SerializableProperty>>(); List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>();
if (entity is Item item) if (entity is Item item)
{ {
@@ -2518,14 +2558,14 @@ namespace Barotrauma
for (int k = 0; k < properties.Count; k++) for (int k = 0; k < properties.Count; k++)
{ {
allProperties.Add(new Pair<object, SerializableProperty>(entity, properties[k])); allProperties.Add((entity, properties[k]));
} }
} }
for (int j = 0; j < allProperties.Count; j++) for (int j = 0; j < allProperties.Count; j++)
{ {
var property = allProperties[j].Second; var property = allProperties[j].property;
string propertyName = (allProperties[j].First.GetType().Name + "." + property.PropertyInfo.Name).ToLowerInvariant(); string propertyName = (allProperties[j].obj.GetType().Name + "." + property.PropertyInfo.Name).ToLowerInvariant();
LocalizedString displayName = TextManager.Get($"sp.{propertyName}.name"); LocalizedString displayName = TextManager.Get($"sp.{propertyName}.name");
if (displayName.IsNullOrEmpty()) if (displayName.IsNullOrEmpty())
{ {
@@ -308,7 +308,7 @@ namespace Barotrauma
AlwaysOverrideCursor = true AlwaysOverrideCursor = true
}; };
LocalizedString translatedText = TextManager.Get(text); LocalizedString translatedText = TextManager.Get(text).Fallback(text);
if (speaker?.Info != null && drawChathead) if (speaker?.Info != null && drawChathead)
{ {
@@ -335,7 +335,7 @@ namespace Barotrauma
{ {
foreach (string option in options) foreach (string option in options)
{ {
var btn = new GUIButton(new RectTransform(new Vector2(0.9f, 0.01f), textContent.RectTransform), TextManager.Get(option), style: "ListBoxElement"); var btn = new GUIButton(new RectTransform(new Vector2(0.9f, 0.01f), textContent.RectTransform), TextManager.Get(option).Fallback(option), style: "ListBoxElement");
btn.TextBlock.TextAlignment = Alignment.CenterLeft; btn.TextBlock.TextAlignment = Alignment.CenterLeft;
btn.TextColor = btn.HoverTextColor = GUIStyle.Green; btn.TextColor = btn.HoverTextColor = GUIStyle.Green;
btn.TextBlock.Wrap = true; btn.TextBlock.Wrap = true;
@@ -20,14 +20,12 @@ namespace Barotrauma
if (targetId == Entity.NullEntityID) { continue; } if (targetId == Entity.NullEntityID) { continue; }
Entity target = Entity.FindEntityByID(targetId); Entity target = Entity.FindEntityByID(targetId);
if (target == null) { continue; } if (target == null) { continue; }
existingTargets.Add(target);
allTargets.Add(target); allTargets.Add(target);
} }
ushort spawnedTargetsCount = msg.ReadUInt16(); ushort spawnedTargetsCount = msg.ReadUInt16();
for (int i = 0; i < spawnedTargetsCount; i++) for (int i = 0; i < spawnedTargetsCount; i++)
{ {
var enemy = Character.ReadSpawnData(msg); var enemy = Character.ReadSpawnData(msg);
existingTargets.Add(enemy);
allTargets.Add(enemy); allTargets.Add(enemy);
} }
} }
@@ -81,6 +81,8 @@ namespace Barotrauma
public const int ToggleButtonWidthRaw = 30; public const int ToggleButtonWidthRaw = 30;
private int popupMessageOffset; private int popupMessageOffset;
private GUIDropDown ChatModeDropDown { get; set; }
public ChatBox(GUIComponent parent, bool isSinglePlayer) public ChatBox(GUIComponent parent, bool isSinglePlayer)
{ {
this.IsSinglePlayer = isSinglePlayer; this.IsSinglePlayer = isSinglePlayer;
@@ -107,6 +109,7 @@ namespace Barotrauma
var buttonLeft = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton") var buttonLeft = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
{ {
PlaySoundOnSelect = false,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio)) if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
@@ -150,6 +153,7 @@ namespace Barotrauma
var buttonRight = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton") var buttonRight = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
{ {
PlaySoundOnSelect = false,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio)) if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
@@ -178,6 +182,7 @@ namespace Barotrauma
TextColor = new Color(51, 59, 46), TextColor = new Color(51, 59, 46),
SelectedTextColor = GUIStyle.Green, SelectedTextColor = GUIStyle.Green,
UserData = i, UserData = i,
PlaySoundOnSelect = false,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio)) if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
@@ -223,7 +228,53 @@ namespace Barotrauma
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
InputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft), var bottomContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft), isHorizontal: true)
{
Stretch = true
};
var dropdownRt = new RectTransform(new Vector2(0.1f, 1.0f), bottomContainer.RectTransform)
{
// The chat mode selection dropdown will take a maximum of 45% of the horizontal space
MaxSize = new Point((int)(0.45f * bottomContainer.RectTransform.NonScaledSize.X), int.MaxValue)
};
var chatModes = new ChatMode[] { ChatMode.Local, ChatMode.Radio };
ChatModeDropDown = new GUIDropDown(dropdownRt, elementCount: chatModes.Length, dropAbove: true)
{
OnSelected = (component, userdata) =>
{
GameMain.ActiveChatMode = (ChatMode)userdata;
if (InputBox != null && InputBox.Text.StartsWith(RadioChatString) && GameMain.ActiveChatMode == ChatMode.Local)
{
string text = InputBox.Text;
InputBox.Text = text.Remove(0, RadioChatString.Length);
}
return true;
}
};
float longestDropDownOption = 0.0f;
foreach (ChatMode mode in chatModes)
{
var text = TextManager.Get($"chatmode.{mode}");
ChatModeDropDown.AddItem(text, userData: mode);
if (ChatModeDropDown.ListBox.Content.GetChildByUserData(mode) is GUITextBlock textBlock)
{
if (textBlock.TextSize.X > longestDropDownOption)
{
longestDropDownOption = textBlock.TextSize.X;
}
}
}
ChatModeDropDown.SelectItem(GameMain.ActiveChatMode);
float minDropDownWidth = longestDropDownOption + ChatModeDropDown.Padding.X +
(ChatModeDropDown.DropDownIcon?.RectTransform.NonScaledSize.X ?? 0) +
(ChatModeDropDown.DropDownIcon?.RectTransform.AbsoluteOffset.X ?? 0) * 2;
ChatModeDropDown.RectTransform.MinSize = new Point(
Math.Max((int)minDropDownWidth, ChatModeDropDown.RectTransform.MinSize.X),
ChatModeDropDown.RectTransform.MinSize.Y);
InputBox = new GUITextBox(new RectTransform(new Vector2(0.9f, 1.0f), bottomContainer.RectTransform),
style: "ChatTextBox") style: "ChatTextBox")
{ {
OverflowClip = true, OverflowClip = true,
@@ -236,6 +287,11 @@ namespace Barotrauma
InputBox.OnDeselected += (gui, Keys) => InputBox.OnDeselected += (gui, Keys) =>
{ {
ChatManager.Clear(); ChatManager.Clear();
if (GUIFrame.IsParentOf(GUI.MouseOn))
{
CloseAfterMessageSent = false;
return;
}
ChatMessage.GetChatMessageCommand(InputBox.Text, out var message); ChatMessage.GetChatMessageCommand(InputBox.Text, out var message);
if (string.IsNullOrEmpty(message)) if (string.IsNullOrEmpty(message))
{ {
@@ -245,8 +301,6 @@ namespace Barotrauma
CloseAfterMessageSent = false; CloseAfterMessageSent = false;
} }
} }
//gui.Text = "";
}; };
var chatSendButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.7f), InputBox.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonToggleRight"); var chatSendButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.7f), InputBox.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonToggleRight");
@@ -303,6 +357,10 @@ namespace Barotrauma
{ {
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Private]; textColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
} }
else if (GameMain.ActiveChatMode == ChatMode.Radio)
{
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
}
else else
{ {
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Default]; textColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
@@ -357,10 +415,15 @@ namespace Barotrauma
CanBeFocused = true, CanBeFocused = true,
ForceUpperCase = ForceUpperCase.No, ForceUpperCase = ForceUpperCase.No,
UserData = message.SenderClient, UserData = message.SenderClient,
PlaySoundOnSelect = false,
OnClicked = (_, o) => OnClicked = (_, o) =>
{ {
if (!(o is Client client)) { return false; } if (!(o is Client client)) { return false; }
GameMain.NetLobbyScreen?.SelectPlayer(client); if (GameMain.NetLobbyScreen != null)
{
GameMain.NetLobbyScreen.SelectPlayer(client);
SoundPlayer.PlayUISound(GUISoundType.Select);
}
return true; return true;
}, },
OnSecondaryClicked = (_, o) => OnSecondaryClicked = (_, o) =>
@@ -542,6 +605,25 @@ namespace Barotrauma
showNewMessagesButton.Visible = false; showNewMessagesButton.Visible = false;
} }
if (PlayerInput.KeyHit(InputType.ToggleChatMode) && GUI.KeyboardDispatcher.Subscriber == null && Screen.Selected == GameMain.GameScreen)
{
try
{
var mode = GameMain.ActiveChatMode switch
{
ChatMode.Local => ChatMode.Radio,
ChatMode.Radio => ChatMode.Local,
_ => throw new NotImplementedException()
};
ChatModeDropDown.SelectItem(mode);
// TODO: Play a sound?
}
catch (NotImplementedException)
{
DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\"");
}
}
if (ToggleButton != null) if (ToggleButton != null)
{ {
ToggleButton.Selected = ToggleOpen; ToggleButton.Selected = ToggleOpen;
@@ -692,5 +774,70 @@ namespace Barotrauma
} }
} }
} }
public void ApplySelectionInputs() => ApplySelectionInputs(InputBox, true, ChatKeyStates.GetChatKeyStates());
public struct ChatKeyStates
{
public bool ActiveChatKeyHit { get; set; }
public bool LocalChatKeyHit { get; set; }
public bool RadioChatKeyHit { get; set; }
public bool AnyHit => ActiveChatKeyHit || LocalChatKeyHit || RadioChatKeyHit;
private ChatKeyStates(bool active, bool local, bool radio)
{
ActiveChatKeyHit = active;
LocalChatKeyHit = local;
RadioChatKeyHit = radio;
}
public static ChatKeyStates GetChatKeyStates()
{
return new ChatKeyStates(PlayerInput.KeyHit(InputType.ActiveChat),
PlayerInput.KeyHit(InputType.Chat),
PlayerInput.KeyHit(InputType.RadioChat) && (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100));
}
public (bool active, bool local, bool radio) Deconstruct()
{
return (ActiveChatKeyHit, LocalChatKeyHit, RadioChatKeyHit);
}
}
public void ApplySelectionInputs(GUITextBox inputBox, bool selectInputBox, ChatKeyStates chatKeyStates)
{
inputBox ??= InputBox;
var (activeChatKeyHit, localChatKeyHit, radioChatKeyHit) = chatKeyStates.Deconstruct();
if (localChatKeyHit || (activeChatKeyHit && GameMain.ActiveChatMode == ChatMode.Local))
{
ChatModeDropDown.SelectItem(ChatMode.Local);
inputBox.AddToGUIUpdateList();
GUIFrame.Flash(Color.DarkGreen, 0.5f);
if (!ToggleOpen)
{
CloseAfterMessageSent = !ToggleOpen;
ToggleOpen = true;
}
if (selectInputBox)
{
inputBox.Select(inputBox.Text.Length);
}
}
else if (radioChatKeyHit || (activeChatKeyHit && GameMain.ActiveChatMode == ChatMode.Radio))
{
ChatModeDropDown.SelectItem(ChatMode.Radio);
inputBox.AddToGUIUpdateList();
GUIFrame.Flash(Color.YellowGreen, 0.5f);
if (!ToggleOpen)
{
CloseAfterMessageSent = !ToggleOpen;
ToggleOpen = true;
}
if (selectInputBox)
{
inputBox.Select(inputBox.Text.Length);
}
}
}
} }
} }
@@ -178,7 +178,18 @@ namespace Barotrauma
return Sprites.ContainsKey(state) ? Sprites[state]?.First()?.Sprite : null; return Sprites.ContainsKey(state) ? Sprites[state]?.First()?.Sprite : null;
} }
public void GetSize(XElement element) public void RefreshSize()
{
Width = null;
Height = null;
GetSize(Element);
foreach (var childStyle in ChildStyles.Values)
{
childStyle.RefreshSize();
}
}
private void GetSize(XElement element)
{ {
Point size = new Point(0, 0); Point size = new Point(0, 0);
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
@@ -193,12 +193,13 @@ namespace Barotrauma
}; };
validateHiresButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaigncrew.validate")) validateHiresButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaigncrew.validate"))
{ {
ClickSound = GUISoundType.HireRepairClick, ClickSound = GUISoundType.ConfirmTransaction,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
OnClicked = (b, o) => ValidateHires(PendingHires, true) OnClicked = (b, o) => ValidateHires(PendingHires, true)
}; };
clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall")) clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall"))
{ {
ClickSound = GUISoundType.Cart,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
Enabled = HasPermission, Enabled = HasPermission,
OnClicked = (b, o) => RemoveAllPendingHires() OnClicked = (b, o) => RemoveAllPendingHires()
@@ -403,6 +404,7 @@ namespace Barotrauma
{ {
var hireButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton") var hireButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
{ {
ClickSound = GUISoundType.Cart,
UserData = characterInfo, UserData = characterInfo,
Enabled = HasPermission, Enabled = HasPermission,
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo) OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
@@ -429,6 +431,7 @@ namespace Barotrauma
{ {
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton") new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
{ {
ClickSound = GUISoundType.Cart,
UserData = characterInfo, UserData = characterInfo,
Enabled = HasPermission, Enabled = HasPermission,
OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo) OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo)
@@ -182,7 +182,10 @@ namespace Barotrauma
window = new GUIFrame(new RectTransform(Vector2.One * 0.8f, backgroundFrame.RectTransform, Anchor.Center)); window = new GUIFrame(new RectTransform(Vector2.One * 0.8f, backgroundFrame.RectTransform, Anchor.Center));
var horizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, window.RectTransform, Anchor.Center), true); var horizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, window.RectTransform, Anchor.Center), true);
sidebar = new GUIListBox(new RectTransform(new Vector2(0.29f, 1.0f), horizontalLayout.RectTransform)); sidebar = new GUIListBox(new RectTransform(new Vector2(0.29f, 1.0f), horizontalLayout.RectTransform))
{
PlaySoundOnSelect = true
};
var drives = System.IO.DriveInfo.GetDrives(); var drives = System.IO.DriveInfo.GetDrives();
foreach (var drive in drives) foreach (var drive in drives)
@@ -241,6 +244,7 @@ namespace Barotrauma
fileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), fileListLayout.RectTransform)) fileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), fileListLayout.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = (child, userdata) => OnSelected = (child, userdata) =>
{ {
if (userdata is null) { return false; } if (userdata is null) { return false; }
@@ -24,15 +24,17 @@ namespace Barotrauma
ChatMessage, ChatMessage,
RadioMessage, RadioMessage,
DeadMessage, DeadMessage,
Click, Select,
PickItem, PickItem,
PickItemFail, PickItemFail,
DropItem, DropItem,
PopupMenu, PopupMenu,
DecreaseQuantity, Decrease,
IncreaseQuantity, Increase,
HireRepairClick, UISwitch,
UISwitch TickBox,
ConfirmTransaction,
Cart,
} }
public enum CursorState public enum CursorState
@@ -301,6 +303,7 @@ namespace Barotrauma
} }
float startY = 10.0f; float startY = 10.0f;
float yStep = AdjustForTextScale(18) * yScale;
if (GameMain.ShowFPS || GameMain.DebugDraw || GameMain.ShowPerf) if (GameMain.ShowFPS || GameMain.DebugDraw || GameMain.ShowPerf)
{ {
float y = startY; float y = startY;
@@ -309,11 +312,38 @@ namespace Barotrauma
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
if (GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 1.0) if (GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 1.0)
{ {
y += AdjustForTextScale(15) * yScale; y += yStep;
DrawString(spriteBatch, new Vector2(10, y), DrawString(spriteBatch, new Vector2(10, y),
$"Physics: {GameMain.CurrentUpdateRate}", $"Physics: {GameMain.CurrentUpdateRate}",
(GameMain.CurrentUpdateRate < Timing.FixedUpdateRate) ? Color.Red : Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); (GameMain.CurrentUpdateRate < Timing.FixedUpdateRate) ? Color.Red : Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
} }
if (GameMain.DebugDraw || GameMain.ShowPerf)
{
y += yStep;
DrawString(spriteBatch, new Vector2(10, y),
"Active lights: " + Lights.LightManager.ActiveLightCount,
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += yStep;
DrawString(spriteBatch, new Vector2(10, y),
"Physics: " + GameMain.World.UpdateTime.TotalMilliseconds + " ms",
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += yStep;
try
{
DrawString(spriteBatch, new Vector2(10, y),
$"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.Count(b => b != null && b.Awake && b.Enabled)} awake, {GameMain.World.BodyList.Count(b => b != null && b.Awake && b.BodyType == BodyType.Dynamic && b.Enabled)} dynamic)",
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
}
catch (InvalidOperationException)
{
DebugConsole.AddWarning("Exception while rendering debug info. Physics bodies may have been created or removed while rendering.");
}
y += yStep;
DrawString(spriteBatch, new Vector2(10, y),
"Particle count: " + GameMain.ParticleManager.ParticleCount + "/" + GameMain.ParticleManager.MaxParticles,
Color.Lerp(GUIStyle.Green, GUIStyle.Red, (GameMain.ParticleManager.ParticleCount / (float)GameMain.ParticleManager.MaxParticles)), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
}
} }
if (GameMain.ShowPerf) if (GameMain.ShowPerf)
@@ -324,67 +354,53 @@ namespace Barotrauma
"Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" + "Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" +
" Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms", " Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms",
GUIStyle.Green, Color.Black * 0.8f, font: GUIStyle.SmallFont); GUIStyle.Green, Color.Black * 0.8f, font: GUIStyle.SmallFont);
y += 15 * yScale; y += yStep;
GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle((int)x, (int)y, 170, 50), color: GUIStyle.Green); GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle((int)x, (int)y, 170, 50), color: GUIStyle.Green);
y += 50 * yScale; y += yStep * 4;
DrawString(spriteBatch, new Vector2(x, y), DrawString(spriteBatch, new Vector2(x, y),
"Update - Avg: " + GameMain.PerformanceCounter.UpdateTimeGraph.Average().ToString("0.00") + " ms" + "Update - Avg: " + GameMain.PerformanceCounter.UpdateTimeGraph.Average().ToString("0.00") + " ms" +
" Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms", " Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms",
Color.LightBlue, Color.Black * 0.8f, font: GUIStyle.SmallFont); Color.LightBlue, Color.Black * 0.8f, font: GUIStyle.SmallFont);
y += 15 * yScale; y += yStep;
GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle((int)x, (int)y, 170, 50), color: Color.LightBlue); GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle((int)x, (int)y, 170, 50), color: Color.LightBlue);
y += 50 * yScale; y += yStep * 4;
foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers) foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers.OrderBy(i => i))
{ {
float elapsedMillisecs = GameMain.PerformanceCounter.GetAverageElapsedMillisecs(key); float elapsedMillisecs = GameMain.PerformanceCounter.GetAverageElapsedMillisecs(key);
DrawString(spriteBatch, new Vector2(x, y),
key + ": " + elapsedMillisecs.ToString("0.00"),
Color.Lerp(Color.LightGreen, GUIStyle.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15 * yScale;
foreach (string childKey in GameMain.PerformanceCounter.GetSavedPartialIdentifiers(key))
{
elapsedMillisecs = GameMain.PerformanceCounter.GetPartialAverageElapsedMillisecs(key, childKey);
DrawString(spriteBatch, new Vector2(x + 15, y),
childKey + ": " + elapsedMillisecs.ToString("0.00"),
Color.Lerp(Color.LightGreen, GUIStyle.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15 * yScale;
}
}
int categoryDepth = key.Count(c => c == ':');
//color the more fine-grained counters red more easily (ok for the whole Update to take a longer time than specific part of the update)
float runningSlowThreshold = 10.0f / categoryDepth;
DrawString(spriteBatch, new Vector2(x + categoryDepth * 15, y),
key.Split(':').Last() + ": " + elapsedMillisecs.ToString("0.00"),
ToolBox.GradientLerp(elapsedMillisecs / runningSlowThreshold, Color.LightGreen, GUIStyle.Yellow, GUIStyle.Orange, GUIStyle.Red, Color.Magenta), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += yStep;
}
if (Powered.Grids != null) if (Powered.Grids != null)
{ {
DrawString(spriteBatch, new Vector2(x, y), "Grids: " + Powered.Grids.Count, Color.LightGreen, Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(x, y), "Grids: " + Powered.Grids.Count, Color.LightGreen, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15 * yScale; y += yStep;
} }
if (Settings.EnableDiagnostics) if (Settings.EnableDiagnostics)
{ {
x += 20 * xScale; x += yStep * 2;
DrawString(spriteBatch, new Vector2(x, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(x, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds.ToString("0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 15 * yScale), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(x, y + yStep), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds.ToString("0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 30 * yScale), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(x, y + yStep * 2), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds.ToString("0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 45 * yScale), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(x, y + yStep * 3), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds.ToString("0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 60 * yScale), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(x, y + yStep * 4), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds.ToString("0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 75 * yScale), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(x, y + yStep * 5), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds.ToString("0.00"), Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
} }
} }
if (GameMain.DebugDraw && !Submarine.Unloading && !(Screen.Selected is RoundSummaryScreen)) if (GameMain.DebugDraw && !Submarine.Unloading && !(Screen.Selected is RoundSummaryScreen))
{ {
float y = startY + 15 * yScale; float y = startY + yStep * 6;
DrawString(spriteBatch, new Vector2(10, y),
"Physics: " + GameMain.World.UpdateTime,
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15 * yScale;
DrawString(spriteBatch, new Vector2(10, y),
$"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.Count(b => b != null && b.Awake && b.Enabled)} awake, {GameMain.World.BodyList.Count(b => b != null && b.Awake && b.BodyType == BodyType.Dynamic && b.Enabled)} dynamic)",
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
if (Screen.Selected.Cam != null) if (Screen.Selected.Cam != null)
{ {
y += 15 * yScale; y += yStep;
DrawString(spriteBatch, new Vector2(10, y), DrawString(spriteBatch, new Vector2(10, y),
"Camera pos: " + Screen.Selected.Cam.Position.ToPoint() + ", zoom: " + Screen.Selected.Cam.Zoom, "Camera pos: " + Screen.Selected.Cam.Position.ToPoint() + ", zoom: " + Screen.Selected.Cam.Zoom,
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
@@ -392,23 +408,18 @@ namespace Barotrauma
if (Submarine.MainSub != null) if (Submarine.MainSub != null)
{ {
y += 15 * yScale; y += yStep;
DrawString(spriteBatch, new Vector2(10, y), DrawString(spriteBatch, new Vector2(10, y),
"Sub pos: " + Submarine.MainSub.Position.ToPoint(), "Sub pos: " + Submarine.MainSub.Position.ToPoint(),
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
} }
y += 20 * yScale;
DrawString(spriteBatch, new Vector2(10, y),
"Particle count: " + GameMain.ParticleManager.ParticleCount + "/" + GameMain.ParticleManager.MaxParticles,
Color.Lerp(GUIStyle.Green, GUIStyle.Red, (GameMain.ParticleManager.ParticleCount / (float)GameMain.ParticleManager.MaxParticles)), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
if (loadedSpritesText == null || DateTime.Now > loadedSpritesUpdateTime) if (loadedSpritesText == null || DateTime.Now > loadedSpritesUpdateTime)
{ {
loadedSpritesText = "Loaded sprites: " + Sprite.LoadedSprites.Count() + "\n(" + Sprite.LoadedSprites.Select(s => s.FilePath).Distinct().Count() + " unique textures)"; loadedSpritesText = "Loaded sprites: " + Sprite.LoadedSprites.Count() + "\n(" + Sprite.LoadedSprites.Select(s => s.FilePath).Distinct().Count() + " unique textures)";
loadedSpritesUpdateTime = DateTime.Now + new TimeSpan(0, 0, seconds: 5); loadedSpritesUpdateTime = DateTime.Now + new TimeSpan(0, 0, seconds: 5);
} }
y += 25 * yScale; y += yStep * 2;
DrawString(spriteBatch, new Vector2(10, y), loadedSpritesText, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(10, y), loadedSpritesText, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
if (debugDrawSounds) if (debugDrawSounds)
@@ -416,21 +427,21 @@ namespace Barotrauma
float soundTextY = 0; float soundTextY = 0;
DrawString(spriteBatch, new Vector2(500, soundTextY), DrawString(spriteBatch, new Vector2(500, soundTextY),
"Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); "Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
soundTextY += 15 * yScale; soundTextY += yStep;
DrawString(spriteBatch, new Vector2(500, soundTextY), DrawString(spriteBatch, new Vector2(500, soundTextY),
"Current playback amplitude: " + GameMain.SoundManager.PlaybackAmplitude.ToString(), Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); "Current playback amplitude: " + GameMain.SoundManager.PlaybackAmplitude.ToString(), Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
soundTextY += 15 * yScale; soundTextY += yStep;
DrawString(spriteBatch, new Vector2(500, soundTextY), DrawString(spriteBatch, new Vector2(500, soundTextY),
"Compressed dynamic range gain: " + GameMain.SoundManager.CompressionDynamicRangeGain.ToString(), Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); "Compressed dynamic range gain: " + GameMain.SoundManager.CompressionDynamicRangeGain.ToString(), Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
soundTextY += 15 * yScale; soundTextY += yStep;
DrawString(spriteBatch, new Vector2(500, soundTextY), DrawString(spriteBatch, new Vector2(500, soundTextY),
"Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); "Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
soundTextY += 15 * yScale; soundTextY += yStep;
for (int i = 0; i < SoundManager.SOURCE_COUNT; i++) for (int i = 0; i < SoundManager.SOURCE_COUNT; i++)
{ {
@@ -479,7 +490,7 @@ namespace Barotrauma
} }
DrawString(spriteBatch, new Vector2(500, soundTextY), soundStr, clr, Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, new Vector2(500, soundTextY), soundStr, clr, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
soundTextY += 15 * yScale; soundTextY += yStep;
} }
} }
else else
@@ -1981,7 +1992,7 @@ namespace Barotrauma
var element = new GUIFrame(new RectTransform(new Vector2(0.22f, 1), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point(150, 50) }, style: null); var element = new GUIFrame(new RectTransform(new Vector2(0.22f, 1), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point(150, 50) }, style: null);
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), RectComponentLabels[i], font: font, textAlignment: Alignment.CenterLeft); new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), RectComponentLabels[i], font: font, textAlignment: Alignment.CenterLeft);
GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
GUINumberInput.NumberType.Int) NumberType.Int)
{ {
Font = font Font = font
}; };
@@ -2025,7 +2036,7 @@ namespace Barotrauma
var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null); var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), VectorComponentLabels[i], font: GUIStyle.SmallFont, textAlignment: Alignment.CenterLeft); new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), VectorComponentLabels[i], font: GUIStyle.SmallFont, textAlignment: Alignment.CenterLeft);
GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
GUINumberInput.NumberType.Int) NumberType.Int)
{ {
Font = GUIStyle.SmallFont Font = GUIStyle.SmallFont
}; };
@@ -2055,7 +2066,7 @@ namespace Barotrauma
{ {
var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null); var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), VectorComponentLabels[i], font: font, textAlignment: Alignment.CenterLeft); new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), VectorComponentLabels[i], font: font, textAlignment: Alignment.CenterLeft);
GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), GUINumberInput.NumberType.Float) { Font = font }; GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), NumberType.Float) { Font = font };
switch (i) switch (i)
{ {
case 0: case 0:
@@ -2369,7 +2380,7 @@ namespace Barotrauma
CreateButton("PauseMenuResume", buttonContainer, null); CreateButton("PauseMenuResume", buttonContainer, null);
CreateButton("PauseMenuSettings", buttonContainer, () => SettingsMenuOpen = true); CreateButton("PauseMenuSettings", buttonContainer, () => SettingsMenuOpen = true);
bool IsOutpostLevel() => GameMain.GameSession != null && Level.IsLoadedOutpost; bool IsFriendlyOutpostLevel() => GameMain.GameSession != null && Level.IsLoadedFriendlyOutpost;
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession != null) if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession != null)
{ {
if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode) if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode)
@@ -2384,11 +2395,11 @@ namespace Barotrauma
GameMain.GameSession.LoadPreviousSave(); GameMain.GameSession.LoadPreviousSave();
}); });
if (IsOutpostLevel()) if (IsFriendlyOutpostLevel())
{ {
CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToMainMenuVerification", action: () => CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToMainMenuVerification", action: () =>
{ {
if (IsOutpostLevel()) { GameMain.QuitToMainMenu(save: true); } if (IsFriendlyOutpostLevel()) { GameMain.QuitToMainMenu(save: true); }
}); });
} }
} }
@@ -2401,7 +2412,7 @@ namespace Barotrauma
} }
else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound)) else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound))
{ {
bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsOutpostLevel(); bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
if (canSave) if (canSave)
{ {
CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () => CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
@@ -111,6 +111,11 @@ namespace Barotrauma
set { textBlock.SelectedTextColor = value; } set { textBlock.SelectedTextColor = value; }
} }
public Color DisabledTextColor
{
get { return textBlock.DisabledTextColor; }
}
public override float FlashTimer public override float FlashTimer
{ {
get { return Frame.FlashTimer; } get { return Frame.FlashTimer; }
@@ -159,7 +164,9 @@ namespace Barotrauma
private float pulseExpand; private float pulseExpand;
private bool flashed; private bool flashed;
public GUISoundType ClickSound { get; set; } = GUISoundType.Click; public GUISoundType ClickSound { get; set; } = GUISoundType.Select;
public override bool PlaySoundOnSelect { get; set; } = true;
public GUIButton(RectTransform rectT, Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : this(rectT, new RawLString(""), textAlignment, style, color) { } public GUIButton(RectTransform rectT, Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : this(rectT, new RawLString(""), textAlignment, style, color) { }
@@ -247,7 +254,10 @@ namespace Barotrauma
} }
else if (PlayerInput.PrimaryMouseButtonClicked()) else if (PlayerInput.PrimaryMouseButtonClicked())
{ {
SoundPlayer.PlayUISound(ClickSound); if (PlaySoundOnSelect)
{
SoundPlayer.PlayUISound(ClickSound);
}
if (OnClicked != null) if (OnClicked != null)
{ {
if (OnClicked(this, UserData)) if (OnClicked(this, UserData))
@@ -8,6 +8,8 @@ namespace Barotrauma
{ {
public class GUICanvas : RectTransform public class GUICanvas : RectTransform
{ {
private static readonly object mutex = new object();
protected GUICanvas() : base(size, parent: null) { } protected GUICanvas() : base(size, parent: null) { }
private static GUICanvas _instance; private static GUICanvas _instance;
@@ -39,22 +41,25 @@ namespace Barotrauma
private static void OnChildrenChanged(RectTransform _) private static void OnChildrenChanged(RectTransform _)
{ {
//add weak reference if we don't have one yet lock (mutex)
foreach (var child in _instance.Children)
{ {
if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child)) //add weak reference if we don't have one yet
foreach (var child in _instance.Children)
{ {
_instance.childrenWeakRef.Add(new WeakReference<RectTransform>(child)); if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child))
{
_instance.childrenWeakRef.Add(new WeakReference<RectTransform>(child));
}
} }
} //get rid of strong references
//get rid of strong references _instance.children.Clear();
_instance.children.Clear(); //remove dead children
//remove dead children for (int i = _instance.childrenWeakRef.Count - 2; i >= 0; i--)
for (int i = _instance.childrenWeakRef.Count - 2; i >= 0; i--)
{
if (!_instance.childrenWeakRef[i].TryGetTarget(out var child) || child.Parent != _instance)
{ {
_instance.childrenWeakRef.RemoveAt(i); if (!_instance.childrenWeakRef[i].TryGetTarget(out var child) || child.Parent != _instance)
{
_instance.childrenWeakRef.RemoveAt(i);
}
} }
} }
} }
@@ -383,6 +383,8 @@ namespace Barotrauma
public bool ExternalHighlight = false; public bool ExternalHighlight = false;
public virtual bool PlaySoundOnSelect { get; set; } = false;
private RectTransform rectTransform; private RectTransform rectTransform;
public RectTransform RectTransform public RectTransform RectTransform
{ {
@@ -113,7 +113,8 @@ namespace Barotrauma
{ {
AutoHideScrollBar = false, AutoHideScrollBar = false,
ScrollBarVisible = false, ScrollBarVisible = false,
Padding = hasHeader ? new Vector4(4, 0, 4, 4) : padding Padding = hasHeader ? new Vector4(4, 0, 4, 4) : padding,
PlaySoundOnSelect = true
}; };
foreach (var (option, size) in optionsAndSizes) foreach (var (option, size) in optionsAndSizes)
@@ -290,7 +291,7 @@ namespace Barotrauma
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0) public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{ {
base.AddToGUIUpdateList(ignoreChildren, order); base.AddToGUIUpdateList(ignoreChildren, order);
SubMenu?.AddToGUIUpdateList(); SubMenu?.AddToGUIUpdateList(order: 2);
} }
public static void AddActiveToGUIUpdateList() public static void AddActiveToGUIUpdateList()
@@ -300,7 +301,7 @@ namespace Barotrauma
CurrentContextMenu = null; CurrentContextMenu = null;
} }
CurrentContextMenu?.AddToGUIUpdateList(); CurrentContextMenu?.AddToGUIUpdateList(order: 2);
} }
} }
} }
@@ -161,6 +161,10 @@ namespace Barotrauma
} }
} }
public GUIImage DropDownIcon => icon;
public Vector4 Padding => button.TextBlock.Padding;
public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft) : base(style, rectT) public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft) : base(style, rectT)
{ {
text ??= new RawLString(""); text ??= new RawLString("");
@@ -183,7 +187,8 @@ namespace Barotrauma
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot) listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
{ IsFixedSize = false }, style: null) { IsFixedSize = false }, style: null)
{ {
Enabled = !selectMultiple Enabled = !selectMultiple,
PlaySoundOnSelect = true,
}; };
if (!selectMultiple) { listBox.OnSelected = SelectItem; } if (!selectMultiple) { listBox.OnSelected = SelectItem; }
GUIStyle.Apply(listBox, "GUIListBox", this); GUIStyle.Apply(listBox, "GUIListBox", this);
@@ -309,6 +309,45 @@ namespace Barotrauma
} }
} }
public override bool PlaySoundOnSelect { get; set; } = false;
public bool PlaySoundOnDragStop { get; set; } = false;
public GUISoundType? SoundOnDragStart { get; set; } = null;
public GUISoundType? SoundOnDragStop { get; set; } = null;
#region enums
public enum Force
{
Yes,
No
}
public enum AutoScroll
{
Enabled,
Disabled
}
public enum TakeKeyBoardFocus
{
Yes,
No
}
public enum PlaySelectSound
{
Yes,
No
}
private AutoScroll GetAutoScroll(bool b)
{
return b ? AutoScroll.Enabled : AutoScroll.Disabled;
}
#endregion
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param> /// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT) public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT)
{ {
@@ -396,7 +435,7 @@ namespace Barotrauma
UpdateScrollBarSize(); UpdateScrollBarSize();
} }
public void Select(object userData, bool force = false, bool autoScroll = true) public void Select(object userData, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled)
{ {
var children = Content.Children; var children = Content.Children;
int i = 0; int i = 0;
@@ -515,9 +554,12 @@ namespace Barotrauma
/// Scrolls the list to the specific element. /// Scrolls the list to the specific element.
/// </summary> /// </summary>
/// <param name="component"></param> /// <param name="component"></param>
public void ScrollToElement(GUIComponent component, bool playSound = true) public void ScrollToElement(GUIComponent component, PlaySelectSound playSelectSound = PlaySelectSound.No)
{ {
if (playSound) { SoundPlayer.PlayUISound(GUISoundType.Click); } if (playSelectSound == PlaySelectSound.Yes)
{
SoundPlayer.PlayUISound(GUISoundType.Select);
}
List<GUIComponent> children = Content.Children.ToList(); List<GUIComponent> children = Content.Children.ToList();
int index = children.IndexOf(component); int index = children.IndexOf(component);
if (index < 0) { return; } if (index < 0) { return; }
@@ -573,9 +615,16 @@ namespace Barotrauma
} }
} }
private double lastDragStartTime;
private void StartDraggingElement(GUIComponent child) private void StartDraggingElement(GUIComponent child)
{ {
DraggedElement = child; DraggedElement = child;
if (Timing.TotalTime > lastDragStartTime + 0.2f)
{
lastDragStartTime = Timing.TotalTime;
SoundPlayer.PlayUISound(SoundOnDragStart);
}
} }
private bool UpdateDragging() private bool UpdateDragging()
@@ -586,6 +635,10 @@ namespace Barotrauma
var draggedElem = draggedElement; var draggedElem = draggedElement;
OnRearranged?.Invoke(this, draggedElem.UserData); OnRearranged?.Invoke(this, draggedElem.UserData);
DraggedElement = null; DraggedElement = null;
if (PlaySoundOnDragStop)
{
SoundPlayer.PlayUISound(SoundOnDragStop);
}
RepositionChildren(); RepositionChildren();
if (AllSelected.Contains(draggedElem)) { return true; } if (AllSelected.Contains(draggedElem)) { return true; }
} }
@@ -710,7 +763,7 @@ namespace Barotrauma
int index = Content.Children.ToList().IndexOf(component); int index = Content.Children.ToList().IndexOf(component);
if (index >= 0) if (index >= 0)
{ {
Select(index, false, false, takeKeyBoardFocus: true); Select(index, autoScroll: AutoScroll.Disabled, takeKeyBoardFocus: TakeKeyBoardFocus.Yes);
} }
} }
} }
@@ -733,7 +786,7 @@ namespace Barotrauma
{ {
ScrollToElement(child); ScrollToElement(child);
} }
Select(i, autoScroll: false, takeKeyBoardFocus: true); Select(i, autoScroll: AutoScroll.Disabled, takeKeyBoardFocus: TakeKeyBoardFocus.Yes, playSelectSound: PlaySelectSound.Yes);
} }
if (CurrentDragMode != DragMode.NoDragging if (CurrentDragMode != DragMode.NoDragging
@@ -929,14 +982,13 @@ namespace Barotrauma
if (ClampScrollToElements) if (ClampScrollToElements)
{ {
bool scrollDown = Math.Clamp(PlayerInput.ScrollWheelSpeed, 0, 1) > 0; bool scrollDown = Math.Clamp(PlayerInput.ScrollWheelSpeed, 0, 1) > 0;
if (scrollDown) if (scrollDown)
{ {
SelectPrevious(takeKeyBoardFocus: true); SelectPrevious(takeKeyBoardFocus: TakeKeyBoardFocus.Yes, playSelectSound: PlaySelectSound.Yes);
} }
else else
{ {
SelectNext(takeKeyBoardFocus: true); SelectNext(takeKeyBoardFocus: TakeKeyBoardFocus.Yes, playSelectSound: PlaySelectSound.Yes);
} }
} }
} }
@@ -964,7 +1016,7 @@ namespace Barotrauma
return FindScrollableParentListBox(target.Parent); return FindScrollableParentListBox(target.Parent);
} }
public void SelectNext(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) public void SelectNext(Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus = TakeKeyBoardFocus.No, PlaySelectSound playSelectSound = PlaySelectSound.No)
{ {
int index = SelectedIndex + 1; int index = SelectedIndex + 1;
while (index < Content.CountChildren) while (index < Content.CountChildren)
@@ -972,10 +1024,10 @@ namespace Barotrauma
GUIComponent child = Content.GetChild(index); GUIComponent child = Content.GetChild(index);
if (child.Visible) if (child.Visible)
{ {
Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus); Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll) if (SmoothScroll)
{ {
ScrollToElement(child); ScrollToElement(child, playSelectSound);
} }
break; break;
} }
@@ -983,7 +1035,7 @@ namespace Barotrauma
} }
} }
public void SelectPrevious(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) public void SelectPrevious(Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus = TakeKeyBoardFocus.No, PlaySelectSound playSelectSound = PlaySelectSound.No)
{ {
int index = SelectedIndex - 1; int index = SelectedIndex - 1;
while (index >= 0) while (index >= 0)
@@ -991,10 +1043,10 @@ namespace Barotrauma
GUIComponent child = Content.GetChild(index); GUIComponent child = Content.GetChild(index);
if (child.Visible) if (child.Visible)
{ {
Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus); Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll) if (SmoothScroll)
{ {
ScrollToElement(child); ScrollToElement(child, playSelectSound);
} }
break; break;
} }
@@ -1002,7 +1054,7 @@ namespace Barotrauma
} }
} }
public void Select(int childIndex, bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) public void Select(int childIndex, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus = TakeKeyBoardFocus.No, PlaySelectSound playSelectSound = PlaySelectSound.No)
{ {
if (childIndex >= Content.CountChildren || childIndex < 0) { return; } if (childIndex >= Content.CountChildren || childIndex < 0) { return; }
@@ -1013,7 +1065,7 @@ namespace Barotrauma
if (OnSelected != null) if (OnSelected != null)
{ {
// TODO: The callback is called twice, fix this! // TODO: The callback is called twice, fix this!
wasSelected = force || OnSelected(child, child.UserData); wasSelected = force == Force.Yes || OnSelected(child, child.UserData);
} }
if (!wasSelected) { return; } if (!wasSelected) { return; }
@@ -1055,7 +1107,7 @@ namespace Barotrauma
// Ensure that the selected element is visible. This may not be the case, if the selection is run from code. (e.g. if we have two list boxes that are synced) // Ensure that the selected element is visible. This may not be the case, if the selection is run from code. (e.g. if we have two list boxes that are synced)
// TODO: This method only works when moving one item up/down (e.g. when using the up and down arrows) // TODO: This method only works when moving one item up/down (e.g. when using the up and down arrows)
if (autoScroll) if (autoScroll == AutoScroll.Enabled)
{ {
if (ScrollBar.IsHorizontal) if (ScrollBar.IsHorizontal)
{ {
@@ -1086,11 +1138,19 @@ namespace Barotrauma
} }
// If one of the children is the subscriber, we don't want to register, because it will unregister the child. // If one of the children is the subscriber, we don't want to register, because it will unregister the child.
if (takeKeyBoardFocus && CanTakeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber)) if (takeKeyBoardFocus == TakeKeyBoardFocus.Yes && CanTakeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber))
{ {
Selected = true; Selected = true;
GUI.KeyboardDispatcher.Subscriber = this; GUI.KeyboardDispatcher.Subscriber = this;
} }
// List box child components can be parents to other components that can play sounds when selected (e.g. store elements)
// so the list box shouldn't play the Select sound if the GUI.MouseOn component has a sound to play
if (playSelectSound == PlaySelectSound.Yes && PlaySoundOnSelect && !child.PlaySoundOnSelect &&
(GUI.MouseOn == null || GUI.MouseOn.Parent == Content || !GUI.MouseOn.PlaySoundOnSelect))
{
SoundPlayer.PlayUISound(GUISoundType.Select);
}
} }
public void Select(IEnumerable<GUIComponent> children) public void Select(IEnumerable<GUIComponent> children)
@@ -1293,16 +1353,16 @@ namespace Barotrauma
switch (key) switch (key)
{ {
case Keys.Down: case Keys.Down:
if (!isHorizontal && AllowArrowKeyScroll) { SelectNext(); } if (!isHorizontal && AllowArrowKeyScroll) { SelectNext(playSelectSound: PlaySelectSound.Yes); }
break; break;
case Keys.Up: case Keys.Up:
if (!isHorizontal && AllowArrowKeyScroll) { SelectPrevious(); } if (!isHorizontal && AllowArrowKeyScroll) { SelectPrevious(playSelectSound: PlaySelectSound.Yes); }
break; break;
case Keys.Left: case Keys.Left:
if (isHorizontal && AllowArrowKeyScroll) { SelectPrevious(); } if (isHorizontal && AllowArrowKeyScroll) { SelectPrevious(playSelectSound: PlaySelectSound.Yes); }
break; break;
case Keys.Right: case Keys.Right:
if (isHorizontal && AllowArrowKeyScroll) { SelectNext(); } if (isHorizontal && AllowArrowKeyScroll) { SelectNext(playSelectSound: PlaySelectSound.Yes); }
break; break;
case Keys.Enter: case Keys.Enter:
case Keys.Space: case Keys.Space:
@@ -7,11 +7,6 @@ namespace Barotrauma
{ {
class GUINumberInput : GUIComponent class GUINumberInput : GUIComponent
{ {
public enum NumberType
{
Int, Float
}
public delegate void OnValueEnteredHandler(GUINumberInput numberInput); public delegate void OnValueEnteredHandler(GUINumberInput numberInput);
public OnValueEnteredHandler OnValueEntered; public OnValueEnteredHandler OnValueEntered;
@@ -187,7 +182,7 @@ namespace Barotrauma
public float valueStep; public float valueStep;
private float pressedTimer; private float pressedTimer;
private float pressedDelay = 0.5f; private readonly float pressedDelay = 0.5f;
private bool IsPressedTimerRunning { get { return pressedTimer > 0; } } private bool IsPressedTimerRunning { get { return pressedTimer > 0; } }
public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null, bool hidePlusMinusButtons = false) : base(style, rectT) public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null, bool hidePlusMinusButtons = false) : base(style, rectT)
@@ -233,6 +228,7 @@ namespace Barotrauma
var buttonArea = new GUIFrame(new RectTransform(new Vector2(_relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform, Anchor.CenterRight), style: null); var buttonArea = new GUIFrame(new RectTransform(new Vector2(_relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform, Anchor.CenterRight), style: null);
PlusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform), style: null); PlusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform), style: null);
GUIStyle.Apply(PlusButton, "PlusButton", this); GUIStyle.Apply(PlusButton, "PlusButton", this);
PlusButton.ClickSound = GUISoundType.Increase;
PlusButton.OnButtonDown += () => PlusButton.OnButtonDown += () =>
{ {
pressedTimer = pressedDelay; pressedTimer = pressedDelay;
@@ -254,6 +250,7 @@ namespace Barotrauma
MinusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform, Anchor.BottomRight), style: null); MinusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform, Anchor.BottomRight), style: null);
GUIStyle.Apply(MinusButton, "MinusButton", this); GUIStyle.Apply(MinusButton, "MinusButton", this);
MinusButton.ClickSound = GUISoundType.Decrease;
MinusButton.OnButtonDown += () => MinusButton.OnButtonDown += () =>
{ {
pressedTimer = pressedDelay; pressedTimer = pressedDelay;
@@ -428,8 +425,8 @@ namespace Barotrauma
intValue = Math.Min(intValue, MaxValueInt.Value); intValue = Math.Min(intValue, MaxValueInt.Value);
UpdateText(); UpdateText();
} }
PlusButton.Enabled = intValue < MaxValueInt; PlusButton.Enabled = MaxValueInt == null || intValue < MaxValueInt;
MinusButton.Enabled = intValue > MinValueInt; MinusButton.Enabled = MinValueInt == null || intValue > MinValueInt;
} }
private void UpdateText() private void UpdateText()
@@ -98,7 +98,6 @@ namespace Barotrauma
foreach (var subElement in element.Elements().Reverse()) foreach (var subElement in element.Elements().Reverse())
{ {
if (subElement.NameAsIdentifier() != "override") { continue; } if (subElement.NameAsIdentifier() != "override") { continue; }
if (subElement.GetAttributeBool("iscjk", false)) if (subElement.GetAttributeBool("iscjk", false))
{ {
return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice); return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice);
@@ -111,8 +110,7 @@ namespace Barotrauma
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; } if (IsValidOverride(subElement))
if (GameSettings.CurrentConfig.Language == subElement.GetAttributeIdentifier("language", "").ToLanguageIdentifier())
{ {
return subElement.GetAttributeContentPath("file")?.Value; return subElement.GetAttributeContentPath("file")?.Value;
} }
@@ -125,8 +123,7 @@ namespace Barotrauma
//check if any of the language override fonts want to override the font size as well //check if any of the language override fonts want to override the font size as well
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; } if (IsValidOverride(subElement))
if (GameSettings.CurrentConfig.Language == subElement.GetAttributeIdentifier("language", "").ToLanguageIdentifier())
{ {
uint overrideFontSize = GetFontSize(subElement, 0); uint overrideFontSize = GetFontSize(subElement, 0);
if (overrideFontSize > 0) { return (uint)Math.Round(overrideFontSize * GameSettings.CurrentConfig.Graphics.TextScale); } if (overrideFontSize > 0) { return (uint)Math.Round(overrideFontSize * GameSettings.CurrentConfig.Graphics.TextScale); }
@@ -149,8 +146,7 @@ namespace Barotrauma
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; } if (IsValidOverride(subElement))
if (GameSettings.CurrentConfig.Language == subElement.GetAttributeIdentifier("language", "").ToLanguageIdentifier())
{ {
return subElement.GetAttributeBool("dynamicloading", false); return subElement.GetAttributeBool("dynamicloading", false);
} }
@@ -162,14 +158,20 @@ namespace Barotrauma
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; } if (IsValidOverride(subElement))
if (GameSettings.CurrentConfig.Language == subElement.GetAttributeIdentifier("language", "").ToLanguageIdentifier())
{ {
return subElement.GetAttributeBool("iscjk", false); return subElement.GetAttributeBool("iscjk", false);
} }
} }
return element.GetAttributeBool("iscjk", false); return element.GetAttributeBool("iscjk", false);
} }
private bool IsValidOverride(XElement element)
{
if (!element.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { return false; }
var languages = element.GetAttributeIdentifierArray("language", Array.Empty<Identifier>());
return languages.Any(l => l.ToLanguageIdentifier() == GameSettings.CurrentConfig.Language);
}
} }
public class GUIFont : GUISelector<GUIFontPrefab> public class GUIFont : GUISelector<GUIFontPrefab>
@@ -322,9 +322,8 @@ namespace Barotrauma
{ {
if (!enabled || !PlayerInput.PrimaryMouseButtonDown()) { return false; } if (!enabled || !PlayerInput.PrimaryMouseButtonDown()) { return false; }
if (barSize >= 1.0f) { return false; } if (barSize >= 1.0f) { return false; }
DraggingBar = this; DraggingBar = this;
SoundPlayer.PlayUISound(GUISoundType.Select);
return true; return true;
} }
@@ -34,7 +34,6 @@ namespace Barotrauma
public readonly static PrefabCollection<GUIComponentStyle> ComponentStyles = new PrefabCollection<GUIComponentStyle>(); public readonly static PrefabCollection<GUIComponentStyle> ComponentStyles = new PrefabCollection<GUIComponentStyle>();
public readonly static GUIFont Font = new GUIFont("Font"); public readonly static GUIFont Font = new GUIFont("Font");
public readonly static GUIFont GlobalFont = new GUIFont("GlobalFont");
public readonly static GUIFont UnscaledSmallFont = new GUIFont("UnscaledSmallFont"); public readonly static GUIFont UnscaledSmallFont = new GUIFont("UnscaledSmallFont");
public readonly static GUIFont SmallFont = new GUIFont("SmallFont"); public readonly static GUIFont SmallFont = new GUIFont("SmallFont");
public readonly static GUIFont LargeFont = new GUIFont("LargeFont"); public readonly static GUIFont LargeFont = new GUIFont("LargeFont");
@@ -142,10 +141,6 @@ namespace Barotrauma
public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium"); public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium");
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh"); public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
public readonly static GUIColor EquipmentIndicatorNotEquipped = new GUIColor("EquipmentIndicatorNotEquipped");
public readonly static GUIColor EquipmentIndicatorEquipped = new GUIColor("EquipmentIndicatorEquipped");
public readonly static GUIColor EquipmentIndicatorRunningOut = new GUIColor("EquipmentIndicatorRunningOut");
public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale); public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale); public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
@@ -159,7 +154,7 @@ namespace Barotrauma
public static void Apply(GUIComponent targetComponent, Identifier styleName, GUIComponent parent = null) public static void Apply(GUIComponent targetComponent, Identifier styleName, GUIComponent parent = null)
{ {
GUIComponentStyle componentStyle = null; GUIComponentStyle componentStyle;
if (parent != null) if (parent != null)
{ {
GUIComponentStyle parentStyle = parent.Style; GUIComponentStyle parentStyle = parent.Style;
@@ -212,5 +207,21 @@ namespace Barotrauma
return ItemQualityColorNormal; return ItemQualityColorNormal;
} }
} }
public static void RecalculateFonts()
{
foreach (var font in Fonts.Values)
{
font.Prefabs.ForEach(p => p.LoadFont());
}
}
public static void RecalculateSizeRestrictions()
{
foreach (var componentStyle in ComponentStyles)
{
componentStyle.RefreshSize();
}
}
} }
} }
@@ -35,7 +35,6 @@ namespace Barotrauma
public TextGetterHandler TextGetter; public TextGetterHandler TextGetter;
public bool Wrap; public bool Wrap;
private bool playerInput;
public bool RoundToNearestPixel = true; public bool RoundToNearestPixel = true;
@@ -287,8 +286,7 @@ namespace Barotrauma
/// If the rectT height is set 0, the height is calculated from the text. /// If the rectT height is set 0, the height is calculated from the text.
/// </summary> /// </summary>
public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null, public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
bool playerInput = false)
: base(style, rectT) : base(style, rectT)
{ {
if (color.HasValue) if (color.HasValue)
@@ -307,7 +305,6 @@ namespace Barotrauma
this.textAlignment = textAlignment; this.textAlignment = textAlignment;
this.Wrap = wrap; this.Wrap = wrap;
this.Text = text ?? ""; this.Text = text ?? "";
this.playerInput = playerInput;
if (rectT.Rect.Height == 0 && !text.IsNullOrEmpty()) if (rectT.Rect.Height == 0 && !text.IsNullOrEmpty())
{ {
CalculateHeightFromText(); CalculateHeightFromText();
@@ -251,6 +251,8 @@ namespace Barotrauma
public bool Readonly { get; set; } public bool Readonly { get; set; }
public override bool PlaySoundOnSelect { get; set; } = true;
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null, public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true) Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
: base(style, rectT) : base(style, rectT)
@@ -261,7 +263,7 @@ namespace Barotrauma
this.color = color ?? Color.White; this.color = color ?? Color.White;
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color); frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style); GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap, playerInput: true); textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap);
GUIStyle.Apply(textBlock, "", this); GUIStyle.Apply(textBlock, "", this);
if (font != null) { textBlock.Font = font; } if (font != null) { textBlock.Font = font; }
CaretEnabled = true; CaretEnabled = true;
@@ -350,7 +352,7 @@ namespace Barotrauma
caretPosDirty = false; caretPosDirty = false;
} }
public void Select(int forcedCaretIndex = -1) public void Select(int forcedCaretIndex = -1, bool ignoreSelectSound = false)
{ {
skipUpdate = true; skipUpdate = true;
if (memento.Current == null) if (memento.Current == null)
@@ -360,9 +362,14 @@ namespace Barotrauma
CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex; CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
CalculateCaretPos(); CalculateCaretPos();
ClearSelection(); ClearSelection();
bool wasSelected = selected;
selected = true; selected = true;
GUI.KeyboardDispatcher.Subscriber = this; GUI.KeyboardDispatcher.Subscriber = this;
OnSelected?.Invoke(this, Keys.None); OnSelected?.Invoke(this, Keys.None);
if (!wasSelected && PlaySoundOnSelect && !ignoreSelectSound)
{
SoundPlayer.PlayUISound(GUISoundType.Select);
}
} }
public void Deselect() public void Deselect()
@@ -1,15 +1,13 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
public class GUITickBox : GUIComponent public class GUITickBox : GUIComponent
{ {
private GUILayoutGroup layoutGroup; private readonly GUILayoutGroup layoutGroup;
private GUIFrame box; private readonly GUIFrame box;
private GUITextBlock text; private readonly GUITextBlock text;
public delegate bool OnSelectedHandler(GUITickBox obj); public delegate bool OnSelectedHandler(GUITickBox obj);
public OnSelectedHandler OnSelected; public OnSelectedHandler OnSelected;
@@ -129,6 +127,12 @@ namespace Barotrauma
set { text.Text = value; } set { text.Text = value; }
} }
public float ContentWidth { get; private set; }
public GUISoundType SoundType { private get; set; } = GUISoundType.TickBox;
public override bool PlaySoundOnSelect { get; set; } = true;
public GUITickBox(RectTransform rectT, LocalizedString label, GUIFont font = null, string style = "") : base(null, rectT) public GUITickBox(RectTransform rectT, LocalizedString label, GUIFont font = null, string style = "") : base(null, rectT)
{ {
CanBeFocused = true; CanBeFocused = true;
@@ -180,6 +184,7 @@ namespace Barotrauma
box.RectTransform.MinSize = new Point(Rect.Height); box.RectTransform.MinSize = new Point(Rect.Height);
box.RectTransform.Resize(box.RectTransform.MinSize); box.RectTransform.Resize(box.RectTransform.MinSize);
text.SetTextPos(); text.SetTextPos();
ContentWidth = box.Rect.Width + text.Padding.X + text.TextSize.X + text.Padding.Z;
} }
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
@@ -209,6 +214,10 @@ namespace Barotrauma
{ {
Selected = true; Selected = true;
} }
if (PlaySoundOnSelect)
{
SoundPlayer.PlayUISound(SoundType);
}
} }
} }
else if (isSelected) else if (isSelected)
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -14,7 +15,7 @@ namespace Barotrauma
get { return inventoryTopY; } get { return inventoryTopY; }
set set
{ {
if (value == inventoryTopY) return; if (value == inventoryTopY) { return; }
inventoryTopY = value; inventoryTopY = value;
CreateAreas(); CreateAreas();
} }
@@ -90,8 +91,6 @@ namespace Barotrauma
if (GameMain.Instance != null) if (GameMain.Instance != null)
{ {
GameMain.Instance.ResolutionChanged += CreateAreas; GameMain.Instance.ResolutionChanged += CreateAreas;
#warning TODO: reimplement
//GameSettings.CurrentConfig.OnHUDScaleChanged += CreateAreas;
CreateAreas(); CreateAreas();
CharacterInfo.Init(); CharacterInfo.Init();
} }
@@ -122,7 +121,17 @@ namespace Barotrauma
//horizontal slices at the corners of the screen for health bar and affliction icons //horizontal slices at the corners of the screen for health bar and affliction icons
int afflictionAreaHeight = (int)(50 * GUI.Scale); int afflictionAreaHeight = (int)(50 * GUI.Scale);
int healthBarWidth = (int)(BottomRightInfoArea.Width * 1.58f); int healthBarWidth = BottomRightInfoArea.Width;
var healthBarChildStyles = GUIStyle.GetComponentStyle("CharacterHealthBar")?.ChildStyles;
if (healthBarChildStyles!= null && healthBarChildStyles.TryGetValue("GUIFrame".ToIdentifier(), out var style))
{
if (style.Sprites.TryGetValue(GUIComponent.ComponentState.None, out var uiSprites) && uiSprites.FirstOrDefault() is { } uiSprite)
{
// The default health bar uses a sliced sprite so let's make sure the health bar area is calculated accordingly
healthBarWidth += (int)(uiSprite.NonSliceSize.X * Math.Min(GUI.Scale, 1f));
}
}
int healthBarHeight = (int)(50f * GUI.Scale); int healthBarHeight = (int)(50f * GUI.Scale);
HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight); HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight);
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight); AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
@@ -569,6 +569,7 @@ namespace Barotrauma
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), footerLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterRight); GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), footerLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterRight);
GUIButton healButton = new GUIButton(new RectTransform(new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get("medicalclinic.heal")) GUIButton healButton = new GUIButton(new RectTransform(new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get("medicalclinic.heal"))
{ {
ClickSound = GUISoundType.ConfirmTransaction,
Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetBalance() >= medicalClinic.GetTotalCost(), Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetBalance() >= medicalClinic.GetTotalCost(),
OnClicked = (button, _) => OnClicked = (button, _) =>
{ {
@@ -595,6 +596,7 @@ namespace Barotrauma
GUIButton clearButton = new GUIButton(new RectTransform(new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get("campaignstore.clearall")) GUIButton clearButton = new GUIButton(new RectTransform(new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get("campaignstore.clearall"))
{ {
ClickSound = GUISoundType.Cart,
OnClicked = (button, _) => OnClicked = (button, _) =>
{ {
button.Enabled = false; button.Enabled = false;
@@ -657,6 +659,7 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
GUILayoutGroup parentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, backgroundFrame.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup parentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, backgroundFrame.RectTransform), isHorizontal: true) { Stretch = true };
if (!(affliction.Prefab is { } prefab)) { return; } if (!(affliction.Prefab is { } prefab)) { return; }
@@ -676,13 +679,14 @@ namespace Barotrauma
GUIFrame textContainer = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), textLayout.RectTransform), style: null); GUIFrame textContainer = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), textLayout.RectTransform), style: null);
GUITextBlock afflictionName = new GUITextBlock(new RectTransform(Vector2.One, textContainer.RectTransform), name, font: GUIStyle.SubHeadingFont); GUITextBlock afflictionName = new GUITextBlock(new RectTransform(Vector2.One, textContainer.RectTransform), name, font: GUIStyle.SubHeadingFont);
GUITextBlock healCost = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.LargeFont) GUITextBlock healCost = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
{ {
Padding = Vector4.Zero Padding = Vector4.Zero
}; };
GUIButton healButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), style: "CrewManagementRemoveButton") GUIButton healButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), style: "CrewManagementRemoveButton")
{ {
ClickSound = GUISoundType.Cart,
OnClicked = (button, _) => OnClicked = (button, _) =>
{ {
button.Enabled = false; button.Enabled = false;
@@ -746,12 +750,12 @@ namespace Barotrauma
ClosePopup(); ClosePopup();
GUIFrame mainFrame = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.45f), container.RectTransform) GUIFrame mainFrame = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.5f), container.RectTransform)
{ {
ScreenSpaceOffset = location.ToPoint() ScreenSpaceOffset = location.ToPoint()
}); });
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), mainFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.01f, Stretch = true }; GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), mainFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.01f, Stretch = true };
if (mainFrame.Rect.Bottom > GameMain.GraphicsHeight) if (mainFrame.Rect.Bottom > GameMain.GraphicsHeight)
{ {
@@ -765,6 +769,7 @@ namespace Barotrauma
GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1f, 0.2f), mainLayout.RectTransform), TextManager.Get("medicalclinic.treatall")) GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1f, 0.2f), mainLayout.RectTransform), TextManager.Get("medicalclinic.treatall"))
{ {
ClickSound = GUISoundType.Cart,
Font = GUIStyle.SubHeadingFont, Font = GUIStyle.SubHeadingFont,
Visible = false Visible = false
}; };
@@ -819,7 +824,9 @@ namespace Barotrauma
if (!(affliction.Prefab is { } prefab)) { return ImmutableArray<GUIComponent>.Empty; } if (!(affliction.Prefab is { } prefab)) { return ImmutableArray<GUIComponent>.Empty; }
GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement"); GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement");
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), backgroundFrame.RectTransform, Anchor.Center)) new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform, Anchor.BottomCenter), style: "HorizontalLine");
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), backgroundFrame.RectTransform, Anchor.Center))
{ {
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
@@ -862,15 +869,32 @@ namespace Barotrauma
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUILayoutGroup bottomTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), bottomLayout.RectTransform)); GUILayoutGroup bottomTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), bottomLayout.RectTransform))
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), bottomTextLayout.RectTransform), ToolBox.LimitString(prefab.Description, GUIStyle.Font, GUI.IntScale(64)), wrap: true) {
RelativeSpacing = 0.05f
};
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform), prefab.Description, font: GUIStyle.SmallFont, wrap: true)
{ {
ToolTip = prefab.Description ToolTip = prefab.Description
}; };
bool truncated = false;
while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n'))
{
var split = descriptionBlock.WrappedText.Value.Split('\n');
descriptionBlock.Text = string.Join('\n', split.Take(split.Length - 1));
truncated = true;
}
if (truncated)
{
descriptionBlock.Text += "...";
}
GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.LargeFont); GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont);
GUIButton buyButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.75f), bottomLayout.RectTransform), style: "CrewManagementAddButton"); GUIButton buyButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.75f), bottomLayout.RectTransform), style: "CrewManagementAddButton")
{
ClickSound = GUISoundType.Cart
};
ImmutableArray<GUIComponent> elementsToDisable = ImmutableArray.Create<GUIComponent>(prefabBlock, backgroundFrame, icon, vitalityBlock, severityBlock, buyButton, descriptionBlock, priceBlock); ImmutableArray<GUIComponent> elementsToDisable = ImmutableArray.Create<GUIComponent>(prefabBlock, backgroundFrame, icon, vitalityBlock, severityBlock, buyButton, descriptionBlock, priceBlock);
@@ -923,6 +947,7 @@ namespace Barotrauma
}); });
} }
#warning TODO: this doesn't seem like the right place for this, and it's not clear from the method signature how this differs from ToolBox.LimitString
public static void EnsureTextDoesntOverflow(string? text, GUITextBlock textBlock, Rectangle bounds, ImmutableArray<GUILayoutGroup>? layoutGroups = null) public static void EnsureTextDoesntOverflow(string? text, GUITextBlock textBlock, Rectangle bounds, ImmutableArray<GUILayoutGroup>? layoutGroups = null)
{ {
if (string.IsNullOrWhiteSpace(text)) { return; } if (string.IsNullOrWhiteSpace(text)) { return; }
@@ -46,9 +46,7 @@ namespace Barotrauma
private int buyTotal, sellTotal, sellFromSubTotal; private int buyTotal, sellTotal, sellFromSubTotal;
private GUITextBlock storeNameBlock; private GUITextBlock storeNameBlock;
private GUITextBlock merchantBalanceBlock; private GUITextBlock reputationEffectBlock;
private GUITextBlock currentSellValueBlock, newSellValueBlock;
private GUIImage sellValueChangeArrow;
private GUIDropDown sortingDropDown; private GUIDropDown sortingDropDown;
private GUITextBox searchBox; private GUITextBox searchBox;
private GUILayoutGroup categoryButtonContainer; private GUILayoutGroup categoryButtonContainer;
@@ -376,41 +374,29 @@ namespace Barotrauma
AutoScaleVertical = true, AutoScaleVertical = true,
ForceUpperCase = ForceUpperCase.Yes ForceUpperCase = ForceUpperCase.Yes
}; };
merchantBalanceBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), "",
"", font: GUIStyle.SubHeadingFont) color: Color.White, font: GUIStyle.SubHeadingFont)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
TextScale = 1.1f, TextScale = 1.1f,
TextGetter = () => TextGetter = () => GetMerchantBalanceText()
{
merchantBalanceBlock.TextColor = ActiveStore?.BalanceColor ?? Color.Red;
return GetMerchantBalanceText();
}
}; };
// Item sell value ------------------------------------------------ // Item sell value ------------------------------------------------
var sellValueContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform)) var reputationEffectContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform))
{ {
CanBeFocused = true, CanBeFocused = true,
RelativeSpacing = 0.005f RelativeSpacing = 0.005f,
ToolTip = TextManager.Get("campaignstore.reputationtooltip")
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform),
TextManager.Get("campaignstore.sellvalue"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft) TextManager.Get("reputationmodifier"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
CanBeFocused = false, CanBeFocused = false,
ForceUpperCase = ForceUpperCase.Yes ForceUpperCase = ForceUpperCase.Yes,
}; };
reputationEffectBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform), "", font: GUIStyle.SubHeadingFont)
var valueChangeGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
CanBeFocused = false,
RelativeSpacing = 0.02f
};
float blockWidth = GUI.IsFourByThree() ? 0.32f : 0.28f;
Point blockMaxSize = new Point((int)(GameSettings.CurrentConfig.Graphics.TextScale * 60), valueChangeGroup.Rect.Height);
currentSellValueBlock = new GUITextBlock(new RectTransform(new Vector2(blockWidth, 1.0f), valueChangeGroup.RectTransform) { MaxSize = blockMaxSize },
"", font: GUIStyle.SubHeadingFont)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
CanBeFocused = false, CanBeFocused = false,
@@ -419,64 +405,27 @@ namespace Barotrauma
{ {
if (CurrentLocation != null) if (CurrentLocation != null)
{ {
int balanceAfterTransaction = activeTab switch Color textColor = GUIStyle.ColorReputationNeutral;
string sign = "";
int reputationModifier = (int)MathF.Round((CurrentLocation.GetStoreReputationModifier(activeTab == StoreTab.Buy) - 1) * 100);
if (reputationModifier > 0)
{ {
StoreTab.Buy => ActiveStore.Balance + buyTotal, textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh;
StoreTab.Sell => ActiveStore.Balance - sellTotal, sign = "+";
StoreTab.SellSub => ActiveStore.Balance - sellFromSubTotal,
_ => throw new NotImplementedException(),
};
if (balanceAfterTransaction != ActiveStore.Balance)
{
var newStatus = CurrentLocation.GetStoreBalanceStatus(balanceAfterTransaction);
if (ActiveStore.ActiveBalanceStatus.SellPriceModifier != newStatus.SellPriceModifier)
{
string tooltipTag = newStatus.SellPriceModifier > ActiveStore.ActiveBalanceStatus.SellPriceModifier ?
"campaingstore.valueincreasetooltip" : "campaingstore.valuedecreasetooltip";
sellValueContainer.ToolTip = TextManager.Get(tooltipTag);
currentSellValueBlock.TextColor = newStatus.Color;
sellValueChangeArrow.Color = newStatus.Color;
sellValueChangeArrow.Visible = true;
newSellValueBlock.TextColor = newStatus.Color;
newSellValueBlock.Text = $"{(newStatus.SellPriceModifier * 100).FormatZeroDecimal()} %";
return $"{(ActiveStore.ActiveBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %";
}
} }
sellValueContainer.ToolTip = TextManager.Get("campaignstore.sellvaluetooltip"); else if (reputationModifier < 0)
currentSellValueBlock.TextColor = ActiveStore.BalanceColor; {
sellValueChangeArrow.Visible = false; textColor = IsBuying ? GUIStyle.ColorReputationHigh : GUIStyle.ColorReputationLow;
newSellValueBlock.Text = null; }
return $"{(ActiveStore.ActiveBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %"; reputationEffectBlock.TextColor = textColor;
return $"{sign}{reputationModifier}%";
} }
else else
{ {
sellValueContainer.ToolTip = null; return "";
sellValueChangeArrow.Visible = false;
newSellValueBlock.Text = null;
return null;
} }
} }
}; };
Vector4 newPadding = currentSellValueBlock.Padding;
newPadding.Z = 0;
currentSellValueBlock.Padding = newPadding;
float relativeHeight = 0.45f;
float relativeWidth = (relativeHeight * valueChangeGroup.Rect.Height) / valueChangeGroup.Rect.Width;
sellValueChangeArrow = new GUIImage(new RectTransform(new Vector2(relativeWidth, relativeHeight), valueChangeGroup.RectTransform), "StoreArrow", scaleToFit: true)
{
CanBeFocused = false,
Visible = false
};
newSellValueBlock = new GUITextBlock(new RectTransform(new Vector2(blockWidth, 1.0f), valueChangeGroup.RectTransform) { MaxSize = blockMaxSize },
"", font: GUIStyle.SubHeadingFont)
{
AutoScaleVertical = true,
CanBeFocused = false,
TextScale = 1.1f
};
newPadding = newSellValueBlock.Padding;
newPadding.X = 0;
newSellValueBlock.Padding = newPadding;
// Store mode buttons ------------------------------------------------ // Store mode buttons ------------------------------------------------
var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f / 14.0f), storeContent.RectTransform), style: null); var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f / 14.0f), storeContent.RectTransform), style: null);
@@ -707,7 +656,7 @@ namespace Barotrauma
SetConfirmButtonBehavior(); SetConfirmButtonBehavior();
clearAllButton = new GUIButton(new RectTransform(new Vector2(0.35f, 1.0f), buttonContainer.RectTransform), TextManager.Get("campaignstore.clearall")) clearAllButton = new GUIButton(new RectTransform(new Vector2(0.35f, 1.0f), buttonContainer.RectTransform), TextManager.Get("campaignstore.clearall"))
{ {
ClickSound = GUISoundType.DecreaseQuantity, ClickSound = GUISoundType.Cart,
Enabled = HasActiveTabPermissions(), Enabled = HasActiveTabPermissions(),
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
OnClicked = (button, userData) => OnClicked = (button, userData) =>
@@ -1597,7 +1546,7 @@ namespace Barotrauma
{ {
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
amountInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), shoppingCrateAmountGroup.RectTransform), GUINumberInput.NumberType.Int) amountInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), shoppingCrateAmountGroup.RectTransform), NumberType.Int)
{ {
MinValueInt = 0, MinValueInt = 0,
MaxValueInt = GetMaxAvailable(pi.ItemPrefab, containingTab), MaxValueInt = GetMaxAvailable(pi.ItemPrefab, containingTab),
@@ -1618,8 +1567,6 @@ namespace Barotrauma
} }
AddToShoppingCrate(purchasedItem, quantity: numberInput.IntValue - purchasedItem.Quantity); AddToShoppingCrate(purchasedItem, quantity: numberInput.IntValue - purchasedItem.Quantity);
}; };
amountInput.PlusButton.ClickSound = GUISoundType.IncreaseQuantity;
amountInput.MinusButton.ClickSound = GUISoundType.DecreaseQuantity;
frame.HoverColor = frame.SelectedColor = Color.Transparent; frame.HoverColor = frame.SelectedColor = Color.Transparent;
} }
@@ -1673,7 +1620,7 @@ namespace Barotrauma
{ {
new GUIButton(new RectTransform(new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style: "StoreAddToCrateButton") new GUIButton(new RectTransform(new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style: "StoreAddToCrateButton")
{ {
ClickSound = GUISoundType.IncreaseQuantity, ClickSound = GUISoundType.Cart,
Enabled = !forceDisable && pi.Quantity > 0, Enabled = !forceDisable && pi.Quantity > 0,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
UserData = "addbutton", UserData = "addbutton",
@@ -1684,7 +1631,7 @@ namespace Barotrauma
{ {
new GUIButton(new RectTransform(new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style: "StoreRemoveFromCrateButton") new GUIButton(new RectTransform(new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style: "StoreRemoveFromCrateButton")
{ {
ClickSound = GUISoundType.DecreaseQuantity, ClickSound = GUISoundType.Cart,
Enabled = !forceDisable, Enabled = !forceDisable,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
UserData = "removebutton", UserData = "removebutton",
@@ -2127,11 +2074,13 @@ namespace Barotrauma
{ {
if (IsBuying) if (IsBuying)
{ {
confirmButton.ClickSound = GUISoundType.ConfirmTransaction;
confirmButton.Text = TextManager.Get("CampaignStore.Purchase"); confirmButton.Text = TextManager.Get("CampaignStore.Purchase");
confirmButton.OnClicked = (b, o) => BuyItems(); confirmButton.OnClicked = (b, o) => BuyItems();
} }
else else
{ {
confirmButton.ClickSound = GUISoundType.Select;
confirmButton.Text = TextManager.Get("CampaignStoreTab.Sell"); confirmButton.Text = TextManager.Get("CampaignStoreTab.Sell");
confirmButton.OnClicked = (b, o) => confirmButton.OnClicked = (b, o) =>
{ {
@@ -2139,6 +2088,7 @@ namespace Barotrauma
TextManager.Get("FireWarningHeader"), TextManager.Get("FireWarningHeader"),
TextManager.Get("CampaignStore.SellWarningText"), TextManager.Get("CampaignStore.SellWarningText"),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }); new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmDialog.Buttons[0].ClickSound = GUISoundType.ConfirmTransaction;
confirmDialog.Buttons[0].OnClicked = (b, o) => SellItems(); confirmDialog.Buttons[0].OnClicked = (b, o) => SellItems();
confirmDialog.Buttons[0].OnClicked += confirmDialog.Close; confirmDialog.Buttons[0].OnClicked += confirmDialog.Close;
confirmDialog.Buttons[1].OnClicked = confirmDialog.Close; confirmDialog.Buttons[1].OnClicked = confirmDialog.Close;
@@ -2258,7 +2208,7 @@ namespace Barotrauma
} }
updateStopwatch.Stop(); updateStopwatch.Stop();
GameMain.PerformanceCounter.AddPartialElapsedTicks("GameSessionUpdate", "StoreUpdate", updateStopwatch.ElapsedTicks); GameMain.PerformanceCounter.AddElapsedTicks("Update:GameSession:Store", updateStopwatch.ElapsedTicks);
} }
} }
} }
@@ -29,6 +29,8 @@ namespace Barotrauma
private GUITextBlock descriptionTextBlock; private GUITextBlock descriptionTextBlock;
private int selectionIndicatorThickness; private int selectionIndicatorThickness;
private GUIImage listBackground; private GUIImage listBackground;
private GUITickBox transferItemsTickBox;
private GUITextBlock itemTransferReminderBlock;
private readonly List<SubmarineInfo> subsToShow; private readonly List<SubmarineInfo> subsToShow;
private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage]; private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
@@ -61,6 +63,23 @@ namespace Barotrauma
public GUIButton previewButton; public GUIButton previewButton;
} }
private bool TransferItemsOnSwitch
{
get
{
return transferItemsOnSwitch;
}
set
{
transferItemsOnSwitch = value;
if (transferItemsTickBox != null)
{
transferItemsTickBox.Selected = value;
}
}
}
private bool transferItemsOnSwitch = true;
public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent) public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent)
{ {
if (GameMain.GameSession.Campaign == null) { return; } if (GameMain.GameSession.Campaign == null) { return; }
@@ -149,11 +168,12 @@ namespace Barotrauma
GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) }; GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) };
descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUIStyle.Font, wrap: true) { CanBeFocused = false }; descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUIStyle.Font, wrap: true) { CanBeFocused = false };
GUILayoutGroup buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), content.RectTransform), childAnchor: Anchor.CenterRight) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding }; GUILayoutGroup bottomContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.075f), content.RectTransform, Anchor.CenterRight), childAnchor: Anchor.CenterRight) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding };
float transferInfoFrameWidth = 1.0f;
if (closeAction != null) if (closeAction != null)
{ {
GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), TextManager.Get("Close"), style: "GUIButtonFreeScale") GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), TextManager.Get("Close"), style: "GUIButtonFreeScale")
{ {
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
@@ -161,11 +181,33 @@ namespace Barotrauma
return true; return true;
} }
}; };
transferInfoFrameWidth -= closeButton.RectTransform.RelativeSize.X;
} }
if (purchaseService) confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale"); if (purchaseService)
confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), purchaseService ? purchaseAndSwitchText : deliveryFee > 0 ? deliveryText : switchText, style: "GUIButtonFreeScale"); {
confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale");
transferInfoFrameWidth -= confirmButtonAlt.RectTransform.RelativeSize.X;
}
confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseService ? purchaseAndSwitchText : deliveryFee > 0 ? deliveryText : switchText, style: "GUIButtonFreeScale");
SetConfirmButtonState(false); SetConfirmButtonState(false);
transferInfoFrameWidth -= confirmButton.RectTransform.RelativeSize.X;
GUIFrame transferInfoFrame = new GUIFrame(new RectTransform(new Vector2(transferInfoFrameWidth, 1.0f), bottomContainer.RectTransform), style: null)
{
CanBeFocused = false
};
transferItemsTickBox = new GUITickBox(new RectTransform(new Vector2(0.2f, 1.0f), transferInfoFrame.RectTransform, Anchor.CenterRight), TextManager.Get("transferitems"), font: GUIStyle.SubHeadingFont)
{
Selected = TransferItemsOnSwitch,
Visible = false,
OnSelected = (tb) => transferItemsOnSwitch = tb.Selected
};
transferItemsTickBox.RectTransform.Resize(new Point(Math.Min((int)transferItemsTickBox.ContentWidth, transferInfoFrame.Rect.Width), transferItemsTickBox.Rect.Height));
itemTransferReminderBlock = new GUITextBlock(new RectTransform(Vector2.One, transferInfoFrame.RectTransform, Anchor.CenterRight), null)
{
TextAlignment = Alignment.CenterRight,
Visible = false
};
pageIndicatorHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.5f), submarineControlsGroup.RectTransform), style: null); pageIndicatorHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.5f), submarineControlsGroup.RectTransform), style: null);
pageIndicator = GUIStyle.GetComponentStyle("GUIPageIndicator").GetDefaultSprite(); pageIndicator = GUIStyle.GetComponentStyle("GUIPageIndicator").GetDefaultSprite();
@@ -272,7 +314,7 @@ namespace Barotrauma
} }
} }
public void RefreshSubmarineDisplay(bool updateSubs) public void RefreshSubmarineDisplay(bool updateSubs, bool setTransferOptionToTrue = false)
{ {
if (!initialized) if (!initialized)
{ {
@@ -286,6 +328,10 @@ namespace Barotrauma
{ {
playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
} }
if (setTransferOptionToTrue)
{
TransferItemsOnSwitch = true;
}
if (updateSubs) if (updateSubs)
{ {
UpdateSubmarines(); UpdateSubmarines();
@@ -401,6 +447,10 @@ namespace Barotrauma
{ {
SelectSubmarine(null, Rectangle.Empty); SelectSubmarine(null, Rectangle.Empty);
} }
else
{
UpdateItemTransferInfoFrame();
}
} }
private void UpdateSubmarines() private void UpdateSubmarines()
@@ -553,6 +603,40 @@ namespace Barotrauma
selectedSubmarineIndicator.RectTransform.NonScaledSize = Point.Zero; selectedSubmarineIndicator.RectTransform.NonScaledSize = Point.Zero;
SetConfirmButtonState(false); SetConfirmButtonState(false);
} }
UpdateItemTransferInfoFrame();
}
private void UpdateItemTransferInfoFrame()
{
if (selectedSubmarine != null)
{
var pendingSub = GameMain.GameSession?.Campaign?.PendingSubmarineSwitch;
if (Submarine.MainSub?.Info?.Name == selectedSubmarine.Name && pendingSub == null)
{
transferItemsTickBox.Visible = false;
itemTransferReminderBlock.Visible = false;
}
else if (pendingSub?.Name == selectedSubmarine.Name)
{
transferItemsTickBox.Visible = false;
itemTransferReminderBlock.Text = GameMain.GameSession.Campaign.TransferItemsOnSubSwitch ?
TextManager.Get("itemtransferenabledreminder") :
TextManager.Get("itemtransferdisabledreminder");
itemTransferReminderBlock.Visible = true;
}
else
{
transferItemsTickBox.Selected = TransferItemsOnSwitch;
transferItemsTickBox.Visible = true;
itemTransferReminderBlock.Visible = false;
}
}
else
{
transferItemsTickBox.Visible = false;
itemTransferReminderBlock.Visible = false;
}
} }
private void SetConfirmButtonState(bool state) private void SetConfirmButtonState(bool state)
@@ -614,24 +698,27 @@ namespace Barotrauma
("[submarinename2]", CurrentOrPendingSubmarine().DisplayName), ("[submarinename2]", CurrentOrPendingSubmarine().DisplayName),
("[amount]", deliveryFee.ToString()), ("[amount]", deliveryFee.ToString()),
("[currencyname]", currencyName)), messageBoxOptions); ("[currencyname]", currencyName)), messageBoxOptions);
msgBox.Buttons[0].ClickSound = GUISoundType.ConfirmTransaction;
} }
else else
{ {
msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), TextManager.GetWithVariables("switchsubmarinetext", var text = TextManager.GetWithVariables("switchsubmarinetext",
("[submarinename1]", CurrentOrPendingSubmarine().DisplayName), ("[submarinename1]", CurrentOrPendingSubmarine().DisplayName),
("[submarinename2]", selectedSubmarine.DisplayName)), messageBoxOptions); ("[submarinename2]", selectedSubmarine.DisplayName));
text += GetItemTransferText();
msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), text, messageBoxOptions);
} }
msgBox.Buttons[0].OnClicked = (applyButton, obj) => msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, deliveryFee);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
{ {
GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.SwitchSub); GameMain.Client.InitiateSubmarineChange(selectedSubmarine, TransferItemsOnSwitch, Networking.VoteType.SwitchSub);
} }
return true; return true;
}; };
@@ -653,23 +740,25 @@ namespace Barotrauma
if (!purchaseOnly) if (!purchaseOnly)
{ {
msgBox = new GUIMessageBox(TextManager.Get("purchaseandswitchsubmarineheader"), TextManager.GetWithVariables("purchaseandswitchsubmarinetext", var text = TextManager.GetWithVariables("purchaseandswitchsubmarinetext",
("[submarinename1]", selectedSubmarine.DisplayName), ("[submarinename1]", selectedSubmarine.DisplayName),
("[amount]", selectedSubmarine.Price.ToString()), ("[amount]", selectedSubmarine.Price.ToString()),
("[currencyname]", currencyName), ("[currencyname]", currencyName),
("[submarinename2]", CurrentOrPendingSubmarine().DisplayName)), messageBoxOptions); ("[submarinename2]", CurrentOrPendingSubmarine().DisplayName));
text += GetItemTransferText();
msgBox = new GUIMessageBox(TextManager.Get("purchaseandswitchsubmarineheader"), text, messageBoxOptions);
msgBox.Buttons[0].OnClicked = (applyButton, obj) => msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, 0);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
{ {
GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.PurchaseAndSwitchSub); GameMain.Client.InitiateSubmarineChange(selectedSubmarine, TransferItemsOnSwitch, Networking.VoteType.PurchaseAndSwitchSub);
} }
return true; return true;
}; };
@@ -690,14 +779,20 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.PurchaseSub); GameMain.Client.InitiateSubmarineChange(selectedSubmarine, false, Networking.VoteType.PurchaseSub);
} }
return true; return true;
}; };
} }
msgBox.Buttons[0].ClickSound = GUISoundType.ConfirmTransaction;
msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close; msgBox.Buttons[1].OnClicked = msgBox.Close;
} }
private LocalizedString GetItemTransferText()
{
return "\n\n" + TextManager.Get(TransferItemsOnSwitch ? "itemswillbetransferred" : "itemswontbetransferred");
}
} }
} }
@@ -54,47 +54,65 @@ namespace Barotrauma
private ushort currentPing; private ushort currentPing;
private readonly Character character; private readonly Character character;
private readonly bool hasCharacter; private readonly bool wasCharacterAlive;
private readonly GUITextBlock textBlock; private readonly GUITextBlock textBlock;
private readonly GUIFrame frame; private readonly GUIFrame frame;
private readonly GUIImage permissionIcon; private readonly GUIImage permissionIcon;
public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock, GUIImage permissionIcon) public LinkedGUI(Client client, GUIFrame frame, GUITextBlock textBlock, GUIImage permissionIcon)
{ {
this.Client = client; this.Client = client;
this.textBlock = textBlock; this.textBlock = textBlock;
this.frame = frame; this.frame = frame;
this.hasCharacter = hasCharacter;
this.permissionIcon = permissionIcon; this.permissionIcon = permissionIcon;
character = client?.Character;
wasCharacterAlive = client?.Character != null && !client.Character.IsDead;
} }
public LinkedGUI(Character character, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock) public LinkedGUI(Character character, GUIFrame frame, GUITextBlock textBlock)
{ {
this.character = character; this.character = character;
this.textBlock = textBlock; this.textBlock = textBlock;
this.frame = frame; this.frame = frame;
this.hasCharacter = hasCharacter; wasCharacterAlive = character != null && !character.IsDead;
} }
public bool HasMultiplayerCharacterChanged() public bool HasMultiplayerCharacterChanged()
{ {
if (Client == null) { return false; } if (Client == null) { return false; }
bool characterState = Client.Character != null;
if (characterState && Client.Character.IsDead) characterState = false; if (GameSettings.CurrentConfig.VerboseLogging)
return hasCharacter != characterState; {
if (Client.Character != character)
{
DebugConsole.Log($"Refreshing tab menu crew list (client \"{Client.Name}\"'s character changed from \"{character?.Name ?? "null"}\" to \"{Client.Character?.Name ?? "null"}\")");
}
}
return Client.Character != character;
} }
public bool HasMultiplayerCharacterDied() public bool HasCharacterDied()
{
if (Client == null || !hasCharacter || Client.Character == null) { return false; }
return Client.Character.IsDead;
}
public bool HasAICharacterDied()
{ {
if (character == null) { return false; } if (character == null) { return false; }
return character.IsDead; bool isAlive = !(character?.IsDead ?? true);
if (GameSettings.CurrentConfig.VerboseLogging)
{
if (wasCharacterAlive && !isAlive)
{
DebugConsole.Log(Client == null ?
$"Refreshing tab menu crew list (character \"{character?.Name ?? "null"}\" died)" :
$"Refreshing tab menu crew list (client \"{Client.Name}\"'s character \"{character?.Name ?? "null"}\" died)");
}
else if (!wasCharacterAlive && isAlive)
{
DebugConsole.Log(Client == null ?
$"Refreshing tab menu crew list (character \"{character?.Name ?? "null"}\" came back to life)" :
$"Refreshing tab menu crew list (client \"{Client.Name}\"'s character \"{character?.Name ?? "null"}\" came back to life)");
}
}
return isAlive != wasCharacterAlive;
} }
public void TryPingRefresh() public void TryPingRefresh()
@@ -207,7 +225,7 @@ namespace Barotrauma
{ {
linkedGUIList[i].TryPingRefresh(); linkedGUIList[i].TryPingRefresh();
linkedGUIList[i].TryPermissionIconRefresh(GetPermissionIcon(linkedGUIList[i].Client)); linkedGUIList[i].TryPermissionIconRefresh(GetPermissionIcon(linkedGUIList[i].Client));
if (linkedGUIList[i].HasMultiplayerCharacterChanged() || linkedGUIList[i].HasMultiplayerCharacterDied() || linkedGUIList[i].HasAICharacterDied()) if (linkedGUIList[i].HasMultiplayerCharacterChanged() || linkedGUIList[i].HasCharacterDied())
{ {
RemoveCurrentElements(); RemoveCurrentElements();
CreateMultiPlayerList(true); CreateMultiPlayerList(true);
@@ -219,10 +237,11 @@ namespace Barotrauma
{ {
for (int i = 0; i < linkedGUIList.Count; i++) for (int i = 0; i < linkedGUIList.Count; i++)
{ {
if (linkedGUIList[i].HasAICharacterDied()) if (linkedGUIList[i].HasCharacterDied())
{ {
RemoveCurrentElements(); RemoveCurrentElements();
CreateSinglePlayerList(true); CreateSinglePlayerList(true);
return;
} }
} }
} }
@@ -297,6 +316,10 @@ namespace Barotrauma
var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame"); var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame");
GUITextBlock balanceText = new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), string.Empty, textAlignment: Alignment.Right); GUITextBlock balanceText = new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), string.Empty, textAlignment: Alignment.Right);
if (GameMain.IsMultiplayer)
{
balanceText.ToolTip = TextManager.Get("bankdescription");
}
GUIFrame bottomDisclaimerFrame = new GUIFrame(new RectTransform(new Vector2(contentFrameSize.X, 0.1f), infoFrame.RectTransform) GUIFrame bottomDisclaimerFrame = new GUIFrame(new RectTransform(new Vector2(contentFrameSize.X, 0.1f), infoFrame.RectTransform)
{ {
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8)) AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
@@ -337,7 +360,7 @@ namespace Barotrauma
var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character"); var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character");
talentsButton.OnAddedToGUIUpdateList += (component) => talentsButton.OnAddedToGUIUpdateList += (component) =>
{ {
talentsButton.Enabled = Character.Controlled?.Info != null; talentsButton.Enabled = Character.Controlled?.Info != null || GameMain.Client?.CharacterInfo != null;
if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents)
{ {
SelectInfoFrameTab(InfoFrameTab.Crew); SelectInfoFrameTab(InfoFrameTab.Crew);
@@ -430,7 +453,8 @@ namespace Barotrauma
GUIListBox crewList = new GUIListBox(new RectTransform(crewListSize, content.RectTransform)) GUIListBox crewList = new GUIListBox(new RectTransform(crewListSize, content.RectTransform))
{ {
Padding = new Vector4(2, 5, 0, 0), Padding = new Vector4(2, 5, 0, 0),
AutoHideScrollBar = false AutoHideScrollBar = false,
PlaySoundOnSelect = true
}; };
crewList.UpdateDimensions(); crewList.UpdateDimensions();
@@ -560,7 +584,7 @@ namespace Barotrauma
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor); ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, textBlock: null)); linkedGUIList.Add(new LinkedGUI(character, frame, textBlock: null));
} }
private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame) private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame)
@@ -657,7 +681,7 @@ namespace Barotrauma
if (client != null) if (client != null)
{ {
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon); CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
linkedGUIList.Add(new LinkedGUI(client, frame, true, linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center), new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon)); permissionIcon));
} }
@@ -668,12 +692,12 @@ namespace Barotrauma
if (character is AICharacter) if (character is AICharacter)
{ {
linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, linkedGUIList.Add(new LinkedGUI(character, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes })); new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
} }
else else
{ {
linkedGUIList.Add(new LinkedGUI(client: null, frame, true, textBlock: null, permissionIcon: null)); linkedGUIList.Add(new LinkedGUI(client: null, frame, textBlock: null, permissionIcon: null));
new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect)) new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect))
{ {
@@ -718,7 +742,7 @@ namespace Barotrauma
}; };
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon); CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
linkedGUIList.Add(new LinkedGUI(client, frame, false, linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center), new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon)); permissionIcon));
@@ -775,19 +799,27 @@ namespace Barotrauma
Stretch = true Stretch = true
}; };
new GUIFrame(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), style: null)
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("walletdescription")
};
if (character.IsBot) { return; } if (character.IsBot) { return; }
Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite; Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite;
GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), walletSprite, scaleToFit: true); GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), walletSprite, scaleToFit: true) { CanBeFocused = false };
GUITextBlock walletBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), string.Empty, textAlignment: Alignment.Right, font: GUIStyle.Font) GUITextBlock walletBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), string.Empty, textAlignment: Alignment.Right, font: GUIStyle.Font)
{ {
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
Padding = Vector4.Zero Padding = Vector4.Zero,
CanBeFocused = false
}; };
GUIImage largeIcon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), walletSprite, scaleToFit: true) GUIImage largeIcon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), walletSprite, scaleToFit: true)
{ {
CanBeFocused = false,
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false Visible = false
}; };
@@ -897,8 +929,8 @@ namespace Barotrauma
} }
else else
{ {
Vector2 stringOffset = GUIStyle.GlobalFont.MeasureString(inLobbyString) / 2f; Vector2 stringOffset = GUIStyle.Font.MeasureString(inLobbyString) / 2f;
GUIStyle.GlobalFont.DrawString(spriteBatch, inLobbyString, area.Center.ToVector2() - stringOffset, Color.White); GUIStyle.Font.DrawString(spriteBatch, inLobbyString, area.Center.ToVector2() - stringOffset, Color.White);
} }
} }
@@ -971,16 +1003,25 @@ namespace Barotrauma
float relativeX = icon.RectTransform.NonScaledSize.X / (float)icon.Parent.RectTransform.NonScaledSize.X; float relativeX = icon.RectTransform.NonScaledSize.X / (float)icon.Parent.RectTransform.NonScaledSize.X;
GUILayoutGroup headerTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativeX, 1f), headerLayout.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup headerTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativeX, 1f), headerLayout.RectTransform), isHorizontal: true) { Stretch = true };
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.Get("crewwallet.wallet"), font: GUIStyle.LargeFont); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.Get("crewwallet.wallet"), font: GUIStyle.LargeFont);
GUIFrame walletTooltipFrame = new GUIFrame(new RectTransform(Vector2.One, headerLayout.RectTransform), style: null)
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("walletdescription")
};
GUITextBlock moneyBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.FormatCurrency(targetWallet.Balance), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right); GUITextBlock moneyBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.FormatCurrency(targetWallet.Balance), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right);
GUILayoutGroup middleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), walletLayout.RectTransform)); GUILayoutGroup middleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), walletLayout.RectTransform));
GUILayoutGroup salaryTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true); GUILayoutGroup salaryTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true);
GUITextBlock salaryTitle = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), TextManager.Get("crewwallet.salary"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); GUITextBlock salaryTitle = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), TextManager.Get("crewwallet.salary"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
GUITextBlock rewardBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), string.Empty, textAlignment: Alignment.BottomRight); GUITextBlock rewardBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), string.Empty, textAlignment: Alignment.BottomRight);
GUIFrame salaryTooltipFrame = new GUIFrame(new RectTransform(Vector2.One, middleLayout.RectTransform), style: null)
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("crewwallet.salary.tooltip")
};
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center); GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center);
GUIScrollBar salarySlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 1f), sliderLayout.RectTransform), style: "GUISlider", barSize: 0.03f) GUIScrollBar salarySlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 1f), sliderLayout.RectTransform), style: "GUISlider", barSize: 0.03f)
{ {
ToolTip = TextManager.Get("crewwallet.salary.tooltip"),
Range = new Vector2(0, 1), Range = new Vector2(0, 1),
BarScrollValue = targetWallet.RewardDistribution / 100f, BarScrollValue = targetWallet.RewardDistribution / 100f,
Step = 0.01f, Step = 0.01f,
@@ -1024,7 +1065,7 @@ namespace Barotrauma
GUIButton centerButton = new GUIButton(new RectTransform(new Vector2(1f), centerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight, anchor: Anchor.Center), style: "GUIButtonTransferArrow"); GUIButton centerButton = new GUIButton(new RectTransform(new Vector2(1f), centerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight, anchor: Anchor.Center), style: "GUIButtonTransferArrow");
GUILayoutGroup inputLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center); GUILayoutGroup inputLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center);
GUINumberInput transferAmountInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), inputLayout.RectTransform), GUINumberInput.NumberType.Int, hidePlusMinusButtons: true) GUINumberInput transferAmountInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), inputLayout.RectTransform), NumberType.Int, hidePlusMinusButtons: true)
{ {
MinValueInt = 0 MinValueInt = 0
}; };
@@ -1050,149 +1091,195 @@ namespace Barotrauma
return true; return true;
} }
}; };
Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier();
ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen); ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen);
ToggleCenterButton(centerButton, isSending); ToggleCenterButton(centerButton, isSending);
if (!(Character.Controlled is { } myCharacter))
{
salarySlider.Enabled = false;
transferAmountInput.Enabled = false;
centerButton.Enabled = false;
confirmButton.Enabled = false;
return;
}
bool hasMoneyPermissions = CampaignMode.AllowedToManageWallets();
salarySlider.Enabled = hasMoneyPermissions;
Wallet otherWallet; Wallet otherWallet;
GameMain.Client?.OnPermissionChanged.RegisterOverwriteExisting(eventIdentifier, e => UpdateWalletInterface(registerEvents: false));
UpdateWalletInterface(registerEvents: true);
switch (hasMoneyPermissions) void UpdateWalletInterface(bool registerEvents)
{ {
case true: if (!(Character.Controlled is { } myCharacter))
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
break;
case false when character == myCharacter:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
isSending = true;
ToggleCenterButton(centerButton, isSending);
break;
default:
rightName.Text = myCharacter.Name;
otherWallet = campaign.PersonalWallet;
break;
}
MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups);
updateButtonText();
if (!hasMoneyPermissions)
{
if (character != Character.Controlled)
{ {
centerButton.Enabled = centerButton.CanBeFocused = false; salarySlider.Enabled = false;
transferAmountInput.Enabled = false;
centerButton.Enabled = false;
confirmButton.Enabled = false;
return;
} }
salarySlider.Enabled = salarySlider.CanBeFocused = false;
}
leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); bool hasMoneyPermissions = CampaignMode.AllowedToManageWallets();
salarySlider.Enabled = hasMoneyPermissions;
UpdateAllInputs(); switch (hasMoneyPermissions)
{
case true:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
break;
case false when character == myCharacter:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
isSending = true;
ToggleCenterButton(centerButton, isSending);
break;
default:
rightName.Text = myCharacter.Name;
otherWallet = campaign.PersonalWallet;
break;
}
MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups);
UpdatedConfirmButtonText();
if (!hasMoneyPermissions)
{
if (character != Character.Controlled)
{
centerButton.Enabled = centerButton.CanBeFocused = false;
}
salarySlider.Enabled = salarySlider.CanBeFocused = false;
}
leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
centerButton.OnClicked = (btn, o) =>
{
isSending = !isSending;
updateButtonText();
ToggleCenterButton(btn, isSending);
UpdateAllInputs(); UpdateAllInputs();
return true;
};
void updateButtonText() if (!registerEvents) { return; }
{
confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney");
}
transferAmountInput.OnValueChanged = input => centerButton.OnClicked = (btn, o) =>
{
UpdateInputs();
};
transferAmountInput.OnValueEntered = input =>
{
UpdateAllInputs();
};
Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier();
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{
if (e.Wallet == targetWallet)
{ {
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance); isSending = !isSending;
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f; UpdatedConfirmButtonText();
ToggleCenterButton(btn, isSending);
UpdateAllInputs();
return true;
};
transferAmountInput.OnValueChanged = input =>
{
UpdateInputs();
};
transferAmountInput.OnValueEntered = input =>
{
UpdateAllInputs();
};
resetButton.OnClicked = (button, o) =>
{
transferAmountInput.IntValue = 0;
UpdateAllInputs();
return true;
};
confirmButton.OnClicked = (button, o) =>
{
int amount = transferAmountInput.IntValue;
if (amount == 0) { return false; }
Option<Character> target1 = Option<Character>.Some(character),
target2 = otherWallet == campaign.Bank ? Option<Character>.None() : Option<Character>.Some(myCharacter);
if (isSending) { (target1, target2) = (target2, target1); }
SendTransaction(target1, target2, amount);
isTransferMenuOpen = false;
ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen);
return true;
};
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{
if (e.Wallet == targetWallet)
{
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance);
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f;
}
UpdateAllInputs();
});
registeredEvents.Add(eventIdentifier);
void UpdatedConfirmButtonText()
{
confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney");
} }
UpdateAllInputs();
});
registeredEvents.Add(eventIdentifier);
resetButton.OnClicked = (button, o) => void UpdateAllInputs()
{
transferAmountInput.IntValue = 0;
UpdateAllInputs();
return true;
};
confirmButton.OnClicked = (button, o) =>
{
int amount = transferAmountInput.IntValue;
if (amount == 0) { return false; }
Option<Character> target1 = Option<Character>.Some(character),
target2 = otherWallet == campaign.Bank ? Option<Character>.None() : Option<Character>.Some(myCharacter);
if (isSending) { (target1, target2) = (target2, target1); }
SendTransaction(target1, target2, amount);
isTransferMenuOpen = false;
ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen);
return true;
};
void UpdateAllInputs()
{
UpdateInputs();
UpdateMaxInput();
}
void UpdateInputs()
{
confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0;
if (transferAmountInput.IntValue == 0)
{ {
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); UpdateInputs();
rightBalance.TextColor = GUIStyle.TextColorNormal; UpdateMaxInput();
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance);
leftBalance.TextColor = GUIStyle.TextColorNormal;
} }
else if (isSending)
void UpdateInputs()
{ {
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue); confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0;
rightBalance.TextColor = GUIStyle.Blue; if (transferAmountInput.IntValue == 0)
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue); {
leftBalance.TextColor = GUIStyle.Red; rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
rightBalance.TextColor = GUIStyle.TextColorNormal;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance);
leftBalance.TextColor = GUIStyle.TextColorNormal;
}
else if (isSending)
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Blue;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Red;
}
else
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Red;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Blue;
}
} }
else
void UpdateMaxInput()
{ {
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue); int maxValue = isSending ? targetWallet.Balance : otherWallet.Balance;
rightBalance.TextColor = GUIStyle.Red; transferAmountInput.MaxValueInt = maxValue;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Blue; transferAmountInput.Enabled = true;
transferAmountInput.ToolTip = string.Empty;
if (!hasMoneyPermissions && GameMain.Client?.ServerSettings is { } serverSettings)
{
transferAmountInput.MaxValueInt = Math.Min(maxValue, serverSettings.MaximumMoneyTransferRequest);
if (serverSettings.MaximumMoneyTransferRequest <= 0)
{
transferAmountInput.Enabled = false;
transferAmountInput.ToolTip = TextManager.Get("wallettransferrequestdisabled");
}
}
} }
} }
void UpdateMaxInput() void SetRewardText(int value, GUITextBlock block)
{ {
transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance; var (_, percentage, sum) = Mission.GetRewardShare(value, salaryCrew, Option<int>.None());
LocalizedString tooltip = string.Empty;
block.TextColor = GUIStyle.TextColorNormal;
if (sum > 100)
{
tooltip = TextManager.GetWithVariables("crewwallet.salary.over100toolitp", ("[sum]", $"{(int)sum}"), ("[newvalue]", $"{percentage}"));
block.TextColor = GUIStyle.Orange;
}
LocalizedString text = TextManager.GetWithVariable("percentageformat", "[value]", $"{value}");
block.Text = text;
block.ToolTip = RichString.Rich(tooltip);
} }
static void ToggleTransferMenuIcon(GUIButton btn, bool open) static void ToggleTransferMenuIcon(GUIButton btn, bool open)
@@ -1235,24 +1322,6 @@ namespace Barotrauma
transfer.Write(msg); transfer.Write(msg);
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
} }
void SetRewardText(int value, GUITextBlock block)
{
var (_, percentage, sum) = Mission.GetRewardShare(value, salaryCrew, Option<int>.None());
LocalizedString tooltip = string.Empty;
block.TextColor = GUIStyle.TextColorNormal;
if (sum > 100)
{
tooltip = TextManager.GetWithVariables("crewwallet.salary.over100toolitp", ("[sum]", $"{(int)sum}"), ("[newvalue]", $"{percentage}"));
block.TextColor = GUIStyle.Orange;
}
LocalizedString text = TextManager.GetWithVariable("percentageformat", "[value]", $"{value}");
block.Text = text;
block.ToolTip = RichString.Rich(tooltip);
}
} }
private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null) private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null)
@@ -1490,10 +1559,10 @@ namespace Barotrauma
RichString missionReputationString = RichString.Rich(reputationText, wrapMissionText(GUIStyle.Font)); RichString missionReputationString = RichString.Rich(reputationText, wrapMissionText(GUIStyle.Font));
RichString missionDescriptionString = RichString.Rich(descriptionText, wrapMissionText(GUIStyle.Font)); RichString missionDescriptionString = RichString.Rich(descriptionText, wrapMissionText(GUIStyle.Font));
Vector2 missionNameSize = GUIStyle.LargeFont.MeasureString(missionNameString); Vector2 missionNameSize = GUIStyle.LargeFont.MeasureString(missionNameString.SanitizedValue);
Vector2 missionDescriptionSize = GUIStyle.Font.MeasureString(missionDescriptionString); Vector2 missionDescriptionSize = GUIStyle.Font.MeasureString(missionDescriptionString.SanitizedValue);
Vector2 missionRewardSize = GUIStyle.Font.MeasureString(missionRewardString); Vector2 missionRewardSize = GUIStyle.Font.MeasureString(missionRewardString.SanitizedValue);
Vector2 missionReputationSize = GUIStyle.Font.MeasureString(missionReputationString); Vector2 missionReputationSize = GUIStyle.Font.MeasureString(missionReputationString.SanitizedValue);
float ySize = missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y + missionReputationSize.Y + missionTextGroup.AbsoluteSpacing * 4; float ySize = missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y + missionReputationSize.Y + missionTextGroup.AbsoluteSpacing * 4;
bool displayDifficulty = mission.Difficulty.HasValue; bool displayDifficulty = mission.Difficulty.HasValue;
@@ -1740,9 +1809,6 @@ namespace Barotrauma
talentButtons.Clear(); talentButtons.Clear();
talentCornerIcons.Clear(); talentCornerIcons.Clear();
Character controlledCharacter = Character.Controlled;
if (controlledCharacter == null) { return; }
GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15); int padding = GUI.IntScale(15);
GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
@@ -1762,13 +1828,20 @@ namespace Barotrauma
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false); GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
} }
/*Character controlledCharacter = Character.Controlled;
if (controlledCharacter == null) { return; }
if (controlledCharacter.Info is null) if (controlledCharacter.Info is null)
{ {
DebugConsole.ThrowError("No character info found for talent UI"); DebugConsole.ThrowError("No character info found for talent UI");
return; return;
} }*/
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); Character controlledCharacter = Character.Controlled;
CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info == null) { return; }
Job job = info.Job;
GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
@@ -1777,8 +1850,6 @@ namespace Barotrauma
GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true); GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true);
CharacterInfo info = controlledCharacter.Info;
Job job = info.Job;
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) =>
{ {
@@ -1789,23 +1860,28 @@ namespace Barotrauma
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), talentInfoLayoutGroup.RectTransform)) { RelativeSpacing = 0.05f }; GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), talentInfoLayoutGroup.RectTransform)) { RelativeSpacing = 0.05f };
Vector2 nameSize = GUIStyle.SubHeadingFont.MeasureString(info.Name); Vector2 nameSize = GUIStyle.SubHeadingFont.MeasureString(info.Name);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont) { TextColor = job.Prefab.UIColor }; GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
nameBlock.RectTransform.NonScaledSize = nameSize.Pad(nameBlock.Padding).ToPoint(); nameBlock.RectTransform.NonScaledSize = nameSize.Pad(nameBlock.Padding).ToPoint();
Vector2 jobSize = GUIStyle.SmallFont.MeasureString(job.Name); if (!info.OmitJobInMenus)
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor }; {
jobBlock.RectTransform.NonScaledSize = jobSize.Pad(jobBlock.Padding).ToPoint(); nameBlock.TextColor = job.Prefab.UIColor;
Vector2 jobSize = GUIStyle.SmallFont.MeasureString(job.Name);
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
jobBlock.RectTransform.NonScaledSize = jobSize.Pad(jobBlock.Padding).ToPoint();
}
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + info.PersonalityTrait.Name.Replace(" ", ""))); LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + info.PersonalityTrait.Name.Replace(" ", "")));
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString); Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont); GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
GUIFrame endocrineFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null); GUIFrame talentsOutsideTreeFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null);
if (!(GameMain.NetworkMember is null)) if (!(GameMain.NetworkMember is null))
{ {
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), endocrineFrame.RectTransform, Anchor.TopLeft), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), talentsOutsideTreeFrame.RectTransform, Anchor.TopLeft),
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"))
{ {
IgnoreLayoutGroups = true IgnoreLayoutGroups = true
}; };
@@ -1844,6 +1920,7 @@ namespace Barotrauma
{ {
OnClicked = (button, o) => OnClicked = (button, o) =>
{ {
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
characterSettingsFrame!.Visible = false; characterSettingsFrame!.Visible = false;
talentFrameMain.Visible = true; talentFrameMain.Visible = true;
return true; return true;
@@ -1852,13 +1929,14 @@ namespace Barotrauma
} }
} }
IEnumerable<TalentPrefab> endocrineTalents = info.GetEndocrineTalents().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)); IEnumerable<TalentPrefab> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e));
if (endocrineTalents.Count() > 0) if (talentsOutsideTree.Count() > 0)
{ {
GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), endocrineFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon") //TODO: replace with something more generic
GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), talentsOutsideTreeFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon")
{ {
ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", endocrineTalents.Select(e => e.DisplayName))}" ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", talentsOutsideTree.Select(e => e.DisplayName))}"
}; };
} }
@@ -1870,49 +1948,55 @@ namespace Barotrauma
skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint(); skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint();
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null); skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
CreateTalentSkillList(controlledCharacter, skillListBox); CreateTalentSkillList(controlledCharacter, info, skillListBox);
if (!TalentTree.JobTalentTrees.TryGet(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } if (controlledCharacter != null)
new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine");
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{ {
GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null); if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine");
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
for (int i = 0; i < 4; i++) GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
selectedTalents = info.GetUnlockedTalentsInTree().ToList();
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{ {
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize; GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground"); for (int i = 0; i < 4; i++)
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{ {
CanBeFocused = false GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize; Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
{
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{
CanBeFocused = false,
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
if (subTree.TalentOptionStages.Count <= i) { continue; }
if (subTree.TalentOptionStages.Count > i)
{
TalentOption talentOption = subTree.TalentOptionStages[i]; TalentOption talentOption = subTree.TalentOptionStages[i];
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft); GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier)) foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier))
@@ -1929,6 +2013,7 @@ namespace Barotrauma
ToolTip = RichString.Rich(talent.DisplayName + "\n\n" + talent.Description), ToolTip = RichString.Rich(talent.DisplayName + "\n\n" + talent.Description),
UserData = talent.Identifier, UserData = talent.Identifier,
PressedColor = pressedColor, PressedColor = pressedColor,
Enabled = controlledCharacter != null,
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
// deselect other buttons in tier by removing their selected talents from pool // deselect other buttons in tier by removing their selected talents from pool
@@ -1961,7 +2046,7 @@ namespace Barotrauma
}, },
}; };
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent; talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
GUIComponent iconImage; GUIComponent iconImage;
if (talent.Icon is null) if (talent.Icon is null)
@@ -1971,6 +2056,7 @@ namespace Barotrauma
OutlineColor = GUIStyle.Red, OutlineColor = GUIStyle.Red,
TextColor = GUIStyle.Red, TextColor = GUIStyle.Red,
PressedColor = unselectableColor, PressedColor = unselectableColor,
DisabledColor = unselectableColor,
CanBeFocused = false, CanBeFocused = false,
}; };
} }
@@ -1979,63 +2065,63 @@ namespace Barotrauma
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{ {
PressedColor = unselectableColor, PressedColor = unselectableColor,
DisabledColor = unselectableColor * 0.5f,
CanBeFocused = false, CanBeFocused = false,
}; };
} }
iconImage.Enabled = talentButton.Enabled;
talentButtons.Add((talentButton, iconImage)); talentButtons.Add((talentButton, iconImage));
} }
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
} }
} }
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f };
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
{
IsHorizontal = true,
};
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{
Shadow = true,
ToolTip = TextManager.Get("experiencetooltip")
};
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{
OnClicked = ApplyTalentSelection,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
} }
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f };
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
{
IsHorizontal = true,
};
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{
Shadow = true,
ToolTip = TextManager.Get("experiencetooltip")
};
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{
OnClicked = ApplyTalentSelection,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
UpdateTalentInfo(); UpdateTalentInfo();
} }
private void CreateTalentSkillList(Character character, GUIListBox parent) private void CreateTalentSkillList(Character character, CharacterInfo info, GUIListBox parent)
{ {
parent.Content.ClearChildren(); parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>(); List<GUITextBlock> skillNames = new List<GUITextBlock>();
foreach (Skill skill in character.Info.Job.GetSkills()) foreach (Skill skill in info.Job.GetSkills())
{ {
GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false };
skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value))); skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value)));
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) }; new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) };
float modifiedSkillLevel = character.GetSkillLevel(skill.Identifier); float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level;
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level))) if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
{ {
int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level); int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level);
@@ -2129,7 +2215,7 @@ namespace Barotrauma
talentButton.icon.HoverColor = hoverColor; talentButton.icon.HoverColor = hoverColor;
} }
CreateTalentSkillList(controlledCharacter, skillListBox); CreateTalentSkillList(controlledCharacter, controlledCharacter.Info, skillListBox);
} }
private void ApplyTalents(Character controlledCharacter) private void ApplyTalents(Character controlledCharacter)
@@ -2157,6 +2243,7 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData) private bool ResetTalentSelection(GUIButton guiButton, object userData)
{ {
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return false; }
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentInfo(); UpdateTalentInfo();
return true; return true;
@@ -27,6 +27,11 @@ namespace Barotrauma
set; set;
} }
/// <summary>
/// The size of fixed area around the slice area
/// </summary>
public Point NonSliceSize { get; set; }
public bool MaintainAspectRatio public bool MaintainAspectRatio
{ {
get; get;
@@ -72,6 +77,7 @@ namespace Barotrauma
maxBorderScale = element.GetAttributeFloat("minborderscale", 10.0f); maxBorderScale = element.GetAttributeFloat("minborderscale", 10.0f);
Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y)); Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y));
NonSliceSize = new Point(Sprite.SourceRect.Width - slice.Width, Sprite.SourceRect.Height - slice.Height);
Slices = new Rectangle[9]; Slices = new Rectangle[9];
@@ -462,7 +462,7 @@ namespace Barotrauma
button.Enabled = false; button.Enabled = false;
} }
return true; return true;
}); }, overrideConfirmButtonSound: GUISoundType.ConfirmTransaction);
} }
else else
{ {
@@ -497,7 +497,7 @@ namespace Barotrauma
button.Enabled = false; button.Enabled = false;
} }
return true; return true;
}); }, overrideConfirmButtonSound: GUISoundType.ConfirmTransaction);
} }
else else
{ {
@@ -539,7 +539,7 @@ namespace Barotrauma
GameMain.Client?.SendCampaignState(); GameMain.Client?.SendCampaignState();
} }
return true; return true;
}); }, overrideConfirmButtonSound: GUISoundType.ConfirmTransaction);
} }
else else
{ {
@@ -589,7 +589,7 @@ namespace Barotrauma
new GUITextBlock(rectT(1, 0, textLayout), title, font: GUIStyle.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true }; new GUITextBlock(rectT(1, 0, textLayout), title, font: GUIStyle.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true };
new GUITextBlock(rectT(1, 0, textLayout), TextManager.FormatCurrency(price)); new GUITextBlock(rectT(1, 0, textLayout), TextManager.FormatCurrency(price));
GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = "buybutton" }; GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = "buybutton" };
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { ClickSound = GUISoundType.HireRepairClick, Enabled = PlayerBalance >= price && !isDisabled, OnClicked = onPressed }; new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { Enabled = PlayerBalance >= price && !isDisabled, OnClicked = onPressed };
contentLayout.Recalculate(); contentLayout.Recalculate();
buyButtonLayout.Recalculate(); buyButtonLayout.Recalculate();
@@ -622,7 +622,8 @@ namespace Barotrauma
PadBottom = true, PadBottom = true,
SelectTop = true, SelectTop = true,
ClampScrollToElements = true, ClampScrollToElements = true,
Spacing = 8 Spacing = 8,
PlaySoundOnSelect = true
}; };
Dictionary<UpgradeCategory, List<UpgradePrefab>> upgrades = new Dictionary<UpgradeCategory, List<UpgradePrefab>>(); Dictionary<UpgradeCategory, List<UpgradePrefab>> upgrades = new Dictionary<UpgradeCategory, List<UpgradePrefab>>();
@@ -1123,7 +1124,10 @@ namespace Barotrauma
{ {
priceText.Text = string.Empty; priceText.Text = string.Empty;
} }
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) { Enabled = false }; new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle)
{
Enabled = false
};
if (upgradePrefab != null) if (upgradePrefab != null)
{ {
var increaseText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), "", textAlignment: Alignment.Center); var increaseText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), "", textAlignment: Alignment.Center);
@@ -1212,7 +1216,7 @@ namespace Barotrauma
Campaign.UpgradeManager.PurchaseUpgrade(prefab, category); Campaign.UpgradeManager.PurchaseUpgrade(prefab, category);
GameMain.Client?.SendCampaignState(); GameMain.Client?.SendCampaignState();
return true; return true;
}); }, overrideConfirmButtonSound: GUISoundType.ConfirmTransaction);
return true; return true;
}; };
@@ -1400,7 +1404,7 @@ namespace Barotrauma
if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradeTab == UpgradeTab.Upgrade && currentStoreLayout != null) if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradeTab == UpgradeTab.Upgrade && currentStoreLayout != null)
{ {
ScrollToCategory(data => data.Category.IsWallUpgrade); ScrollToCategory(data => data.Category.IsWallUpgrade, GUIListBox.PlaySelectSound.Yes);
} }
} }
} }
@@ -1682,7 +1686,7 @@ namespace Barotrauma
} }
} }
private void ScrollToCategory(Predicate<CategoryData> predicate) private void ScrollToCategory(Predicate<CategoryData> predicate, GUIListBox.PlaySelectSound playSelectSound = GUIListBox.PlaySelectSound.No)
{ {
if (currentStoreLayout == null) { return; } if (currentStoreLayout == null) { return; }
@@ -1690,7 +1694,7 @@ namespace Barotrauma
{ {
if (child.UserData is CategoryData data && predicate(data)) if (child.UserData is CategoryData data && predicate(data))
{ {
currentStoreLayout.ScrollToElement(child); currentStoreLayout.ScrollToElement(child, playSelectSound);
break; break;
} }
} }
@@ -26,7 +26,7 @@ namespace Barotrauma
private Color SubmarineColor => GUIStyle.Orange; private Color SubmarineColor => GUIStyle.Orange;
private Point createdForResolution; private Point createdForResolution;
public static VotingInterface CreateSubmarineVotingInterface(Client starter, SubmarineInfo info, VoteType type, float votingTime) public static VotingInterface CreateSubmarineVotingInterface(Client starter, SubmarineInfo info, VoteType type, bool transferItems, float votingTime)
{ {
if (starter == null || info == null) { return null; } if (starter == null || info == null) { return null; }
@@ -38,7 +38,7 @@ namespace Barotrauma
getMaxVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountMax(type) ?? 0, getMaxVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountMax(type) ?? 0,
}; };
subVoting.onVoteEnd = () => subVoting.SendSubmarineVoteEndMessage(info, type); subVoting.onVoteEnd = () => subVoting.SendSubmarineVoteEndMessage(info, type);
subVoting.SetSubmarineVotingText(starter, info, type); subVoting.SetSubmarineVotingText(starter, info, transferItems, type);
subVoting.Initialize(starter, type); subVoting.Initialize(starter, type);
return subVoting; return subVoting;
} }
@@ -160,19 +160,21 @@ namespace Barotrauma
} }
#region Submarine Voting #region Submarine Voting
private void SetSubmarineVotingText(Client starter, SubmarineInfo info, VoteType type)
private void SetSubmarineVotingText(Client starter, SubmarineInfo info, bool transferItems, VoteType type)
{ {
string name = starter.Name; string name = starter.Name;
JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab; JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab;
Color nameColor = prefab != null ? prefab.UIColor : Color.White; Color nameColor = prefab != null ? prefab.UIColor : Color.White;
string characterRichString = $"‖color:{nameColor.R},{nameColor.G},{nameColor.B}‖{name}‖color:end‖"; string characterRichString = $"‖color:{nameColor.R},{nameColor.G},{nameColor.B}‖{name}‖color:end‖";
string submarineRichString = $"‖color:{SubmarineColor.R},{SubmarineColor.G},{SubmarineColor.B}‖{info.DisplayName}‖color:end‖"; string submarineRichString = $"‖color:{SubmarineColor.R},{SubmarineColor.G},{SubmarineColor.B}‖{info.DisplayName}‖color:end‖";
string tag = string.Empty;
LocalizedString text = string.Empty; LocalizedString text = string.Empty;
switch (type) switch (type)
{ {
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
text = TextManager.GetWithVariables("submarinepurchaseandswitchvote", tag = transferItems ? "submarinepurchaseandswitchwithitemsvote" : "submarinepurchaseandswitchvote";
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString), ("[playername]", characterRichString),
("[submarinename]", submarineRichString), ("[submarinename]", submarineRichString),
("[amount]", info.Price.ToString()), ("[amount]", info.Price.ToString()),
@@ -189,7 +191,8 @@ namespace Barotrauma
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
if (deliveryFee > 0) if (deliveryFee > 0)
{ {
text = TextManager.GetWithVariables("submarineswitchfeevote", tag = transferItems ? "submarineswitchwithitemsfeevote" : "submarineswitchfeevote";
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString), ("[playername]", characterRichString),
("[submarinename]", submarineRichString), ("[submarinename]", submarineRichString),
("[locationname]", endLocation.Name), ("[locationname]", endLocation.Name),
@@ -198,13 +201,13 @@ namespace Barotrauma
} }
else else
{ {
text = TextManager.GetWithVariables("submarineswitchnofeevote", tag = transferItems ? "submarineswitchwithitemsnofeevote" : "submarineswitchnofeevote";
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString), ("[playername]", characterRichString),
("[submarinename]", submarineRichString)); ("[submarinename]", submarineRichString));
} }
break; break;
} }
votingOnText = RichString.Rich(text); votingOnText = RichString.Rich(text);
} }
@@ -202,6 +202,8 @@ namespace Barotrauma
public static bool CancelQuickStart; public static bool CancelQuickStart;
#endif #endif
public static ChatMode ActiveChatMode { get; set; } = ChatMode.Radio;
public GameMain(string[] args) public GameMain(string[] args)
{ {
Content.RootDirectory = "Content"; Content.RootDirectory = "Content";
@@ -280,9 +282,9 @@ namespace Barotrauma
screen.OnFileDropped(filePath, extension); screen.OnFileDropped(filePath, extension);
} }
public void ApplyGraphicsSettings() public void ApplyGraphicsSettings(bool recalculateFontsAndStyles = false)
{ {
void updateConfig() static void updateConfig()
{ {
var config = GameSettings.CurrentConfig; var config = GameSettings.CurrentConfig;
config.Graphics.Width = GraphicsWidth; config.Graphics.Width = GraphicsWidth;
@@ -321,6 +323,12 @@ namespace Barotrauma
defaultViewport = GraphicsDevice.Viewport; defaultViewport = GraphicsDevice.Viewport;
if (recalculateFontsAndStyles)
{
GUIStyle.RecalculateFonts();
GUIStyle.RecalculateSizeRestrictions();
}
ResolutionChanged?.Invoke(); ResolutionChanged?.Invoke();
} }
@@ -546,6 +554,10 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
#if DEBUG
LevelGenerationParams.CheckValidity();
#endif
MainMenuScreen.Select(); MainMenuScreen.Select();
foreach (Identifier steamError in SteamManager.InitializationErrors) foreach (Identifier steamError in SteamManager.InitializationErrors)
@@ -920,7 +932,7 @@ namespace Barotrauma
updateCount++; updateCount++;
sw.Stop(); sw.Stop();
PerformanceCounter.AddElapsedTicks("Update total", sw.ElapsedTicks); PerformanceCounter.AddElapsedTicks("Update", sw.ElapsedTicks);
PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency); PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
} }
@@ -939,6 +951,23 @@ namespace Barotrauma
Timing.Accumulator = 0.0f; Timing.Accumulator = 0.0f;
} }
private void FixRazerCortex()
{
#if WINDOWS
//Razer Cortex's overlay is broken.
//For whatever reason, it messes up the blendstate and,
//because MonoGame reasonably assumes that you don't need
//to touch it if you're setting it to the exact same one
//you were already using, it doesn't fix Razer's mess.
//Therefore, we need to change the blendstate TWICE:
//once to force MonoGame to change it, and then again to
//use the blendstate we actually want.
var oldBlendState = GraphicsDevice.BlendState;
GraphicsDevice.BlendState = oldBlendState == BlendState.Opaque ? BlendState.NonPremultiplied : BlendState.Opaque;
GraphicsDevice.BlendState = oldBlendState;
#endif
}
/// <summary> /// <summary>
/// This is called when the game should draw itself. /// This is called when the game should draw itself.
/// </summary> /// </summary>
@@ -947,6 +976,8 @@ namespace Barotrauma
Stopwatch sw = new Stopwatch(); Stopwatch sw = new Stopwatch();
sw.Start(); sw.Start();
FixRazerCortex();
double deltaTime = gameTime.ElapsedGameTime.TotalSeconds; double deltaTime = gameTime.ElapsedGameTime.TotalSeconds;
if (Timing.FrameLimit > 0) if (Timing.FrameLimit > 0)
@@ -1003,7 +1034,7 @@ namespace Barotrauma
} }
sw.Stop(); sw.Stop();
PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks); PerformanceCounter.AddElapsedTicks("Draw", sw.ElapsedTicks);
PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency); PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
} }
@@ -1039,7 +1070,7 @@ namespace Barotrauma
} }
// Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called) // Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called)
if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedOutpost && spCampaign.Map?.CurrentLocation != null && spCampaign.CargoManager != null) if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost && spCampaign.Map?.CurrentLocation != null && spCampaign.CargoManager != null)
{ {
spCampaign.Map.CurrentLocation.AddStock(spCampaign.CargoManager.SoldItems); spCampaign.Map.CurrentLocation.AddStock(spCampaign.CargoManager.SoldItems);
spCampaign.CargoManager.ClearSoldItemsProjSpecific(); spCampaign.CargoManager.ClearSoldItemsProjSpecific();
@@ -1164,7 +1195,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), linkHolder.RectTransform), TextManager.Get("bugreportgithubform"), style: "MainMenuGUIButton", textAlignment: Alignment.Left) new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), linkHolder.RectTransform), TextManager.Get("bugreportgithubform"), style: "MainMenuGUIButton", textAlignment: Alignment.Left)
{ {
UserData = "https://github.com/Regalis11/Barotrauma/issues/new?template=bug_report.md", UserData = "https://github.com/Regalis11/Barotrauma/issues/new/choose",
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
ShowOpenUrlInWebBrowserPrompt(userdata as string); ShowOpenUrlInWebBrowserPrompt(userdata as string);
@@ -81,7 +81,6 @@ namespace Barotrauma
: this(isSinglePlayer) : this(isSinglePlayer)
{ {
AddCharacterElements(element); AddCharacterElements(element);
ActiveOrdersElement = element.GetChildElement("activeorders");
} }
partial void InitProjectSpecific() partial void InitProjectSpecific()
@@ -148,9 +147,10 @@ namespace Barotrauma
string msgCommand = ChatMessage.GetChatMessageCommand(text, out string msg); string msgCommand = ChatMessage.GetChatMessageCommand(text, out string msg);
// add to local history // add to local history
ChatBox.ChatManager.Store(text); ChatBox.ChatManager.Store(text);
WifiComponent headset = null; bool isUsingRadioMode = GameMain.ActiveChatMode == ChatMode.Radio;
ChatMessageType messageType = bool containsRadioCommand = msgCommand == "r" || msgCommand == "radio";
((msgCommand == "r" || msgCommand == "radio") && ChatMessage.CanUseRadio(Character.Controlled, out headset)) ? ChatMessageType.Radio : ChatMessageType.Default; bool canUseRadio = ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent headset);
ChatMessageType messageType = ((isUsingRadioMode && msgCommand == "") || containsRadioCommand) && canUseRadio ? ChatMessageType.Radio : ChatMessageType.Default;
AddSinglePlayerChatMessage( AddSinglePlayerChatMessage(
Character.Controlled.Info.Name, Character.Controlled.Info.Name,
msg, messageType, msg, messageType,
@@ -1554,40 +1554,9 @@ namespace Barotrauma
{ {
ChatBox.Update(deltaTime); ChatBox.Update(deltaTime);
ChatBox.InputBox.Visible = Character.Controlled != null; ChatBox.InputBox.Visible = Character.Controlled != null;
if (!DebugConsole.IsOpen && ChatBox.InputBox.Visible && GUI.KeyboardDispatcher.Subscriber == null && !ChatBox.InputBox.Selected)
if (!DebugConsole.IsOpen && ChatBox.InputBox.Visible && GUI.KeyboardDispatcher.Subscriber == null)
{ {
if (PlayerInput.KeyHit(InputType.Chat) && !ChatBox.InputBox.Selected) ChatBox.ApplySelectionInputs();
{
ChatBox.InputBox.AddToGUIUpdateList();
ChatBox.GUIFrame.Flash(Color.DarkGreen, 0.5f);
if (!ChatBox.ToggleOpen)
{
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
ChatBox.ToggleOpen = true;
}
ChatBox.InputBox.Select(ChatBox.InputBox.Text.Length);
}
if (PlayerInput.KeyHit(InputType.RadioChat) && !ChatBox.InputBox.Selected)
{
if (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100)
{
ChatBox.InputBox.AddToGUIUpdateList();
ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
if (!ChatBox.ToggleOpen)
{
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
ChatBox.ToggleOpen = true;
}
if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString))
{
ChatBox.InputBox.Text = ChatBox.RadioChatString;
}
ChatBox.InputBox.Select(ChatBox.InputBox.Text.Length);
}
}
} }
} }
@@ -1609,7 +1578,7 @@ namespace Barotrauma
{ {
if (character == Character.Controlled && crewList.SelectedComponent != characterComponent) if (character == Character.Controlled && crewList.SelectedComponent != characterComponent)
{ {
crewList.Select(character, force: true); crewList.Select(character, GUIListBox.Force.Yes);
} }
// Icon colors might change based on the target so we check if they need to be updated // Icon colors might change based on the target so we check if they need to be updated
if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList) if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
@@ -3661,9 +3630,9 @@ namespace Barotrauma
crewList.ClearChildren(); crewList.ClearChildren();
} }
public void Save(XElement parentElement) public XElement Save(XElement parentElement)
{ {
XElement element = new XElement("crew"); var element = new XElement("crew");
for (int i = 0; i < characterInfos.Count; i++) for (int i = 0; i < characterInfos.Count; i++)
{ {
var ci = characterInfos[i]; var ci = characterInfos[i];
@@ -3674,8 +3643,8 @@ namespace Barotrauma
infoElement.Add(new XAttribute("crewlistindex", ci.CrewListIndex)); infoElement.Add(new XAttribute("crewlistindex", ci.CrewListIndex));
if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); } if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); }
} }
SaveActiveOrders(element); parentElement?.Add(element);
parentElement.Add(element); return element;
} }
public static void ClientReadActiveOrders(IReadMessage inc) public static void ClientReadActiveOrders(IReadMessage inc)
@@ -13,6 +13,11 @@ namespace Barotrauma
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged) partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged)
{ {
if (Owner is Some<Character> { Value: var character })
{
if (!character.IsPlayer) { return; }
}
CampaignMode campaign = GameMain.GameSession?.Campaign; CampaignMode campaign = GameMain.GameSession?.Campaign;
WalletChangedData data = new WalletChangedData WalletChangedData data = new WalletChangedData
{ {
@@ -587,196 +587,78 @@ namespace Barotrauma
//static because we may need to instantiate the campaign if it hasn't been done yet //static because we may need to instantiate the campaign if it hasn't been done yet
public static void ClientRead(IReadMessage msg) public static void ClientRead(IReadMessage msg)
{ {
NetFlags requiredFlags = (NetFlags)msg.ReadUInt16();
bool isFirstRound = msg.ReadBoolean(); bool isFirstRound = msg.ReadBoolean();
byte campaignID = msg.ReadByte(); byte campaignID = msg.ReadByte();
UInt16 updateID = msg.ReadUInt16();
UInt16 saveID = msg.ReadUInt16(); UInt16 saveID = msg.ReadUInt16();
string mapSeed = msg.ReadString(); string mapSeed = msg.ReadString();
UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionCount = msg.ReadByte(); bool refreshCampaignUI = false;
List<int> selectedMissionIndices = new List<int>();
for (int i = 0; i < selectedMissionCount; i++)
{
selectedMissionIndices.Add(msg.ReadByte());
}
ushort ownedSubCount = msg.ReadUInt16();
List<ushort> ownedSubIndices = new List<ushort>();
for (int i = 0; i < ownedSubCount; i++)
{
ownedSubIndices.Add(msg.ReadUInt16());
}
bool allowDebugTeleport = msg.ReadBoolean();
float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
Dictionary<Identifier, float> factionReps = new Dictionary<Identifier, float>();
byte factionsCount = msg.ReadByte();
for (int i = 0; i < factionsCount; i++)
{
factionReps.Add(msg.ReadIdentifier(), msg.ReadSingle());
}
bool forceMapUI = msg.ReadBoolean();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean();
byte missionCount = msg.ReadByte();
var availableMissions = new List<(Identifier Identifier, byte ConnectionIndex)>();
for (int i = 0; i < missionCount; i++)
{
Identifier missionIdentifier = msg.ReadIdentifier();
byte connectionIndex = msg.ReadByte();
availableMissions.Add((missionIdentifier, connectionIndex));
}
var storeBalances = new Dictionary<Identifier, UInt16>();
if (msg.ReadBoolean())
{
byte storeCount = msg.ReadByte();
for (int i = 0; i < storeCount; i++)
{
Identifier identifier = msg.ReadIdentifier();
UInt16 storeBalance = msg.ReadUInt16();
storeBalances.Add(identifier, storeBalance);
}
}
var buyCrateItems = ReadPurchasedItems(msg, sender: null);
var subSellCrateItems = ReadPurchasedItems(msg, sender: null);
var purchasedItems = ReadPurchasedItems(msg, sender: null);
var soldItems = ReadSoldItems(msg);
ushort pendingUpgradeCount = msg.ReadUInt16();
List<PurchasedUpgrade> pendingUpgrades = new List<PurchasedUpgrade>();
for (int i = 0; i < pendingUpgradeCount; i++)
{
Identifier upgradeIdentifier = msg.ReadIdentifier();
UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier);
Identifier categoryIdentifier = msg.ReadIdentifier();
UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier);
int upgradeLevel = msg.ReadByte();
if (prefab == null || category == null) { continue; }
pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel));
}
ushort purchasedItemSwapCount = msg.ReadUInt16();
List<PurchasedItemSwap> purchasedItemSwaps = new List<PurchasedItemSwap>();
for (int i = 0; i < purchasedItemSwapCount; i++)
{
UInt16 itemToRemoveID = msg.ReadUInt16();
Identifier itemToInstallIdentifier = msg.ReadIdentifier();
ItemPrefab itemToInstall = itemToInstallIdentifier.IsEmpty ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier);
if (!(Entity.FindEntityByID(itemToRemoveID) is Item itemToRemove)) { continue; }
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
}
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
if (hasCharacterData)
{
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
}
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaignID != campaign.CampaignID) if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaignID != campaign.CampaignID)
{ {
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Unsure, mapSeed); GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty, mapSeed);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID; campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
} }
//server has a newer save file //server has a newer save file
if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID)) if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID)) { campaign.PendingSaveID = saveID; }
campaign.IsFirstRound = isFirstRound;
if (requiredFlags.HasFlag(NetFlags.Misc))
{ {
campaign.PendingSaveID = saveID; DebugConsole.Log("Received campaign update (Misc)");
UInt16 id = msg.ReadUInt16();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean();
if (ShouldApply(NetFlags.Misc, id, requireUpToDateSave: false))
{
refreshCampaignUI = campaign.PurchasedHullRepairs != purchasedHullRepairs ||
campaign.PurchasedItemRepairs != purchasedItemRepairs ||
campaign.PurchasedLostShuttles != purchasedLostShuttles;
campaign.PurchasedHullRepairs = purchasedHullRepairs;
campaign.PurchasedItemRepairs = purchasedItemRepairs;
campaign.PurchasedLostShuttles = purchasedLostShuttles;
}
} }
if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID)) if (requiredFlags.HasFlag(NetFlags.MapAndMissions))
{ {
campaign.SuppressStateSending = true; DebugConsole.Log("Received campaign update (MapAndMissions)");
campaign.IsFirstRound = isFirstRound; UInt16 id = msg.ReadUInt16();
bool forceMapUI = msg.ReadBoolean();
bool allowDebugTeleport = msg.ReadBoolean();
UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16();
//we need to have the latest save file to display location/mission/store byte missionCount = msg.ReadByte();
if (campaign.LastSaveID == saveID) var availableMissions = new List<(Identifier Identifier, byte ConnectionIndex)>();
for (int i = 0; i < missionCount; i++)
{
Identifier missionIdentifier = msg.ReadIdentifier();
byte connectionIndex = msg.ReadByte();
availableMissions.Add((missionIdentifier, connectionIndex));
}
byte selectedMissionCount = msg.ReadByte();
List<int> selectedMissionIndices = new List<int>();
for (int i = 0; i < selectedMissionCount; i++)
{
selectedMissionIndices.Add(msg.ReadByte());
}
if (ShouldApply(NetFlags.MapAndMissions, id, requireUpToDateSave: true))
{ {
campaign.ForceMapUI = forceMapUI; campaign.ForceMapUI = forceMapUI;
campaign.Map.AllowDebugTeleport = allowDebugTeleport;
UpgradeStore.WaitForServerUpdate = false;
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndices);
GameMain.GameSession.OwnedSubmarines.Clear();
foreach (int ownedSubIndex in ownedSubIndices)
{
SubmarineInfo sub = GameMain.Client.ServerSubmarines[ownedSubIndex];
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Owned))
{
GameMain.GameSession.OwnedSubmarines.Add(sub);
}
}
campaign.Map.AllowDebugTeleport = allowDebugTeleport;
campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems);
campaign.CargoManager.SetItemsInSubSellCrate(subSellCrateItems);
campaign.CargoManager.SetPurchasedItems(purchasedItems);
campaign.CargoManager.SetSoldItems(soldItems);
foreach (var balance in storeBalances)
{
if (campaign.Map.CurrentLocation.GetStore(balance.Key) is { } store)
{
store.Balance = balance.Value;
}
}
campaign.UpgradeManager.SetPendingUpgrades(pendingUpgrades);
campaign.UpgradeManager.PurchasedUpgrades.Clear();
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
campaign.UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, force: true);
}
else
{
campaign.UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, force: true);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
item.PendingItemSwap = null;
}
}
foreach (var (identifier, rep) in factionReps)
{
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier == identifier);
if (faction?.Reputation != null)
{
faction.Reputation.SetReputation(rep);
}
else
{
DebugConsole.ThrowError($"Received an update for a faction that doesn't exist \"{identifier}\".");
}
}
if (reputation.HasValue)
{
campaign.Map.CurrentLocation.Reputation.SetReputation(reputation.Value);
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
foreach (var availableMission in availableMissions) foreach (var availableMission in availableMissions)
{ {
MissionPrefab missionPrefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == availableMission.Identifier); MissionPrefab missionPrefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == availableMission.Identifier);
@@ -800,36 +682,269 @@ namespace Barotrauma
campaign.Map.CurrentLocation.UnlockMission(missionPrefab, connection); campaign.Map.CurrentLocation.UnlockMission(missionPrefab, connection);
} }
} }
campaign.Map.SelectMission(selectedMissionIndices);
GameMain.NetLobbyScreen.ToggleCampaignMode(true); ReadStores(msg, apply: true);
}
bool shouldRefresh = campaign.PurchasedHullRepairs != purchasedHullRepairs ||
campaign.PurchasedItemRepairs != purchasedItemRepairs ||
campaign.PurchasedLostShuttles != purchasedLostShuttles;
campaign.PurchasedHullRepairs = purchasedHullRepairs;
campaign.PurchasedItemRepairs = purchasedItemRepairs;
campaign.PurchasedLostShuttles = purchasedLostShuttles;
if (shouldRefresh)
{
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
if (myCharacterInfo != null)
{
GameMain.Client.CharacterInfo = myCharacterInfo;
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(myCharacterInfo);
} }
else else
{ {
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null); ReadStores(msg, apply: false);
}
}
if (requiredFlags.HasFlag(NetFlags.SubList))
{
DebugConsole.Log("Received campaign update (SubList)");
UInt16 id = msg.ReadUInt16();
ushort ownedSubCount = msg.ReadUInt16();
List<ushort> ownedSubIndices = new List<ushort>();
for (int i = 0; i < ownedSubCount; i++)
{
ownedSubIndices.Add(msg.ReadUInt16());
} }
campaign.lastUpdateID = updateID; if (ShouldApply(NetFlags.SubList, id, requireUpToDateSave: false))
campaign.SuppressStateSending = false; {
GameMain.GameSession.OwnedSubmarines.Clear();
foreach (int ownedSubIndex in ownedSubIndices)
{
SubmarineInfo sub = GameMain.Client.ServerSubmarines[ownedSubIndex];
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Owned))
{
GameMain.GameSession.OwnedSubmarines.Add(sub);
}
}
}
} }
if (requiredFlags.HasFlag(NetFlags.UpgradeManager))
{
DebugConsole.Log("Received campaign update (UpgradeManager)");
UInt16 id = msg.ReadUInt16();
ushort pendingUpgradeCount = msg.ReadUInt16();
List<PurchasedUpgrade> pendingUpgrades = new List<PurchasedUpgrade>();
for (int i = 0; i < pendingUpgradeCount; i++)
{
Identifier upgradeIdentifier = msg.ReadIdentifier();
UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier);
Identifier categoryIdentifier = msg.ReadIdentifier();
UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier);
int upgradeLevel = msg.ReadByte();
if (prefab == null || category == null) { continue; }
pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel));
}
ushort purchasedItemSwapCount = msg.ReadUInt16();
List<PurchasedItemSwap> purchasedItemSwaps = new List<PurchasedItemSwap>();
for (int i = 0; i < purchasedItemSwapCount; i++)
{
UInt16 itemToRemoveID = msg.ReadUInt16();
Identifier itemToInstallIdentifier = msg.ReadIdentifier();
ItemPrefab itemToInstall = itemToInstallIdentifier.IsEmpty ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier);
if (!(Entity.FindEntityByID(itemToRemoveID) is Item itemToRemove)) { continue; }
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
}
if (!Submarine.Unloading && !(Submarine.MainSub is { Loading: true }) &&
ShouldApply(NetFlags.UpgradeManager, id, requireUpToDateSave: true))
{
UpgradeStore.WaitForServerUpdate = false;
campaign.UpgradeManager.SetPendingUpgrades(pendingUpgrades);
campaign.UpgradeManager.PurchasedUpgrades.Clear();
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
campaign.UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, force: true);
}
else
{
campaign.UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, force: true);
}
}
foreach (Item item in Item.ItemList.ToList())
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
item.PendingItemSwap = null;
}
}
}
}
if (requiredFlags.HasFlag(NetFlags.ItemsInBuyCrate))
{
DebugConsole.Log("Received campaign update (ItemsInBuyCrate)");
UInt16 id = msg.ReadUInt16();
var buyCrateItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.ItemsInBuyCrate, id, requireUpToDateSave: true))
{
campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems);
campaign.SetLastUpdateIdForFlag(NetFlags.ItemsInBuyCrate, id);
ReadStores(msg, apply: true);
}
else
{
ReadStores(msg, apply: false);
}
}
if (requiredFlags.HasFlag(NetFlags.ItemsInSellFromSubCrate))
{
DebugConsole.Log("Received campaign update (ItemsInSellFromSubCrate)");
UInt16 id = msg.ReadUInt16();
var subSellCrateItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.ItemsInSellFromSubCrate, id, requireUpToDateSave: true))
{
campaign.CargoManager.SetItemsInSubSellCrate(subSellCrateItems);
campaign.SetLastUpdateIdForFlag(NetFlags.ItemsInSellFromSubCrate, id);
ReadStores(msg, apply: true);
}
else
{
ReadStores(msg, apply: false);
}
}
if (requiredFlags.HasFlag(NetFlags.PurchasedItems))
{
DebugConsole.Log("Received campaign update (PuchasedItems)");
UInt16 id = msg.ReadUInt16();
var purchasedItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.PurchasedItems, id, requireUpToDateSave: true))
{
campaign.CargoManager.SetPurchasedItems(purchasedItems);
campaign.SetLastUpdateIdForFlag(NetFlags.PurchasedItems, id);
ReadStores(msg, apply: true);
}
else
{
ReadStores(msg, apply: false);
}
}
if (requiredFlags.HasFlag(NetFlags.SoldItems))
{
DebugConsole.Log("Received campaign update (SoldItems)");
UInt16 id = msg.ReadUInt16();
var soldItems = ReadSoldItems(msg);
if (ShouldApply(NetFlags.SoldItems, id, requireUpToDateSave: true))
{
campaign.CargoManager.SetSoldItems(soldItems);
campaign.SetLastUpdateIdForFlag(NetFlags.SoldItems, id);
ReadStores(msg, apply: true);
}
else
{
ReadStores(msg, apply: false);
}
}
if (requiredFlags.HasFlag(NetFlags.Reputation))
{
DebugConsole.Log("Received campaign update (Reputation)");
UInt16 id = msg.ReadUInt16();
float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
Dictionary<Identifier, float> factionReps = new Dictionary<Identifier, float>();
byte factionsCount = msg.ReadByte();
for (int i = 0; i < factionsCount; i++)
{
factionReps.Add(msg.ReadIdentifier(), msg.ReadSingle());
}
if (ShouldApply(NetFlags.Reputation, id, requireUpToDateSave: true))
{
if (reputation.HasValue)
{
campaign.Map.CurrentLocation.Reputation.SetReputation(reputation.Value);
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
foreach (var (identifier, rep) in factionReps)
{
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier == identifier);
if (faction?.Reputation != null)
{
faction.Reputation.SetReputation(rep);
}
else
{
DebugConsole.ThrowError($"Received an update for a faction that doesn't exist \"{identifier}\".");
}
}
}
}
if (requiredFlags.HasFlag(NetFlags.CharacterInfo))
{
DebugConsole.Log("Received campaign update (CharacterInfo)");
UInt16 id = msg.ReadUInt16();
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
if (hasCharacterData)
{
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
}
if (ShouldApply(NetFlags.CharacterInfo, id, requireUpToDateSave: true))
{
if (myCharacterInfo != null)
{
GameMain.Client.CharacterInfo = myCharacterInfo;
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(myCharacterInfo);
}
else
{
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
}
}
}
campaign.SuppressStateSending = true;
//we need to have the latest save file to display location/mission/store
if (campaign.LastSaveID == saveID)
{
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
if (refreshCampaignUI)
{
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
campaign.SuppressStateSending = false;
bool ShouldApply(NetFlags flag, UInt16 id, bool requireUpToDateSave)
{
if (NetIdUtils.IdMoreRecent(id, campaign.GetLastUpdateIdForFlag(flag)) &&
(!requireUpToDateSave || saveID == campaign.LastSaveID))
{
campaign.SetLastUpdateIdForFlag(flag, id);
return true;
}
else
{
return false;
}
}
void ReadStores(IReadMessage msg, bool apply)
{
var storeBalances = new Dictionary<Identifier, UInt16>();
if (msg.ReadBoolean())
{
byte storeCount = msg.ReadByte();
for (int i = 0; i < storeCount; i++)
{
Identifier identifier = msg.ReadIdentifier();
UInt16 storeBalance = msg.ReadUInt16();
storeBalances.Add(identifier, storeBalance);
}
}
if (apply)
{
foreach (var balance in storeBalances)
{
if (campaign.Map?.CurrentLocation?.GetStore(balance.Key) is { } store)
{
store.Balance = balance.Value;
}
}
}
}
} }
public void ClientReadCrew(IReadMessage msg) public void ClientReadCrew(IReadMessage msg)
@@ -35,11 +35,7 @@ namespace Barotrauma
} }
} }
if (CrewManager.ChatBox != null) CrewManager.ChatBox?.Update(deltaTime);
{
CrewManager.ChatBox.Update(deltaTime);
}
CrewManager.UpdateReports(); CrewManager.UpdateReports();
} }
@@ -58,12 +54,12 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Instantiates a new single player campaign /// Instantiates a new single player campaign
/// </summary> /// </summary>
private SinglePlayerCampaign(string mapSeed, CampaignSettings settings) : base(GameModePreset.SinglePlayerCampaign) private SinglePlayerCampaign(string mapSeed, CampaignSettings settings) : base(GameModePreset.SinglePlayerCampaign, settings)
{ {
CampaignMetadata = new CampaignMetadata(this); CampaignMetadata = new CampaignMetadata(this);
UpgradeManager = new UpgradeManager(this); UpgradeManager = new UpgradeManager(this);
map = new Map(this, mapSeed, settings);
Settings = settings; Settings = settings;
map = new Map(this, mapSeed);
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{ {
for (int i = 0; i < jobPrefab.InitialCount; i++) for (int i = 0; i < jobPrefab.InitialCount; i++)
@@ -79,7 +75,7 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Loads a previously saved single player campaign from XML /// Loads a previously saved single player campaign from XML
/// </summary> /// </summary>
private SinglePlayerCampaign(XElement element) : base(GameModePreset.SinglePlayerCampaign) private SinglePlayerCampaign(XElement element) : base(GameModePreset.SinglePlayerCampaign, CampaignSettings.Empty)
{ {
IsFirstRound = false; IsFirstRound = false;
@@ -87,14 +83,15 @@ namespace Barotrauma
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "campaignsettings": case CampaignSettings.LowerCaseSaveElementName:
Settings = new CampaignSettings(subElement); Settings = new CampaignSettings(subElement);
break; break;
case "crew": case "crew":
GameMain.GameSession.CrewManager = new CrewManager(subElement, true); GameMain.GameSession.CrewManager = new CrewManager(subElement, true);
ActiveOrdersElement = subElement.GetChildElement("activeorders");
break; break;
case "map": case "map":
map = Map.Load(this, subElement, Settings); map = Map.Load(this, subElement);
break; break;
case "metadata": case "metadata":
CampaignMetadata = new CampaignMetadata(this, subElement); CampaignMetadata = new CampaignMetadata(this, subElement);
@@ -162,21 +159,14 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Start a completely new single player campaign /// Start a completely new single player campaign
/// </summary> /// </summary>
public static SinglePlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub, CampaignSettings settings) public static SinglePlayerCampaign StartNew(string mapSeed, CampaignSettings startingSettings) => new SinglePlayerCampaign(mapSeed, startingSettings);
{
var campaign = new SinglePlayerCampaign(mapSeed, settings);
return campaign;
}
/// <summary> /// <summary>
/// Load a previously saved single player campaign from xml /// Load a previously saved single player campaign from xml
/// </summary> /// </summary>
/// <param name="element"></param> /// <param name="element"></param>
/// <returns></returns> /// <returns></returns>
public static SinglePlayerCampaign Load(XElement element) public static SinglePlayerCampaign Load(XElement element) => new SinglePlayerCampaign(element);
{
return new SinglePlayerCampaign(element);
}
private void InitUI() private void InitUI()
{ {
@@ -242,11 +232,10 @@ namespace Barotrauma
crewDead = false; crewDead = false;
endTimer = 5.0f; endTimer = 5.0f;
CrewManager.InitSinglePlayerRound(); CrewManager.InitSinglePlayerRound();
if (petsElement != null) LoadPets();
{ LoadActiveOrders();
PetBehavior.LoadPets(petsElement);
} CargoManager.InitPurchasedIDCards();
CrewManager.LoadActiveOrders();
GUI.DisableSavingIndicatorDelayed(); GUI.DisableSavingIndicatorDelayed();
} }
@@ -461,41 +450,8 @@ namespace Barotrauma
if (success) if (success)
{ {
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
{
Submarine.MainSub = leavingSub;
GameMain.GameSession.Submarine = leavingSub;
GameMain.GameSession.SubmarineInfo = leavingSub.Info;
leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub");
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info);
foreach (Submarine sub in subsToLeaveBehind)
{
GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name);
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
LinkedSubmarine.CreateDummy(leavingSub, sub);
}
}
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
if (PendingSubmarineSwitch != null)
{
SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo;
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++)
{
if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name)
{
GameMain.GameSession.OwnedSubmarines[i] = previousSub;
break;
}
}
}
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
PendingSubmarineSwitch = null;
} }
else else
{ {
@@ -766,11 +722,10 @@ namespace Barotrauma
c.Info.SaveOrderData(); c.Info.SaveOrderData();
} }
petsElement = new XElement("pets"); SavePets(modeElement);
PetBehavior.SavePets(petsElement); var crewManagerElement = CrewManager.Save(modeElement);
modeElement.Add(petsElement); SaveActiveOrders(crewManagerElement);
CrewManager.Save(modeElement);
CampaignMetadata.Save(modeElement); CampaignMetadata.Save(modeElement);
Map.Save(modeElement); Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement); CargoManager?.SavePurchasedItems(modeElement);
@@ -102,29 +102,11 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman"); radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman");
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false; GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
var revolver = FindOrGiveItem(captain, "revolver".ToIdentifier()); foreach (Item item in captain.Inventory.AllItemsMod)
revolver.Unequip(captain);
captain.Inventory.RemoveItem(revolver);
var captainscap =
captain.Inventory.FindItemByIdentifier("captainscap1".ToIdentifier()) ??
captain.Inventory.FindItemByIdentifier("captainscap2".ToIdentifier()) ??
captain.Inventory.FindItemByIdentifier("captainscap3".ToIdentifier());
if (captainscap != null)
{ {
captainscap.Unequip(captain); if (item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
captain.Inventory.RemoveItem(captainscap); item.Unequip(captain);
} captain.Inventory.RemoveItem(item);
var captainsuniform =
captain.Inventory.FindItemByIdentifier("captainsuniform1".ToIdentifier()) ??
captain.Inventory.FindItemByIdentifier("captainsuniform2".ToIdentifier()) ??
captain.Inventory.FindItemByIdentifier("captainsuniform3".ToIdentifier());
if (captainsuniform != null)
{
captainsuniform.Unequip(captain);
captain.Inventory.RemoveItem(captainsuniform);
} }
var steerOrder = OrderPrefab.Prefabs["steer"]; var steerOrder = OrderPrefab.Prefabs["steer"];
@@ -105,21 +105,12 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
doctor = Character.Controlled; doctor = Character.Controlled;
var bandages = FindOrGiveItem(doctor, "antibleeding1".ToIdentifier()); foreach (Item item in doctor.Inventory.AllItemsMod)
bandages.Unequip(doctor); {
doctor.Inventory.RemoveItem(bandages); if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(doctor);
var syringegun = FindOrGiveItem(doctor, "syringegun".ToIdentifier()); doctor.Inventory.RemoveItem(item);
syringegun.Unequip(doctor); }
doctor.Inventory.RemoveItem(syringegun);
var antibiotics = FindOrGiveItem(doctor, "antibiotics".ToIdentifier());
antibiotics.Unequip(doctor);
doctor.Inventory.RemoveItem(antibiotics);
var morphine = FindOrGiveItem(doctor, "antidama1".ToIdentifier());
morphine.Unequip(doctor);
doctor.Inventory.RemoveItem(morphine);
doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent<ItemContainer>(); doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent<ItemContainer>();
doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent<ItemContainer>(); doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent<ItemContainer>();
@@ -260,7 +251,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2.0f); yield return new WaitForSeconds(2.0f);
}*/ }*/
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Medical supplies objective TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Medical supplies objective
do do
{ {
@@ -131,9 +131,12 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
engineer = Character.Controlled; engineer = Character.Controlled;
var toolbelt = FindOrGiveItem(engineer, "toolbelt".ToIdentifier()); foreach (Item item in engineer.Inventory.AllItemsMod)
toolbelt.Unequip(engineer); {
engineer.Inventory.RemoveItem(toolbelt); if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(engineer);
engineer.Inventory.RemoveItem(item);
}
var repairOrder = OrderPrefab.Prefabs["repairsystems"]; var repairOrder = OrderPrefab.Prefabs["repairsystems"];
engineer_repairIcon = repairOrder.SymbolSprite; engineer_repairIcon = repairOrder.SymbolSprite;
@@ -278,7 +281,7 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected); do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(0.5f, false); yield return new WaitForSeconds(0.5f, false);
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Retrieve equipment TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Retrieve equipment
bool firstSlotRemoved = false; bool firstSlotRemoved = false;
bool secondSlotRemoved = false; bool secondSlotRemoved = false;
bool thirdSlotRemoved = false; bool thirdSlotRemoved = false;
@@ -160,13 +160,12 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
mechanic = Character.Controlled; mechanic = Character.Controlled;
var toolbelt = FindOrGiveItem(mechanic, "toolbelt".ToIdentifier()); foreach (Item item in mechanic.Inventory.AllItemsMod)
toolbelt.Unequip(mechanic); {
mechanic.Inventory.RemoveItem(toolbelt); if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(mechanic);
var crowbar = FindOrGiveItem(mechanic, "crowbar".ToIdentifier()); mechanic.Inventory.RemoveItem(item);
crowbar.Unequip(mechanic); }
mechanic.Inventory.RemoveItem(crowbar);
var repairOrder = OrderPrefab.Prefabs["repairsystems"]; var repairOrder = OrderPrefab.Prefabs["repairsystems"];
mechanic_repairIcon = repairOrder.SymbolSprite; mechanic_repairIcon = repairOrder.SymbolSprite;
@@ -297,7 +296,10 @@ namespace Barotrauma.Tutorials
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f); if (mechanic_brokenhull_1 != null)
{
mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f);
}
base.Update(deltaTime); base.Update(deltaTime);
} }
@@ -334,7 +336,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(0.0f, false); yield return new WaitForSeconds(0.0f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected); do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected);
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Equipment & inventory objective TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Equipment & inventory objective
SetHighlight(mechanic_equipmentCabinet.Item, true); SetHighlight(mechanic_equipmentCabinet.Item, true);
bool firstSlotRemoved = false; bool firstSlotRemoved = false;
bool secondSlotRemoved = false; bool secondSlotRemoved = false;
@@ -377,7 +379,7 @@ namespace Barotrauma.Tutorials
// Room 3 // Room 3
do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected); do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected);
TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Welding objective TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Welding objective
do do
{ {
if (!mechanic.HasEquippedItem("divingmask".ToIdentifier())) if (!mechanic.HasEquippedItem("divingmask".ToIdentifier()))
@@ -413,7 +415,7 @@ namespace Barotrauma.Tutorials
} }
} while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining } while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining
SetHighlight(mechanic_workingPump.Item, false); SetHighlight(mechanic_workingPump.Item, false);
do { yield return null; } while (mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained do { yield return null; } while (mechanic_brokenhull_1 != null && mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained
RemoveCompletedObjective(3); RemoveCompletedObjective(3);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective3"); GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective3");
@@ -141,36 +141,12 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
officer = Character.Controlled; officer = Character.Controlled;
var handcuffs = FindOrGiveItem(officer, "handcuffs".ToIdentifier()); foreach (Item item in officer.Inventory.AllItemsMod)
handcuffs.Unequip(officer); {
officer.Inventory.RemoveItem(handcuffs); if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(officer);
var stunbaton = FindOrGiveItem(officer, "stunbaton".ToIdentifier()); officer.Inventory.RemoveItem(item);
stunbaton.Unequip(officer); }
officer.Inventory.RemoveItem(stunbaton);
var smg = FindOrGiveItem(officer, "smg".ToIdentifier());
smg.Unequip(officer);
officer.Inventory.RemoveItem(smg);
var divingknife = FindOrGiveItem(officer, "divingknife".ToIdentifier());
divingknife.Unequip(officer);
officer.Inventory.RemoveItem(divingknife);
var steroids = FindOrGiveItem(officer, "steroids".ToIdentifier());
steroids.Unequip(officer);
officer.Inventory.RemoveItem(steroids);
var ballistichelmet =
officer.Inventory.FindItemByIdentifier("ballistichelmet1".ToIdentifier()) ??
officer.Inventory.FindItemByIdentifier("ballistichelmet2".ToIdentifier()) ??
FindOrGiveItem(officer, "ballistichelmet3".ToIdentifier());
ballistichelmet.Unequip(officer);
officer.Inventory.RemoveItem(ballistichelmet);
var bodyarmor = FindOrGiveItem(officer, "bodyarmor".ToIdentifier());
bodyarmor.Unequip(officer);
officer.Inventory.RemoveItem(bodyarmor);
var gunOrder = OrderPrefab.Prefabs["operateweapons"]; var gunOrder = OrderPrefab.Prefabs["operateweapons"];
officer_gunIcon = gunOrder.SymbolSprite; officer_gunIcon = gunOrder.SymbolSprite;
@@ -1,10 +1,7 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Tutorials namespace Barotrauma.Tutorials
{ {
@@ -106,7 +103,7 @@ namespace Barotrauma.Tutorials
Character.Controlled = character; Character.Controlled = character;
character.GiveJobItems(null); character.GiveJobItems(null);
var idCard = character.Inventory.FindItemByIdentifier("idcard".ToIdentifier()); var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
if (idCard == null) if (idCard == null)
{ {
DebugConsole.ThrowError("Item prefab \"ID Card\" not found!"); DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
@@ -228,6 +225,8 @@ namespace Barotrauma.Tutorials
{ {
CoroutineManager.StopCoroutines(tutorialCoroutine); CoroutineManager.StopCoroutines(tutorialCoroutine);
} }
GUI.PreventPauseMenuToggle = false;
ContentRunning = false;
infoBox = null; infoBox = null;
} }
else if (Character.Controlled.IsDead) else if (Character.Controlled.IsDead)
@@ -28,7 +28,7 @@ namespace Barotrauma
UserListData = "ReadyUserList", UserListData = "ReadyUserList",
ReadySpriteData = "ReadySprite"; ReadySpriteData = "ReadySprite";
private int lastSecond; private int lastSecond = 1;
private GUIMessageBox? msgBox; private GUIMessageBox? msgBox;
private GUIMessageBox? resultsBox; private GUIMessageBox? resultsBox;
@@ -44,7 +44,7 @@ namespace Barotrauma
msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true }; msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true };
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.125f), msgBox.Content.RectTransform), childAnchor: Anchor.Center); GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.125f), msgBox.Content.RectTransform), childAnchor: Anchor.Center);
new GUIProgressBar(new RectTransform(new Vector2(0.8f, 1f), contentLayout.RectTransform), time / endTime, GUIStyle.Orange) { UserData = TimerData }; new GUIProgressBar(new RectTransform(new Vector2(0.8f, 1f), contentLayout.RectTransform), 0.0f, GUIStyle.Orange) { UserData = TimerData };
// Yes // Yes
msgBox.Buttons[0].OnClicked = delegate msgBox.Buttons[0].OnClicked = delegate
@@ -116,17 +116,18 @@ namespace Barotrauma
private void UpdateBar() private void UpdateBar()
{ {
double elapsedTime = (DateTime.Now - startTime).TotalSeconds;
if (msgBox != null && !msgBox.Closed && GUIMessageBox.MessageBoxes.Contains(msgBox)) if (msgBox != null && !msgBox.Closed && GUIMessageBox.MessageBoxes.Contains(msgBox))
{ {
if (msgBox.FindChild(TimerData, true) is GUIProgressBar bar) if (msgBox.FindChild(TimerData, true) is GUIProgressBar bar)
{ {
bar.BarSize = time / endTime; bar.BarSize = (float)(elapsedTime / (endTime - startTime).TotalSeconds);
} }
} }
// play click sound after a second has passed // play click sound after a second has passed
int second = (int) Math.Ceiling(time); int second = (int)Math.Ceiling(elapsedTime);
if (second < lastSecond) if (second > lastSecond)
{ {
if (msgBox != null && !msgBox.Closed) if (msgBox != null && !msgBox.Closed)
{ {
@@ -156,7 +157,8 @@ namespace Barotrauma
bool isOwn = false; bool isOwn = false;
byte authorId = 0; byte authorId = 0;
float duration = inc.ReadSingle(); long startTime = inc.ReadInt64();
long endTime = inc.ReadInt64();
string author = inc.ReadString(); string author = inc.ReadString();
bool hasAuthor = inc.ReadBoolean(); bool hasAuthor = inc.ReadBoolean();
@@ -173,7 +175,9 @@ namespace Barotrauma
clients.Add(inc.ReadByte()); clients.Add(inc.ReadByte());
} }
ReadyCheck rCheck = new ReadyCheck(clients, duration); ReadyCheck rCheck = new ReadyCheck(clients,
DateTimeOffset.FromUnixTimeSeconds(startTime).LocalDateTime,
DateTimeOffset.FromUnixTimeSeconds(endTime).LocalDateTime);
crewManager.ActiveReadyCheck = rCheck; crewManager.ActiveReadyCheck = rCheck;
if (isOwn) if (isOwn)
@@ -192,12 +196,10 @@ namespace Barotrauma
} }
break; break;
case ReadyCheckState.Update: case ReadyCheckState.Update:
float time = inc.ReadSingle();
ReadyStatus newState = (ReadyStatus) inc.ReadByte(); ReadyStatus newState = (ReadyStatus) inc.ReadByte();
byte targetId = inc.ReadByte(); byte targetId = inc.ReadByte();
if (crewManager.ActiveReadyCheck != null) if (crewManager.ActiveReadyCheck != null)
{ {
crewManager.ActiveReadyCheck.time = time;
crewManager.ActiveReadyCheck?.UpdateState(targetId, newState); crewManager.ActiveReadyCheck?.UpdateState(targetId, newState);
} }
break; break;
@@ -64,7 +64,6 @@ namespace Barotrauma
public Vector2[] SlotPositions; public Vector2[] SlotPositions;
public static Point SlotSize; public static Point SlotSize;
public static int Spacing; public static int Spacing;
public static int HideButtonWidth;
private Layout layout; private Layout layout;
public Layout CurrentLayout public Layout CurrentLayout
@@ -77,64 +76,11 @@ namespace Barotrauma
SetSlotPositions(layout); SetSlotPositions(layout);
} }
} }
public bool Hidden { get; set; }
private bool hidePersonalSlots;
private float hidePersonalSlotsState;
private GUIButton hideButton;
private Rectangle personalSlotArea; private Rectangle personalSlotArea;
public bool HidePersonalSlots
{
get { return hidePersonalSlots; }
}
public Rectangle PersonalSlotArea
{
get { return personalSlotArea; }
}
private readonly GUIImage[] indicators = new GUIImage[5];
private readonly int[] indicatorIndices = new int[5];
private Vector2 indicatorSpriteSize;
private GUILayoutGroup indicatorGroup;
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
Hidden = true;
hideButton = new GUIButton(new RectTransform(new Point((int)(31f * (HUDLayoutSettings.BottomRightInfoArea.Height / 100f)), HUDLayoutSettings.BottomRightInfoArea.Height), GUI.Canvas)
{ AbsoluteOffset = HUDLayoutSettings.CrewArea.Location },
"", style: "EquipmentToggleButton");
indicatorGroup = new GUILayoutGroup(new RectTransform(Point.Zero, hideButton.RectTransform)) { IsHorizontal = false };
indicatorGroup.ChildAnchor = Anchor.TopCenter;
indicatorSpriteSize = GUIStyle.GetComponentStyle("EquipmentIndicatorDivingSuit").GetDefaultSprite().size;
indicators[0] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorDivingSuit");
indicators[1] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorID");
indicators[2] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorOutfit");
indicators[3] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorHeadwear");
indicators[4] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorHeadphones");
indicatorIndices[0] = FindLimbSlot(InvSlotType.OuterClothes);
indicatorIndices[1] = FindLimbSlot(InvSlotType.Card);
indicatorIndices[2] = FindLimbSlot(InvSlotType.InnerClothes);
indicatorIndices[3] = FindLimbSlot(InvSlotType.Head);
indicatorIndices[4] = FindLimbSlot(InvSlotType.Headset);
for (int i = 0; i < indicators.Length; i++)
{
indicators[i].CanBeFocused = false;
}
hideButton.OnClicked += (GUIButton btn, object userdata) =>
{
hidePersonalSlots = !hidePersonalSlots;
return true;
};
hidePersonalSlots = false;
SlotPositions = new Vector2[SlotTypes.Length]; SlotPositions = new Vector2[SlotTypes.Length];
CurrentLayout = Layout.Default; CurrentLayout = Layout.Default;
SetSlotPositions(layout); SetSlotPositions(layout);
@@ -271,25 +217,6 @@ namespace Barotrauma
return false; return false;
} }
private void SetIndicatorSizes()
{
indicatorGroup.RectTransform.AbsoluteOffset = new Point((int)Math.Round(4 * GUI.Scale), (int)Math.Round(7 * GUI.Scale));
indicatorGroup.RectTransform.NonScaledSize = new Point(hideButton.Rect.Width - indicatorGroup.RectTransform.AbsoluteOffset.X * 2, hideButton.Rect.Height - indicatorGroup.RectTransform.AbsoluteOffset.Y * 2);
indicatorGroup.AbsoluteSpacing = (int)Math.Ceiling(2 * GUI.Scale);
int indicatorHeight = (indicatorGroup.RectTransform.NonScaledSize.Y - indicatorGroup.AbsoluteSpacing * (indicators.Length - 1)) / indicators.Length;
int indicatorWidth = (int)(indicatorSpriteSize.X / (indicatorSpriteSize.Y / indicatorHeight));
if (HideButtonWidth % 2 != indicatorWidth % 2) indicatorWidth++;
Point indicatorSize = new Point(indicatorWidth, indicatorHeight);
for (int i = 0; i < indicators.Length; i++)
{
indicators[i].RectTransform.NonScaledSize = indicatorSize;
}
}
private void SetSlotPositions(Layout layout) private void SetSlotPositions(Layout layout)
{ {
bool isFourByThree = GUI.IsFourByThree(); bool isFourByThree = GUI.IsFourByThree();
@@ -302,13 +229,9 @@ namespace Barotrauma
Spacing = (int)(8 * UIScale); Spacing = (int)(8 * UIScale);
} }
HideButtonWidth = (int)(31f * (HUDLayoutSettings.BottomRightInfoArea.Height / 100f));
SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint(); SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint();
int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight; int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight;
hideButton.Visible = false;
if (visualSlots == null) { CreateSlots(); } if (visualSlots == null) { CreateSlots(); }
if (visualSlots.None()) { return; } if (visualSlots.None()) { return; }
@@ -320,7 +243,7 @@ namespace Barotrauma
int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface); int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface);
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2; int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing * 4 - HideButtonWidth; int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing;
//make sure the rightmost normal slot doesn't overlap with the personal slots //make sure the rightmost normal slot doesn't overlap with the personal slots
x -= Math.Max((x + normalSlotCount * (SlotSize.X + Spacing)) - (upperX - personalSlotCount * (SlotSize.X + Spacing)), 0); x -= Math.Max((x + normalSlotCount * (SlotSize.X + Spacing)) - (upperX - personalSlotCount * (SlotSize.X + Spacing)), 0);
@@ -343,16 +266,6 @@ namespace Barotrauma
x += SlotSize.X + Spacing; x += SlotSize.X + Spacing;
} }
} }
if (hideButtonSlotIndex > -1)
{
hideButton.RectTransform.SetPosition(Anchor.TopLeft, Pivot.TopLeft);
hideButton.RectTransform.NonScaledSize = new Point(HideButtonWidth, HUDLayoutSettings.BottomRightInfoArea.Height);
hideButton.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.BottomRightInfoArea.Left - HideButtonWidth + GUI.IntScaleCeiling(2f), HUDLayoutSettings.BottomRightInfoArea.Y + GUI.IntScaleCeiling(1f));
hideButton.Visible = Screen.Selected != GameMain.SubEditorScreen || !GameMain.SubEditorScreen.WiringMode;
SetIndicatorSizes();
}
} }
break; break;
case Layout.Right: case Layout.Right:
@@ -525,6 +438,7 @@ namespace Barotrauma
if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner)
{ {
syncItemsDelay = Math.Max(syncItemsDelay - deltaTime, 0.0f); syncItemsDelay = Math.Max(syncItemsDelay - deltaTime, 0.0f);
doubleClickedItems.Clear();
return; return;
} }
@@ -532,58 +446,13 @@ namespace Barotrauma
bool hoverOnInventory = GUI.MouseOn == null && bool hoverOnInventory = GUI.MouseOn == null &&
((selectedSlot != null && selectedSlot.IsSubSlot) || (DraggingItems.Any() && (DraggingSlot == null || !DraggingSlot.MouseOn()))); ((selectedSlot != null && selectedSlot.IsSubSlot) || (DraggingItems.Any() && (DraggingSlot == null || !DraggingSlot.MouseOn())));
if (CharacterHealth.OpenHealthWindow != null) hoverOnInventory = true; if (CharacterHealth.OpenHealthWindow != null) { hoverOnInventory = true; }
if (layout == Layout.Default && (Screen.Selected != GameMain.SubEditorScreen || Screen.Selected is SubEditorScreen editor && editor.WiringMode))
{
if (hideButton.Visible)
{
hideButton.AddToGUIUpdateList();
hideButton.UpdateManually(deltaTime, alsoChildren: true);
hidePersonalSlotsState = hidePersonalSlots ?
Math.Min(hidePersonalSlotsState + deltaTime * 5.0f, 1.0f) :
Math.Max(hidePersonalSlotsState - deltaTime * 5.0f, 0.0f);
bool personalSlotsMoving = hidePersonalSlotsState > 0 && hidePersonalSlotsState < 1f;
for (int i = 0; i < visualSlots.Length; i++)
{
if (!PersonalSlots.HasFlag(SlotTypes[i])) { continue; }
if (HidePersonalSlots)
{
if (selectedSlot?.Slot == visualSlots[i]) { selectedSlot = null; }
highlightedSubInventorySlots.RemoveWhere(s => s.Slot == visualSlots[i]);
}
visualSlots[i].IsMoving = personalSlotsMoving;
visualSlots[i].DrawOffset = Vector2.Lerp(Vector2.Zero, new Vector2(personalSlotArea.Width, 0.0f), hidePersonalSlotsState);
}
}
}
if (hoverOnInventory) { HideTimer = 0.5f; } if (hoverOnInventory) { HideTimer = 0.5f; }
if (HideTimer > 0.0f) { HideTimer -= deltaTime; } if (HideTimer > 0.0f) { HideTimer -= deltaTime; }
UpdateSlotInput(); UpdateSlotInput();
//force personal slots open if an item is running out of battery/fuel/oxygen/etc
if (hidePersonalSlots)
{
for (int i = 0; i < visualSlots.Length; i++)
{
var item = slots[i].FirstOrDefault();
if (item?.OwnInventory != null && item.OwnInventory.Capacity == 1 && PersonalSlots.HasFlag(SlotTypes[i]))
{
var containedItem = item.OwnInventory.AllItems.FirstOrDefault();
if (containedItem != null &&
containedItem.Condition > 0.0f &&
containedItem.Condition / containedItem.MaxCondition < 0.15f)
{
hidePersonalSlots = false;
}
}
}
}
hideSubInventories.Clear(); hideSubInventories.Clear();
//remove highlighted subinventory slots that can no longer be accessed //remove highlighted subinventory slots that can no longer be accessed
highlightedSubInventorySlots.RemoveWhere(s => highlightedSubInventorySlots.RemoveWhere(s =>
@@ -652,8 +521,6 @@ namespace Barotrauma
if (character == Character.Controlled && character.SelectedCharacter == null) // Permanently open subinventories only available when the default UI layout is in use -> not when grabbing characters if (character == Character.Controlled && character.SelectedCharacter == null) // Permanently open subinventories only available when the default UI layout is in use -> not when grabbing characters
{ {
UpdateEquipmentIndicators();
//remove the highlighted slots of other characters' inventories when not grabbing anyone //remove the highlighted slots of other characters' inventories when not grabbing anyone
highlightedSubInventorySlots.RemoveWhere(s => s.ParentInventory != this && s.ParentInventory?.Owner is Character); highlightedSubInventorySlots.RemoveWhere(s => s.ParentInventory != this && s.ParentInventory?.Owner is Character);
@@ -799,40 +666,6 @@ namespace Barotrauma
} }
} }
private void UpdateEquipmentIndicators()
{
for (int i = 0; i < indicators.Length; i++)
{
if (indicatorIndices[i] < 0) { continue; }
Item item = slots[indicatorIndices[i]].FirstOrDefault();
if (item != null)
{
Wearable wearable = item.GetComponent<Wearable>();
if (wearable != null && wearable.DisplayContainedStatus)
{
float conditionPercentage = item.GetContainedItemConditionPercentage();
if (conditionPercentage != -1)
{
indicators[i].Color = ToolBox.GradientLerp(conditionPercentage, GUIStyle.EquipmentIndicatorRunningOut, GUIStyle.EquipmentIndicatorEquipped);
}
else
{
indicators[i].Color = GUIStyle.EquipmentIndicatorRunningOut;
}
}
else
{
indicators[i].Color = GUIStyle.EquipmentIndicatorEquipped;
}
}
else
{
indicators[i].Color = GUIStyle.EquipmentIndicatorNotEquipped;
}
}
}
private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory) private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory)
{ {
Rectangle hoverArea = GetSubInventoryHoverArea(slotRef); Rectangle hoverArea = GetSubInventoryHoverArea(slotRef);
@@ -931,7 +764,7 @@ namespace Barotrauma
// Move the item from the subinventory to the selected container // Move the item from the subinventory to the selected container
return QuickUseAction.PutToContainer; return QuickUseAction.PutToContainer;
} }
else else if (character.Inventory.AccessibleWhenAlive || character.Inventory.AccessibleByOwner)
{ {
// Take from the subinventory and place it in the character's main inventory if no target container is selected // Take from the subinventory and place it in the character's main inventory if no target container is selected
return QuickUseAction.TakeFromContainer; return QuickUseAction.TakeFromContainer;
@@ -941,6 +774,7 @@ namespace Barotrauma
} }
else else
{ {
bool isEquippable = item.AllowedSlots.Any(s => s != InvSlotType.Any);
var selectedContainer = character.SelectedConstruction?.GetComponent<ItemContainer>(); var selectedContainer = character.SelectedConstruction?.GetComponent<ItemContainer>();
if (selectedContainer != null && if (selectedContainer != null &&
selectedContainer.Inventory != null && selectedContainer.Inventory != null &&
@@ -960,13 +794,15 @@ namespace Barotrauma
else if (character.SelectedBy?.Inventory != null && else if (character.SelectedBy?.Inventory != null &&
Character.Controlled == character.SelectedBy && Character.Controlled == character.SelectedBy &&
!character.SelectedBy.Inventory.Locked && !character.SelectedBy.Inventory.Locked &&
(character.SelectedBy.Inventory.AccessibleWhenAlive || character.SelectedBy.Inventory.AccessibleByOwner) &&
allowInventorySwap) allowInventorySwap)
{ {
return QuickUseAction.TakeFromCharacter; return QuickUseAction.TakeFromCharacter;
} }
else if (character.HeldItems.Any(i => else if (character.HeldItems.Any(i =>
i.OwnInventory != null && i.OwnInventory != null &&
((i.OwnInventory.CanBePut(item) && allowInventorySwap) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item))))) /*disallow putting into equipped item if the item is equippable (equip as the quick action instead)*/
((i.OwnInventory.CanBePut(item) && (allowInventorySwap || !isEquippable)) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
{ {
return QuickUseAction.PutToEquippedItem; return QuickUseAction.PutToEquippedItem;
} }
@@ -1129,11 +965,18 @@ namespace Barotrauma
} }
break; break;
case QuickUseAction.PutToEquippedItem: case QuickUseAction.PutToEquippedItem:
foreach (Item heldItem in character.HeldItems) foreach (Item heldItem in character.HeldItems)
{ {
if (heldItem.OwnInventory == null) { continue; } if (heldItem.OwnInventory == null) { continue; }
//don't allow swapping if we're moving items into an item with 1 slot holding a stack of items
//(in that case, the quick action should just fill up the stack)
bool disallowSwapping =
heldItem.OwnInventory.Capacity == 1 &&
heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) || if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
(heldItem.OwnInventory.Capacity == 1 && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: true, allowCombine: false, user: Character.Controlled))) (heldItem.OwnInventory.Capacity == 1 && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: !disallowSwapping, allowCombine: false, user: Character.Controlled)))
{ {
success = true; success = true;
for (int j = 0; j < capacity; j++) for (int j = 0; j < capacity; j++)
@@ -1196,11 +1039,6 @@ namespace Barotrauma
DrawSlot(spriteBatch, this, visualSlots[i], slots[i].FirstOrDefault(), i, drawItem, SlotTypes[i]); DrawSlot(spriteBatch, this, visualSlots[i], slots[i].FirstOrDefault(), i, drawItem, SlotTypes[i]);
} }
if (hideButton != null && hideButton.Visible && !Locked)
{
hideButton.DrawManually(spriteBatch, alsoChildren: true);
}
VisualSlot highlightedQuickUseSlot = null; VisualSlot highlightedQuickUseSlot = null;
Rectangle inventoryArea = Rectangle.Empty; Rectangle inventoryArea = Rectangle.Empty;
@@ -43,6 +43,31 @@ namespace Barotrauma.Items.Components
corners[2] = center + new Vector2(shadowSize.X, shadowSize.Y) / 2; corners[2] = center + new Vector2(shadowSize.X, shadowSize.Y) / 2;
corners[3] = center + new Vector2(shadowSize.X, -shadowSize.Y) / 2; corners[3] = center + new Vector2(shadowSize.X, -shadowSize.Y) / 2;
if (IsHorizontal)
{
if (item.FlippedX)
{
Vector2 itemCenter = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2);
for (int i = 0; i < corners.Length; i++)
{
corners[i].X = itemCenter.X * 2 - corners[i].X;
}
Array.Reverse(corners);
}
}
else
{
if (item.FlippedY)
{
Vector2 itemCenter = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2);
for (int i = 0; i < corners.Length; i++)
{
corners[i].Y = itemCenter.Y * 2 - corners[i].Y;
}
Array.Reverse(corners);
}
}
return corners; return corners;
} }
@@ -66,6 +91,9 @@ namespace Barotrauma.Items.Components
rect.Height = (int)(rect.Height * (1.0f - openState)); rect.Height = (int)(rect.Height * (1.0f - openState));
} }
//only merge the door's convex hull with overlapping wall segments if it's fully open or fully closed
//it's the heaviest part of changing the convex hull, and doesn't need to be done while the door is still in motion
bool mergeOverlappingSegments = openState <= 0.0f || openState >= 1.0f;
if (Window.Height > 0 && Window.Width > 0) if (Window.Height > 0 && Window.Width > 0)
{ {
if (IsHorizontal) if (IsHorizontal)
@@ -88,7 +116,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull2.Enabled = true; convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2)); convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
} }
} }
} }
@@ -112,7 +140,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull2.Enabled = true; convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2)); convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
} }
} }
} }
@@ -127,7 +155,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull.Enabled = true; convexHull.Enabled = true;
convexHull.SetVertices(GetConvexHullCorners(rect)); convexHull.SetVertices(GetConvexHullCorners(rect), mergeOverlappingSegments);
} }
} }
@@ -160,67 +188,66 @@ namespace Barotrauma.Items.Components
if (stuck > 0.0f && weldedSprite != null) if (stuck > 0.0f && weldedSprite != null)
{ {
Vector2 weldSpritePos = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2.0f) + shakePos; Vector2 weldSpritePos = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2.0f) + shakePos;
if (item.Submarine != null) weldSpritePos += item.Submarine.DrawPosition; if (item.Submarine != null) { weldSpritePos += item.Submarine.DrawPosition; }
weldSpritePos.Y = -weldSpritePos.Y; weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch, weldedSprite.Draw(spriteBatch,
weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale); weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale);
} }
if (openState >= 1.0f) if (openState >= 1.0f) { return; }
{
return;
}
Vector2 pos;
if (IsHorizontal) if (IsHorizontal)
{ {
Vector2 pos = new Vector2(item.Rect.X, item.Rect.Y - item.Rect.Height / 2) + shakePos; pos = new Vector2(item.Rect.X, item.Rect.Y - item.Rect.Height / 2);
if (item.Submarine != null) pos += item.Submarine.DrawPosition; if (item.FlippedX) { pos.X += (int)(doorSprite.size.X * item.Scale * openState); }
pos.Y = -pos.Y;
if (brokenSprite == null || !IsBroken)
{
spriteBatch.Draw(doorSprite.Texture, pos,
new Rectangle((int) (doorSprite.SourceRect.X + doorSprite.size.X * openState),
(int) doorSprite.SourceRect.Y,
(int) (doorSprite.size.X * (1.0f - openState)), (int) doorSprite.size.Y),
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth);
}
if (brokenSprite != null && item.Health < item.MaxCondition)
{
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.MaxCondition) : Vector2.One;
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos,
new Rectangle((int)(brokenSprite.SourceRect.X + brokenSprite.size.X * openState), brokenSprite.SourceRect.Y,
(int)(brokenSprite.size.X * (1.0f - openState)), (int)brokenSprite.size.Y),
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, SpriteEffects.None,
brokenSprite.Depth);
}
} }
else else
{ {
Vector2 pos = new Vector2(item.Rect.Center.X, item.Rect.Y) + shakePos; pos = new Vector2(item.Rect.Center.X, item.Rect.Y);
if (item.Submarine != null) pos += item.Submarine.DrawPosition; if (item.FlippedY) { pos.Y -= (int)(doorSprite.size.Y * item.Scale * openState); }
pos.Y = -pos.Y; }
if (brokenSprite == null || !IsBroken) pos += shakePos;
if (item.Submarine != null) { pos += item.Submarine.DrawPosition; }
pos.Y = -pos.Y;
if (brokenSprite == null || !IsBroken)
{
spriteBatch.Draw(doorSprite.Texture, pos,
getSourceRect(doorSprite, openState, IsHorizontal),
color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth);
}
if (brokenSprite != null && item.Health < item.MaxCondition)
{
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.MaxCondition) : Vector2.One;
if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; }
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos,
getSourceRect(brokenSprite, openState, IsHorizontal),
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, item.SpriteEffects,
brokenSprite.Depth);
}
static Rectangle getSourceRect(Sprite sprite, float openState, bool horizontal)
{
if (horizontal)
{ {
spriteBatch.Draw(doorSprite.Texture, pos, return new Rectangle(
new Rectangle(doorSprite.SourceRect.X, (int)(sprite.SourceRect.X + sprite.size.X * openState),
(int) (doorSprite.SourceRect.Y + doorSprite.size.Y * openState), sprite.SourceRect.Y,
(int) doorSprite.size.X, (int) (doorSprite.size.Y * (1.0f - openState))), (int)(sprite.size.X * (1.0f - openState)),
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth); (int)sprite.size.Y);
} }
else
if (brokenSprite != null && item.Health < item.MaxCondition)
{ {
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.MaxCondition, 1.0f) : Vector2.One; return new Rectangle(
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f; sprite.SourceRect.X,
spriteBatch.Draw(brokenSprite.Texture, pos, (int)(sprite.SourceRect.Y + sprite.size.Y * openState),
new Rectangle(brokenSprite.SourceRect.X, (int)(brokenSprite.SourceRect.Y + brokenSprite.size.Y * openState), (int)sprite.size.X,
(int)brokenSprite.size.X, (int)(brokenSprite.size.Y * (1.0f - openState))), (int)(sprite.size.Y * (1.0f - openState)));
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, SpriteEffects.None, brokenSprite.Depth);
} }
} }
} }
@@ -321,7 +321,7 @@ namespace Barotrauma.Items.Components
{ {
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true); GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), GUINumberInput.NumberType.Int) { IntValue = defaultValue }; GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), NumberType.Int) { IntValue = defaultValue };
return input; return input;
} }
@@ -329,7 +329,7 @@ namespace Barotrauma.Items.Components
{ {
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true); GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), GUINumberInput.NumberType.Float) { FloatValue = defaultValue, DecimalsToDisplay = 2 }; GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), NumberType.Float) { FloatValue = defaultValue, DecimalsToDisplay = 2 };
return input; return input;
} }
@@ -341,7 +341,7 @@ namespace Barotrauma.Items.Components
for (var i = 0; i < values.Length; i++) for (var i = 0; i < values.Length; i++)
{ {
float value = values[i]; float value = values[i];
GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f / values.Length, 1f), layout.RectTransform), GUINumberInput.NumberType.Float) GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f / values.Length, 1f), layout.RectTransform), NumberType.Float)
{ {
FloatValue = value, DecimalsToDisplay = 2, FloatValue = value, DecimalsToDisplay = 2,
MinValueFloat = min, MinValueFloat = min,
@@ -116,12 +116,6 @@ namespace Barotrauma.Items.Components
loadAttachments(Attachments, disguisedBeardElement, WearableType.Beard); loadAttachments(Attachments, disguisedBeardElement, WearableType.Beard);
loadAttachments(Attachments, disguisedMoustacheElement, WearableType.Moustache); loadAttachments(Attachments, disguisedMoustacheElement, WearableType.Moustache);
loadAttachments(Attachments, disguisedHairElement, WearableType.Hair); loadAttachments(Attachments, disguisedHairElement, WearableType.Hair);
loadAttachments(Attachments,
characterInfo.OmitJobInPortraitClothing
? JobPrefab.NoJobElement?.GetChildElement("PortraitClothing")
: JobPrefab?.ClothingElement,
WearableType.JobIndicator);
} }
HairColor = hairColor; HairColor = hairColor;
@@ -203,7 +203,7 @@ namespace Barotrauma.Items.Components
private float lastMuffleCheckTime; private float lastMuffleCheckTime;
private ItemSound loopingSound; private ItemSound loopingSound;
private SoundChannel loopingSoundChannel; private SoundChannel loopingSoundChannel;
private List<SoundChannel> playingOneshotSoundChannels = new List<SoundChannel>(); private readonly List<SoundChannel> playingOneshotSoundChannels = new List<SoundChannel>();
public ItemComponent ReplacedBy; public ItemComponent ReplacedBy;
public ItemComponent GetReplacementOrThis() public ItemComponent GetReplacementOrThis()
@@ -211,13 +211,16 @@ namespace Barotrauma.Items.Components
return ReplacedBy?.GetReplacementOrThis() ?? this; return ReplacedBy?.GetReplacementOrThis() ?? this;
} }
public bool NeedsSoundUpdate()
{
if (hasSoundsOfType[(int)ActionType.Always]) { return true; }
if (loopingSoundChannel != null && loopingSoundChannel.IsPlaying) { return true; }
if (playingOneshotSoundChannels.Count > 0) { return true; }
return false;
}
public void UpdateSounds() public void UpdateSounds()
{ {
if (!isActive || item.Condition <= 0.0f)
{
StopSounds(ActionType.OnActive);
}
if (loopingSound != null && loopingSoundChannel != null && loopingSoundChannel.IsPlaying) if (loopingSound != null && loopingSoundChannel != null && loopingSoundChannel.IsPlaying)
{ {
if (Timing.TotalTime > lastMuffleCheckTime + 0.2f) if (Timing.TotalTime > lastMuffleCheckTime + 0.2f)
@@ -280,6 +283,7 @@ namespace Barotrauma.Items.Components
loopingSound.RoundSound.GetRandomFrequencyMultiplier(), loopingSound.RoundSound.GetRandomFrequencyMultiplier(),
SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull)); SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true; loopingSoundChannel.Looping = true;
item.CheckNeedsSoundUpdate(this);
//TODO: tweak //TODO: tweak
loopingSoundChannel.Near = loopingSound.Range * 0.4f; loopingSoundChannel.Near = loopingSound.Range * 0.4f;
loopingSoundChannel.Far = loopingSound.Range; loopingSoundChannel.Far = loopingSound.Range;
@@ -298,7 +302,6 @@ namespace Barotrauma.Items.Components
loopingSound = null; loopingSound = null;
} }
} }
return; return;
} }
@@ -333,6 +336,7 @@ namespace Barotrauma.Items.Components
} }
PlaySound(matchingSounds[index], item.WorldPosition); PlaySound(matchingSounds[index], item.WorldPosition);
item.CheckNeedsSoundUpdate(this);
} }
} }
private void PlaySound(ItemSound itemSound, Vector2 position) private void PlaySound(ItemSound itemSound, Vector2 position)
@@ -401,7 +405,7 @@ namespace Barotrauma.Items.Components
float newVolume; float newVolume;
try try
{ {
newVolume = property.GetFloatValue(this); newVolume = Math.Min(property.GetFloatValue(this), 1.0f);
} }
catch catch
{ {
@@ -280,9 +280,9 @@ namespace Barotrauma.Items.Components
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y); transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; } if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
if (Math.Abs(item.Rotation) > 0.01f) if (Math.Abs(item.RotationRad) > 0.01f)
{ {
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation)); Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
transformedItemPos = Vector2.Transform(transformedItemPos - item.DrawPosition, transform) + item.DrawPosition; transformedItemPos = Vector2.Transform(transformedItemPos - item.DrawPosition, transform) + item.DrawPosition;
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
@@ -56,9 +56,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
Vector2 pos = item.DrawPosition; Light.Position = item.Position;
if (item.Submarine != null) { pos -= item.Submarine.DrawPosition; }
Light.Position = pos;
} }
PhysicsBody body = Light.ParentBody; PhysicsBody body = Light.ParentBody;
if (body != null) if (body != null)
@@ -68,7 +66,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
Light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation); Light.Rotation = -Rotation - item.RotationRad;
Light.LightSpriteEffect = item.SpriteEffects; Light.LightSpriteEffect = item.SpriteEffects;
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
@@ -78,7 +79,7 @@ namespace Barotrauma.Items.Components
activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
{ {
TextBlock = { AutoScaleHorizontal = true }, TextBlock = { AutoScaleHorizontal = true },
OnClicked = ToggleActive OnClicked = OnActivateButtonClicked
}; };
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
TextManager.Get("DeconstructorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true) TextManager.Get("DeconstructorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
@@ -164,7 +165,7 @@ namespace Barotrauma.Items.Components
} }
} }
} }
activateButton.Enabled = outputsFound; activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
activateButton.Text = TextManager.Get(ActivateButtonText); activateButton.Text = TextManager.Get(ActivateButtonText);
}; };
} }
@@ -236,8 +237,19 @@ namespace Barotrauma.Items.Components
inSufficientPowerWarning.Visible = IsActive && !hasPower; inSufficientPowerWarning.Visible = IsActive && !hasPower;
} }
private bool ToggleActive(GUIButton button, object obj) private bool OnActivateButtonClicked(GUIButton button, object obj)
{ {
var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false);
if (disallowedItem != null && !DeconstructItemsSimultaneously)
{
int index = inputContainer.Inventory.FindIndex(disallowedItem);
if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length)
{
var slot = inputContainer.Inventory.visualSlots[index];
slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
}
return true;
}
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
pendingState = !IsActive; pendingState = !IsActive;
@@ -247,7 +259,6 @@ namespace Barotrauma.Items.Components
{ {
SetActive(!IsActive, Character.Controlled); SetActive(!IsActive, Character.Controlled);
} }
return true; return true;
} }
@@ -108,6 +108,7 @@ namespace Barotrauma.Items.Components
itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), paddedItemFrame.RectTransform), style: null) itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), paddedItemFrame.RectTransform), style: null)
{ {
PlaySoundOnSelect = true,
OnSelected = (component, userdata) => OnSelected = (component, userdata) =>
{ {
selectedItem = userdata as FabricationRecipe; selectedItem = userdata as FabricationRecipe;
@@ -333,6 +333,7 @@ namespace Barotrauma.Items.Components
GUIListBox listBox = new GUIListBox(new RectTransform(Vector2.One, searchAutoComplete.RectTransform)) GUIListBox listBox = new GUIListBox(new RectTransform(Vector2.One, searchAutoComplete.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = (component, o) => OnSelected = (component, o) =>
{ {
if (o is ItemPrefab prefab) if (o is ItemPrefab prefab)
@@ -613,7 +614,7 @@ namespace Barotrauma.Items.Components
if (hullData.Distort) if (hullData.Distort)
{ {
hullData.ReceivedOxygenAmount = Rand.Range(0.0f, 100.0f); hullData.ReceivedOxygenAmount = Rand.Range(0.0f, 100.0f);
hullData.ReceivedWaterAmount = Rand.Range(0.0f, 1.0f); hullData.ReceivedWaterAmount = Rand.Range(0.0f, 100.0f);
} }
hullData.DistortionTimer = Rand.Range(1.0f, 10.0f); hullData.DistortionTimer = Rand.Range(1.0f, 10.0f);
} }
@@ -681,7 +682,7 @@ namespace Barotrauma.Items.Components
var sprite = GUIStyle.UIGlowSolidCircular.Value?.Sprite; var sprite = GUIStyle.UIGlowSolidCircular.Value?.Sprite;
float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f;
if (sprite != null) if (sprite != null && ShowHullIntegrity)
{ {
Vector2 spriteSize = sprite.size; Vector2 spriteSize = sprite.size;
Rectangle worldBorders = item.Submarine.GetDockedBorders(); Rectangle worldBorders = item.Submarine.GetDockedBorders();
@@ -744,11 +745,11 @@ namespace Barotrauma.Items.Components
if (key == Keys.Down) if (key == Keys.Down)
{ {
listBox.SelectNext(true, autoScroll: true); listBox.SelectNext(force: GUIListBox.Force.Yes, playSelectSound: GUIListBox.PlaySelectSound.Yes);
} }
else if (key == Keys.Up) else if (key == Keys.Up)
{ {
listBox.SelectPrevious(true, autoScroll: true); listBox.SelectPrevious(force: GUIListBox.Force.Yes, playSelectSound: GUIListBox.PlaySelectSound.Yes);
} }
else if (key == Keys.Enter) else if (key == Keys.Enter)
{ {
@@ -782,7 +783,7 @@ namespace Barotrauma.Items.Components
if (component.Visible && first) if (component.Visible && first)
{ {
listBox.Select(i, force: true, autoScroll: false); listBox.Select(i, GUIListBox.Force.Yes, GUIListBox.AutoScroll.Disabled);
first = false; first = false;
} }
} }
@@ -1014,13 +1015,13 @@ namespace Barotrauma.Items.Components
hullData.HullWaterAmount = 0.0f; hullData.HullWaterAmount = 0.0f;
foreach (Hull linkedHull in hullData.LinkedHulls) foreach (Hull linkedHull in hullData.LinkedHulls)
{ {
hullData.HullWaterAmount += Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f); hullData.HullWaterAmount += WaterDetector.GetWaterPercentage(linkedHull);
} }
hullData.HullWaterAmount /= hullData.LinkedHulls.Count; hullData.HullWaterAmount /= hullData.LinkedHulls.Count;
} }
else else
{ {
hullData.HullWaterAmount = Math.Min(hull.WaterVolume / hull.Volume, 1.0f); hullData.HullWaterAmount = WaterDetector.GetWaterPercentage(hull);
} }
float gapOpenSum = 0.0f; float gapOpenSum = 0.0f;
@@ -1052,8 +1053,8 @@ namespace Barotrauma.Items.Components
LocalizedString line3 = waterAmount == null ? LocalizedString line3 = waterAmount == null ?
TextManager.Get("MiniMapWaterLevelUnavailable") : TextManager.Get("MiniMapWaterLevelUnavailable") :
TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)Math.Round(waterAmount.Value * 100.0f) + "%"); TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)Math.Round(waterAmount.Value) + "%");
Color line3Color = waterAmount == null ? GUIStyle.Red : Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)waterAmount); Color line3Color = waterAmount == null ? GUIStyle.Red : Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)waterAmount / 100.0f);
SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color); SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color);
} }
@@ -1188,7 +1189,8 @@ namespace Barotrauma.Items.Components
if (hullsVisible && hullData.HullWaterAmount is { } waterAmount) if (hullsVisible && hullData.HullWaterAmount is { } waterAmount)
{ {
if (!RequireWaterDetectors) { waterAmount = hull.WaterPercentage / 100.0f; } if (!RequireWaterDetectors) { waterAmount = WaterDetector.GetWaterPercentage(hull); }
waterAmount /= 100.0f;
if (hullFrame.Rect.Height * waterAmount > 1.0f) if (hullFrame.Rect.Height * waterAmount > 1.0f)
{ {
RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount); RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount);
@@ -1327,7 +1329,7 @@ namespace Barotrauma.Items.Components
pos.X += inflate; pos.X += inflate;
pos.Y += inflate; pos.Y += inflate;
sprite.Draw(spriteBatch, pos, item.SpriteColor, sprite.Origin, MathHelper.ToRadians(item.Rotation), spriteScale, item.SpriteEffects); sprite.Draw(spriteBatch, pos, item.SpriteColor, sprite.Origin, item.RotationRad, spriteScale, item.SpriteEffects);
void DrawAdditionalSprite(Vector2 basePos, Sprite addSprite, float rotation) void DrawAdditionalSprite(Vector2 basePos, Sprite addSprite, float rotation)
{ {
@@ -18,7 +18,7 @@ namespace Barotrauma.Items.Components
} }
GuiFrame = selectionUI.GuiFrame; GuiFrame = selectionUI.GuiFrame;
selectionUI.RefreshSubmarineDisplay(true); selectionUI.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true);
IsActive = true; IsActive = true;
return base.Select(character); return base.Select(character);
} }
@@ -133,7 +133,6 @@ namespace Barotrauma.Items.Components
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
float rotationRad = MathHelper.ToRadians(item.Rotation);
if (FlowPercentage < 0.0f) if (FlowPercentage < 0.0f)
{ {
foreach (var (position, emitter) in pumpOutEmitters) foreach (var (position, emitter) in pumpOutEmitters)
@@ -142,8 +141,8 @@ namespace Barotrauma.Items.Components
//only emit "pump out" particles when underwater //only emit "pump out" particles when underwater
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition; Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? rotationRad : -rotationRad); relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? item.RotationRad : -item.RotationRad);
float angle = -rotationRad; float angle = -item.RotationRad;
if (item.FlippedX) if (item.FlippedX)
{ {
relativeParticlePos.X = -relativeParticlePos.X; relativeParticlePos.X = -relativeParticlePos.X;
@@ -163,8 +162,8 @@ namespace Barotrauma.Items.Components
foreach (var (position, emitter) in pumpInEmitters) foreach (var (position, emitter) in pumpInEmitters)
{ {
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition; Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? rotationRad : -rotationRad); relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? item.RotationRad : -item.RotationRad);
float angle = -rotationRad; float angle = -item.RotationRad;
if (item.FlippedX) if (item.FlippedX)
{ {
relativeParticlePos.X = -relativeParticlePos.X; relativeParticlePos.X = -relativeParticlePos.X;
@@ -1367,6 +1367,15 @@ namespace Barotrauma.Items.Components
pingRadius, prevPingRadius, pingRadius, prevPingRadius,
250.0f, 150.0f, range, pingStrength, passive); 250.0f, 150.0f, range, pingStrength, passive);
} }
if (pingSource.Y - Level.Loaded.BottomPos < range)
{
CreateBlipsForLine(
new Vector2(pingSource.X - range, Level.Loaded.BottomPos),
new Vector2(pingSource.X + range, Level.Loaded.BottomPos),
pingSource, transducerPos,
pingRadius, prevPingRadius,
250.0f, 150.0f, range, pingStrength, passive);
}
List<Voronoi2.VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7); List<Voronoi2.VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7);
foreach (Voronoi2.VoronoiCell cell in cells) foreach (Voronoi2.VoronoiCell cell in cells)
@@ -927,6 +927,8 @@ namespace Barotrauma.Items.Components
bool autoPilot = msg.ReadBoolean(); bool autoPilot = msg.ReadBoolean();
bool dockingButtonClicked = msg.ReadBoolean(); bool dockingButtonClicked = msg.ReadBoolean();
ushort userID = msg.ReadUInt16();
Vector2 newSteeringInput = steeringInput; Vector2 newSteeringInput = steeringInput;
Vector2 newTargetVelocity = targetVelocity; Vector2 newTargetVelocity = targetVelocity;
float newSteeringAdjustSpeed = steeringAdjustSpeed; float newSteeringAdjustSpeed = steeringAdjustSpeed;
@@ -935,7 +937,7 @@ namespace Barotrauma.Items.Components
if (dockingButtonClicked) if (dockingButtonClicked)
{ {
item.SendSignal("1", "toggle_docking"); item.SendSignal(new Signal("1", sender: Entity.FindEntityByID(userID) as Character), "toggle_docking");
} }
if (autoPilot) if (autoPilot)
@@ -40,8 +40,6 @@ namespace Barotrauma.Items.Components
} }
} }
private LightComponent lightComponent;
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{ {
for (var i = 0; i < GrowableSeeds.Length; i++) for (var i = 0; i < GrowableSeeds.Length; i++)
@@ -23,10 +23,10 @@ namespace Barotrauma.Items.Components
} }
#endif #endif
private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private List<ParticleEmitter> particleEmitterHitStructure = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitterHitStructure = new List<ParticleEmitter>();
private List<ParticleEmitter> particleEmitterHitCharacter = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitterHitCharacter = new List<ParticleEmitter>();
private List<Pair<RelatedItem, ParticleEmitter>> particleEmitterHitItem = new List<Pair<RelatedItem, ParticleEmitter>>(); private readonly List<(RelatedItem relatedItem, ParticleEmitter emitter)> particleEmitterHitItem = new List<(RelatedItem relatedItem, ParticleEmitter emitter)>();
private float prevProgressBarState; private float prevProgressBarState;
private Item prevProgressBarTarget = null; private Item prevProgressBarTarget = null;
@@ -46,10 +46,7 @@ namespace Barotrauma.Items.Components
Identifier[] excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifiers", Array.Empty<Identifier>()); Identifier[] excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifiers", Array.Empty<Identifier>());
if (excludedIdentifiers.Length == 0) { excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifier", Array.Empty<Identifier>()); } if (excludedIdentifiers.Length == 0) { excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifier", Array.Empty<Identifier>()); }
particleEmitterHitItem.Add( particleEmitterHitItem.Add((new RelatedItem(identifiers, excludedIdentifiers), new ParticleEmitter(subElement)));
new Pair<RelatedItem, ParticleEmitter>(
new RelatedItem(identifiers, excludedIdentifiers),
new ParticleEmitter(subElement)));
break; break;
case "particleemitterhitstructure": case "particleemitterhitstructure":
particleEmitterHitStructure.Add(new ParticleEmitter(subElement)); particleEmitterHitStructure.Add(new ParticleEmitter(subElement));
@@ -139,11 +136,11 @@ namespace Barotrauma.Items.Components
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition); Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition; if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
foreach (var emitter in particleEmitterHitItem) foreach ((RelatedItem relatedItem, ParticleEmitter emitter) in particleEmitterHitItem)
{ {
if (!emitter.First.MatchesItem(targetItem)) { continue; } if (!relatedItem.MatchesItem(targetItem)) { continue; }
float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Second.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
} }
} }
#if DEBUG #if DEBUG
@@ -1,11 +1,10 @@
using System; using Barotrauma.Networking;
using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -419,7 +418,7 @@ namespace Barotrauma.Items.Components
if (!GameMain.IsMultiplayer) { RepairBoost(qteSuccess); } if (!GameMain.IsMultiplayer) { RepairBoost(qteSuccess); }
SoundPlayer.PlayUISound(qteSuccess ? GUISoundType.IncreaseQuantity : GUISoundType.DecreaseQuantity); SoundPlayer.PlayUISound(qteSuccess ? GUISoundType.Increase : GUISoundType.Decrease);
//on failure during cooldown reset cursor to beginning //on failure during cooldown reset cursor to beginning
if (!qteSuccess && qteCooldown > 0.0f) { qteTimer = QteDuration; } if (!qteSuccess && qteCooldown > 0.0f) { qteTimer = QteDuration; }
@@ -92,6 +92,8 @@ namespace Barotrauma.Items.Components
{ {
if (target == null || target.Removed) { return; } if (target == null || target.Removed) { return; }
if (target.ParentInventory != null) { return; } if (target.ParentInventory != null) { return; }
if (source is Limb limb && limb.Removed) { return; }
if (source is Entity e && e.Removed) { return; }
Vector2 startPos = GetSourcePos(); Vector2 startPos = GetSourcePos();
startPos.Y = -startPos.Y; startPos.Y = -startPos.Y;
@@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components
int totalWireCount = 0; int totalWireCount = 0;
foreach (Connection c in panel.Connections) foreach (Connection c in panel.Connections)
{ {
totalWireCount += c.Wires.Count(w => w != null); totalWireCount += c.Wires.Count;
} }
Wire equippedWire = null; Wire equippedWire = null;
@@ -87,8 +87,8 @@ namespace Barotrauma.Items.Components
(DraggingConnected.Connections[0] == null && DraggingConnected.Connections[1] == null) || (DraggingConnected.Connections[0] == null && DraggingConnected.Connections[1] == null) ||
(DraggingConnected.Connections.Contains(c) && DraggingConnected.Connections.Contains(null))) (DraggingConnected.Connections.Contains(c) && DraggingConnected.Connections.Contains(null)))
{ {
int linkIndex = c.FindWireIndex(DraggingConnected.Item); var linkedWire = c.FindWireByItem(DraggingConnected.Item);
if (linkIndex > -1 || panel.DisconnectedWires.Contains(DraggingConnected)) if (linkedWire != null || panel.DisconnectedWires.Contains(DraggingConnected))
{ {
Inventory.DraggingItems.Clear(); Inventory.DraggingItems.Clear();
Inventory.DraggingItems.Add(DraggingConnected.Item); Inventory.DraggingItems.Add(DraggingConnected.Item);
@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval); c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval);
} }
rightPos.Y += connectorIntervalLeft; rightPos.Y += connectorIntervalLeft;
rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; rightWirePos.Y += c.Wires.Count * wireInterval;
} }
else else
{ {
@@ -121,7 +121,7 @@ namespace Barotrauma.Items.Components
c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval); c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval);
} }
leftPos.Y += connectorIntervalRight; leftPos.Y += connectorIntervalRight;
leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; leftWirePos.Y += c.Wires.Count * wireInterval;
} }
} }
} }
@@ -228,15 +228,15 @@ namespace Barotrauma.Items.Components
{ {
float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale; float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale;
for (int i = 0; i < MaxWires; i++) foreach (var wire in wires)
{ {
if (wires[i] == null || wires[i].Hidden || (DraggingConnected == wires[i] && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; } if (wire.Hidden || (DraggingConnected == wire && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; }
if (wires[i].HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; } if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
Connection recipient = wires[i].OtherConnection(this); Connection recipient = wire.OtherConnection(this);
LocalizedString label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})"; LocalizedString label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
if (wires[i].Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); } if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
DrawWire(spriteBatch, wires[i], position, wirePosition, equippedWire, panel, label); DrawWire(spriteBatch, wire, position, wirePosition, equippedWire, panel, label);
wirePosition.Y += wireInterval; wirePosition.Y += wireInterval;
} }
@@ -248,18 +248,17 @@ namespace Barotrauma.Items.Components
if (!PlayerInput.PrimaryMouseButtonHeld()) if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
if ((GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled)) && if ((GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled)) &&
Wires.Count(w => w != null) < MaxPlayerConnectableWires) Wires.Count < MaxPlayerConnectableWires)
{ {
//find an empty cell for the new connection //find an empty cell for the new connection
int index = FindEmptyIndex(); if (WireSlotsAvailable() && !Wires.Contains(DraggingConnected))
if (index > -1 && !Wires.Contains(DraggingConnected))
{ {
bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item); bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item);
DraggingConnected.RemoveConnection(panel.Item); DraggingConnected.RemoveConnection(panel.Item);
if (DraggingConnected.Connect(this, !alreadyConnected, true)) if (DraggingConnected.Connect(this, !alreadyConnected, true))
{ {
var otherConnection = DraggingConnected.OtherConnection(this); var otherConnection = DraggingConnected.OtherConnection(this);
SetWire(index, DraggingConnected); ConnectWire(DraggingConnected);
} }
} }
} }
@@ -284,7 +283,7 @@ namespace Barotrauma.Items.Components
flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale); flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale);
} }
if (Wires.Any(w => w != null && w != DraggingConnected && !w.Hidden && (!w.HiddenInGame || Screen.Selected != GameMain.GameScreen))) if (Wires.Any(w => w != DraggingConnected && !w.Hidden && (!w.HiddenInGame || Screen.Selected != GameMain.GameScreen)))
{ {
int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count;
screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale); screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale);
@@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components
} }
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount, bool ignoreContacts = false)
{ {
if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; } if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; }
MoveConnectedWires(amount); MoveConnectedWires(amount);
@@ -147,9 +147,10 @@ namespace Barotrauma.Items.Components
//because some of the wires connected to the panel may not exist yet //because some of the wires connected to the panel may not exist yet
long msgStartPos = msg.BitPosition; long msgStartPos = msg.BitPosition;
msg.ReadUInt16(); //user ID msg.ReadUInt16(); //user ID
foreach (Connection connection in Connections) foreach (Connection _ in Connections)
{ {
for (int i = 0; i < connection.MaxWires; i++) uint wireCount = msg.ReadVariableUInt32();
for (int i = 0; i < wireCount; i++)
{ {
msg.ReadUInt16(); msg.ReadUInt16();
} }
@@ -173,8 +174,7 @@ namespace Barotrauma.Items.Components
private void ApplyRemoteState(IReadMessage msg) private void ApplyRemoteState(IReadMessage msg)
{ {
List<Wire> prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList(); List<Wire> prevWires = Connections.SelectMany(c => c.Wires).ToList();
List<Wire> newWires = new List<Wire>();
ushort userID = msg.ReadUInt16(); ushort userID = msg.ReadUInt16();
@@ -195,7 +195,9 @@ namespace Barotrauma.Items.Components
foreach (Connection connection in Connections) foreach (Connection connection in Connections)
{ {
for (int i = 0; i < connection.MaxWires; i++) HashSet<Wire> newWires = new HashSet<Wire>();
uint wireCount = msg.ReadVariableUInt32();
for (int i = 0; i < wireCount; i++)
{ {
ushort wireId = msg.ReadUInt16(); ushort wireId = msg.ReadUInt16();
@@ -204,9 +206,18 @@ namespace Barotrauma.Items.Components
if (wireComponent == null) { continue; } if (wireComponent == null) { continue; }
newWires.Add(wireComponent); newWires.Add(wireComponent);
}
connection.SetWire(i, wireComponent); Wire[] oldWires = connection.Wires.Where(w => !newWires.Contains(w)).ToArray();
wireComponent.Connect(connection, false); foreach (var wire in oldWires)
{
connection.DisconnectWire(wire);
}
foreach (var wire in newWires.Where(w => !connection.Wires.Contains(w)).ToArray())
{
connection.ConnectWire(wire);
wire.Connect(connection, false);
} }
} }
@@ -45,7 +45,7 @@ namespace Barotrauma.Items.Components
}; };
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform),
TextManager.Get(ciElement.Label).Fallback(ciElement.Label)); TextManager.Get(ciElement.Label).Fallback(ciElement.Label));
if (!ciElement.IsIntegerInput) if (!ciElement.IsNumberInput)
{ {
var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon") var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon")
{ {
@@ -77,29 +77,71 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
int.TryParse(ciElement.Signal, out int signal); GUINumberInput numberInput = null;
var numberInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), GUINumberInput.NumberType.Int) if (ciElement.NumberType == NumberType.Float)
{ {
UserData = ciElement, TryParseFloatInvariantCulture(ciElement.Signal, out float floatSignal);
MinValueInt = ciElement.NumberInputMin, TryParseFloatInvariantCulture(ciElement.NumberInputMin, out float numberInputMin);
MaxValueInt = ciElement.NumberInputMax, TryParseFloatInvariantCulture(ciElement.NumberInputMax, out float numberInputMax);
IntValue = Math.Clamp(signal, ciElement.NumberInputMin, ciElement.NumberInputMax) TryParseFloatInvariantCulture(ciElement.NumberInputStep, out float numberInputStep);
}; numberInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), NumberType.Float)
//reset size restrictions set by the Style to make sure the elements can fit the interface {
numberInput.RectTransform.MinSize = numberInput.LayoutGroup.RectTransform.MinSize = new Point(0, 0); UserData = ciElement,
numberInput.RectTransform.MaxSize = numberInput.LayoutGroup.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue); MinValueFloat = numberInputMin,
numberInput.OnValueChanged += (ni) => MaxValueFloat = numberInputMax,
FloatValue = Math.Clamp(floatSignal, numberInputMin, numberInputMax),
DecimalsToDisplay = ciElement.NumberInputDecimalPlaces,
valueStep = numberInputStep,
OnValueChanged = (ni) =>
{
if (GameMain.Client == null)
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
}
else
{
item.CreateClientEvent(this);
}
}
};
}
else if (ciElement.NumberType == NumberType.Int)
{ {
if (GameMain.Client == null) int.TryParse(ciElement.Signal, out int intSignal);
int.TryParse(ciElement.NumberInputMin, out int numberInputMin);
int.TryParse(ciElement.NumberInputMax, out int numberInputMax);
TryParseFloatInvariantCulture(ciElement.NumberInputStep, out float numberInputStep);
numberInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), NumberType.Int)
{ {
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue); UserData = ciElement,
} MinValueInt = numberInputMin,
else MaxValueInt = numberInputMax,
{ IntValue = Math.Clamp(intSignal, numberInputMin, numberInputMax),
item.CreateClientEvent(this); valueStep = numberInputStep,
} OnValueChanged = (ni) =>
}; {
uiElements.Add(numberInput); if (GameMain.Client == null)
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
}
else
{
item.CreateClientEvent(this);
}
}
};
}
else
{
DebugConsole.ShowError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\"");
}
if (numberInput != null)
{
//reset size restrictions set by the Style to make sure the elements can fit the interface
numberInput.RectTransform.MinSize = numberInput.LayoutGroup.RectTransform.MinSize = new Point(0, 0);
numberInput.RectTransform.MaxSize = numberInput.LayoutGroup.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
uiElements.Add(numberInput);
}
} }
} }
else if (ciElement.ContinuousSignal) else if (ciElement.ContinuousSignal)
@@ -205,7 +247,7 @@ namespace Barotrauma.Items.Components
foreach (var uiElement in uiElements) foreach (var uiElement in uiElements)
{ {
if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; } if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; }
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Any(w => w != null)); bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
if (visible) { visibleElementCount++; } if (visible) { visibleElementCount++; }
if (uiElement.Visible != visible) if (uiElement.Visible != visible)
{ {
@@ -293,7 +335,7 @@ namespace Barotrauma.Items.Components
} }
else if (uiElements[i] is GUINumberInput ni) else if (uiElements[i] is GUINumberInput ni)
{ {
if (ni.InputType == GUINumberInput.NumberType.Int) if (ni.InputType == NumberType.Int)
{ {
int.TryParse(customInterfaceElementList[i].Signal, out int value); int.TryParse(customInterfaceElementList[i].Signal, out int value);
ni.IntValue = value; ni.IntValue = value;
@@ -307,18 +349,28 @@ namespace Barotrauma.Items.Components
//extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything) //extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything)
for (int i = 0; i < customInterfaceElementList.Count; i++) for (int i = 0; i < customInterfaceElementList.Count; i++)
{ {
if (customInterfaceElementList[i].HasPropertyName) var element = customInterfaceElementList[i];
if (element.HasPropertyName)
{ {
if (!customInterfaceElementList[i].IsIntegerInput) if (!element.IsNumberInput)
{ {
msg.Write(((GUITextBox)uiElements[i]).Text); msg.Write(((GUITextBox)uiElements[i]).Text);
} }
else else
{ {
msg.Write(((GUINumberInput)uiElements[i]).IntValue.ToString()); switch (element.NumberType)
{
case NumberType.Float:
msg.Write(((GUINumberInput)uiElements[i]).FloatValue.ToString());
break;
case NumberType.Int:
default:
msg.Write(((GUINumberInput)uiElements[i]).IntValue.ToString());
break;
}
} }
} }
else if (customInterfaceElementList[i].ContinuousSignal) else if (element.ContinuousSignal)
{ {
msg.Write(((GUITickBox)uiElements[i]).Selected); msg.Write(((GUITickBox)uiElements[i]).Selected);
} }
@@ -333,29 +385,38 @@ namespace Barotrauma.Items.Components
{ {
for (int i = 0; i < customInterfaceElementList.Count; i++) for (int i = 0; i < customInterfaceElementList.Count; i++)
{ {
if (customInterfaceElementList[i].HasPropertyName) var element = customInterfaceElementList[i];
if (element.HasPropertyName)
{ {
if (!customInterfaceElementList[i].IsIntegerInput) string newValue = msg.ReadString();
if (!element.IsNumberInput)
{ {
TextChanged(customInterfaceElementList[i], msg.ReadString()); TextChanged(element, newValue);
} }
else else
{ {
int.TryParse(msg.ReadString(), out int value); switch (element.NumberType)
ValueChanged(customInterfaceElementList[i], value); {
case NumberType.Int when int.TryParse(newValue, out int value):
ValueChanged(element, value);
break;
case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
ValueChanged(element, value);
break;
}
} }
} }
else else
{ {
bool elementState = msg.ReadBoolean(); bool elementState = msg.ReadBoolean();
if (customInterfaceElementList[i].ContinuousSignal) if (element.ContinuousSignal)
{ {
((GUITickBox)uiElements[i]).Selected = elementState; ((GUITickBox)uiElements[i]).Selected = elementState;
TickBoxToggled(customInterfaceElementList[i], elementState); TickBoxToggled(element, elementState);
} }
else if (elementState) else if (elementState)
{ {
ButtonClicked(customInterfaceElementList[i]); ButtonClicked(element);
} }
} }
} }
@@ -58,6 +58,8 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
layoutGroup.Recalculate();
} }
// Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox // Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox
@@ -100,7 +102,7 @@ namespace Barotrauma.Items.Components
GUITextBlock newBlock = new GUITextBlock( GUITextBlock newBlock = new GUITextBlock(
new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter),
"> " + input, "> " + input,
textColor: color, wrap: true, font: UseMonospaceFont ? GUIStyle.MonospacedFont : GUIStyle.GlobalFont) textColor: color, wrap: true, font: UseMonospaceFont ? GUIStyle.MonospacedFont : GUIStyle.Font)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -526,7 +526,7 @@ namespace Barotrauma.Items.Components
} }
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount, bool ignoreContacts = false)
{ {
//only used in the sub editor, hence only in the client project //only used in the sub editor, hence only in the client project
if (!item.IsSelected) { return; } if (!item.IsSelected) { return; }
@@ -175,7 +175,7 @@ namespace Barotrauma.Items.Components
}; };
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount, bool ignoreContacts = false)
{ {
widgets.Clear(); widgets.Clear();
} }
@@ -6,8 +6,10 @@ using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
{ {
private GUIMessageBox autodockingVerification;
public Vector2 DrawSize public Vector2 DrawSize
{ {
//use the extents of the item as the draw size //use the extents of the item as the draw size
@@ -180,5 +182,10 @@ namespace Barotrauma.Items.Components
Undock(); Undock();
} }
} }
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.Write((byte)allowOutpostAutoDocking);
}
} }
} }
@@ -265,7 +265,7 @@ namespace Barotrauma
else else
{ {
LocalizedString description = item.Description; LocalizedString description = item.Description;
if (item.Prefab.Identifier == "idcard" || item.Tags.Contains("despawncontainer")) if (item.HasTag("identitycard") || item.HasTag("despawncontainer"))
{ {
string[] readTags = item.Tags.Split(','); string[] readTags = item.Tags.Split(',');
string idName = null; string idName = null;
@@ -1,4 +1,6 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.MapCreatures.Behavior;
using Barotrauma.Networking; using Barotrauma.Networking;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@@ -6,13 +8,8 @@ using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.MapCreatures.Behavior;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -355,7 +352,7 @@ namespace Barotrauma
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? RotationRad : -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteBatch, decorativeSprite.Sprite.DrawTiled(spriteBatch,
@@ -379,17 +376,17 @@ namespace Barotrauma
} }
if (color.A > 0) if (color.A > 0)
{ {
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth); activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, RotationRad, Scale, activeSprite.effects, depth);
if (fadeInBrokenSprite != null) if (fadeInBrokenSprite != null)
{ {
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f); float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
fadeInBrokenSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, d); fadeInBrokenSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, RotationRad, Scale, activeSprite.effects, d);
} }
} }
if (Infector != null && (Infector.ParentBallastFlora.HasBrokenThrough || BallastFloraBehavior.AlwaysShowBallastFloraSprite)) if (Infector != null && (Infector.ParentBallastFlora.HasBrokenThrough || BallastFloraBehavior.AlwaysShowBallastFloraSprite))
{ {
Prefab.InfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, Prefab.InfectedSprite.Origin, rotationRad, Scale, activeSprite.effects, depth - 0.001f); Prefab.InfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, Prefab.InfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.001f);
Prefab.DamagedInfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, Infector.HealthColor, Prefab.DamagedInfectedSprite.Origin, rotationRad, Scale, activeSprite.effects, depth - 0.002f); Prefab.DamagedInfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, Infector.HealthColor, Prefab.DamagedInfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.002f);
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
@@ -397,11 +394,11 @@ namespace Barotrauma
float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
bool flipX = flippedX && Prefab.CanSpriteFlipX; bool flipX = flippedX && Prefab.CanSpriteFlipX;
bool flipY = flippedY && Prefab.CanSpriteFlipY; bool flipY = flippedY && Prefab.CanSpriteFlipY;
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flipX ^ flipY ? rotationRad : -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flipX ^ flipY ? RotationRad : -RotationRad) * Scale;
if (flipX) { offset.X = -offset.X; } if (flipX) { offset.X = -offset.X; }
if (flipY) { offset.Y = -offset.Y; } if (flipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
rotationRad + rot, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects, RotationRad + rot, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
} }
} }
@@ -448,7 +445,7 @@ namespace Barotrauma
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
var ca = (float)Math.Cos(-body.Rotation); var ca = (float)Math.Cos(-body.Rotation);
@@ -469,7 +466,7 @@ namespace Barotrauma
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
@@ -572,6 +569,18 @@ namespace Barotrauma
} }
} }
public void CheckNeedsSoundUpdate(ItemComponent ic)
{
if (ic.NeedsSoundUpdate())
{
if (!updateableComponents.Contains(ic))
{
updateableComponents.Add(ic);
}
isActive = true;
}
}
public void UpdateSpriteStates(float deltaTime) public void UpdateSpriteStates(float deltaTime)
{ {
if (activeContainedSprite != null) if (activeContainedSprite != null)
@@ -720,7 +729,7 @@ namespace Barotrauma
//remove identifiers from the available container tags //remove identifiers from the available container tags
//(otherwise the list will include many irrelevant options, //(otherwise the list will include many irrelevant options,
//e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc) //e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc)
.Where(t => !ItemPrefab.Prefabs.Any(ip => ip.Identifier == t)) .Where(t => !ItemPrefab.Prefabs.ContainsKey(t))
.ToImmutableHashSet(); .ToImmutableHashSet();
new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...") new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...")
{ {
@@ -943,6 +952,7 @@ namespace Barotrauma
var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter)) var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter))
{ {
PlaySoundOnSelect = true,
OnSelected = (component, userData) => OnSelected = (component, userData) =>
{ {
if (!(userData is Identifier)) { return true; } if (!(userData is Identifier)) { return true; }
@@ -1174,7 +1184,7 @@ namespace Barotrauma
texts.Clear(); texts.Clear();
string nameText = Name; string nameText = Name;
if (Prefab.Identifier == "idcard" || Tags.Contains("despawncontainer")) if (Prefab.Tags.Contains("identitycard") || Tags.Contains("despawncontainer"))
{ {
string[] readTags = Tags.Split(','); string[] readTags = Tags.Split(',');
string idName = null; string idName = null;
@@ -183,8 +183,21 @@ namespace Barotrauma.MapCreatures.Behavior
Color branchColor = (branch.IsRoot || branch.IsRootGrowth) ? RootColor : Color.White; Color branchColor = (branch.IsRoot || branch.IsRootGrowth) ? RootColor : Color.White;
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
if (branch.DisconnectedFromRoot && branch.ParentBranch == null)
{
branchColor = Color.Yellow;
}
else if (branch.DisconnectedFromRoot)
{
branchColor = Color.Cyan;
}
else if (branch.ParentBranch == null)
{
branchColor = Color.Magenta;
}
#if DEBUG #if DEBUG
Vector2 basePos = Parent.WorldPosition; Vector2 basePos = Parent.WorldPosition;
foreach (var (from, to) in debugSearchLines) foreach (var (from, to) in debugSearchLines)
@@ -33,7 +33,7 @@ namespace Barotrauma
DrawArrow(FlowTargetHull, IsHorizontal ? rect.Height: rect.Width, Math.Abs(lerpedFlowForce.Length()), Color.Red * 0.3f); DrawArrow(FlowTargetHull, IsHorizontal ? rect.Height: rect.Width, Math.Abs(lerpedFlowForce.Length()), Color.Red * 0.3f);
} }
if (outsideCollisionBlocker.Enabled && Submarine != null) if (Submarine != null && outsideCollisionBlocker != null && outsideCollisionBlocker.Enabled)
{ {
var edgeShape = outsideCollisionBlocker.FixtureList[0].Shape as FarseerPhysics.Collision.Shapes.EdgeShape; var edgeShape = outsideCollisionBlocker.FixtureList[0].Shape as FarseerPhysics.Collision.Shapes.EdgeShape;
Vector2 startPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex1)) + Submarine.Position; Vector2 startPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex1)) + Submarine.Position;
@@ -80,15 +80,17 @@ namespace Barotrauma
} }
Vector2 center = new Vector2((minX + maxX) / 2.0f, (minY + maxY) / 2.0f); Vector2 center = new Vector2((minX + maxX) / 2.0f, (minY + maxY) / 2.0f);
if (Submarine.MainSub != null) { center -= Submarine.MainSub.HiddenSubPosition; } if (Submarine.MainSub != null) { center -= Submarine.MainSub.HiddenSubPosition; }
center.X -= MathUtils.RoundTowardsClosest(center.X, Submarine.GridSize.X);
center.Y -= MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y); Vector2 offsetFromGrid = new Vector2(
MathUtils.RoundTowardsClosest(center.X, Submarine.GridSize.X) - center.X,
MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2);
MapEntity.SelectedList.Clear(); MapEntity.SelectedList.Clear();
assemblyEntities.ForEach(e => MapEntity.AddSelection(e)); assemblyEntities.ForEach(e => MapEntity.AddSelection(e));
foreach (MapEntity mapEntity in assemblyEntities) foreach (MapEntity mapEntity in assemblyEntities)
{ {
mapEntity.Move(-center); mapEntity.Move(-center - offsetFromGrid);
mapEntity.Submarine = Submarine.MainSub; mapEntity.Submarine = Submarine.MainSub;
var entityElement = mapEntity.Save(element); var entityElement = mapEntity.Save(element);
if (disabledEntities.Contains(mapEntity)) if (disabledEntities.Contains(mapEntity))
@@ -60,12 +60,6 @@ namespace Barotrauma
} }
public void Dispose() public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{ {
IsDisposed = true; IsDisposed = true;
WallEdgeBuffer?.Dispose(); WallEdgeBuffer?.Dispose();
@@ -482,12 +476,6 @@ namespace Barotrauma
} }
public void Dispose() public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{ {
foreach (var vertexBuffer in vertexBuffers) foreach (var vertexBuffer in vertexBuffers)
{ {
@@ -230,14 +230,6 @@ namespace Barotrauma
public void Dispose() public void Dispose()
{ {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
if (WaterEffect != null) if (WaterEffect != null)
{ {
WaterEffect.Dispose(); WaterEffect.Dispose();
@@ -250,6 +242,5 @@ namespace Barotrauma
basicEffect = null; basicEffect = null;
} }
} }
} }
} }
@@ -461,7 +461,7 @@ namespace Barotrauma.Lights
Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) * Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
Matrix.CreateRotationZ(amount) * Matrix.CreateRotationZ(amount) *
Matrix.CreateTranslation(origin.X, origin.Y, 0.0f); Matrix.CreateTranslation(origin.X, origin.Y, 0.0f);
SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix); SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix);
} }
private void CalculateDimensions() private void CalculateDimensions()
@@ -541,7 +541,7 @@ namespace Barotrauma.Lights
} }
} }
public void SetVertices(Vector2[] points, Matrix? rotationMatrix = null) public void SetVertices(Vector2[] points, bool mergeOverlappingSegments = true, Matrix? rotationMatrix = null)
{ {
Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported"); Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported");
@@ -594,13 +594,16 @@ namespace Barotrauma.Lights
if (ParentEntity == null) { return; } if (ParentEntity == null) { return; }
var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine); if (mergeOverlappingSegments)
if (chList != null)
{ {
overlappingHulls.Clear(); var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine);
foreach (ConvexHull ch in chList.List) if (chList != null)
{ {
MergeOverlappingSegments(ch); overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
}
} }
} }
} }
@@ -11,6 +11,18 @@ namespace Barotrauma.Lights
{ {
class LightManager class LightManager
{ {
/// <summary>
/// How many light sources are allowed to recalculate their light volumes per frame.
/// Pending calculations will be done on subsequent frames, starting from the light sources that have been waiting for a recalculation the longest.
/// </summary>
const int MaxLightVolumeRecalculationsPerFrame = 5;
/// <summary>
/// If zoomed further out than this, characters no longer obstruct lights behind them.
/// Improves performance, and isn't very noticeable if we do it after zoomed far out enough.
/// </summary>
const float ObstructLightsBehindCharactersZoomThreshold = 0.5f;
public static Entity ViewTarget { get; set; } public static Entity ViewTarget { get; set; }
private float currLightMapScale; private float currLightMapScale;
@@ -59,6 +71,8 @@ namespace Barotrauma.Lights
private Vector2 losOffset; private Vector2 losOffset;
private int recalculationCount;
public IEnumerable<LightSource> Lights public IEnumerable<LightSource> Lights
{ {
get { return lights; } get { return lights; }
@@ -151,6 +165,9 @@ namespace Barotrauma.Lights
} }
private readonly List<LightSource> activeLights = new List<LightSource>(capacity: 100); private readonly List<LightSource> activeLights = new List<LightSource>(capacity: 100);
private readonly List<LightSource> activeLightsWithLightVolume = new List<LightSource>(capacity: 100);
public static int ActiveLightCount { get; private set; }
public void Update(float deltaTime) public void Update(float deltaTime)
{ {
@@ -180,11 +197,13 @@ namespace Barotrauma.Lights
Rectangle viewRect = cam.WorldView; Rectangle viewRect = cam.WorldView;
viewRect.Y -= cam.WorldView.Height; viewRect.Y -= cam.WorldView.Height;
//check which lights need to be drawn //check which lights need to be drawn
recalculationCount = 0;
activeLights.Clear(); activeLights.Clear();
foreach (LightSource light in lights) foreach (LightSource light in lights)
{ {
if (!light.Enabled) { continue; } if (!light.Enabled) { continue; }
if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; } if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; }
if (light.ParentBody != null) if (light.ParentBody != null)
{ {
light.ParentBody.UpdateDrawPosition(); light.ParentBody.UpdateDrawPosition();
@@ -205,8 +224,44 @@ namespace Barotrauma.Lights
range = Math.Max(Math.Max(spriteRange, targetSize), range); range = Math.Max(Math.Max(spriteRange, targetSize), range);
} }
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; } if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; }
activeLights.Add(light);
light.Priority = lightPriority(range, light);
int i = 0;
while (i < activeLights.Count && light.Priority < activeLights[i].Priority)
{
i++;
}
activeLights.Insert(i, light);
} }
ActiveLightCount = activeLights.Count;
float lightPriority(float range, LightSource light)
{
return
range *
((Character.Controlled?.Submarine != null && light.ParentSub == Character.Controlled?.Submarine) ? 2.0f : 1.0f) *
(light.CastShadows ? 10.0f : 1.0f) *
(light.LightSourceParams.OverrideLightSpriteAlpha ?? (light.Color.A / 255.0f));
}
//find the lights with an active light volume
activeLightsWithLightVolume.Clear();
foreach (var activeLight in activeLights)
{
if (activeLight.Range < 1.0f || activeLight.Color.A < 1 || activeLight.CurrentBrightness <= 0.0f) { continue; }
activeLightsWithLightVolume.Add(activeLight);
}
//remove some lights with a light volume if there's too many of them
if (activeLightsWithLightVolume.Count > GameSettings.CurrentConfig.Graphics.VisibleLightLimit)
{
for (int i = GameSettings.CurrentConfig.Graphics.VisibleLightLimit; i < activeLightsWithLightVolume.Count; i++)
{
activeLights.Remove(activeLightsWithLightVolume[i]);
}
}
activeLights.Sort((l1, l2) => l1.LastRecalculationTime.CompareTo(l2.LastRecalculationTime));
//draw light sprites attached to characters //draw light sprites attached to characters
//render into a separate rendertarget using alpha blending (instead of on top of everything else with alpha blending) //render into a separate rendertarget using alpha blending (instead of on top of everything else with alpha blending)
@@ -235,7 +290,7 @@ namespace Barotrauma.Lights
{ {
if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; } if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawSprite(spriteBatch, cam); light.DrawSprite(spriteBatch, cam);
light.DrawLightVolume(spriteBatch, lightEffect, transform); light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
} }
GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive); GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
spriteBatch.End(); spriteBatch.End();
@@ -243,14 +298,6 @@ namespace Barotrauma.Lights
//draw a black rectangle on hulls to hide background lights behind subs //draw a black rectangle on hulls to hide background lights behind subs
//--------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------
/*if (backgroundObstructor != null)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0,
(int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black);
spriteBatch.End();
}*/
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform);
Dictionary<Hull, Rectangle> visibleHulls = GetVisibleHulls(cam); Dictionary<Hull, Rectangle> visibleHulls = GetVisibleHulls(cam);
foreach (KeyValuePair<Hull, Rectangle> hull in visibleHulls) foreach (KeyValuePair<Hull, Rectangle> hull in visibleHulls)
@@ -292,41 +339,44 @@ namespace Barotrauma.Lights
//draw characters to obstruct the highlighted items/characters and light sprites //draw characters to obstruct the highlighted items/characters and light sprites
//--------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------
if (cam.Zoom > ObstructLightsBehindCharactersZoomThreshold)
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"];
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Character character in Character.CharacterList)
{ {
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; } SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"];
if (Character.Controlled?.FocusedCharacter == character) { continue; } spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? foreach (Character character in Character.CharacterList)
Color.Black :
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
foreach (Limb limb in character.AnimController.Limbs)
{ {
if (limb.DeformSprite != null) { continue; } if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
limb.Draw(spriteBatch, cam, lightColor); if (Character.Controlled?.FocusedCharacter == character) { continue; }
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
Color.Black :
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.DeformSprite != null) { continue; }
limb.Draw(spriteBatch, cam, lightColor);
}
} }
} spriteBatch.End();
spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"]; DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"];
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply(); DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
foreach (Character character in Character.CharacterList) foreach (Character character in Character.CharacterList)
{
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
if (Character.Controlled?.FocusedCharacter == character) { continue; }
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
Color.Black :
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
foreach (Limb limb in character.AnimController.Limbs)
{ {
if (limb.DeformSprite == null) { continue; } if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
limb.Draw(spriteBatch, cam, lightColor); if (Character.Controlled?.FocusedCharacter == character) { continue; }
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
Color.Black :
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.DeformSprite == null) { continue; }
limb.Draw(spriteBatch, cam, lightColor);
}
} }
spriteBatch.End();
} }
spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"]; DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
graphics.BlendState = BlendState.Additive; graphics.BlendState = BlendState.Additive;
@@ -344,7 +394,7 @@ namespace Barotrauma.Lights
foreach (LightSource light in activeLights) foreach (LightSource light in activeLights)
{ {
if (light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; } if (light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawLightVolume(spriteBatch, lightEffect, transform); light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
} }
lightEffect.World = transform; lightEffect.World = transform;
@@ -205,7 +205,7 @@ namespace Barotrauma.Lights
private VertexPositionColorTexture[] vertices; private VertexPositionColorTexture[] vertices;
private short[] indices; private short[] indices;
private List<ConvexHullList> hullsInRange; private readonly List<ConvexHullList> hullsInRange;
public Texture2D texture; public Texture2D texture;
@@ -246,9 +246,9 @@ namespace Barotrauma.Lights
} }
//when were the vertices of the light volume last calculated //when were the vertices of the light volume last calculated
private float lastRecalculationTime; public float LastRecalculationTime { get; private set; }
private Dictionary<Submarine, Vector2> diffToSub; private readonly Dictionary<Submarine, Vector2> diffToSub;
private DynamicVertexBuffer lightVolumeBuffer; private DynamicVertexBuffer lightVolumeBuffer;
private DynamicIndexBuffer lightVolumeIndexBuffer; private DynamicIndexBuffer lightVolumeIndexBuffer;
@@ -376,6 +376,8 @@ namespace Barotrauma.Lights
} }
} }
public float Priority;
private Vector2 lightTextureTargetSize; private Vector2 lightTextureTargetSize;
public Vector2 LightTextureTargetSize public Vector2 LightTextureTargetSize
@@ -423,7 +425,7 @@ namespace Barotrauma.Lights
public bool Enabled = true; public bool Enabled = true;
private ISerializableEntity conditionalTarget; private readonly ISerializableEntity conditionalTarget;
private readonly PropertyConditional.Comparison comparison; private readonly PropertyConditional.Comparison comparison;
private readonly List<PropertyConditional> conditionals = new List<PropertyConditional>(); private readonly List<PropertyConditional> conditionals = new List<PropertyConditional>();
@@ -561,7 +563,7 @@ namespace Barotrauma.Lights
foreach (var ch in chList.List) foreach (var ch in chList.List)
{ {
if (ch.LastVertexChangeTime > lastRecalculationTime && !chList.IsHidden.Contains(ch)) if (ch.LastVertexChangeTime > LastRecalculationTime && !chList.IsHidden.Contains(ch))
{ {
NeedsRecalculation = true; NeedsRecalculation = true;
break; break;
@@ -1289,7 +1291,7 @@ namespace Barotrauma.Lights
} }
//visualize light recalculations //visualize light recalculations
float timeSinceRecalculation = (float)Timing.TotalTime - lastRecalculationTime; float timeSinceRecalculation = (float)Timing.TotalTime - LastRecalculationTime;
if (timeSinceRecalculation < 0.1f) if (timeSinceRecalculation < 0.1f)
{ {
GUI.DrawRectangle(spriteBatch, drawPos - Vector2.One * 10, Vector2.One * 20, GUIStyle.Red * (1.0f - timeSinceRecalculation * 10.0f), isFilled: true); GUI.DrawRectangle(spriteBatch, drawPos - Vector2.One * 10, Vector2.One * 20, GUIStyle.Red * (1.0f - timeSinceRecalculation * 10.0f), isFilled: true);
@@ -1313,7 +1315,7 @@ namespace Barotrauma.Lights
} }
} }
public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform) public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform, bool allowRecalculation, ref int recalculationCount)
{ {
if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; } if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; }
@@ -1338,8 +1340,9 @@ namespace Barotrauma.Lights
CheckHullsInRange(); CheckHullsInRange();
if (NeedsRecalculation) if (NeedsRecalculation && allowRecalculation)
{ {
recalculationCount++;
var verts = FindRaycastHits(); var verts = FindRaycastHits();
if (verts == null) if (verts == null)
{ {
@@ -1352,7 +1355,7 @@ namespace Barotrauma.Lights
CalculateLightVertices(verts); CalculateLightVertices(verts);
lastRecalculationTime = (float)Timing.TotalTime; LastRecalculationTime = (float)Timing.TotalTime;
NeedsRecalculation = false; NeedsRecalculation = false;
} }
@@ -98,7 +98,7 @@ namespace Barotrauma
OnClicked = (btn, userData) => OnClicked = (btn, userData) =>
{ {
Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed)); Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));
Generate(); Generate(GameMain.GameSession.GameMode is CampaignMode campaign ? campaign.Settings : CampaignSettings.Empty);
InitProjectSpecific(); InitProjectSpecific();
return true; return true;
} }
@@ -642,11 +642,11 @@ namespace Barotrauma
} }
} }
if (GameMain.DebugDraw && location == HighlightedLocation && (!location.Discovered || !location.HasOutpost())) if (GameMain.DebugDraw)
{ {
if (location.Reputation != null) Vector2 dPos = pos;
if (location == HighlightedLocation && (!location.Discovered || !location.HasOutpost()) && location.Reputation != null)
{ {
Vector2 dPos = pos;
dPos.Y += 48; dPos.Y += 48;
string name = $"Reputation: {location.Name}"; string name = $"Reputation: {location.Name}";
Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name); Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name);
@@ -663,6 +663,8 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, dPos + (new Vector2(256, 32) / 2) - (repValueSize / 2), reputationValue, Color.White, Color.Black, font: GUIStyle.SubHeadingFont); GUI.DrawString(spriteBatch, dPos + (new Vector2(256, 32) / 2) - (repValueSize / 2), reputationValue, Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32), Color.White); GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32), Color.White);
} }
dPos.Y += 48;
GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatZeroDecimal()}", Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
} }
} }
} }
@@ -977,7 +979,7 @@ namespace Barotrauma
Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom; Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom;
if (viewArea.Contains(center) && connection.Biome != null) if (viewArea.Contains(center) && connection.Biome != null)
{ {
GUI.DrawString(spriteBatch, center, connection.Biome.Identifier + " (" + connection.Difficulty + ")", Color.White); GUI.DrawString(spriteBatch, center, (connection.LevelData?.GenerationParams?.Identifier ?? connection.Biome.Identifier) + " (" + (int)connection.Difficulty + ")", Color.White);
} }
} }
@@ -81,11 +81,18 @@ namespace Barotrauma
} }
catch (System.IO.FileNotFoundException e) catch (System.IO.FileNotFoundException e)
{ {
string errorMsg = "Failed to load sound file \"" + filename + "\"."; string errorMsg = "Failed to load sound file \"" + filename + "\" (file not found).";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null; return null;
} }
catch (System.IO.InvalidDataException e)
{
string errorMsg = "Failed to load sound file \"" + filename + "\" (invalid data).";
DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:InvalidData" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null;
}
} }
RoundSound newSound = new RoundSound(element, existingSound); RoundSound newSound = new RoundSound(element, existingSound);
@@ -1,55 +1,61 @@
using Barotrauma.Networking; using Barotrauma.Items.Components;
using Barotrauma.RuinGeneration; using Barotrauma.Networking;
using Barotrauma.Sounds;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma namespace Barotrauma
{ {
partial class Submarine : Entity, IServerPositionSync partial class Submarine : Entity, IServerPositionSync
{ {
public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub)
{
Vector2 position = PlayerInput.MousePosition;
position = cam.ScreenToWorld(position);
Vector2 worldGridPos = VectorToWorldGrid(position);
if (sub != null)
{
worldGridPos.X += sub.Position.X % GridSize.X;
worldGridPos.Y += sub.Position.Y % GridSize.Y;
}
return worldGridPos;
}
//drawing ---------------------------------------------------- //drawing ----------------------------------------------------
private static readonly HashSet<Submarine> visibleSubs = new HashSet<Submarine>(); private static readonly HashSet<Submarine> visibleSubs = new HashSet<Submarine>();
private static double prevCullTime;
private static Rectangle prevCullArea;
/// <summary>
/// Interval at which we force culled entites to be updated, regardless if the camera has moved
/// </summary>
private const float CullInterval = 0.25f;
/// <summary>
/// Margin applied around the view area when culling entities (i.e. entities that are this far outside the view are still considered visible)
/// </summary>
private const int CullMargin = 500;
/// <summary>
/// Update entity culling when any corner of the view has moved more than this
/// </summary>
private const int CullMoveThreshold = 50;
public static void CullEntities(Camera cam) public static void CullEntities(Camera cam)
{ {
Rectangle camView = cam.WorldView;
camView = new Rectangle(camView.X - CullMargin, camView.Y + CullMargin, camView.Width + CullMargin * 2, camView.Height + CullMargin * 2);
if (Math.Abs(camView.X - prevCullArea.X) < CullMoveThreshold &&
Math.Abs(camView.Y - prevCullArea.Y) < CullMoveThreshold &&
Math.Abs(camView.Right - prevCullArea.Right) < CullMoveThreshold &&
Math.Abs(camView.Bottom - prevCullArea.Bottom) < CullMoveThreshold &&
prevCullTime > Timing.TotalTime - CullInterval)
{
return;
}
visibleSubs.Clear(); visibleSubs.Clear();
foreach (Submarine sub in Loaded) foreach (Submarine sub in Loaded)
{ {
if (Level.Loaded != null && sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; } if (Level.Loaded != null && sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; }
int margin = 500;
Rectangle worldBorders = new Rectangle( Rectangle worldBorders = new Rectangle(
sub.VisibleBorders.X + (int)sub.WorldPosition.X - margin, sub.VisibleBorders.X + (int)sub.WorldPosition.X,
sub.VisibleBorders.Y + (int)sub.WorldPosition.Y + margin, sub.VisibleBorders.Y + (int)sub.WorldPosition.Y,
sub.VisibleBorders.Width + margin * 2, sub.VisibleBorders.Width,
sub.VisibleBorders.Height + margin * 2); sub.VisibleBorders.Height);
if (RectsOverlap(worldBorders, cam.WorldView)) if (RectsOverlap(worldBorders, camView))
{ {
visibleSubs.Add(sub); visibleSubs.Add(sub);
} }
@@ -64,16 +70,22 @@ namespace Barotrauma
visibleEntities.Clear(); visibleEntities.Clear();
} }
Rectangle worldView = cam.WorldView;
foreach (MapEntity entity in MapEntity.mapEntityList) foreach (MapEntity entity in MapEntity.mapEntityList)
{ {
if (entity.Submarine != null) if (entity.Submarine != null)
{ {
if (!visibleSubs.Contains(entity.Submarine)) { continue; } if (!visibleSubs.Contains(entity.Submarine)) { continue; }
} }
if (entity.IsVisible(camView)) { visibleEntities.Add(entity); }
if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); }
} }
prevCullArea = camView;
prevCullTime = Timing.TotalTime;
}
public static void ForceVisibilityRecheck()
{
prevCullTime = 0;
} }
public static void Draw(SpriteBatch spriteBatch, bool editing = false) public static void Draw(SpriteBatch spriteBatch, bool editing = false)
@@ -148,7 +160,7 @@ namespace Barotrauma
{ {
if (predicate != null) if (predicate != null)
{ {
if (!predicate(e)) continue; if (!predicate(e)) { continue; }
} }
float drawDepth = structure.GetDrawDepth(); float drawDepth = structure.GetDrawDepth();
int i = 0; int i = 0;
@@ -525,7 +537,7 @@ namespace Barotrauma
Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2)); Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2));
for (int i = 0; i < item.Connections.Count; i++) for (int i = 0; i < item.Connections.Count; i++)
{ {
int wireCount = item.Connections[i].Wires.Count(w => w != null); int wireCount = item.Connections[i].Wires.Count;
if (doorLinks + wireCount > item.Connections[i].MaxWires) if (doorLinks + wireCount > item.Connections[i].MaxWires)
{ {
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
@@ -679,6 +691,22 @@ namespace Barotrauma
return GameMain.LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount; return GameMain.LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount;
} }
public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub)
{
Vector2 position = PlayerInput.MousePosition;
position = cam.ScreenToWorld(position);
Vector2 worldGridPos = VectorToWorldGrid(position);
if (sub != null)
{
worldGridPos.X += sub.Position.X % GridSize.X;
worldGridPos.Y += sub.Position.Y % GridSize.Y;
}
return worldGridPos;
}
public void ClientReadPosition(IReadMessage msg, float sendingTime) public void ClientReadPosition(IReadMessage msg, float sendingTime)
{ {
var posInfo = PhysicsBody.ClientRead(msg, sendingTime, parentDebugName: Info.Name); var posInfo = PhysicsBody.ClientRead(msg, sendingTime, parentDebugName: Info.Name);
@@ -154,13 +154,13 @@ namespace Barotrauma
crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height); crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height);
} }
if (!string.IsNullOrEmpty(RecommendedCrewExperience)) if (RecommendedCrewExperience != CrewExperienceLevel.Unknown)
{ {
var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true) TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false }; { CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft), new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true) TextManager.Get(RecommendedCrewExperience.ToIdentifier()), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false }; { CanBeFocused = false };
crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height); crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height);
} }
@@ -16,7 +16,7 @@ namespace Barotrauma
class SubmarinePreview : IDisposable class SubmarinePreview : IDisposable
{ {
private SpriteRecorder spriteRecorder; private SpriteRecorder spriteRecorder;
private SubmarineInfo submarineInfo; private readonly SubmarineInfo submarineInfo;
private Camera camera; private Camera camera;
private Task loadTask; private Task loadTask;
private volatile bool isDisposed; private volatile bool isDisposed;
@@ -66,7 +66,7 @@ namespace Barotrauma
public static void Close() public static void Close()
{ {
instance?.Dispose(); instance?.Dispose(); instance = null;
} }
private SubmarinePreview(SubmarineInfo subInfo) private SubmarinePreview(SubmarineInfo subInfo)
@@ -100,12 +100,16 @@ namespace Barotrauma
GUIListBox specsContainer = null; GUIListBox specsContainer = null;
new GUICustomComponent(new RectTransform(Vector2.One, innerPadded.RectTransform, Anchor.Center), new GUICustomComponent(new RectTransform(Vector2.One, innerPadded.RectTransform, Anchor.Center),
(spriteBatch, component) => { (spriteBatch, component) =>
{
if (isDisposed) { return; }
camera.UpdateTransform(interpolate: true, updateListener: false); camera.UpdateTransform(interpolate: true, updateListener: false);
Rectangle drawRect = new Rectangle(component.Rect.X + 1, component.Rect.Y + 1, component.Rect.Width - 2, component.Rect.Height - 2); Rectangle drawRect = new Rectangle(component.Rect.X + 1, component.Rect.Y + 1, component.Rect.Width - 2, component.Rect.Height - 2);
RenderSubmarine(spriteBatch, drawRect, component); RenderSubmarine(spriteBatch, drawRect, component);
}, },
(deltaTime, component) => { (deltaTime, component) =>
{
if (isDisposed) { return; }
bool isMouseOnComponent = GUI.MouseOn == component; bool isMouseOnComponent = GUI.MouseOn == component;
camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false); camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false);
if (isMouseOnComponent && if (isMouseOnComponent &&
@@ -294,8 +298,8 @@ namespace Barotrauma
private void BakeMapEntity(XElement element) private void BakeMapEntity(XElement element)
{ {
string identifier = element.GetAttributeString("identifier", ""); Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
if (string.IsNullOrEmpty(identifier)) { return; } if (identifier.IsEmpty) { return; }
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty); Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
if (rect.Equals(Rectangle.Empty)) { return; } if (rect.Equals(Rectangle.Empty)) { return; }
@@ -308,7 +312,16 @@ namespace Barotrauma
float rotation = element.GetAttributeFloat("rotation", 0f); float rotation = element.GetAttributeFloat("rotation", 0f);
MapEntityPrefab prefab = MapEntityPrefab.List.FirstOrDefault(p => p.Identifier == identifier); MapEntityPrefab prefab = null;
if (element.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase) &&
ItemPrefab.Prefabs.TryGet(identifier, out ItemPrefab ip))
{
prefab = ip;
}
else
{
prefab = MapEntityPrefab.List.FirstOrDefault(p => p.Identifier == identifier);
}
if (prefab == null) { return; } if (prefab == null) { return; }
var texture = prefab.Sprite.Texture; var texture = prefab.Sprite.Texture;
@@ -329,7 +342,6 @@ namespace Barotrauma
bool overrideSprite = false; bool overrideSprite = false;
ItemPrefab itemPrefab = prefab as ItemPrefab; ItemPrefab itemPrefab = prefab as ItemPrefab;
StructurePrefab structurePrefab = prefab as StructurePrefab;
if (itemPrefab != null) if (itemPrefab != null)
{ {
BakeItemComponents(itemPrefab, rect, color, scale, rotation, depth, out overrideSprite); BakeItemComponents(itemPrefab, rect, color, scale, rotation, depth, out overrideSprite);
@@ -337,7 +349,7 @@ namespace Barotrauma
if (!overrideSprite) if (!overrideSprite)
{ {
if (structurePrefab != null) if (prefab is StructurePrefab structurePrefab)
{ {
ParseUpgrades(structurePrefab.ConfigElement, ref scale); ParseUpgrades(structurePrefab.ConfigElement, ref scale);
@@ -655,7 +667,8 @@ namespace Barotrauma
previewFrame.RectTransform.Parent = null; previewFrame.RectTransform.Parent = null;
previewFrame = null; previewFrame = null;
} }
spriteRecorder?.Dispose(); spriteRecorder?.Dispose(); spriteRecorder = null;
camera?.Dispose(); camera = null;
isDisposed = true; isDisposed = true;
} }
} }
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System; using System;
namespace Barotrauma.Networking namespace Barotrauma.Networking
@@ -9,14 +10,15 @@ namespace Barotrauma.Networking
{ {
msg.Write((byte)ClientNetObject.CHAT_MESSAGE); msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID); msg.Write(NetStateID);
msg.Write((byte)Type); msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
msg.Write(Text); msg.Write(Text);
} }
public static void ClientRead(IReadMessage msg) public static void ClientRead(IReadMessage msg)
{ {
UInt16 id = msg.ReadUInt16(); UInt16 id = msg.ReadUInt16();
ChatMessageType type = (ChatMessageType)msg.ReadByte(); ChatMessageType type = (ChatMessageType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None; PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None;
string txt = ""; string txt = "";
string styleSetting = string.Empty; string styleSetting = string.Empty;
@@ -183,6 +185,11 @@ namespace Barotrauma.Networking
break; break;
default: default:
GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType, textColor: textColor); GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType, textColor: textColor);
if (type == ChatMessageType.Radio && CanUseRadio(senderCharacter, out WifiComponent radio))
{
Signal s = new Signal(txt, sender: senderCharacter, source: radio.Item);
radio.TransmitSignal(s, sentFromChat: true);
}
break; break;
} }
LastID = id; LastID = id;
@@ -146,26 +146,19 @@ namespace Barotrauma.Networking
} }
private bool disposed = false; private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing)
{
if (WriteStream != null)
{
WriteStream.Flush();
WriteStream.Close();
WriteStream.Dispose();
WriteStream = null;
}
}
disposed = true;
}
public void Dispose() public void Dispose()
{ {
Dispose(true); if (disposed) { return; }
if (WriteStream != null)
{
WriteStream.Flush();
WriteStream.Close();
WriteStream.Dispose();
WriteStream = null;
}
disposed = true;
} }
} }
@@ -3,6 +3,7 @@ using Barotrauma.Steam;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.IO; using Barotrauma.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
@@ -11,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -182,6 +184,20 @@ namespace Barotrauma.Networking
get { return ownerKey > 0 || steamP2POwner; } get { return ownerKey > 0 || steamP2POwner; }
} }
internal readonly struct PermissionChangedEvent
{
public readonly ClientPermissions NewPermissions;
public readonly ImmutableArray<string> NewPermittedConsoleCommands;
public PermissionChangedEvent(ClientPermissions newPermissions, IReadOnlyList<string> newPermittedConsoleCommands)
{
NewPermissions = newPermissions;
NewPermittedConsoleCommands = newPermittedConsoleCommands.ToImmutableArray();
}
}
public readonly NamedEvent<PermissionChangedEvent> OnPermissionChanged = new NamedEvent<PermissionChangedEvent>();
public GameClient(string newName, string ip, UInt64 steamId, string serverName = null, int ownerKey = 0, bool steamP2POwner = false) public GameClient(string newName, string ip, UInt64 steamId, string serverName = null, int ownerKey = 0, bool steamP2POwner = false)
{ {
//TODO: gui stuff should probably not be here? //TODO: gui stuff should probably not be here?
@@ -570,7 +586,12 @@ namespace Barotrauma.Networking
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
#if DEBUG #if DEBUG
if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.P)) return; if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.P)) return;
if (PlayerInput.KeyHit(Keys.Home))
{
OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, permittedConsoleCommands));
}
#endif #endif
foreach (Client c in ConnectedClients) foreach (Client c in ConnectedClients)
@@ -668,7 +689,7 @@ namespace Barotrauma.Networking
if (ChildServerRelay.Process?.HasExited ?? true) if (ChildServerRelay.Process?.HasExited ?? true)
{ {
Disconnect(); Disconnect();
if (!GUIMessageBox.MessageBoxes.Any(mb => (mb as GUIMessageBox)?.Text.Text == ChildServerRelay.CrashMessage)) if (!GUIMessageBox.MessageBoxes.Any(mb => (mb as GUIMessageBox)?.Text?.Text == ChildServerRelay.CrashMessage))
{ {
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage); var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu; msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
@@ -803,7 +824,11 @@ namespace Barotrauma.Networking
byte campaignID = inc.ReadByte(); byte campaignID = inc.ReadByte();
UInt16 campaignSaveID = inc.ReadUInt16(); UInt16 campaignSaveID = inc.ReadUInt16();
UInt16 campaignUpdateID = inc.ReadUInt16(); Dictionary<MultiPlayerCampaign.NetFlags, UInt16> campaignUpdateIDs = new Dictionary<MultiPlayerCampaign.NetFlags, ushort>();
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
campaignUpdateIDs[flag] = inc.ReadUInt16();
}
IWriteMessage readyToStartMsg = new WriteOnlyMessage(); IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME); readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME);
@@ -822,7 +847,7 @@ namespace Barotrauma.Networking
campaign != null && campaign != null &&
campaign.CampaignID == campaignID && campaign.CampaignID == campaignID &&
campaign.LastSaveID == campaignSaveID && campaign.LastSaveID == campaignSaveID &&
campaign.LastUpdateID == campaignUpdateID; campaignUpdateIDs.All(kvp => campaign.GetLastUpdateIdForFlag(kvp.Key) == kvp.Value);
} }
readyToStartMsg.Write(readyToStart); readyToStartMsg.Write(readyToStart);
@@ -1019,40 +1044,25 @@ namespace Barotrauma.Networking
GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers); GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers);
} }
byte equalityCheckValueCount = inc.ReadByte(); var levelEqualityCheckValues = new Dictionary<Level.LevelGenStage, int>();
List<int> levelEqualityCheckValues = new List<int>(); foreach (Level.LevelGenStage stage in Enum.GetValues(typeof(Level.LevelGenStage)).OfType<Level.LevelGenStage>().OrderBy(s => s))
for (int i = 0; i < equalityCheckValueCount; i++)
{ {
levelEqualityCheckValues.Add(inc.ReadInt32()); levelEqualityCheckValues.Add(stage, inc.ReadInt32());
} }
if (Level.Loaded.EqualityCheckValues.Count != levelEqualityCheckValues.Count) foreach (var stage in levelEqualityCheckValues.Keys)
{ {
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" + if (Level.Loaded.EqualityCheckValues[stage] != levelEqualityCheckValues[stage])
" (client value count: " + Level.Loaded.EqualityCheckValues.Count +
", level value count: " + levelEqualityCheckValues.Count +
", seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
", mirrored: " + Level.Loaded.Mirrored + ").";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
else
{
for (int i = 0; i < equalityCheckValueCount; i++)
{ {
if (Level.Loaded.EqualityCheckValues[i] != levelEqualityCheckValues[i]) 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") +
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" + ", server value " + stage + ": " + levelEqualityCheckValues[stage].ToString("X") +
" (client value #" + i + ": " + Level.Loaded.EqualityCheckValues[i] + ", level value count: " + levelEqualityCheckValues.Count +
", server value #" + i + ": " + levelEqualityCheckValues[i].ToString("X") + ", seed: " + Level.Loaded.Seed +
", level value count: " + levelEqualityCheckValues.Count + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
", seed: " + Level.Loaded.Seed + ", mirrored: " + Level.Loaded.Mirrored + ").";
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
", mirrored: " + Level.Loaded.Mirrored + ")."; throw new Exception(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
} }
} }
@@ -1076,6 +1086,7 @@ namespace Barotrauma.Networking
reconnectBox?.Close(); reconnectBox?.Close();
reconnectBox = null; reconnectBox = null;
GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore(); ContentPackageManager.EnabledPackages.Restore();
GUI.ClearCursorWait(); GUI.ClearCursorWait();
@@ -1189,7 +1200,14 @@ namespace Barotrauma.Networking
new LocalizedString[] { TextManager.Get("Cancel") }); new LocalizedString[] { TextManager.Get("Cancel") });
reconnectBox.Buttons[0].OnClicked += (btn, userdata) => { CancelConnect(); return true; }; reconnectBox.Buttons[0].OnClicked += (btn, userdata) => { CancelConnect(); return true; };
connected = false; connected = false;
var prevContentPackages = clientPeer.ServerContentPackages;
ConnectToServer(serverEndpoint, serverName); ConnectToServer(serverEndpoint, serverName);
if (clientPeer != null)
{
//restore the previous list of content packages so we can reconnect immediately without having to recheck that the packages match
clientPeer.ServerContentPackages = prevContentPackages;
}
} }
else else
{ {
@@ -1380,18 +1398,13 @@ namespace Barotrauma.Networking
private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable<string> permittedConsoleCommands) private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable<string> permittedConsoleCommands)
{ {
if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) ||
permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c))))
{ {
if (newPermissions == permissions) return; if (newPermissions == permissions) return;
} }
bool refreshCampaignUI = false; bool refreshCampaignUI = permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) ||
permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound);
if (permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) ||
permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound))
{
refreshCampaignUI = true;
}
permissions = newPermissions; permissions = newPermissions;
this.permittedConsoleCommands = new List<string>(permittedConsoleCommands); this.permittedConsoleCommands = new List<string>(permittedConsoleCommands);
@@ -1430,7 +1443,7 @@ namespace Barotrauma.Networking
if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands)) if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands))
{ {
var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform),
TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont); TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont);
var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)); var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform));
foreach (string permittedCommand in permittedConsoleCommands) foreach (string permittedCommand in permittedConsoleCommands)
{ {
@@ -1469,6 +1482,7 @@ namespace Barotrauma.Networking
} }
GameMain.NetLobbyScreen.RefreshEnabledElements(); GameMain.NetLobbyScreen.RefreshEnabledElements();
OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, this.permittedConsoleCommands));
} }
private IEnumerable<CoroutineStatus> StartGame(IReadMessage inc) private IEnumerable<CoroutineStatus> StartGame(IReadMessage inc)
@@ -1516,6 +1530,7 @@ namespace Barotrauma.Networking
serverSettings.LockAllDefaultWires = inc.ReadBoolean(); serverSettings.LockAllDefaultWires = inc.ReadBoolean();
serverSettings.AllowRagdollButton = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean();
serverSettings.AllowLinkingWifiToChat = inc.ReadBoolean(); serverSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
serverSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte(); GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits(); bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
@@ -1681,6 +1696,13 @@ namespace Barotrauma.Networking
} }
} }
if (clientPeer == null)
{
DebugConsole.ThrowError("There was an error initializing the round (disconnected during the StartGame coroutine.)");
roundInitStatus = RoundInitStatus.Error;
yield return CoroutineStatus.Failure;
}
roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize; roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize;
DateTime? timeOut = null; DateTime? timeOut = null;
@@ -1823,7 +1845,8 @@ namespace Barotrauma.Networking
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
AddChatMessage($"ServerMessage.HowToCommunicate~[chatbutton]={GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Chat)}~[radiobutton]={GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.RadioChat)}", ChatMessageType.Server); // TODO: Re-enable the server message once it's been edited and translated
//AddChatMessage($"ServerMessage.HowToCommunicate~[chatbutton]={GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Chat)}~[radiobutton]={GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.RadioChat)}", ChatMessageType.Server);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -2397,7 +2420,10 @@ namespace Barotrauma.Networking
{ {
outmsg.Write(campaign.LastSaveID); outmsg.Write(campaign.LastSaveID);
outmsg.Write(campaign.CampaignID); outmsg.Write(campaign.CampaignID);
outmsg.Write(campaign.LastUpdateID); foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.Write(campaign.GetLastUpdateIdForFlag(netFlag));
}
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
} }
@@ -2442,7 +2468,10 @@ namespace Barotrauma.Networking
{ {
outmsg.Write(campaign.LastSaveID); outmsg.Write(campaign.LastSaveID);
outmsg.Write(campaign.CampaignID); outmsg.Write(campaign.CampaignID);
outmsg.Write(campaign.LastUpdateID); foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.Write(campaign.GetLastUpdateIdForFlag(flag));
}
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
} }
@@ -2489,6 +2518,7 @@ namespace Barotrauma.Networking
message, message,
type, type,
gameStarted && myCharacter != null ? myCharacter : null); gameStarted && myCharacter != null ? myCharacter : null);
chatMessage.ChatMode = GameMain.ActiveChatMode;
lastQueueChatMsgID++; lastQueueChatMsgID++;
chatMessage.NetStateID = lastQueueChatMsgID; chatMessage.NetStateID = lastQueueChatMsgID;
@@ -2640,7 +2670,7 @@ namespace Barotrauma.Networking
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.CampaignID != campaignID) if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.CampaignID != campaignID)
{ {
string savePath = transfer.FilePath; string savePath = transfer.FilePath;
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Unsure); GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID; campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -2670,9 +2700,12 @@ namespace Barotrauma.Networking
} }
DebugConsole.Log("Campaign save received (" + GameMain.GameSession.SavePath + "), save ID " + campaign.LastSaveID); DebugConsole.Log("Campaign save received (" + GameMain.GameSession.SavePath + "), save ID " + campaign.LastSaveID);
//decrement campaign update ID so the server will send us the latest data //decrement campaign update IDs so the server will send us the latest data
//(as there may have been campaign updates after the save file was created) //(as there may have been campaign updates after the save file was created)
campaign.LastUpdateID--; foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
campaign.SetLastUpdateIdForFlag(flag, (ushort)(campaign.GetLastUpdateIdForFlag(flag) - 1));
}
break; break;
case FileTransferType.Mod: case FileTransferType.Mod:
if (!(Screen.Selected is ModDownloadScreen)) { return; } if (!(Screen.Selected is ModDownloadScreen)) { return; }
@@ -2771,10 +2804,21 @@ namespace Barotrauma.Networking
GameMain.GameSession = null; GameMain.GameSession = null;
} }
public void WriteCharacterInfo(IWriteMessage msg) public void SendCharacterInfo(string newName = null)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
WriteCharacterInfo(msg, newName);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
{ {
msg.Write(characterInfo == null); msg.Write(characterInfo == null);
if (characterInfo == null) return; if (characterInfo == null) { return; }
msg.Write(newName ?? string.Empty);
msg.Write((byte)characterInfo.Head.Preset.TagSet.Count); msg.Write((byte)characterInfo.Head.Preset.TagSet.Count);
foreach (Identifier tag in characterInfo.Head.Preset.TagSet) foreach (Identifier tag in characterInfo.Head.Preset.TagSet)
@@ -2820,18 +2864,18 @@ namespace Barotrauma.Networking
} }
#region Submarine Change Voting #region Submarine Change Voting
public void InitiateSubmarineChange(SubmarineInfo sub, VoteType voteType) public void InitiateSubmarineChange(SubmarineInfo sub, bool transferItems, VoteType voteType)
{ {
if (sub == null) { return; } if (sub == null) { return; }
Vote(voteType, sub); Vote(voteType, (sub, transferItems));
} }
public void ShowSubmarineChangeVoteInterface(Client starter, SubmarineInfo info, VoteType type, float timeOut) public void ShowSubmarineChangeVoteInterface(Client starter, SubmarineInfo info, VoteType type, bool transferItems, float timeOut)
{ {
if (info == null) { return; } if (info == null) { return; }
if (votingInterface != null && votingInterface.VoteRunning) { return; } if (votingInterface != null && votingInterface.VoteRunning) { return; }
votingInterface?.Remove(); votingInterface?.Remove();
votingInterface = VotingInterface.CreateSubmarineVotingInterface(starter, info, type, timeOut); votingInterface = VotingInterface.CreateSubmarineVotingInterface(starter, info, type, transferItems, timeOut);
} }
#endregion #endregion
@@ -3010,7 +3054,7 @@ namespace Barotrauma.Networking
msg.Write(mapSeed); msg.Write(mapSeed);
msg.Write(sub.Name); msg.Write(sub.Name);
msg.Write(sub.MD5Hash.StringRepresentation); msg.Write(sub.MD5Hash.StringRepresentation);
settings.Serialize(msg); msg.Write(settings);
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
@@ -3258,10 +3302,8 @@ namespace Barotrauma.Networking
{ {
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
{ {
bool chatKeyHit = PlayerInput.KeyHit(InputType.Chat); var chatKeyStates = ChatBox.ChatKeyStates.GetChatKeyStates();
bool radioKeyHit = PlayerInput.KeyHit(InputType.RadioChat) && (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100); if (chatKeyStates.AnyHit)
if (chatKeyHit || radioKeyHit)
{ {
if (msgBox.Selected) if (msgBox.Selected)
{ {
@@ -3272,34 +3314,8 @@ namespace Barotrauma.Networking
{ {
if (Screen.Selected == GameMain.GameScreen) if (Screen.Selected == GameMain.GameScreen)
{ {
if (chatKeyHit) ChatBox.ApplySelectionInputs(msgBox, false, chatKeyStates);
{
msgBox.AddToGUIUpdateList();
ChatBox.GUIFrame.Flash(Color.DarkGreen, 0.5f);
if (!chatBox.ToggleOpen)
{
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
ChatBox.ToggleOpen = true;
}
}
if (radioKeyHit)
{
msgBox.AddToGUIUpdateList();
ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
if (!chatBox.ToggleOpen)
{
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
ChatBox.ToggleOpen = true;
}
if (!msgBox.Text.StartsWith(ChatBox.RadioChatString))
{
msgBox.Text = ChatBox.RadioChatString;
}
}
} }
msgBox.Select(msgBox.Text.Length); msgBox.Select(msgBox.Text.Length);
} }
} }
@@ -3567,13 +3583,13 @@ namespace Barotrauma.Networking
return true; return true;
}; };
durationInputDays = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), GUINumberInput.NumberType.Int) durationInputDays = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), NumberType.Int)
{ {
MinValueInt = 0, MinValueInt = 0,
MaxValueFloat = 1000 MaxValueFloat = 1000
}; };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), TextManager.Get("Days")); new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), TextManager.Get("Days"));
durationInputHours = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), GUINumberInput.NumberType.Int) durationInputHours = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), NumberType.Int)
{ {
MinValueInt = 0, MinValueInt = 0,
MaxValueFloat = 24 MaxValueFloat = 24
@@ -3680,7 +3696,9 @@ namespace Barotrauma.Networking
} }
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); errorLines.Add("Level: " + Level.Loaded.Seed + ", "
+ string.Join("; ", Level.Loaded.EqualityCheckValues.Select(cv
=> cv.Key + "=" + cv.Value.ToString("X"))));
errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate); errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate);
errorLines.Add("Entities:"); errorLines.Add("Entities:");
foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex)) foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex))
@@ -126,7 +126,7 @@ namespace Barotrauma
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip") ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip")
}; };
var numInput = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), GUINumberInput.NumberType.Int) var numInput = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), NumberType.Int)
{ {
MinValueInt = min, MinValueInt = min,
MaxValueInt = max MaxValueInt = max
@@ -1,4 +1,6 @@
namespace Barotrauma.Networking using System;
namespace Barotrauma.Networking
{ {
partial class OrderChatMessage : ChatMessage partial class OrderChatMessage : ChatMessage
{ {
@@ -6,7 +8,8 @@
{ {
msg.Write((byte)ClientNetObject.CHAT_MESSAGE); msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID); msg.Write(NetStateID);
msg.Write((byte)ChatMessageType.Order); msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
WriteOrder(msg); WriteOrder(msg);
} }
} }
@@ -50,7 +50,7 @@ namespace Barotrauma.Networking
} }
} }
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; private set; } = public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
ImmutableArray<ServerContentPackage>.Empty; ImmutableArray<ServerContentPackage>.Empty;
public delegate void MessageCallback(IReadMessage message); public delegate void MessageCallback(IReadMessage message);
@@ -117,8 +117,6 @@ namespace Barotrauma.Networking
PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
//Console.WriteLine(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte);
if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success) if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success)
{ {
ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false)); ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
@@ -373,12 +373,6 @@ namespace Barotrauma.Networking
OnDisconnect?.Invoke(disableReconnect); OnDisconnect?.Invoke(disableReconnect);
} }
~SteamP2PClientPeer()
{
OnDisconnect = null;
Close();
}
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg) protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
{ {
Steamworks.P2PSend sendType; Steamworks.P2PSend sendType;

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