OBT/1.2.1(Summer update)

Sync with upstream
This commit is contained in:
NotAlwaysTrue
2026-06-16 22:17:29 +08:00
committed by GitHub
parent 59bc21973a
commit d109c8f827
85 changed files with 1377 additions and 497 deletions
@@ -258,7 +258,9 @@ namespace Barotrauma
//RelativeSpacing = 0.05f
};
InventorySlotContainer = new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1.0f), characterIndicatorArea.RectTransform, Anchor.TopLeft, Pivot.TopRight),
GUIFrame left = new(new RectTransform(new Vector2(0.25f, 1f), characterIndicatorArea.RectTransform), style: null);
InventorySlotContainer = new GUICustomComponent(new RectTransform(Vector2.One, left.RectTransform),
(spriteBatch, component) =>
{
for (int i = 0; i < character.Inventory.Capacity; i++)
@@ -266,6 +268,10 @@ namespace Barotrauma
if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
if (character.Inventory.HideSlot(i)) { continue; }
int width = Character.Inventory.visualSlots[i].Rect.Width;
left.RectTransform.MinSize = new Point(width, left.RectTransform.MinSize.Y);
if (afflictionIconList != null) { afflictionIconList.RectTransform.MinSize = new Point(width, afflictionIconList.RectTransform.MinSize.Y); }
//don't draw the item if it's being dragged out of the slot
bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn();
@@ -292,8 +298,7 @@ namespace Barotrauma
}
});
cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
cprButton = new GUIButton(new RectTransform(new Vector2(0.75f), left.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
{
UserData = UIHighlightAction.ElementId.CPRButton,
OnClicked = (button, userData) =>
@@ -316,12 +321,11 @@ namespace Barotrauma
return true;
},
ToolTip = TextManager.Get("doctor.cprobjective"),
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("tutorial.roles.medic.objective.cpr"),
Visible = false
};
var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform),
var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.5f, 1.0f), characterIndicatorArea.RectTransform),
(spriteBatch, component) =>
{
DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true);
@@ -368,8 +372,6 @@ namespace Barotrauma
CanBeFocused = false
};
characterIndicatorArea.Recalculate();
healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null)
{
HoverCursor = CursorState.Hand
@@ -866,7 +866,20 @@ namespace Barotrauma
GameSettings.SaveCurrentConfig();
});
}, isCheat: false));
commands.Add(new Command("togglespoofeventmanagerid", "togglespoofeventmanagerid: Forces the client to report the last received event ID as always being 1, making the server believe the client is always behind.", (string[] args) =>
{
if (GameMain.Client != null)
{
GameMain.Client.SpoofEntityManagerReceivedId = !GameMain.Client.SpoofEntityManagerReceivedId;
DebugConsole.NewMessage(GameMain.Client.SpoofEntityManagerReceivedId ? "Spoofing enabled ": "Spoofing disabled", Color.Green);
}
else
{
DebugConsole.NewMessage("Not connected to server", Color.Red);
}
}));
commands.Add(new Command("togglegrid", "Toggle visual snap grid in sub editor.", (string[] args) =>
{
SubEditorScreen.ShouldDrawGrid = !SubEditorScreen.ShouldDrawGrid;
@@ -4454,17 +4467,24 @@ namespace Barotrauma
public static void StartLocalMPSession(int numClients = 2)
{
string extraArguments = "-multiclienttestmode";
if (NetConfig.UseLenientHandshake)
{
extraArguments += " -lenienthandshake";
}
try
{
if (Process.GetProcessesByName("DedicatedServer").Length == 0)
{
#if WINDOWS
Process.Start("DedicatedServer.exe", arguments: "-multiclienttestmode");
Process.Start("DedicatedServer.exe", arguments: extraArguments);
#else
Process.Start("./DedicatedServer", arguments: "-multiclienttestmode");
Process.Start("./DedicatedServer", arguments: extraArguments);
#endif
System.Threading.Thread.Sleep(1000);
}
#if DEBUG
GameClient.MultiClientTestMode = true;
#endif
@@ -4478,10 +4498,13 @@ namespace Barotrauma
for (int i = 2; i <= numClients; i++)
{
System.Threading.Thread.Sleep(1000);
string clientArguments = $"-connect server localhost -username client{i} -skipintro";
#if WINDOWS
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
Process.Start("Barotrauma.exe", arguments: $"{clientArguments} {extraArguments}");
#else
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
Process.Start("./Barotrauma", arguments: $"{clientArguments} {extraArguments}");
#endif
}
}
@@ -14,6 +14,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace Barotrauma
{
@@ -649,12 +650,12 @@ namespace Barotrauma
DrawMessages(spriteBatch, cam);
if (MouseOn != null)
{
{
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
{
MouseOn.DrawToolTip(spriteBatch);
}
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
}
if (SubEditorScreen.IsSubEditor())
@@ -2323,10 +2324,10 @@ namespace Barotrauma
/// <summary>
/// Creates a 7-segment display.
/// </summary>
/// <param name="leftLabel">Returns <see langword="null"/> if <paramref name="leftLabelText"/> is <see langword="null"/> or empty.</param>
/// <param name="leftLabel">Returns <see langword="null"/> if <paramref name="leftLabelText"/> is <see langword="null"/>.</param>
/// <param name="rightLabelText">Defaults to <c>TextManager.Get("kilowatt")</c>.</param>
/// <param name="leftLabelFont">Defaults to <see cref="GUIStyle.LargeFont"/>.</param>
public static GUITextBlock CreateDigitalDisplay(RectTransform rect, out GUITextBlock? leftLabel, out GUITextBlock rightLabel, LocalizedString? leftLabelText = null, LocalizedString? rightLabelText = null, LocalizedString? tooltip = null, GUIFont? leftLabelFont = null)
public static GUITextBlock CreateDigitalDisplay(RectTransform rect, [NotNullIfNotNull(nameof(leftLabelText))] out GUITextBlock? leftLabel, out GUITextBlock rightLabel, LocalizedString? leftLabelText = null, LocalizedString? rightLabelText = null, LocalizedString? tooltip = null, GUIFont? leftLabelFont = null)
{
GUILayoutGroup textArea = new(rect, isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
@@ -2337,7 +2338,7 @@ namespace Barotrauma
};
leftLabel = null;
if (!leftLabelText.IsNullOrEmpty())
if (leftLabelText != null)
{
leftLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), textArea.RectTransform), leftLabelText, textColor: GUIStyle.TextColorBright, font: leftLabelFont ?? GUIStyle.LargeFont, textAlignment: Alignment.CenterRight);
}
@@ -384,14 +384,14 @@ namespace Barotrauma
CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
CalculateCaretPos();
ClearSelection();
bool wasSelected = selected;
selected = true;
GUI.KeyboardDispatcher.Subscriber = this;
OnSelected?.Invoke(this, Keys.None);
if (!wasSelected && PlaySoundOnSelect && !ignoreSelectSound)
if (!selected && PlaySoundOnSelect && !ignoreSelectSound)
{
SoundPlayer.PlayUISound(GUISoundType.Select);
}
selected = true;
//set this after we've set selected to true -> otherwise the textbox taking keyboard focus will trigger Select again
GUI.KeyboardDispatcher.Subscriber = this;
}
public void Deselect()
@@ -113,7 +113,10 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
if (currentBackgroundTexture.Texture != null)
{
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
}
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
double noiseT = Timing.TotalTime * 0.02f;
@@ -278,6 +278,12 @@ namespace Barotrauma
GameClient.MultiClientTestMode = true;
}
#endif
if (ConsoleArguments.Contains("-lenienthandshake"))
{
NetConfig.UseLenientHandshake = true;
}
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
PerformanceCounter = new PerformanceCounter();
@@ -127,12 +127,6 @@ namespace Barotrauma
(int)SlotPositions[i].X,
(int)SlotPositions[i].Y,
(int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier));
if (SlotTypes[i] == InvSlotType.HealthInterface &&
character.CharacterHealth?.InventorySlotContainer != null)
{
slotRect.Width = slotRect.Height = (int)(character.CharacterHealth.InventorySlotContainer.Rect.Width * 1.2f);
}
ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>();
if (itemContainer != null)
@@ -622,6 +616,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++)
{
if (HideSlot(i)) { continue; }
var item = slots[i].FirstOrDefault();
if (item != null)
{
@@ -26,7 +26,7 @@ namespace Barotrauma.Items.Components
}
else
{
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg));
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg)).Fallback(Msg);
}
CharacterHUD.RecreateHudTextsIfControlling(Character.Controlled);
@@ -12,9 +12,12 @@ namespace Barotrauma.Items.Components
private partial class PowerGroup
{
private GUIFrame? frame;
private GUIFrame? groupContent;
private GUILayoutGroup? nameGroup;
private GUITextBox? nameBox;
private GUITextBlock? loadDisplayNameLabel;
private GUIScrollBar? ratioSlider;
private readonly List<GUITextBlock> powerUnitLabels = new List<GUITextBlock>();
private readonly List<GUITextBlock> powerUnitLabels = [];
private GUIFrame? divider;
public bool IsVisible { get; private set; } = true;
@@ -22,9 +25,9 @@ namespace Barotrauma.Items.Components
public void CreateGUI()
{
frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), distributor.groupList!.Content.RectTransform, minSize: (0, 130)), style: null);
GUIFrame groupContent = new(new RectTransform(frame.Rect.Size - new Point(10), frame.RectTransform, Anchor.Center), style: null);
groupContent = new GUIFrame(new RectTransform(frame.Rect.Size - new Point(10), frame.RectTransform, Anchor.Center), style: null);
GUILayoutGroup nameGroup = new(new RectTransform(new Vector2(0.65f, 0.33f), groupContent.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.65f, 0.33f), groupContent.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true
};
@@ -37,7 +40,7 @@ namespace Barotrauma.Items.Components
return true;
}
};
nameBox = new GUITextBox(new RectTransform(Vector2.One, nameGroup.RectTransform), Name, font: GUIStyle.SubHeadingFont, style: "GUITextBoxNoStyle")
nameBox = new GUITextBox(new RectTransform(Vector2.One, nameGroup.RectTransform), Screen.Selected == GameMain.SubEditorScreen ? Name : DisplayName.Value, font: GUIStyle.SubHeadingFont, style: "GUITextBoxNoStyle")
{
MaxTextLength = MaxNameLength,
OverflowClip = true,
@@ -48,17 +51,40 @@ namespace Barotrauma.Items.Components
return true;
}
};
nameBox.OnSelected += (tb, _) =>
{
if (tb.Selected) { return; }
tb.Text = Name;
};
nameBox.OnDeselected += (tb, _) =>
{
Name = tb.Text;
tb.CaretIndex = 0;
if (GameMain.Client == null) { return; }
distributor.item.CreateClientEvent(distributor, new EventData(this, EventType.NameChange));
};
nameBox.GetChild<GUIFrame>().GetChild<GUICustomComponent>().OnDrawToolTip = comp =>
{
if (Screen.Selected != GameMain.SubEditorScreen && !nameBox.Selected)
{
comp.ToolTip = null;
return;
}
LocalizedString localizedText = TextManager.Get(nameBox.Text);
comp.ToolTip = localizedText.IsNullOrEmpty()
? TextManager.GetWithVariable("StringPropertyCannotTranslate", "[tag]", nameBox.Text)
: TextManager.GetWithVariable("StringPropertyTranslate", "[translation]", localizedText);
};
GUITextBlock loadDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.TopRight) { AbsoluteOffset = (5, 0) },
out GUITextBlock? _, out GUITextBlock loadDisplayUnitLabel, TextManager.Get("PowerTransferLoadLabel"), tooltip: TextManager.Get("PowerTransferTipLoad"), leftLabelFont: GUIStyle.Font);
out loadDisplayNameLabel, out GUITextBlock loadDisplayUnitLabel, TextManager.Get("PowerTransferLoadLabel"), tooltip: TextManager.Get("PowerTransferTipLoad"), leftLabelFont: GUIStyle.Font);
loadDisplay.TextGetter = () => MathUtils.RoundToInt(Load).ToString();
float textAndPaddingWidth = loadDisplayNameLabel!.Font.MeasureString(loadDisplayNameLabel!.Text).X + loadDisplayNameLabel.Padding.X + loadDisplayNameLabel.Padding.Z;
float availableWidth = groupContent!.Rect.Width - loadDisplayNameLabel.Parent.Rect.Width + loadDisplayNameLabel.Rect.Width - textAndPaddingWidth;
nameGroup!.RectTransform.Resize(new Point((int)availableWidth, nameGroup.Rect.Height));
ratioSlider = new GUIScrollBar(new RectTransform(new Vector2(1f, 0.33f), groupContent.RectTransform, Anchor.Center), barSize: 0.15f, style: "DeviceSlider")
{
Step = SupplyRatioStep,
@@ -78,16 +104,17 @@ namespace Barotrauma.Items.Components
ratioSlider.Bar.RectTransform.MaxSize = new Point(ratioSlider.Bar.Rect.Height);
GUITextBlock ratioDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.2f, 0.33f), groupContent.RectTransform, Anchor.BottomLeft),
out GUITextBlock? _, out GUITextBlock _,
out GUITextBlock? _, out GUITextBlock ratioDisplayUnitLabel,
rightLabelText: "%");
ratioDisplay.TextGetter = () => DisplayRatio.ToString();
GUITextBlock outputDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.BottomRight) { AbsoluteOffset = (5, 0) },
out GUITextBlock? _, out GUITextBlock outputDisplayUnitLabel,
out GUITextBlock? outputDisplayNameLabel, out GUITextBlock outputDisplayUnitLabel,
TextManager.Get("powerdistributor.supplylabel"), tooltip: TextManager.Get("PowerTransferTipPower"), leftLabelFont: GUIStyle.Font);
outputDisplay.TextGetter = () => distributor.IsShortCircuited(PowerOut) ? "err" : MathUtils.RoundToInt(distributor.CalculatePowerOut(this)).ToString();
powerUnitLabels.Add(loadDisplayUnitLabel);
powerUnitLabels.Add(ratioDisplayUnitLabel);
powerUnitLabels.Add(outputDisplayUnitLabel);
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
@@ -111,7 +138,14 @@ namespace Barotrauma.Items.Components
IsVisible = PowerOut.Wires.Count >= 1;
frame!.Visible = IsVisible;
divider!.Visible = IsVisible && distributor.powerGroups.Last(group => group.frame!.Visible) != this;
if (distributor.prevLanguage != GameSettings.CurrentConfig.Language) { GUITextBlock.AutoScaleAndNormalize(powerUnitLabels); }
if (distributor.prevLanguage != GameSettings.CurrentConfig.Language)
{
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
float textAndPaddingWidth = loadDisplayNameLabel!.Font.MeasureString(loadDisplayNameLabel!.Text).X + loadDisplayNameLabel.Padding.X + loadDisplayNameLabel.Padding.Z;
float availableWidth = groupContent!.Rect.Width - loadDisplayNameLabel.Parent.Rect.Width + loadDisplayNameLabel.Rect.Width - textAndPaddingWidth;
nameGroup!.RectTransform.Resize(new Point((int)availableWidth, nameGroup.Rect.Height));
}
}
}
@@ -716,13 +716,19 @@ namespace Barotrauma.Items.Components
GetAvailablePower(out float batteryCharge, out float batteryCapacity);
List<Item> availableAmmo = new List<Item>();
List<Item> availableAmmo = [];
AddAmmoFromContainer(item.GetComponent<ItemContainer>());
foreach (MapEntity e in item.linkedTo)
{
if (!(e is Item linkedItem)) { continue; }
var itemContainer = linkedItem.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
if (e is not Item linkedItem) { continue; }
AddAmmoFromContainer(linkedItem.GetComponent<ItemContainer>());
}
void AddAmmoFromContainer(ItemContainer itemContainer)
{
if (itemContainer == null) { return; }
availableAmmo.AddRange(itemContainer.Inventory.AllItems);
//add empty slots too
for (int i = 0; i < itemContainer.Inventory.Capacity - itemContainer.Inventory.AllItems.Count(); i++)
{
availableAmmo.Add(null);
@@ -339,6 +339,19 @@ namespace Barotrauma
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
}
if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; }
if (item.Prefab.UnlockedRecipeInToolTip.Length > 0 && GameMain.GameSession is { } GameSession)
{
if (item.Prefab.UnlockedRecipeInToolTip.All(id => GameSession.HasUnlockedRecipe(Character.Controlled, id)))
{
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Green)}‖{TextManager.Get("unlockedrecipe.true")}‖color:end‖";
}
else
{
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Yellow)}‖{TextManager.Get("unlockedrecipe.false")}‖color:end‖";
}
}
if (item.Prefab.ContentPackage != GameMain.VanillaContent && item.Prefab.ContentPackage != null)
{
colorStr = XMLExtensions.ToStringHex(Color.MediumPurple);
@@ -356,19 +369,7 @@ namespace Barotrauma
}
#if DEBUG
toolTip += $" ({item.Prefab.Identifier})";
#endif
if (!item.Prefab.UnlockedRecipeInToolTip.IsEmpty && GameMain.GameSession is { } GameSession)
{
if (GameSession.HasUnlockedRecipe(Character.Controlled, item.Prefab.UnlockedRecipeInToolTip))
{
toolTip += TextManager.Get("unlockedrecipe.true");
}
else
{
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Yellow)}‖{TextManager.Get("unlockedrecipe.false")}‖color:end‖";
}
}
#endif
if (PlayerInput.KeyDown(InputType.ContextualCommand))
{
toolTip += $"\n‖color:gui.blue‖{TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders"))}‖color:end‖";
@@ -1300,8 +1301,7 @@ namespace Barotrauma
SubEditorScreen.StoreCommand(new InventoryPlaceCommand(DraggingItems.First().ParentInventory, new List<Item>(DraggingItems), true));
}
}
SoundPlayer.PlayUISound(GUISoundType.DropItem);
bool removed = false;
if (Screen.Selected is SubEditorScreen editor)
{
@@ -75,11 +75,11 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
// ReSharper restore FieldCanBeMadeReadOnly.Local
private const string SettingsResetButtonText = "LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings";
private const string SettingsResetPromptTitle = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title";
private const string SettingsResetPromptContents = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message";
private const string SettingsResetPromptYesText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes";
private const string SettingsResetPromptNoText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No";
private const string SettingsResetButtonText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetVisibleSettings";
private const string SettingsResetPromptTitle = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Title";
private const string SettingsResetPromptContents = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Message";
private const string SettingsResetPromptYesText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Yes";
private const string SettingsResetPromptNoText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.No";
private event Action OnApplyInstalledModsChanges;
@@ -100,7 +100,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
var menuTitleLayoutGroup = new GUILayoutGroup(
new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
GUIUtil.Label(menuTitleLayoutGroup,
GetLocalizedString("LuaCsForBarotrauma.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
GetLocalizedString($"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
GUIStyle.LargeFont, new Vector2(1f, 1f));
// page contents
@@ -46,10 +46,10 @@ public class SettingsMenuSystem : ISettingsMenuSystem
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
"LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
$"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton");
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
"SettingsMenuTab.Controls", $"{LuaCsSetup.PackageName}.SettingsMenu.ModControlsButton");
*/
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance);
@@ -8,7 +8,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
class BackgroundCreature : ISteerable
class BackgroundCreature : ISteerable, ILevelRenderableObject
{
const float MaxDepth = 10000.0f;
@@ -76,6 +76,8 @@ namespace Barotrauma
set;
}
public Vector3 Position => new Vector3(position.X, position.Y, Depth);
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
{
this.Prefab = prefab;
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -15,52 +14,11 @@ namespace Barotrauma
private float checkVisibleTimer;
private readonly List<BackgroundCreature> creatures = new List<BackgroundCreature>();
private readonly List<BackgroundCreature> creatures = [];
private readonly List<BackgroundCreature> visibleCreatures = new List<BackgroundCreature>();
private readonly List<BackgroundCreature> visibleCreatures = [];
public BackgroundCreatureManager()
{
/*foreach(var file in files)
{
LoadConfig(file.Path);
}*/
}
/*public BackgroundCreatureManager(string path)
{
DebugConsole.AddWarning($"Couldn't find any BackgroundCreaturePrefabs files, falling back to {path}");
LoadConfig(ContentPath.FromRaw(null, path));
}
private void LoadConfig(ContentPath configPath)
{
try
{
XDocument doc = XMLExtensions.TryLoadXml(configPath);
if (doc == null) { return; }
var mainElement = doc.Root.FromPackage(configPath.ContentPackage);
if (mainElement.IsOverride())
{
mainElement = mainElement.FirstElement();
Prefabs.Clear();
DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.MediumPurple);
}
else if (Prefabs.Any())
{
DebugConsole.NewMessage($"Loading additional background creatures from file '{configPath}'");
}
foreach (var element in mainElement.Elements())
{
Prefabs.Add(new BackgroundCreaturePrefab(element));
};
}
catch (Exception e)
{
DebugConsole.ThrowError(String.Format("Failed to load BackgroundCreatures from {0}", configPath), e);
}
}*/
public IEnumerable<BackgroundCreature> VisibleCreatures => visibleCreatures;
public void SpawnCreatures(Level level, int count, Vector2? position = null)
{
@@ -161,14 +119,6 @@ namespace Barotrauma
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature creature in visibleCreatures)
{
creature.Draw(spriteBatch, cam);
}
}
public void DrawLights(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature creature in visibleCreatures)
@@ -140,7 +140,7 @@ namespace Barotrauma
public void DrawFront(SpriteBatch spriteBatch, Camera cam)
{
renderer?.DrawForeground(spriteBatch, cam, LevelObjectManager);
renderer?.DrawForeground(spriteBatch, cam, backgroundCreatureManager, LevelObjectManager);
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
@@ -12,7 +12,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelObject
partial class LevelObject : ILevelRenderableObject
{
public float SwingTimer;
public float ScaleOscillateTimer;
@@ -8,13 +8,20 @@ using System.Linq;
namespace Barotrauma
{
public interface ILevelRenderableObject
{
public Vector3 Position { get; }
}
partial class LevelObjectManager
{
// Pre-initialized to the max size, so that we don't have to resize the lists at runtime. TODO: Could the capacity (of some collections?) be lower?
private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>(MaxVisibleObjects);
private readonly List<LevelObject> visibleObjectsMid = new List<LevelObject>(MaxVisibleObjects);
private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>(MaxVisibleObjects);
private readonly HashSet<LevelObject> allVisibleObjects = new HashSet<LevelObject>(MaxVisibleObjects);
private readonly List<ILevelRenderableObject> visibleObjectsBack = new List<ILevelRenderableObject>(MaxVisibleObjects);
private readonly List<ILevelRenderableObject> visibleObjectsMid = new List<ILevelRenderableObject>(MaxVisibleObjects);
private readonly List<ILevelRenderableObject> visibleObjectsFront = new List<ILevelRenderableObject>(MaxVisibleObjects);
private readonly HashSet<ILevelRenderableObject> allVisibleObjects = new HashSet<ILevelRenderableObject>(MaxVisibleObjects);
private double NextRefreshTime;
@@ -35,35 +42,44 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
foreach (LevelObject obj in visibleObjectsBack)
foreach (ILevelRenderableObject obj in visibleObjectsBack)
{
obj.Update(deltaTime, cam);
if (obj is LevelObject levelObj)
{
levelObj.Update(deltaTime, cam);
}
}
foreach (LevelObject obj in visibleObjectsMid)
foreach (ILevelRenderableObject obj in visibleObjectsMid)
{
obj.Update(deltaTime, cam);
if (obj is LevelObject levelObj)
{
levelObj.Update(deltaTime, cam);
}
}
foreach (LevelObject obj in visibleObjectsFront)
foreach (ILevelRenderableObject obj in visibleObjectsFront)
{
obj.Update(deltaTime, cam);
if (obj is LevelObject levelObj)
{
levelObj.Update(deltaTime, cam);
}
}
}
/// <summary>
/// Returns all visible objects, but not in order, because internally uses a HashSet.
/// </summary>
public IEnumerable<LevelObject> GetAllVisibleObjects()
public IEnumerable<ILevelRenderableObject> GetAllVisibleObjects()
{
allVisibleObjects.Clear();
foreach (LevelObject obj in visibleObjectsBack)
foreach (ILevelRenderableObject obj in visibleObjectsBack)
{
allVisibleObjects.Add(obj);
}
foreach (LevelObject obj in visibleObjectsMid)
foreach (ILevelRenderableObject obj in visibleObjectsMid)
{
allVisibleObjects.Add(obj);
}
foreach (LevelObject obj in visibleObjectsFront)
foreach (ILevelRenderableObject obj in visibleObjectsFront)
{
allVisibleObjects.Add(obj);
}
@@ -73,7 +89,7 @@ namespace Barotrauma
/// <summary>
/// Checks which level objects are in camera view and adds them to the visibleObjects lists
/// </summary>
private void RefreshVisibleObjects(Rectangle currentIndices, float zoom)
private void RefreshVisibleObjects(Rectangle currentIndices, BackgroundCreatureManager backgroundCreatureManager, float zoom)
{
visibleObjectsBack.Clear();
visibleObjectsMid.Clear();
@@ -152,6 +168,27 @@ namespace Barotrauma
}
}
foreach (var backgroundCreature in backgroundCreatureManager.VisibleCreatures)
{
int drawOrderIndex = 0;
for (int i = 0; i < visibleObjectsBack.Count; i++)
{
if (visibleObjectsBack[i].Position.Z > backgroundCreature.Position.Z)
{
break;
}
else
{
drawOrderIndex = i + 1;
if (drawOrderIndex >= MaxVisibleObjects) { break; }
}
}
if (drawOrderIndex >= 0 && drawOrderIndex < MaxVisibleObjects)
{
visibleObjectsBack.Insert(drawOrderIndex, backgroundCreature);
}
}
//object grid is sorted in an ascending order
//(so we prefer the objects in the foreground instead of ones in the background if some need to be culled)
//rendering needs to be done in a descending order though to get the background objects to be drawn first -> reverse the lists
@@ -165,28 +202,28 @@ namespace Barotrauma
/// <summary>
/// Draw the objects behind the level walls
/// </summary>
public void DrawObjectsBack(SpriteBatch spriteBatch, Camera cam)
public void DrawObjectsBack(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
{
DrawObjects(spriteBatch, cam, visibleObjectsBack);
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsBack);
}
/// <summary>
/// Draw the objects in front of the level walls, but behind characters
/// </summary>
public void DrawObjectsMid(SpriteBatch spriteBatch, Camera cam)
public void DrawObjectsMid(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
{
DrawObjects(spriteBatch, cam, visibleObjectsMid);
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsMid);
}
/// <summary>
/// Draw the objects in front of the level walls and characters
/// </summary>
public void DrawObjectsFront(SpriteBatch spriteBatch, Camera cam)
public void DrawObjectsFront(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
{
DrawObjects(spriteBatch, cam, visibleObjectsFront);
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsFront);
}
private void DrawObjects(SpriteBatch spriteBatch, Camera cam, List<LevelObject> objectList)
private void DrawObjects(SpriteBatch spriteBatch, Camera cam, BackgroundCreatureManager backgroundCreatureManager, List<ILevelRenderableObject> objectList)
{
Rectangle indices = Rectangle.Empty;
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
@@ -207,7 +244,7 @@ namespace Barotrauma
float z = 0.0f;
if (ForceRefreshVisibleObjects || (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime))
{
RefreshVisibleObjects(indices, cam.Zoom);
RefreshVisibleObjects(indices, backgroundCreatureManager, cam.Zoom);
ForceRefreshVisibleObjects = false;
if (cam.Zoom < 0.1f)
{
@@ -216,61 +253,93 @@ namespace Barotrauma
}
}
foreach (LevelObject obj in objectList)
bool prevObjectHasDeformableSprite = false;
foreach (ILevelRenderableObject obj2 in objectList)
{
Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter;
Vector2 camDiff = new Vector2(obj2.Position.X, obj2.Position.Y) - cam.WorldViewCenter;
camDiff.Y = -camDiff.Y;
Sprite activeSprite = obj.Sprite;
activeSprite?.Draw(
spriteBatch,
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength,
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / obj.Prefab.FadeOutDepth),
activeSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
SpriteEffects.None,
z);
if (obj.ActivePrefab.DeformableSprite != null)
bool hasDeformableSprite = false;
if (obj2 is LevelObject levelObject)
{
if (obj.CurrentSpriteDeformation != null)
hasDeformableSprite = levelObject.ActivePrefab.DeformableSprite != null;
if (hasDeformableSprite != prevObjectHasDeformableSprite)
{
obj.ActivePrefab.DeformableSprite.Deform(obj.CurrentSpriteDeformation);
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearWrap, DepthStencilState.DepthRead,
transformMatrix: cam.Transform);
}
else
Sprite activeSprite = levelObject.Sprite;
activeSprite?.Draw(
spriteBatch,
new Vector2(levelObject.Position.X, -levelObject.Position.Y) - camDiff * levelObject.Position.Z * ParallaxStrength,
Color.Lerp(levelObject.Prefab.SpriteColor, levelObject.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), levelObject.Position.Z / levelObject.Prefab.FadeOutDepth),
activeSprite.Origin,
levelObject.CurrentRotation,
levelObject.CurrentScale,
SpriteEffects.None,
z);
if (hasDeformableSprite)
{
obj.ActivePrefab.DeformableSprite.Reset();
}
obj.ActivePrefab.DeformableSprite?.Draw(cam,
new Vector3(new Vector2(obj.Position.X, obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength, z * 10.0f),
obj.ActivePrefab.DeformableSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 5000.0f));
}
if (GameMain.DebugDraw)
{
GUI.DrawRectangle(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(10.0f, 10.0f), GUIStyle.Red, true);
if (obj.Triggers == null) { continue; }
foreach (LevelTrigger trigger in obj.Triggers)
{
if (trigger.PhysicsBody == null) continue;
GUI.DrawLine(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), Color.Cyan, 0, 3);
Vector2 flowForce = trigger.GetWaterFlowVelocity();
if (flowForce.LengthSquared() > 1)
if (levelObject.CurrentSpriteDeformation != null)
{
flowForce.Y = -flowForce.Y;
GUI.DrawLine(spriteBatch, new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y) + flowForce * 10, GUIStyle.Orange, 0, 5);
levelObject.ActivePrefab.DeformableSprite.Deform(levelObject.CurrentSpriteDeformation);
}
trigger.PhysicsBody.UpdateDrawPosition();
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
else
{
levelObject.ActivePrefab.DeformableSprite.Reset();
}
levelObject.ActivePrefab.DeformableSprite?.Draw(cam,
new Vector3(new Vector2(levelObject.Position.X, levelObject.Position.Y) - camDiff * levelObject.Position.Z * ParallaxStrength, z * 10.0f),
levelObject.ActivePrefab.DeformableSprite.Origin,
levelObject.CurrentRotation,
levelObject.CurrentScale,
Color.Lerp(levelObject.Prefab.SpriteColor, levelObject.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), levelObject.Position.Z / 5000.0f));
}
prevObjectHasDeformableSprite = hasDeformableSprite;
if (GameMain.DebugDraw)
{
GUI.DrawRectangle(spriteBatch, new Vector2(levelObject.Position.X, -levelObject.Position.Y), new Vector2(10.0f, 10.0f), GUIStyle.Red, true);
if (levelObject.Triggers == null) { continue; }
foreach (LevelTrigger trigger in levelObject.Triggers)
{
if (trigger.PhysicsBody == null) continue;
GUI.DrawLine(spriteBatch, new Vector2(levelObject.Position.X, -levelObject.Position.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), Color.Cyan, 0, 3);
Vector2 flowForce = trigger.GetWaterFlowVelocity();
if (flowForce.LengthSquared() > 1)
{
flowForce.Y = -flowForce.Y;
GUI.DrawLine(spriteBatch, new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y) + flowForce * 10, GUIStyle.Orange, 0, 5);
}
trigger.PhysicsBody.UpdateDrawPosition();
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
}
}
}
else if (obj2 is BackgroundCreature backgroundCreature && cam.Zoom > 0.05f)
{
hasDeformableSprite = backgroundCreature.Prefab.DeformableSprite != null;
if (hasDeformableSprite != prevObjectHasDeformableSprite)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearWrap, DepthStencilState.DepthRead,
transformMatrix: cam.Transform);
}
backgroundCreature.Draw(spriteBatch, cam);
}
prevObjectHasDeformableSprite = hasDeformableSprite;
z += 0.0001f;
}
@@ -214,8 +214,9 @@ namespace Barotrauma
//calculate the sum of the forces of nearby level triggers
//and use it to move the water texture and water distortion effect
Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
foreach (LevelObject levelObject in level.LevelObjectManager.GetAllVisibleObjects())
foreach (ILevelRenderableObject obj in level.LevelObjectManager.GetAllVisibleObjects())
{
if (obj is not LevelObject levelObject) { continue; }
if (levelObject.Triggers == null) { continue; }
//use the largest water flow velocity of all the triggers
Vector2 objectMaxFlow = Vector2.Zero;
@@ -274,11 +275,7 @@ namespace Barotrauma
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
cam.Transform);
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, cam);
if (cam.Zoom > 0.05f)
{
backgroundCreatureManager?.Draw(spriteBatch, cam);
}
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, backgroundCreatureManager, cam);
level.GenerationParams.DrawWaterParticles(spriteBatch, cam, waterParticleOffset);
@@ -292,17 +289,18 @@ namespace Barotrauma
BlendState.NonPremultiplied,
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
cam.Transform);
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, cam);
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, backgroundCreatureManager, cam);
spriteBatch.End();
}
public void DrawForeground(SpriteBatch spriteBatch, Camera cam, LevelObjectManager backgroundSpriteManager = null)
public void DrawForeground(SpriteBatch spriteBatch, Camera cam,
BackgroundCreatureManager backgroundCreatureManager, LevelObjectManager backgroundSpriteManager = null)
{
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
cam.Transform);
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, cam);
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, backgroundCreatureManager, cam);
spriteBatch.End();
}
@@ -2547,7 +2547,7 @@ namespace Barotrauma.Networking
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(SpoofEntityManagerReceivedId ? (ushort)1 : EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(LastClientListUpdateID);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
@@ -3370,6 +3370,12 @@ namespace Barotrauma.Networking
{
get { return votingInterface; }
}
/// <summary>
/// Forces the client to report the last received event ID as always being 1, making the server believe the client is always behind.
/// </summary>
public bool SpoofEntityManagerReceivedId { get; set; }
private VotingInterface votingInterface;
public bool TypingChatMessage(GUITextBox textBox, string text)
@@ -89,6 +89,7 @@ namespace Barotrauma.Networking
{
byte queueId = msg.ReadByte();
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
bool isRadio = msg.ReadBoolean();
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
if (queue == null)
@@ -117,19 +118,21 @@ namespace Barotrauma.Networking
float rangeMultiplier = spectating ? 2.0f : 1.0f;
WifiComponent senderRadio = null;
var messageType =
!client.VoipQueue.ForceLocal &&
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
(spectating || (ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && senderRadio.CanReceive(recipientRadio)))
? ChatMessageType.Radio : ChatMessageType.Default;
var messageType = isRadio ? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
client.RadioNoise = 0.0f;
if (messageType == ChatMessageType.Radio)
{
//If the client cannot establish a radio, use a headsets default range as a fallback to calculate the radio noise.
//This cannot happen in an un-modded setting as CanUseRadio is part of the server side check for isRadio to be true.
ChatMessage.CanUseRadio(client.Character, out senderRadio);
float senderRadioRange = (senderRadio == null) ? 35000.0f : senderRadio.Range;
client.VoipSound.UsingRadio = true;
client.VoipSound.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier);
client.VoipSound.SetRange(senderRadioRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadioRange * speechImpedimentMultiplier * rangeMultiplier);
if (distanceFactor > RangeNear && !spectating)
{
//noise starts increasing exponentially after 40% range
@@ -533,6 +533,18 @@ namespace Barotrauma
return true;
}
};
new GUITickBox(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 280) },
"Lenient server startup timeouts")
{
Selected = NetConfig.UseLenientHandshake,
ToolTip = "Start with more lenient Lidgren handshake timeouts. The server is more likely to start even when running multiple instances on the same machine under heavy load.",
OnSelected = (tickBox) =>
{
NetConfig.UseLenientHandshake = tickBox.Selected;
return true;
}
};
#endif
var minButtonSize = new Point(120, 20);
@@ -1126,6 +1138,11 @@ namespace Barotrauma
}
#endif
if (NetConfig.UseLenientHandshake)
{
arguments.Add("-lenienthandshake");
}
var processInfo = new ProcessStartInfo
{
FileName = fileName,
@@ -1,19 +1,22 @@
using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components;
using Barotrauma.Sounds;
using Barotrauma.Steam;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Steamworks;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using Barotrauma.LuaCs.Events;
using Barotrauma.Sounds;
namespace Barotrauma
{
@@ -2044,6 +2047,8 @@ namespace Barotrauma
private bool SaveSubToFile(string name, ContentPackage packageToSaveTo)
{
bool remoteStorageWasEnabled = Submarine.MainSub.Info.SaveToRemoteStorage;
Type subFileType = DetermineSubFileType(MainSub?.Info.Type ?? SubmarineType.Player);
static string getExistingFilePath(ContentPackage package, string fileName)
@@ -2272,6 +2277,16 @@ namespace Barotrauma
linkedSubBox.AddItem(sub.Name, sub);
}
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
if (remoteStorageWasEnabled)
{
Submarine.MainSub.Info.SaveToRemoteStorage = true;
RemoteStorageHelper.TryWrite(
localPath: MainSub.Info.FilePath,
saveAs: MainSub.Info.FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false),
allowOverwrite: true);
}
}
}
}
@@ -3223,6 +3238,42 @@ namespace Barotrauma
previewImageButtonHolder.RectTransform.MinSize = new Point(0, previewImageButtonHolder.RectTransform.Children.Max(c => c.MinSize.Y));
if (SteamManager.IsInitialized)
{
GUILayoutGroup remoteStorageArea = new(
new RectTransform(new Vector2(1f, 0.05f), rightColumn.RectTransform,
minSize: new Point(0, minHeight)),
isHorizontal: true,
childAnchor: Anchor.CenterLeft)
{
Stretch = true,
AbsoluteSpacing = 5
};
new GUITextBlock(new RectTransform(Vector2.One, remoteStorageArea.RectTransform),
TextManager.Get("RemoteStorageToggle.Title"), textAlignment: Alignment.CenterLeft, wrap: true);
new GUITickBox(new RectTransform(Vector2.One, remoteStorageArea.RectTransform), label: "")
{
OnAddedToGUIUpdateList = component =>
{
// Values may change outside of game.
component.Enabled = SteamRemoteStorage.IsCloudEnabledForAccount;
component.ToolTip = !SteamRemoteStorage.IsCloudEnabledForAccount ? TextManager.Get("RemoteStorageToggle.Disabled") : "";
((GUITickBox)component).SetSelected(SteamRemoteStorage.IsCloudEnabled && MainSub.Info.SaveToRemoteStorage, callOnSelected: false);
},
OnSelected = tickBox =>
{
if (tickBox.Selected && !SteamRemoteStorage.IsCloudEnabledForApp)
{
RemoteStorageHelper.AskToEnable(onAccepted: () => MainSub.Info.SaveToRemoteStorage = true);
return false;
}
return MainSub.Info.SaveToRemoteStorage = tickBox.Selected;
}
};
}
var contentPackageTabber = new GUILayoutGroup(new RectTransform((1.0f, 0.075f), rightColumn.RectTransform), isHorizontal: true);
GUIButton createTabberBtn(string labelTag)
@@ -3746,7 +3797,7 @@ namespace Barotrauma
}
var package = GetLocalPackageThatOwnsSub(subInfo);
if (package != null)
if (package != null || subInfo.IsFromRemoteStorage)
{
deleteBtn.Enabled = true;
}
@@ -3771,13 +3822,29 @@ namespace Barotrauma
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = sender.Text.IsNullOrEmpty(); };
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
var sortedSubs = GetLoadableSubs()
.OrderBy(s => s.Type)
.ThenBy(s => s.Name)
.ToList();
List<SubmarineInfo> allSubs = [.. SubmarineInfo.SavedSubmarines];
foreach (SteamRemoteStorage.RemoteFile remoteFile in SteamRemoteStorage.Files.Where(file => file.Filename.EndsWith(".sub")))
{
if (!remoteFile.TryRead(out byte[] bytes)) { continue; }
using System.IO.MemoryStream stream = new(bytes);
using GZipStream zipStream = new(stream, CompressionMode.Decompress);
if (XMLExtensions.TryLoadXml(zipStream) is not XDocument doc)
{
DebugConsole.ThrowError($"{RemoteStorageHelper.DebugPrefix} Failed to load submarine \"{remoteFile.Filename}\" from remote storage: file is not a valid XML document.");
continue;
}
SubmarineInfo subInfo = new(remoteFile.Filename, element: doc.Root, tryLoad: false) { IsFromRemoteStorage = true };
allSubs.Add(subInfo);
}
IOrderedEnumerable<SubmarineInfo> sortedSubs = allSubs
.OrderBy(kvp => kvp.Type)
.ThenBy(kvp => kvp.Name)
.ThenBy(kvp => kvp.IsFromRemoteStorage);
SubmarineInfo prevSub = null;
foreach (SubmarineInfo sub in sortedSubs)
{
if (prevSub == null || prevSub.Type != sub.Type)
@@ -3795,35 +3862,44 @@ namespace Barotrauma
prevSub = sub;
}
string pathWithoutUserName = Path.GetFullPath(sub.FilePath);
string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
if (pathWithoutUserName.StartsWith(saveFolder))
string displayPath = sub.FilePath;
if (sub.IsFromRemoteStorage)
{
pathWithoutUserName = "..." + pathWithoutUserName[saveFolder.Length..];
displayPath += $" {TextManager.Get("RemoteStorage")}";
}
else
{
pathWithoutUserName = sub.FilePath;
string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
string fullPath = Path.GetFullPath(displayPath);
if (fullPath.StartsWith(saveFolder))
{
displayPath = $"...{fullPath[saveFolder.Length..]}";
}
}
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80))
LocalizedString limitedName = ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80);
GUITextBlock textBlock = new(new RectTransform(Vector2.UnitX, subList.Content.RectTransform) { MinSize = new Point(0, 30) }, limitedName)
{
UserData = sub,
ToolTip = pathWithoutUserName
ToolTip = displayPath
};
if (!(ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == sub.FilePath) ?? false))
if (sub.IsFromRemoteStorage)
{
// remote storage
textBlock.OverrideTextColor(RemoteStorageHelper.SteamColor);
}
else if (ContentPackageManager.VanillaCorePackage == null || ContentPackageManager.VanillaCorePackage.Files.None(f => f.Path == sub.FilePath))
{
if (GetLocalPackageThatOwnsSub(sub) == null &&
ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage)
{
//workshop mod
// workshop mod
textBlock.OverrideTextColor(Color.MediumPurple);
}
else
{
//local mod
// local mod
textBlock.OverrideTextColor(GUIStyle.TextColorBright);
}
}
@@ -4018,10 +4094,9 @@ namespace Barotrauma
return false;
}
if (!(subList.SelectedComponent?.UserData is SubmarineInfo selectedSubInfo)) { return false; }
if (subList.SelectedComponent?.UserData is not SubmarineInfo selectedSubInfo) { return false; }
var ownerPackage = GetLocalPackageThatOwnsSub(selectedSubInfo);
if (ownerPackage is null)
if (!selectedSubInfo.IsFromRemoteStorage && GetLocalPackageThatOwnsSub(selectedSubInfo) is null)
{
if (IsVanillaSub(selectedSubInfo))
{
@@ -4183,21 +4258,23 @@ namespace Barotrauma
{
if (sub == null) { return; }
//If the sub is included in a content package that only defines that one sub,
//check that it's a local content package and only allow deletion if it is.
//(deleting from the Submarines folder is also currently allowed, but this is temporary)
var subPackage = GetLocalPackageThatOwnsSub(sub);
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { return; }
var msgBox = new GUIMessageBox(
TextManager.Get("DeleteDialogLabel"),
TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
// If the sub is included in a content package that only defines that one sub,
// check that it's a local content package and only allow deletion if it is.
// (deleting from the Submarines folder is also currently allowed, but this is temporary)
ContentPackage subPackage = GetLocalPackageThatOwnsSub(sub);
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { subPackage = null; }
if (!sub.IsFromRemoteStorage && subPackage == null) { return; }
GUIMessageBox msgBox = new(TextManager.Get("DeleteDialogLabel"), TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name), [TextManager.Get("Yes"), TextManager.Get("Cancel")]);
msgBox.Buttons[0].OnClicked += (btn, userData) =>
{
try
if (sub.IsFromRemoteStorage)
{
if (subPackage != null)
RemoteStorageHelper.TryDelete(sub.FilePath);
}
else if (subPackage != null)
{
try
{
File.Delete(sub.FilePath, catchUnauthorizedAccessExceptions: false);
ModProject modProject = new ModProject(subPackage);
@@ -4208,17 +4285,17 @@ namespace Barotrauma
{
MainSub.Info.FilePath = null;
}
}
sub.Dispose();
CreateLoadScreen();
}
catch (Exception e)
{
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
}
}
catch (Exception e)
{
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
}
return true;
sub.Dispose();
CreateLoadScreen();
return msgBox.Close(btn, userData);
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
@@ -445,7 +445,7 @@ namespace Barotrauma
{
DebugConsole.NewMessage("Missing Localization for property: " + propertyTag);
MissingLocalizations.Add($"sp.{propertyTag}.name|{displayName}");
MissingLocalizations.Add($"sp.{propertyTag}.description|{property.GetAttribute<Serialize>().Description}");
MissingLocalizations.Add($"sp.{propertyTag}.description|{property.GetAttribute<Serialize>()?.Description}");
}
}
#endif
@@ -467,7 +467,7 @@ namespace Barotrauma
}
if (toolTip.IsNullOrEmpty())
{
toolTip = property.GetAttribute<Serialize>().Description;
toolTip = property.GetAttribute<Serialize>()?.Description;
}
GUIComponent propertyField = null;
@@ -0,0 +1,34 @@
#nullable enable
using Steamworks;
using System;
namespace Barotrauma.Steam;
internal static partial class RemoteStorageHelper
{
/// <summary>
/// Asks the user if they wish to enable remote storage. Accepting enables it automatically.
/// </summary>
/// <param name="onAccepted">Invoked when the user accepts enabling remote storage.</param>
/// <param name="onRejected">Invoked when the user rejects enabling remote storage.</param>
/// <remarks>Closes automatically if remote storage was enabled outside of the game, or if remote storage can not be enabled.</remarks>
public static void AskToEnable(Action? onAccepted = null, Action? onRejected = null)
{
GUIMessageBox confirmBox = new GUIMessageBox(
TextManager.Get("RemoteStorageEnablePopup.Header"),
TextManager.Get("RemoteStorageEnablePopup.Text"),
[TextManager.Get("Yes"), TextManager.Get("No")],
autoCloseCondition: () => !SteamRemoteStorage.IsCloudEnabledForAccount || SteamRemoteStorage.IsCloudEnabledForApp);
confirmBox.Buttons[0].OnClicked += (btn, data) =>
{
SteamRemoteStorage.IsCloudEnabledForApp = true;
onAccepted?.Invoke();
return confirmBox.Close(btn, data);
};
confirmBox.Buttons[1].OnClicked += (btn, data) =>
{
onRejected?.Invoke();
return confirmBox.Close(btn, data);
};
}
}