Build 0.18.4.0

This commit is contained in:
Markus Isberg
2022-05-31 23:13:05 +09:00
parent 077917fa5d
commit 64db1a6a44
175 changed files with 4916 additions and 2393 deletions
@@ -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)
@@ -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;
} }
}; };
@@ -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)
@@ -107,6 +107,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 +151,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 +180,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))
@@ -357,10 +360,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) =>
@@ -178,7 +178,14 @@ 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);
}
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
@@ -2384,7 +2386,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)
@@ -2399,11 +2401,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); }
}); });
} }
} }
@@ -2416,7 +2418,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: () =>
@@ -159,7 +159,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 +249,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))
@@ -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)
@@ -183,7 +183,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:
@@ -182,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)
@@ -228,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;
@@ -249,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;
@@ -423,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;
@@ -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)
@@ -363,6 +365,10 @@ namespace Barotrauma
selected = true; selected = true;
GUI.KeyboardDispatcher.Subscriber = this; GUI.KeyboardDispatcher.Subscriber = this;
OnSelected?.Invoke(this, Keys.None); OnSelected?.Invoke(this, Keys.None);
if (PlaySoundOnSelect)
{
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)
@@ -122,7 +122,7 @@ 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 = (int)(BottomRightInfoArea.Width * 1.3f);
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;
@@ -684,6 +686,7 @@ namespace Barotrauma
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;
@@ -766,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
}; };
@@ -887,7 +891,10 @@ namespace Barotrauma
GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont); 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);
@@ -390,7 +390,7 @@ namespace Barotrauma
ToolTip = TextManager.Get("campaignstore.reputationtooltip") ToolTip = TextManager.Get("campaignstore.reputationtooltip")
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform),
TextManager.Get("reputation"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft) TextManager.Get("reputationmodifier"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
CanBeFocused = false, CanBeFocused = false,
@@ -656,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) =>
@@ -1567,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;
} }
@@ -1622,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",
@@ -1633,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",
@@ -2076,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) =>
{ {
@@ -2088,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;
@@ -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)
{ {
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);
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");
}
} }
} }
@@ -360,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 || (GameMain.Client?.CharacterInfo != null && GameMain.GameSession?.GameMode is MultiPlayerCampaign); 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);
@@ -453,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();
@@ -928,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);
} }
} }
@@ -1914,6 +1915,7 @@ namespace Barotrauma
{ {
OnClicked = (button, o) => OnClicked = (button, o) =>
{ {
GameMain.Client?.SendCharacterInfo();
characterSettingsFrame!.Visible = false; characterSettingsFrame!.Visible = false;
talentFrameMain.Visible = true; talentFrameMain.Visible = true;
return true; return true;
@@ -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);
} }
@@ -943,6 +943,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>
@@ -950,7 +967,9 @@ 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)
@@ -1043,7 +1062,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();
@@ -1608,7 +1608,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)
@@ -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;
campaign.PendingSaveID = saveID;
}
if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID))
{
campaign.SuppressStateSending = true;
campaign.IsFirstRound = isFirstRound;
//we need to have the latest save file to display location/mission/store if (requiredFlags.HasFlag(NetFlags.Misc))
if (campaign.LastSaveID == 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 (requiredFlags.HasFlag(NetFlags.MapAndMissions))
{
DebugConsole.Log("Received campaign update (MapAndMissions)");
UInt16 id = msg.ReadUInt16();
bool forceMapUI = msg.ReadBoolean();
bool allowDebugTeleport = msg.ReadBoolean();
UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16();
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));
}
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,268 @@ 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 (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)
{
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)
@@ -58,12 +58,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 +79,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,7 +87,7 @@ 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":
@@ -95,7 +95,7 @@ namespace Barotrauma
ActiveOrdersElement = subElement.GetChildElement("activeorders"); 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);
@@ -163,21 +163,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()
{ {
@@ -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:
@@ -533,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 =>
@@ -653,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 +665,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)
{ {
@@ -942,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 &&
@@ -967,8 +800,9 @@ namespace Barotrauma
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;
} }
@@ -1131,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++)
@@ -1197,11 +1038,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;
@@ -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)
@@ -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)
@@ -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;
} }
} }
@@ -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);
} }
@@ -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++)
@@ -418,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; }
@@ -100,7 +100,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
}; };
@@ -569,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)
@@ -940,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; }
@@ -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);
} }
} }
} }
@@ -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);
} }
@@ -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);
@@ -689,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;
@@ -824,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);
@@ -843,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);
@@ -2401,7 +2405,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);
} }
@@ -2446,7 +2453,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);
} }
@@ -2644,7 +2654,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);
@@ -2674,9 +2684,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; }
@@ -2775,6 +2788,15 @@ namespace Barotrauma.Networking
GameMain.GameSession = null; GameMain.GameSession = null;
} }
public void SendCharacterInfo()
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
WriteCharacterInfo(msg);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void WriteCharacterInfo(IWriteMessage msg) public void WriteCharacterInfo(IWriteMessage msg)
{ {
msg.Write(characterInfo == null); msg.Write(characterInfo == null);
@@ -2824,18 +2846,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
@@ -3014,7 +3036,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);
} }
@@ -111,7 +111,7 @@ namespace Barotrauma.Networking
timeout = Screen.Selected == GameMain.GameScreen ? timeout = Screen.Selected == GameMain.GameScreen ?
NetworkConnection.TimeoutThresholdInGame : NetworkConnection.TimeoutThresholdInGame :
NetworkConnection.TimeoutThreshold; NetworkConnection.TimeoutThreshold;
PacketHeader packetHeader = (PacketHeader)data[0]; PacketHeader packetHeader = (PacketHeader)data[0];
if (!packetHeader.IsServerMessage()) { return; } if (!packetHeader.IsServerMessage()) { return; }
@@ -11,7 +11,13 @@ namespace Barotrauma.Networking
private bool isActive; private bool isActive;
private readonly UInt64 selfSteamID; private readonly UInt64 selfSteamID;
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey);
private UInt64 ReadSteamId(IReadMessage inc)
=> inc.ReadUInt64() ^ ownerKey64;
private void WriteSteamId(IWriteMessage msg, UInt64 val)
=> msg.Write(val ^ ownerKey64);
private long sentBytes, receivedBytes; private long sentBytes, receivedBytes;
class RemotePeer class RemotePeer
@@ -58,6 +64,8 @@ namespace Barotrauma.Networking
{ {
if (isActive) { return; } if (isActive) { return; }
this.ownerKey = ownerKey;
initializationStep = ConnectionInitialization.SteamTicketAndVersion; initializationStep = ConnectionInitialization.SteamTicketAndVersion;
ServerConnection = new PipeConnection(selfSteamID); ServerConnection = new PipeConnection(selfSteamID);
@@ -103,7 +111,7 @@ namespace Barotrauma.Networking
//known now //known now
int prevBitPosition = msg.Message.BitPosition; int prevBitPosition = msg.Message.BitPosition;
msg.Message.BitPosition = sizeof(ulong) * 8; msg.Message.BitPosition = sizeof(ulong) * 8;
msg.Message.Write(ownerID); WriteSteamId(msg.Message, ownerID);
msg.Message.BitPosition = prevBitPosition; msg.Message.BitPosition = prevBitPosition;
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone(); byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
Array.Resize(ref msgToSend, msg.Message.LengthBytes); Array.Resize(ref msgToSend, msg.Message.LengthBytes);
@@ -141,8 +149,8 @@ namespace Barotrauma.Networking
} }
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(steamId); WriteSteamId(outMsg, steamId);
outMsg.Write(remotePeer.OwnerSteamID); WriteSteamId(outMsg, remotePeer.OwnerSteamID);
outMsg.Write(data, 1, dataLength - 1); outMsg.Write(data, 1, dataLength - 1);
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0]; DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
@@ -232,7 +240,7 @@ namespace Barotrauma.Networking
{ {
if (!isActive) { return; } if (!isActive) { return; }
UInt64 recipientSteamId = inc.ReadUInt64(); UInt64 recipientSteamId = ReadSteamId(inc);
DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte(); DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte();
int p2pDataStart = inc.BytePosition; int p2pDataStart = inc.BytePosition;
@@ -343,8 +351,8 @@ namespace Barotrauma.Networking
if (packetHeader.IsConnectionInitializationStep()) if (packetHeader.IsConnectionInitializationStep())
{ {
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(selfSteamID); WriteSteamId(outMsg, selfSteamID);
outMsg.Write(selfSteamID); WriteSteamId(outMsg, selfSteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep)); outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep));
outMsg.Write(Name); outMsg.Write(Name);
@@ -436,8 +444,8 @@ namespace Barotrauma.Networking
IWriteMessage msgToSend = new WriteOnlyMessage(); IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = new byte[msg.LengthBytes]; byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
msgToSend.Write(selfSteamID); WriteSteamId(msgToSend, selfSteamID);
msgToSend.Write(selfSteamID); WriteSteamId(msgToSend, selfSteamID);
msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)); msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
msgToSend.Write((UInt16)length); msgToSend.Write((UInt16)length);
msgToSend.Write(msgData, 0, length); msgToSend.Write(msgData, 0, length);
@@ -7,6 +7,20 @@ namespace Barotrauma
{ {
partial class Voting partial class Voting
{ {
private struct SubmarineVoteInfo
{
public SubmarineInfo SubmarineInfo { get; set; }
public bool TransferItems { get; set; }
public int DeliveryFee { get; set; }
public SubmarineVoteInfo(SubmarineInfo submarineInfo, bool transferItems, int deliveryFee)
{
SubmarineInfo = submarineInfo;
TransferItems = transferItems;
DeliveryFee = deliveryFee;
}
}
private readonly Dictionary<VoteType, int> private readonly Dictionary<VoteType, int>
voteCountYes = new Dictionary<VoteType, int>(), voteCountYes = new Dictionary<VoteType, int>(),
voteCountNo = new Dictionary<VoteType, int>(), voteCountNo = new Dictionary<VoteType, int>(),
@@ -131,14 +145,16 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
if (data is SubmarineInfo voteSub) if (data is (SubmarineInfo voteSub, bool transferItems))
{ {
//initiate sub vote //initiate sub vote
msg.Write(true); msg.Write(true);
msg.Write(voteSub.Name); msg.Write(voteSub.Name);
msg.Write(transferItems);
} }
else else
{ {
// vote
if (!(data is int)) { return; } if (!(data is int)) { return; }
msg.Write(false); msg.Write(false);
msg.Write((int)data); msg.Write((int)data);
@@ -246,7 +262,7 @@ namespace Barotrauma
float timeOut = inc.ReadByte(); float timeOut = inc.ReadByte();
Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID); Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID);
if (!myClient.InGame) { return; } if (myClient == null || !myClient.InGame) { return; }
switch (voteType) switch (voteType)
{ {
@@ -254,13 +270,14 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
string subName1 = inc.ReadString(); string subName1 = inc.ReadString();
bool transferItems = inc.ReadBoolean();
SubmarineInfo info = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName1); SubmarineInfo info = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName1);
if (info == null) if (info == null)
{ {
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted");
return; return;
} }
GameMain.Client.ShowSubmarineChangeVoteInterface(starterClient, info, voteType, timeOut); GameMain.Client.ShowSubmarineChangeVoteInterface(starterClient, info, voteType, transferItems, timeOut);
break; break;
case VoteType.TransferMoney: case VoteType.TransferMoney:
byte fromClientId = inc.ReadByte(); byte fromClientId = inc.ReadByte();
@@ -279,39 +296,40 @@ namespace Barotrauma
case VoteState.Passed: case VoteState.Passed:
case VoteState.Failed: case VoteState.Failed:
bool passed = inc.ReadBoolean(); bool passed = inc.ReadBoolean();
SubmarineVoteInfo submarineVoteInfo = default;
SubmarineInfo subInfo = null;
switch (voteType) switch (voteType)
{ {
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
string subName2 = inc.ReadString(); string subName2 = inc.ReadString();
subInfo = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2); var submarineInfo = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2);
if (subInfo == null) bool transferItems = inc.ReadBoolean();
int deliveryFee = inc.ReadInt16();
if (submarineInfo == null)
{ {
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted");
return; return;
} }
submarineVoteInfo = new SubmarineVoteInfo(submarineInfo, transferItems, deliveryFee);
break; break;
} }
GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount); GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount);
if (passed && subInfo != null) if (passed && submarineVoteInfo.SubmarineInfo is { } subInfo)
{ {
int deliveryFee = inc.ReadInt16();
switch (voteType) switch (voteType)
{ {
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
GameMain.GameSession.PurchaseSubmarine(subInfo); GameMain.GameSession.PurchaseSubmarine(subInfo);
GameMain.GameSession.SwitchSubmarine(subInfo, 0); GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems, 0);
break; break;
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
GameMain.GameSession.PurchaseSubmarine(subInfo); GameMain.GameSession.PurchaseSubmarine(subInfo);
break; break;
case VoteType.SwitchSub: case VoteType.SwitchSub:
GameMain.GameSession.SwitchSubmarine(subInfo, deliveryFee); GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems, submarineVoteInfo.DeliveryFee);
break; break;
} }
@@ -1,8 +1,11 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using Barotrauma.IO;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -15,11 +18,11 @@ namespace Barotrauma
protected GUITextBox saveNameBox, seedBox; protected GUITextBox saveNameBox, seedBox;
protected GUIButton loadGameButton; protected GUIButton loadGameButton;
public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame; public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
public Action<string> LoadGame; public Action<string> LoadGame;
protected enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }; protected enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }
protected CategoryFilter subFilter = CategoryFilter.All; protected CategoryFilter subFilter = CategoryFilter.All;
public GUIButton StartButton public GUIButton StartButton
@@ -33,15 +36,11 @@ namespace Barotrauma
get; get;
protected set; protected set;
} }
public GUITickBox EnableRadiationToggle { get; set; }
public GUILayoutGroup CampaignSettingsContent { get; set; }
public CampaignSettings CurrentSettings = new CampaignSettings(element: null);
public GUIButton CampaignCustomizeButton { get; set; } public GUIButton CampaignCustomizeButton { get; set; }
public GUIMessageBox CampaignCustomizeSettings { get; set; } public GUIMessageBox CampaignCustomizeSettings { get; set; }
public GUITextBlock MaxMissionCountText;
public CampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer) public CampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer)
{ {
this.newGameContainer = newGameContainer; this.newGameContainer = newGameContainer;
@@ -102,5 +101,259 @@ namespace Barotrauma
return saveFrame; return saveFrame;
} }
public struct CampaignSettingElements
{
public SettingValue<bool> RadiationEnabled;
public SettingValue<int> MaxMissionCount;
public SettingValue<StartingBalanceAmount> StartingFunds;
public SettingValue<GameDifficulty> Difficulty;
public SettingValue<Identifier> StartItemSet;
public CampaignSettings CreateSettings()
{
return new CampaignSettings(element: null)
{
RadiationEnabled = RadiationEnabled.GetValue(),
MaxMissionCount = MaxMissionCount.GetValue(),
StartingBalanceAmount = StartingFunds.GetValue(),
Difficulty = Difficulty.GetValue(),
StartItemSet = StartItemSet.GetValue()
};
}
}
public readonly struct SettingValue<T>
{
private readonly Func<T> getter;
private readonly Action<T> setter;
public T GetValue()
{
return getter.Invoke();
}
public void SetValue(T value)
{
setter.Invoke(value);
}
public SettingValue(Func<T> get, Action<T> set)
{
getter = get;
setter = set;
}
}
private readonly struct SettingCarouselElement<T>
{
public readonly LocalizedString Label;
public readonly T Value;
public readonly bool IsHidden;
public SettingCarouselElement(T value, string label, bool isHidden = false)
{
Value = value;
Label = TextManager.Get(label).Fallback(label);
IsHidden = isHidden;
}
}
protected static CampaignSettingElements CreateCampaignSettingList(GUIComponent parent, CampaignSettings prevSettings)
{
const float verticalSize = 0.14f;
GUILayoutGroup presetDropdownLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, verticalSize), parent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform), TextManager.Get("campaignsettingpreset"));
GUIDropDown presetDropdown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform), elementCount: CampaignModePresets.List.Length);
presetDropdownLayout.RectTransform.MinSize = new Point(0, presetDropdown.Rect.Height);
foreach (CampaignSettings settings in CampaignModePresets.List)
{
string name = settings.PresetName;
presetDropdown.AddItem(TextManager.Get($"preset.{name}").Fallback(name), settings);
}
GUIListBox settingsList = new GUIListBox(new RectTransform(new Vector2(1f, 1f - verticalSize), parent.RectTransform))
{
Spacing = GUI.IntScale(5)
};
SettingValue<bool> radiationEnabled = CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableRadiation"), TextManager.Get("campaignoption.enableradiation.tooltip"), prevSettings.RadiationEnabled, verticalSize);
ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions = StartItemSet.Sets.OrderBy(s => s.Order).Select(set => new SettingCarouselElement<Identifier>(set.Identifier, $"startitemset.{set.Identifier}")).ToImmutableArray();
SettingCarouselElement<Identifier> prevStartingSet = startingSetOptions.FirstOrNull(element => element.Value == prevSettings.StartItemSet) ?? startingSetOptions[1];
SettingValue<Identifier> startingSetInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startitemset"), TextManager.Get("startitemsettooltip"), prevStartingSet, verticalSize, startingSetOptions);
ImmutableArray<SettingCarouselElement<StartingBalanceAmount>> fundOptions = ImmutableArray.Create(
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.High, "startingfunds.high"),
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.Medium, "startingfunds.medium"),
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.Low, "startingfunds.low")
);
SettingCarouselElement<StartingBalanceAmount> prevStartingFund = fundOptions.FirstOrNull(element => element.Value == prevSettings.StartingBalanceAmount) ?? fundOptions[1];
SettingValue<StartingBalanceAmount> startingFundsInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startingfundsdescription"), TextManager.Get("startingfundstooltip"), prevStartingFund, verticalSize, fundOptions);
ImmutableArray<SettingCarouselElement<GameDifficulty>> difficultyOptions = ImmutableArray.Create(
new SettingCarouselElement<GameDifficulty>(GameDifficulty.Easy, "difficulty.easy"),
new SettingCarouselElement<GameDifficulty>(GameDifficulty.Medium, "difficulty.medium"),
new SettingCarouselElement<GameDifficulty>(GameDifficulty.Hard, "difficulty.hard"),
new SettingCarouselElement<GameDifficulty>(GameDifficulty.Hellish, "difficulty.hellish", isHidden: true)
);
SettingCarouselElement<GameDifficulty> prevDifficulty = difficultyOptions.FirstOrNull(element => element.Value == prevSettings.Difficulty) ?? difficultyOptions[1];
SettingValue<GameDifficulty> difficultyInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("leveldifficulty"), TextManager.Get("leveldifficultyexplanation"), prevDifficulty, verticalSize, difficultyOptions);
SettingValue<int> maxMissionCountInput = CreateGUINumberInputCarousel(settingsList.Content, TextManager.Get("maxmissioncount"), TextManager.Get("maxmissioncounttooltip"), prevSettings.MaxMissionCount, valueStep: 1, verticalSize);
presetDropdown.OnSelected = (selected, o) =>
{
if (o is CampaignSettings settings)
{
radiationEnabled.SetValue(settings.RadiationEnabled);
maxMissionCountInput.SetValue(settings.MaxMissionCount);
startingFundsInput.SetValue(settings.StartingBalanceAmount);
difficultyInput.SetValue(settings.Difficulty);
startingSetInput.SetValue(settings.StartItemSet);
return true;
}
return false;
};
return new CampaignSettingElements
{
RadiationEnabled = radiationEnabled,
MaxMissionCount = maxMissionCountInput,
StartingFunds = startingFundsInput,
Difficulty = difficultyInput,
StartItemSet = startingSetInput
};
// Create a number input with plus and minus buttons because for some reason the default GUINumberInput buttons don't work when in a GUIMessageBox
static SettingValue<int> CreateGUINumberInputCarousel(GUIComponent parent, LocalizedString description, LocalizedString tooltip, int defaultValue, int valueStep, float verticalSize)
{
GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, horizontalSize: 0.55f, verticalSize: verticalSize);
GUIButton minusButton = new GUIButton(new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center)
{
ClickSound = GUISoundType.Decrease,
UserData = -valueStep
};
GUINumberInput numberInput = new GUINumberInput(new RectTransform(Vector2.One, inputContainer.RectTransform, Anchor.Center), NumberType.Int, textAlignment: Alignment.Center, style: "GUITextBox",
hidePlusMinusButtons: true)
{
IntValue = defaultValue
};
inputContainer.RectTransform.Parent.MinSize = new Point(0, numberInput.RectTransform.MinSize.Y);
GUIButton plusButton = new GUIButton(new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center)
{
ClickSound = GUISoundType.Increase,
UserData = valueStep
};
minusButton.OnClicked = plusButton.OnClicked = ChangeValue;
bool ChangeValue(GUIButton btn, object userData)
{
if (!(userData is int change)) { return false; }
numberInput.IntValue += change;
return true;
}
return new SettingValue<int>(() => numberInput.IntValue, i => numberInput.IntValue = i);
}
static SettingValue<T> CreateSelectionCarousel<T>(GUIComponent parent, LocalizedString description, LocalizedString tooltip, SettingCarouselElement<T> defaultValue, float verticalSize,
ImmutableArray<SettingCarouselElement<T>> options)
{
GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, horizontalSize: 0.55f, verticalSize: verticalSize);
GUIButton minusButton = new GUIButton(new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center) { UserData = -1 };
GUIFrame inputFrame = new GUIFrame(new RectTransform(Vector2.One, inputContainer.RectTransform), style: null);
GUINumberInput numberInput = new GUINumberInput(new RectTransform(Vector2.One, inputFrame.RectTransform, Anchor.Center), NumberType.Int, textAlignment: Alignment.Center, style: "GUITextBox", hidePlusMinusButtons: true)
{
IntValue = options.IndexOf(defaultValue),
MinValueInt = 0,
MaxValueInt = options.Length,
Visible = false
};
inputContainer.RectTransform.Parent.MinSize = new Point(0, numberInput.RectTransform.MinSize.Y);
GUITextBox inputLabel = new GUITextBox(new RectTransform(Vector2.One, inputFrame.RectTransform, Anchor.Center), text: defaultValue.Label.Value, textAlignment: Alignment.Center, createPenIcon: false)
{
CanBeFocused = false
};
GUIButton plusButton = new GUIButton(new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center) { UserData = 1 };
minusButton.OnClicked = plusButton.OnClicked = ChangeValue;
bool ChangeValue(GUIButton btn, object userData)
{
if (!(userData is int change)) { return false; }
int hiddenOptions = 0;
for (int i = options.Length - 1; i >= 0; i--)
{
if (options[i].IsHidden)
{
hiddenOptions++;
continue;
}
break;
}
int limit = options.Length - hiddenOptions;
if (PlayerInput.IsShiftDown())
{
limit = options.Length;
}
int newValue = MathUtils.PositiveModulo(Math.Clamp(numberInput.IntValue + change, min: -1, max: limit), limit);
SetValue(newValue);
return true;
}
void SetValue(int value)
{
numberInput.IntValue = value;
inputLabel.Text = options[value].Label.Value;
}
return new SettingValue<T>(() => options[numberInput.IntValue].Value, t => SetValue(options.IndexOf(e => Equals(e.Value, t))));
}
static SettingValue<bool> CreateTickbox(GUIComponent parent, LocalizedString description, LocalizedString tooltip, bool defaultValue, float verticalSize)
{
GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, 0.7f, verticalSize);
GUILayoutGroup tickboxContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1.0f), inputContainer.RectTransform), childAnchor: Anchor.Center);
GUITickBox tickBox = new GUITickBox(new RectTransform(Vector2.One, tickboxContainer.RectTransform), string.Empty)
{
Selected = defaultValue,
ToolTip = tooltip
};
tickBox.Box.IgnoreLayoutGroups = true;
tickBox.Box.RectTransform.SetPosition(Anchor.CenterRight);
inputContainer.RectTransform.Parent.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y);
return new SettingValue<bool>(() => tickBox.Selected, b => tickBox.Selected = b);
}
static GUILayoutGroup CreateSettingBase(GUIComponent parent, LocalizedString description, LocalizedString tooltip, float horizontalSize, float verticalSize)
{
GUILayoutGroup settingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, verticalSize), parent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(horizontalSize, 1f), settingHolder.RectTransform), description, font: parent.Rect.Width < 320 ? GUIStyle.SmallFont : GUIStyle.Font, wrap: true) { ToolTip = tooltip };
GUILayoutGroup inputContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f - horizontalSize, 0.8f), settingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
RelativeSpacing = 0.05f,
Stretch = true
};
inputContainer.RectTransform.IsFixedSize = true;
settingHolder.RectTransform.MinSize = new Point(0, (int)descriptionBlock.TextSize.Y);
return inputContainer;
}
}
} }
} }
@@ -18,71 +18,35 @@ namespace Barotrauma
var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: false) var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: false)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.0f RelativeSpacing = 0.05f
};
GUILayoutGroup nameSeedLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), verticalLayout.RectTransform), isHorizontal: false)
{
Stretch = true
};
GUILayoutGroup campaignSettingLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), verticalLayout.RectTransform), isHorizontal: false)
{
Stretch = true,
RelativeSpacing = 0.05f
}; };
// New game // New game
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, string.Empty) saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, string.Empty)
{ {
textFilterFunction = (string str) => { return ToolBox.RemoveInvalidFileNameChars(str); } textFilterFunction = ToolBox.RemoveInvalidFileNameChars
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
GUIFrame radiationBoxContainer nameSeedLayout.RectTransform.MinSize = new Point(0, nameSeedLayout.Children.Sum(c => c.RectTransform.MinSize.Y));
= new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), verticalLayout.RectTransform), style: null);
GUITickBox radiationEnabledTickBox = null;
if (MapGenerationParams.Instance.RadiationParams != null)
{
radiationEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.5f), radiationBoxContainer.RectTransform, Anchor.Center), TextManager.Get("CampaignOption.EnableRadiation"), font: GUIStyle.Font)
{
Selected = true,
OnSelected = box => true
};
}
var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), verticalLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingLayout, CampaignSettings.Empty);
var maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", "missions"), wrap: true)
{
ToolTip = TextManager.Get("maxmissioncounttooltip")
};
int maxMissionCount = GameMain.NetworkMember.ServerSettings.MaxMissionCount;
var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true };
var maxMissionCountButtons = new GUIButton[2];
maxMissionCountButtons[0]
= new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform),
style: "GUIButtonToggleLeft");
var maxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), "0", textAlignment: Alignment.Center, style: "GUITextBox");
void updateMissionCountText() var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f),
{
maxMissionCount = MathHelper.Clamp(maxMissionCount,
CampaignSettings.MinMissionCountLimit,
CampaignSettings.MaxMissionCountLimit);
maxMissionCountText.Text = maxMissionCount.ToString(CultureInfo.InvariantCulture);
}
maxMissionCountButtons[1]
= new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform),
style: "GUIButtonToggleRight");
maxMissionCountButtons[0].OnClicked = (button, o) =>
{
maxMissionCount--;
updateMissionCountText();
return false;
};
maxMissionCountButtons[1].OnClicked = (button, o) =>
{
maxMissionCount++;
updateMissionCountText();
return false;
};
updateMissionCountText();
maxMissionCountSettingHolder.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip);
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.04f),
verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true);
StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton")) StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton"))
@@ -99,7 +63,7 @@ namespace Barotrauma
if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; } if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; }
selectedSub = GameMain.NetLobbyScreen.SelectedSub; selectedSub = GameMain.NetLobbyScreen.SelectedSub;
if (selectedSub.SubmarineClass == SubmarineClass.Undefined) if (selectedSub.SubmarineClass == SubmarineClass.Undefined)
{ {
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected")); new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected"));
@@ -115,11 +79,7 @@ namespace Barotrauma
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveNameBox.Text); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings CampaignSettings settings = elements.CreateSettings();
{
RadiationEnabled = radiationEnabledTickBox?.Selected ?? GameMain.NetworkMember.ServerSettings.RadiationEnabled,
MaxMissionCount = maxMissionCount
};
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{ {
@@ -172,12 +132,16 @@ namespace Barotrauma
}; };
StartButton.RectTransform.MaxSize = RectTransform.MaxPoint; StartButton.RectTransform.MaxSize = RectTransform.MaxPoint;
StartButton.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint); StartButton.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint);
InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUIStyle.SmallFont, textColor: GUIStyle.Green) InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUIStyle.SmallFont, textColor: GUIStyle.Green)
{ {
TextGetter = () => TextGetter = () =>
{ {
int initialMoney = CampaignMode.InitialMoney; int initialMoney = 8000;
if (CampaignModePresets.Definitions.TryGetValue(nameof(StartingBalanceAmount).ToIdentifier(), out var definition))
{
initialMoney = definition.GetInt(elements.StartingFunds.GetValue().ToIdentifier());
}
if (GameMain.NetLobbyScreen.SelectedSub != null) if (GameMain.NetLobbyScreen.SelectedSub != null)
{ {
initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price; initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price;
@@ -238,6 +202,7 @@ namespace Barotrauma
saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform)) saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = SelectSaveFile OnSelected = SelectSaveFile
}; };
@@ -257,7 +222,7 @@ namespace Barotrauma
file1WriteTime = File.GetLastWriteTime(file1); file1WriteTime = File.GetLastWriteTime(file1);
} }
catch catch
{ {
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list //do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
}; };
try try
@@ -1,12 +1,11 @@
using Barotrauma.Tutorials; using Barotrauma.Extensions;
using Barotrauma.IO;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO; using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using System.Globalization;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -142,7 +141,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont);
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUIStyle.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUIStyle.SubHeadingFont);
var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3); var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3);
@@ -155,8 +154,12 @@ namespace Barotrauma
{ {
Stretch = true Stretch = true
}; };
subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true }; subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform))
{
PlaySoundOnSelect = true,
ScrollBarVisible = true
};
var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUIStyle.Font); var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUIStyle.Font);
var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUIStyle.Font, createClearButton: true); var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUIStyle.Font, createClearButton: true);
@@ -191,7 +194,7 @@ namespace Barotrauma
{ {
TextGetter = () => TextGetter = () =>
{ {
int initialMoney = CampaignMode.InitialMoney; int initialMoney = CurrentSettings.InitialMoney;
if (subList.SelectedData is SubmarineInfo subInfo) if (subList.SelectedData is SubmarineInfo subInfo)
{ {
initialMoney -= subInfo.Price; initialMoney -= subInfo.Price;
@@ -200,12 +203,16 @@ namespace Barotrauma
return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney)); return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney));
} }
}; };
CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), firstPageButtonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton")) CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), firstPageButtonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton"))
{ {
OnClicked = (tb, userdata) => OnClicked = (tb, userdata) =>
{ {
CreateCustomizeWindow(); CreateCustomizeWindow(CurrentSettings, settings =>
{
CurrentSettings = settings;
UpdateSubList(SubmarineInfo.SavedSubmarines);
});
return true; return true;
} }
}; };
@@ -218,7 +225,7 @@ namespace Barotrauma
return false; return false;
} }
}; };
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton") var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton")
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -353,54 +360,21 @@ namespace Barotrauma
StealRandomizeButton(CharacterMenus[i], jobTextContainer); StealRandomizeButton(CharacterMenus[i], jobTextContainer);
} }
} }
private void CreateCustomizeWindow() private void CreateCustomizeWindow(CampaignSettings prevSettings, Action<CampaignSettings> onClosed = null)
{ {
CampaignCustomizeSettings = new GUIMessageBox("", "", new LocalizedString[] { TextManager.Get("OK") }, new Vector2(0.2f, 0.2f)); CampaignCustomizeSettings = new GUIMessageBox("", "", new[] { TextManager.Get("OK") }, new Vector2(0.25f, 0.3f), minSize: new Point(450, 350));
CampaignCustomizeSettings.Buttons[0].OnClicked += CampaignCustomizeSettings.Close;
CampaignSettingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter)) GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter));
{
RelativeSpacing = 0.1f
};
if (MapGenerationParams.Instance.RadiationParams != null)
{
bool prevRadiationToggleEnabled = EnableRadiationToggle?.Selected ?? true;
EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.3f), CampaignSettingsContent.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUIStyle.Font)
{
Selected = prevRadiationToggleEnabled,
ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip")
};
}
var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), CampaignSettingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
ToolTip = TextManager.Get("maxmissioncounttooltip")
};
var maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", "missions"), wrap: true);
var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true };
var maxMissionCountButtons = new GUIButton[2];
maxMissionCountButtons[0] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleLeft")
{
OnClicked = (button, obj) =>
{
MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text.SanitizedValue) - 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString();
return true;
}
};
RichString prevMaxMissionCountText = MaxMissionCountText?.Text ?? CampaignSettings.DefaultMaxMissionCount.ToString(); CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingContent, prevSettings);
MaxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), prevMaxMissionCountText, textAlignment: Alignment.Center, style: "GUITextBox"); CampaignCustomizeSettings.Buttons[0].OnClicked += (button, o) =>
maxMissionCountButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleRight")
{ {
OnClicked = (button, obj) =>
{ onClosed?.Invoke(elements.CreateSettings());
MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text.SanitizedValue) + 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString(); return CampaignCustomizeSettings.Close(button, o);
return true;
}
}; };
maxMissionCountContainer.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip);
} }
private static void StealRandomizeButton(CharacterInfo.AppearanceCustomizationMenu menu, GUIComponent parent) private static void StealRandomizeButton(CharacterInfo.AppearanceCustomizationMenu menu, GUIComponent parent)
@@ -412,7 +386,7 @@ namespace Barotrauma
randomizeButton.RectTransform.Parent = parent.RectTransform; randomizeButton.RectTransform.Parent = parent.RectTransform;
randomizeButton.RectTransform.RelativeSize = Vector2.One * 1.3f; randomizeButton.RectTransform.RelativeSize = Vector2.One * 1.3f;
} }
private bool FinishSetup(GUIButton btn, object userdata) private bool FinishSetup(GUIButton btn, object userdata)
{ {
if (string.IsNullOrWhiteSpace(saveNameBox.Text)) if (string.IsNullOrWhiteSpace(saveNameBox.Text))
@@ -420,7 +394,7 @@ namespace Barotrauma
saveNameBox.Flash(GUIStyle.Red); saveNameBox.Flash(GUIStyle.Red);
return false; return false;
} }
SubmarineInfo selectedSub = null; SubmarineInfo selectedSub = null;
if (!(subList.SelectedData is SubmarineInfo)) { return false; } if (!(subList.SelectedData is SubmarineInfo)) { return false; }
@@ -443,16 +417,7 @@ namespace Barotrauma
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer, saveNameBox.Text); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings(); CampaignSettings settings = CurrentSettings;
settings.RadiationEnabled = EnableRadiationToggle?.Selected ?? false;
if (MaxMissionCountText != null && Int32.TryParse(MaxMissionCountText.Text.SanitizedValue, out int missionCount))
{
settings.MaxMissionCount = missionCount;
}
else
{
settings.MaxMissionCount = CampaignSettings.DefaultMaxMissionCount;
}
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{ {
@@ -499,7 +464,7 @@ namespace Barotrauma
return true; return true;
} }
public void RandomizeSeed() public void RandomizeSeed()
{ {
seedBox.Text = ToolBox.RandomSeed(8); seedBox.Text = ToolBox.RandomSeed(8);
@@ -509,7 +474,7 @@ namespace Barotrauma
{ {
foreach (GUIComponent child in subList.Content.Children) foreach (GUIComponent child in subList.Content.Children)
{ {
var sub = child.UserData as SubmarineInfo; SubmarineInfo sub = child.UserData as SubmarineInfo;
if (sub == null) { return; } if (sub == null) { return; }
child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.Contains(filter.ToLower(), StringComparison.OrdinalIgnoreCase); child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.Contains(filter.ToLower(), StringComparison.OrdinalIgnoreCase);
} }
@@ -523,7 +488,7 @@ namespace Barotrauma
if (!(obj is SubmarineInfo sub)) { return true; } if (!(obj is SubmarineInfo sub)) { return true; }
#if !DEBUG #if !DEBUG
if (sub.Price > CampaignMode.InitialMoney && !GameMain.DebugDraw) if (sub.Price > CurrentSettings.InitialMoney && !GameMain.DebugDraw)
{ {
SetPage(0); SetPage(0);
nextButton.Enabled = false; nextButton.Enabled = false;
@@ -556,8 +521,8 @@ namespace Barotrauma
subsToShow.Sort((s1, s2) => subsToShow.Sort((s1, s2) =>
{ {
int p1 = s1.Price > CampaignMode.InitialMoney ? 10 : 0; int p1 = s1.Price > CurrentSettings.InitialMoney ? 10 : 0;
int p2 = s2.Price > CampaignMode.InitialMoney ? 10 : 0; int p2 = s2.Price > CurrentSettings.InitialMoney ? 10 : 0;
return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name); return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name);
}); });
@@ -582,13 +547,13 @@ namespace Barotrauma
var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight),
TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont) TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont)
{ {
TextColor = sub.Price > CampaignMode.InitialMoney ? GUIStyle.Red : textBlock.TextColor * 0.8f, TextColor = sub.Price > CurrentSettings.InitialMoney ? GUIStyle.Red : textBlock.TextColor * 0.8f,
ToolTip = textBlock.ToolTip ToolTip = textBlock.ToolTip
}; };
#if !DEBUG #if !DEBUG
if (!GameMain.DebugDraw) if (!GameMain.DebugDraw)
{ {
if (sub.Price > CampaignMode.InitialMoney || !sub.IsCampaignCompatible) if (sub.Price > CurrentSettings.InitialMoney || !sub.IsCampaignCompatible)
{ {
textBlock.CanBeFocused = false; textBlock.CanBeFocused = false;
textBlock.TextColor *= 0.5f; textBlock.TextColor *= 0.5f;
@@ -598,7 +563,7 @@ namespace Barotrauma
} }
if (SubmarineInfo.SavedSubmarines.Any()) if (SubmarineInfo.SavedSubmarines.Any())
{ {
var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignMode.InitialMoney).ToList(); var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CurrentSettings.InitialMoney).ToList();
if (validSubs.Count > 0) if (validSubs.Count > 0)
{ {
subList.Select(validSubs[Rand.Int(validSubs.Count)]); subList.Select(validSubs[Rand.Int(validSubs.Count)]);
@@ -625,6 +590,7 @@ namespace Barotrauma
saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform)) saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = SelectSaveFile OnSelected = SelectSaveFile
}; };
@@ -650,8 +616,9 @@ namespace Barotrauma
{ {
var saveFrame = CreateSaveElement(saveInfo); var saveFrame = CreateSaveElement(saveInfo);
if (saveFrame == null) { continue; } if (saveFrame == null) { continue; }
XDocument doc = SaveUtil.LoadGameSessionDoc(saveInfo.FilePath); XDocument doc = SaveUtil.LoadGameSessionDoc(saveInfo.FilePath);
if (doc?.Root == null) if (doc?.Root == null)
{ {
DebugConsole.ThrowError("Error loading save file \"" + saveInfo.FilePath + "\". The file may be corrupted."); DebugConsole.ThrowError("Error loading save file \"" + saveInfo.FilePath + "\". The file may be corrupted.");
@@ -725,9 +692,10 @@ namespace Barotrauma
string subName = doc.Root.GetAttributeString("submarine", ""); string subName = doc.Root.GetAttributeString("submarine", "");
string saveTime = doc.Root.GetAttributeString("savetime", "unknown"); string saveTime = doc.Root.GetAttributeString("savetime", "unknown");
DateTime? time = null;
if (long.TryParse(saveTime, out long unixTime)) if (long.TryParse(saveTime, out long unixTime))
{ {
DateTime time = ToolBox.Epoch.ToDateTime(unixTime); time = ToolBox.Epoch.ToDateTime(unixTime);
saveTime = time.ToString(); saveTime = time.ToString();
} }
@@ -729,7 +729,7 @@ namespace Barotrauma
break; break;
case CampaignMode.InteractionType.PurchaseSub: case CampaignMode.InteractionType.PurchaseSub:
if (submarineSelection == null) submarineSelection = new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform); if (submarineSelection == null) submarineSelection = new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform);
submarineSelection.RefreshSubmarineDisplay(true); submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true);
break; break;
} }
} }
@@ -2858,7 +2858,10 @@ namespace Barotrauma.CharacterEditor
{ {
var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadRagdoll"), "", new LocalizedString[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize); var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadRagdoll"), "", new LocalizedString[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize);
loadBox.Buttons[0].OnClicked += loadBox.Close; loadBox.Buttons[0].OnClicked += loadBox.Close;
var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform, Anchor.TopCenter)); var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform, Anchor.TopCenter))
{
PlaySoundOnSelect = true,
};
var deleteButton = loadBox.Buttons[2]; var deleteButton = loadBox.Buttons[2];
deleteButton.Enabled = false; deleteButton.Enabled = false;
void PopulateListBox() void PopulateListBox()
@@ -2996,7 +2999,10 @@ namespace Barotrauma.CharacterEditor
{ {
var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadAnimation"), "", new LocalizedString[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize); var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadAnimation"), "", new LocalizedString[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize);
loadBox.Buttons[0].OnClicked += loadBox.Close; loadBox.Buttons[0].OnClicked += loadBox.Close;
var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform)); var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform))
{
PlaySoundOnSelect = true,
};
var deleteButton = loadBox.Buttons[2]; var deleteButton = loadBox.Buttons[2];
deleteButton.Enabled = false; deleteButton.Enabled = false;
// Type filtering // Type filtering
@@ -482,7 +482,10 @@ namespace Barotrauma.CharacterEditor
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), limbEditLayout.RectTransform), GetCharacterEditorTranslation("Limbs"), font: GUIStyle.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), limbEditLayout.RectTransform), GetCharacterEditorTranslation("Limbs"), font: GUIStyle.SubHeadingFont);
var limbsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform)); var limbsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform))
{
PlaySoundOnSelect = true,
};
var removeLimbButton = new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), limbEditLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton") var removeLimbButton = new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), limbEditLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton")
{ {
OnClicked = (b, d) => OnClicked = (b, d) =>
@@ -659,7 +662,10 @@ namespace Barotrauma.CharacterEditor
{ {
CanBeFocused = false CanBeFocused = false
}; };
var jointsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform)); var jointsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform))
{
PlaySoundOnSelect = true,
};
var removeJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), style: "GUIMinusButton") var removeJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), style: "GUIMinusButton")
{ {
OnClicked = (b, d) => OnClicked = (b, d) =>
@@ -225,7 +225,7 @@ namespace Barotrauma
return true; return true;
} }
public static GUIMessageBox AskForConfirmation(LocalizedString header, LocalizedString body, Func<bool> onConfirm) public static GUIMessageBox AskForConfirmation(LocalizedString header, LocalizedString body, Func<bool> onConfirm, GUISoundType? overrideConfirmButtonSound = null)
{ {
LocalizedString[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") }; LocalizedString[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons); GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons);
@@ -244,6 +244,10 @@ namespace Barotrauma
msgBox.Close(); msgBox.Close();
return true; return true;
}; };
if (overrideConfirmButtonSound.HasValue)
{
msgBox.Buttons[0].ClickSound = overrideConfirmButtonSound.Value;
}
return msgBox; return msgBox;
} }
@@ -34,6 +34,8 @@ namespace Barotrauma
private readonly GUITickBox lightingEnabled, cursorLightEnabled, allowInvalidOutpost, mirrorLevel; private readonly GUITickBox lightingEnabled, cursorLightEnabled, allowInvalidOutpost, mirrorLevel;
private readonly GUIDropDown selectedSubDropDown;
private Sprite editingSprite; private Sprite editingSprite;
private LightSource pointerLightSource; private LightSource pointerLightSource;
@@ -57,7 +59,10 @@ namespace Barotrauma
RelativeSpacing = 0.01f RelativeSpacing = 0.01f
}; };
paramsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), paddedLeftPanel.RectTransform)); paramsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), paddedLeftPanel.RectTransform))
{
PlaySoundOnSelect = true
};
paramsList.OnSelected += (GUIComponent component, object obj) => paramsList.OnSelected += (GUIComponent component, object obj) =>
{ {
selectedParams = obj as LevelGenerationParams; selectedParams = obj as LevelGenerationParams;
@@ -70,7 +75,10 @@ namespace Barotrauma
var ruinTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.ruinparams"), font: GUIStyle.SubHeadingFont); var ruinTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.ruinparams"), font: GUIStyle.SubHeadingFont);
ruinParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform)); ruinParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform))
{
PlaySoundOnSelect = true
};
ruinParamsList.OnSelected += (GUIComponent component, object obj) => ruinParamsList.OnSelected += (GUIComponent component, object obj) =>
{ {
CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams); CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams);
@@ -79,7 +87,10 @@ namespace Barotrauma
var caveTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.caveparams"), font: GUIStyle.SubHeadingFont); var caveTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.caveparams"), font: GUIStyle.SubHeadingFont);
caveParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform)); caveParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform))
{
PlaySoundOnSelect = true
};
caveParamsList.OnSelected += (GUIComponent component, object obj) => caveParamsList.OnSelected += (GUIComponent component, object obj) =>
{ {
CreateCaveParamsEditor(obj as CaveGenerationParams); CreateCaveParamsEditor(obj as CaveGenerationParams);
@@ -89,7 +100,10 @@ namespace Barotrauma
var outpostTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.outpostparams"), font: GUIStyle.SubHeadingFont); var outpostTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.outpostparams"), font: GUIStyle.SubHeadingFont);
GUITextBlock.AutoScaleAndNormalize(ruinTitle, caveTitle, outpostTitle); GUITextBlock.AutoScaleAndNormalize(ruinTitle, caveTitle, outpostTitle);
outpostParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform)); outpostParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform))
{
PlaySoundOnSelect = true
};
outpostParamsList.OnSelected += (GUIComponent component, object obj) => outpostParamsList.OnSelected += (GUIComponent component, object obj) =>
{ {
CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams); CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams);
@@ -171,6 +185,16 @@ namespace Barotrauma
Vector2 GetSeedElementRelativeSize() => new Vector2(0.5f * (1.0f - randomizeButtonRelativeSize.X), 1.0f); Vector2 GetSeedElementRelativeSize() => new Vector2(0.5f * (1.0f - randomizeButtonRelativeSize.X), 1.0f);
static string GetLevelSeed() => ToolBox.RandomSeed(8); static string GetLevelSeed() => ToolBox.RandomSeed(8);
var subDropDownContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), subDropDownContainer.RectTransform), TextManager.Get("submarine"));
selectedSubDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), subDropDownContainer.RectTransform));
foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines)
{
if (sub.Type != SubmarineType.Player) { continue; }
selectedSubDropDown.AddItem(sub.DisplayName, userData: sub);
}
subDropDownContainer.RectTransform.MinSize = new Point(0, selectedSubDropDown.RectTransform.MinSize.Y);
mirrorLevel = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), TextManager.Get("mirrorentityx")); mirrorLevel = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), TextManager.Get("mirrorentityx"));
allowInvalidOutpost = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.025f), paddedRightPanel.RectTransform), allowInvalidOutpost = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.025f), paddedRightPanel.RectTransform),
@@ -186,11 +210,18 @@ namespace Barotrauma
{ {
bool wasLevelLoaded = Level.Loaded != null; bool wasLevelLoaded = Level.Loaded != null;
Submarine.Unload(); Submarine.Unload();
if (selectedSubDropDown.SelectedData is SubmarineInfo subInfo)
{
Submarine.MainSub = new Submarine(subInfo);
}
GameMain.LightManager.ClearLights(); GameMain.LightManager.ClearLights();
currentLevelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams); currentLevelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams);
currentLevelData.ForceOutpostGenerationParams = outpostParamsList.SelectedData as OutpostGenerationParams; currentLevelData.ForceOutpostGenerationParams = outpostParamsList.SelectedData as OutpostGenerationParams;
currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected; currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected;
Level.Generate(currentLevelData, mirror: mirrorLevel.Selected); var dummyLocations = GameSession.CreateDummyLocations(seed: currentLevelData.Seed);
Level.Generate(currentLevelData, mirror: mirrorLevel.Selected, startLocation: dummyLocations[0], endLocation: dummyLocations[1]);
Submarine.MainSub?.SetPosition(Level.Loaded.StartPosition);
GameMain.LightManager.AddLight(pointerLightSource); GameMain.LightManager.AddLight(pointerLightSource);
if (!wasLevelLoaded || Cam.Position.X < 0 || Cam.Position.Y < 0 || Cam.Position.Y > Level.Loaded.Size.X || Cam.Position.Y > Level.Loaded.Size.Y) if (!wasLevelLoaded || Cam.Position.X < 0 || Cam.Position.Y < 0 || Cam.Position.Y > Level.Loaded.Size.X || Cam.Position.Y > Level.Loaded.Size.Y)
{ {
@@ -228,7 +259,7 @@ namespace Barotrauma
var nonPlayerFiles = ContentPackageManager.EnabledPackages.All.SelectMany(p => p var nonPlayerFiles = ContentPackageManager.EnabledPackages.All.SelectMany(p => p
.GetFiles<BaseSubFile>() .GetFiles<BaseSubFile>()
.Where(f => !(f is SubmarineFile))).ToArray(); .Where(f => !(f is SubmarineFile))).ToArray();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == GameSettings.CurrentConfig.QuickStartSub); SubmarineInfo subInfo = selectedSubDropDown.SelectedData as SubmarineInfo;
subInfo ??= SubmarineInfo.SavedSubmarines.GetRandomUnsynced(s => subInfo ??= SubmarineInfo.SavedSubmarines.GetRandomUnsynced(s =>
s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) && s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) &&
!nonPlayerFiles.Any(f => f.Path == s.FilePath)); !nonPlayerFiles.Any(f => f.Path == s.FilePath));
@@ -259,6 +290,7 @@ namespace Barotrauma
levelObjectList = new GUIListBox(new RectTransform(new Vector2(0.99f, 0.85f), bottomPanel.RectTransform, Anchor.Center)) levelObjectList = new GUIListBox(new RectTransform(new Vector2(0.99f, 0.85f), bottomPanel.RectTransform, Anchor.Center))
{ {
PlaySoundOnSelect = true,
UseGridLayout = true UseGridLayout = true
}; };
levelObjectList.OnSelected += (GUIComponent component, object obj) => levelObjectList.OnSelected += (GUIComponent component, object obj) =>
@@ -866,7 +898,11 @@ namespace Barotrauma
{ {
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
item?.GetComponent<Items.Components.LightComponent>()?.Update((float)deltaTime, Cam); if (item == null) { continue; }
foreach (var light in item.GetComponents<Items.Components.LightComponent>())
{
light.Update((float)deltaTime, Cam);
}
} }
} }
GameMain.LightManager?.Update((float)deltaTime); GameMain.LightManager?.Update((float)deltaTime);
@@ -429,7 +429,10 @@ namespace Barotrauma
//PLACEHOLDER //PLACEHOLDER
var tutorialList = new GUIListBox( var tutorialList = new GUIListBox(
new RectTransform(new Vector2(0.95f, 0.85f), menuTabs[Tab.Tutorials].RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) }); new RectTransform(new Vector2(0.95f, 0.85f), menuTabs[Tab.Tutorials].RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) })
{
PlaySoundOnSelect = true,
};
var tutorialTypes = new List<Type>() var tutorialTypes = new List<Type>()
{ {
typeof(MechanicTutorial), typeof(MechanicTutorial),
@@ -850,16 +853,12 @@ namespace Barotrauma
arguments += " -nopassword"; arguments += " -nopassword";
} }
int ownerKey = 0;
if (Steam.SteamManager.GetSteamID() != 0) if (Steam.SteamManager.GetSteamID() != 0)
{ {
arguments += " -steamid " + Steam.SteamManager.GetSteamID(); arguments += " -steamid " + Steam.SteamManager.GetSteamID();
} }
else int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
{ arguments += " -ownerkey " + ownerKey;
ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
arguments += " -ownerkey " + ownerKey;
}
string filename = Path.Combine( string filename = Path.Combine(
Path.GetDirectoryName(exeName), Path.GetDirectoryName(exeName),
@@ -1244,7 +1243,8 @@ namespace Barotrauma
new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center) new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center)
{ {
UserData = -1, UserData = -1,
OnClicked = ChangeMaxPlayers OnClicked = ChangeMaxPlayers,
ClickSound = GUISoundType.Decrease
}; };
maxPlayersBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), textAlignment: Alignment.Center) maxPlayersBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), textAlignment: Alignment.Center)
{ {
@@ -1264,7 +1264,8 @@ namespace Barotrauma
new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center) new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center)
{ {
UserData = 1, UserData = 1,
OnClicked = ChangeMaxPlayers OnClicked = ChangeMaxPlayers,
ClickSound = GUISoundType.Increase
}; };
maxPlayersLabel.RectTransform.IsFixedSize = true; maxPlayersLabel.RectTransform.IsFixedSize = true;
@@ -179,7 +179,7 @@ namespace Barotrauma
get { return ModeList.SelectedIndex; } get { return ModeList.SelectedIndex; }
set set
{ {
ModeList.Select(value, true); ModeList.Select(value, GUIListBox.Force.Yes);
} }
} }
@@ -504,6 +504,7 @@ namespace Barotrauma
PlayerList = new GUIListBox(new RectTransform(new Vector2(0.4f, 1.0f), socialHolderHorizontal.RectTransform)) PlayerList = new GUIListBox(new RectTransform(new Vector2(0.4f, 1.0f), socialHolderHorizontal.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = (component, userdata) => { SelectPlayer(userdata as Client); return true; } OnSelected = (component, userdata) => { SelectPlayer(userdata as Client); return true; }
}; };
@@ -816,6 +817,7 @@ namespace Barotrauma
SubList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform)) SubList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = VotableClicked OnSelected = VotableClicked
}; };
@@ -901,6 +903,7 @@ namespace Barotrauma
}; };
ModeList = new GUIListBox(new RectTransform(Vector2.One, gameModeHolder.RectTransform)) ModeList = new GUIListBox(new RectTransform(Vector2.One, gameModeHolder.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = VotableClicked OnSelected = VotableClicked
}; };
@@ -1515,6 +1518,7 @@ namespace Barotrauma
JobList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.6f), JobPreferenceContainer.RectTransform, Anchor.BottomCenter), true) JobList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.6f), JobPreferenceContainer.RectTransform, Anchor.BottomCenter), true)
{ {
Enabled = true, Enabled = true,
PlaySoundOnSelect = true,
OnSelected = (child, obj) => OnSelected = (child, obj) =>
{ {
if (child.IsParentOf(GUI.MouseOn)) return false; if (child.IsParentOf(GUI.MouseOn)) return false;
@@ -1600,6 +1604,7 @@ namespace Barotrauma
{ {
Enabled = true, Enabled = true,
KeepSpaceForScrollBar = false, KeepSpaceForScrollBar = false,
PlaySoundOnSelect = true,
ScrollBarEnabled = false, ScrollBarEnabled = false,
ScrollBarVisible = false ScrollBarVisible = false
}; };
@@ -3185,7 +3190,7 @@ namespace Barotrauma
var prevMode = ModeList.Content.GetChild(selectedModeIndex).UserData as GameModePreset; var prevMode = ModeList.Content.GetChild(selectedModeIndex).UserData as GameModePreset;
if ((HighlightedModeIndex == selectedModeIndex || HighlightedModeIndex < 0) && ModeList.SelectedIndex != modeIndex) { ModeList.Select(modeIndex, true); } if ((HighlightedModeIndex == selectedModeIndex || HighlightedModeIndex < 0) && ModeList.SelectedIndex != modeIndex) { ModeList.Select(modeIndex, GUIListBox.Force.Yes); }
selectedModeIndex = modeIndex; selectedModeIndex = modeIndex;
if ((prevMode == GameModePreset.PvP) != (SelectedMode == GameModePreset.PvP)) if ((prevMode == GameModePreset.PvP) != (SelectedMode == GameModePreset.PvP))
@@ -3301,7 +3306,7 @@ namespace Barotrauma
RefreshEnabledElements(); RefreshEnabledElements();
if (enabled) if (enabled)
{ {
ModeList.Select(GameModePreset.MultiPlayerCampaign, true); ModeList.Select(GameModePreset.MultiPlayerCampaign, GUIListBox.Force.Yes);
} }
} }
@@ -3417,7 +3422,7 @@ namespace Barotrauma
UserData = i, UserData = i,
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
JobList.Select((int)obj, true); JobList.Select((int)obj, GUIListBox.Force.Yes);
SwitchJob(btn, null); SwitchJob(btn, null);
if (JobSelectionFrame != null) { JobSelectionFrame.Visible = false; } if (JobSelectionFrame != null) { JobSelectionFrame.Visible = false; }
JobList.Deselect(); JobList.Deselect();
@@ -3553,7 +3558,7 @@ namespace Barotrauma
else else
{ {
subList.OnSelected -= VotableClicked; subList.OnSelected -= VotableClicked;
subList.Select(sub, force: true); subList.Select(sub, GUIListBox.Force.Yes);
subList.OnSelected += VotableClicked; subList.OnSelected += VotableClicked;
} }
@@ -129,7 +129,10 @@ namespace Barotrauma
OnClicked = (btn, userdata) => { FilterEmitters(""); filterBox.Text = ""; filterBox.Flash(Color.White); return true; } OnClicked = (btn, userdata) => { FilterEmitters(""); filterBox.Text = ""; filterBox.Flash(Color.White); return true; }
}; };
prefabList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), paddedLeftPanel.RectTransform)); prefabList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), paddedLeftPanel.RectTransform))
{
PlaySoundOnSelect = true,
};
prefabList.OnSelected += (GUIComponent component, object obj) => prefabList.OnSelected += (GUIComponent component, object obj) =>
{ {
cam.Position = Vector2.Zero; cam.Position = Vector2.Zero;
@@ -504,6 +504,7 @@ namespace Barotrauma
serverList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), serverListContainer.RectTransform, Anchor.Center)) serverList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), serverListContainer.RectTransform, Anchor.Center))
{ {
PlaySoundOnSelect = true,
ScrollBarVisible = true, ScrollBarVisible = true,
OnSelected = (btn, obj) => OnSelected = (btn, obj) =>
{ {
@@ -1473,7 +1474,6 @@ namespace Barotrauma
{ {
friendsDropdownButton = new GUIButton(new RectTransform(Vector2.One, friendsButtonHolder.RectTransform, Anchor.BottomRight, Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight), "\u2022 \u2022 \u2022", style: "GUIButtonFriendsDropdown") friendsDropdownButton = new GUIButton(new RectTransform(Vector2.One, friendsButtonHolder.RectTransform, Anchor.BottomRight, Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight), "\u2022 \u2022 \u2022", style: "GUIButtonFriendsDropdown")
{ {
Font = GUIStyle.GlobalFont,
OnClicked = (button, udt) => OnClicked = (button, udt) =>
{ {
friendsDropdown.RectTransform.NonScaledSize = new Point(friendsButtonHolder.Rect.Height * 5 * 166 / 100, friendsButtonHolder.Rect.Height * 4 * 166 / 100); friendsDropdown.RectTransform.NonScaledSize = new Point(friendsButtonHolder.Rect.Height * 5 * 166 / 100, friendsButtonHolder.Rect.Height * 4 * 166 / 100);
@@ -85,12 +85,12 @@ namespace Barotrauma
{ {
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
var selected = selectedSprites; var selected = selectedSprites.ToList();
Sprite firstSelected = selected.First(); Sprite firstSelected = selected.First();
selected.ForEach(s => s.ReloadTexture()); selected.ForEach(s => s.ReloadTexture());
RefreshLists(); RefreshLists();
textureList.Select(firstSelected.FullPath, autoScroll: false); textureList.Select(firstSelected.FullPath, autoScroll: GUIListBox.AutoScroll.Disabled);
selected.ForEachMod(s => spriteList.Select(s, autoScroll: false)); selected.ForEachMod(s => spriteList.Select(s, autoScroll: GUIListBox.AutoScroll.Disabled));
texturePathText.Text = TextManager.GetWithVariable("spriteeditor.texturesreloaded", "[filepath]", firstSelected.FilePath.Value); texturePathText.Text = TextManager.GetWithVariable("spriteeditor.texturesreloaded", "[filepath]", firstSelected.FilePath.Value);
texturePathText.TextColor = GUIStyle.Green; texturePathText.TextColor = GUIStyle.Green;
return true; return true;
@@ -206,6 +206,7 @@ namespace Barotrauma
textureList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), paddedLeftPanel.RectTransform)) textureList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), paddedLeftPanel.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = (listBox, userData) => OnSelected = (listBox, userData) =>
{ {
var newTexturePath = userData as string; var newTexturePath = userData as string;
@@ -213,7 +214,7 @@ namespace Barotrauma
{ {
selectedTexturePath = newTexturePath; selectedTexturePath = newTexturePath;
ResetZoom(); ResetZoom();
spriteList.Select(loadedSprites.First(s => s.FilePath == selectedTexturePath), autoScroll: false); spriteList.Select(loadedSprites.First(s => s.FilePath == selectedTexturePath), autoScroll: GUIListBox.AutoScroll.Disabled);
UpdateScrollBar(spriteList); UpdateScrollBar(spriteList);
} }
foreach (GUIComponent child in spriteList.Content.Children) foreach (GUIComponent child in spriteList.Content.Children)
@@ -248,6 +249,7 @@ namespace Barotrauma
spriteList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), paddedRightPanel.RectTransform)) spriteList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), paddedRightPanel.RectTransform))
{ {
PlaySoundOnSelect = true,
OnSelected = (listBox, userData) => OnSelected = (listBox, userData) =>
{ {
if (userData is Sprite sprite) if (userData is Sprite sprite)
@@ -481,7 +483,7 @@ namespace Barotrauma
var scaledRect = new Rectangle(textureRect.Location + sprite.SourceRect.Location.Multiply(zoom), sprite.SourceRect.Size.Multiply(zoom)); var scaledRect = new Rectangle(textureRect.Location + sprite.SourceRect.Location.Multiply(zoom), sprite.SourceRect.Size.Multiply(zoom));
if (scaledRect.Contains(PlayerInput.MousePosition)) if (scaledRect.Contains(PlayerInput.MousePosition))
{ {
spriteList.Select(sprite, autoScroll: false); spriteList.Select(sprite, autoScroll: GUIListBox.AutoScroll.Disabled);
UpdateScrollBar(spriteList); UpdateScrollBar(spriteList);
UpdateScrollBar(textureList); UpdateScrollBar(textureList);
// Release the keyboard so that we can nudge the source rects // Release the keyboard so that we can nudge the source rects
@@ -847,7 +849,7 @@ namespace Barotrauma
base.Select(); base.Select();
LoadSprites(); LoadSprites();
RefreshLists(); RefreshLists();
spriteList.Select(0, autoScroll: false); spriteList.Select(0, autoScroll: GUIListBox.AutoScroll.Disabled);
} }
protected override void DeselectEditorSpecific() protected override void DeselectEditorSpecific()
@@ -905,7 +907,7 @@ namespace Barotrauma
} }
if (sprite.FullPath != selectedTexturePath) if (sprite.FullPath != selectedTexturePath)
{ {
textureList.Select(sprite.FullPath, autoScroll: false); textureList.Select(sprite.FullPath, autoScroll: GUIListBox.AutoScroll.Disabled);
UpdateScrollBar(textureList); UpdateScrollBar(textureList);
} }
xmlPathText.Text = string.Empty; xmlPathText.Text = string.Empty;
File diff suppressed because it is too large Load Diff
@@ -1282,6 +1282,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) =>
{ {
string text = userData as string ?? ""; string text = userData as string ?? "";
@@ -171,7 +171,7 @@ namespace Barotrauma
int childIndex = values.IndexOf(currentValue); int childIndex = values.IndexOf(currentValue);
dropdown.Select(childIndex); dropdown.Select(childIndex);
dropdown.ListBox.ForceLayoutRecalculation(); dropdown.ListBox.ForceLayoutRecalculation();
dropdown.ListBox.ScrollToElement(dropdown.ListBox.Content.GetChild(childIndex), playSound: false); dropdown.ListBox.ScrollToElement(dropdown.ListBox.Content.GetChild(childIndex));
dropdown.OnSelected = (dd, obj) => dropdown.OnSelected = (dd, obj) =>
{ {
setter((T)obj); setter((T)obj);
@@ -418,7 +418,7 @@ namespace Barotrauma
} }
else else
{ {
if (!Level.IsLoadedOutpost && Character.Controlled?.CurrentHull?.Submarine is Submarine sub && if (!Level.IsLoadedFriendlyOutpost && Character.Controlled?.CurrentHull?.Submarine is Submarine sub &&
sub.Info != null && !sub.Info.IsOutpost) sub.Info != null && !sub.Info.IsOutpost)
{ {
hullSoundSource = Character.Controlled.CurrentHull; hullSoundSource = Character.Controlled.CurrentHull;
@@ -889,5 +889,13 @@ namespace Barotrauma
.Where(s => s.Type == soundType) .Where(s => s.Type == soundType)
.GetRandomUnsynced()?.Sound?.Play(null, "ui"); .GetRandomUnsynced()?.Sound?.Play(null, "ui");
} }
public static void PlayUISound(GUISoundType? soundType)
{
if (soundType.HasValue)
{
PlayUISound(soundType.Value);
}
}
} }
} }
@@ -176,12 +176,15 @@ namespace Barotrauma.Steam
Directory.CreateDirectory(PublishStagingDir); Directory.CreateDirectory(PublishStagingDir);
await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir, ShouldCorrectPaths.No); await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir, ShouldCorrectPaths.No);
var stagingFileListPath = Path.Combine(PublishStagingDir, ContentPackage.FileListFileName);
ContentPackage tempPkg = ContentPackage.TryLoad(stagingFileListPath) ?? throw new Exception("Staging copy could not be loaded");
//Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be //Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be
ModProject modProject = new ModProject(contentPackage) ModProject modProject = new ModProject(tempPkg)
{ {
ModVersion = modVersion ModVersion = modVersion
}; };
modProject.Save(Path.Combine(PublishStagingDir, ContentPackage.FileListFileName)); modProject.Save(stagingFileListPath);
} }
public static async Task<ContentPackage?> CreateLocalCopy(ContentPackage contentPackage) public static async Task<ContentPackage?> CreateLocalCopy(ContentPackage contentPackage)
@@ -46,7 +46,10 @@ namespace Barotrauma.Steam
regularBox.CanBeFocused = true; regularBox.CanBeFocused = true;
} }
} }
filterBox = CreateSearchBox(mainLayout, width: 1.0f);
var searchRectT = NewItemRectT(mainLayout, heightScale: 1.0f);
searchRectT.RelativeSize = (1.0f, searchRectT.RelativeSize.Y);
filterBox = CreateSearchBox(searchRectT);
Label(mainLayout, TextManager.Get("CannotChangeMods"), GUIStyle.Font); Label(mainLayout, TextManager.Get("CannotChangeMods"), GUIStyle.Font);
} }
@@ -55,9 +58,8 @@ namespace Barotrauma.Steam
{ {
string str = filterBox.Text; string str = filterBox.Text;
regularList.Content.Children regularList.Content.Children
.ForEach(c => c.Visible = str.IsNullOrWhiteSpace() .ForEach(c => c.Visible = !(c.UserData is ContentPackage p)
|| (c.UserData is ContentPackage p || ModNameMatches(p, str));
&& p.Name.Contains(str, StringComparison.OrdinalIgnoreCase)));
} }
} }
} }
@@ -0,0 +1,746 @@
#nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
namespace Barotrauma.Steam
{
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected");
private readonly GUIDropDown enabledCoreDropdown;
private readonly GUIListBox enabledRegularModsList;
private readonly GUIListBox disabledRegularModsList;
private readonly Action<ItemOrPackage> onInstalledInfoButtonHit;
private readonly GUITextBox modsListFilter;
private readonly Dictionary<Filter, GUITickBox> modsListFilterTickboxes;
private readonly GUIButton bulkUpdateButton;
private GUIComponent? draggedElement = null;
private GUIListBox? draggedElementOrigin = null;
private void UpdateSubscribedModInstalls()
{
if (!SteamManager.IsInitialized) { return; }
uint numSubscribedMods = SteamManager.GetNumSubscribedItems();
if (numSubscribedMods == memSubscribedModCount) { return; }
memSubscribedModCount = numSubscribedMods;
var subscribedIds = SteamManager.GetSubscribedItems().ToHashSet();
var installedIds = ContentPackageManager.WorkshopPackages.Select(p => p.SteamWorkshopId).ToHashSet();
foreach (var id in subscribedIds.Where(id2 => !installedIds.Contains(id2)))
{
Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id);
if (!item.IsDownloading && !SteamManager.Workshop.IsInstalling(item))
{
SteamManager.Workshop.DownloadModThenEnqueueInstall(item);
}
}
TaskPool.Add("RemoveUnsubscribedItems", SteamManager.Workshop.GetPublishedItems(), t =>
{
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> publishedItems)) { return; }
var allRequiredInstalled = subscribedIds.Union(publishedItems.Select(it => it.Id)).ToHashSet();
bool needsRefresh = false;
foreach (var id in installedIds.Where(id2 => !allRequiredInstalled.Contains(id2)))
{
Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id);
SteamManager.Workshop.Uninstall(item);
needsRefresh = true;
}
if (needsRefresh)
{
PopulateInstalledModLists();
}
});
}
private static (GUILayoutGroup Left, GUIFrame center, GUILayoutGroup Right) CreateSidebars(
GUIComponent parent,
float leftWidth = 0.3875f,
float centerWidth = 0.025f,
float rightWidth = 0.5875f,
bool split = false,
float height = 1.0f)
{
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform((1.0f, height), parent.RectTransform), isHorizontal: true);
GUILayoutGroup left = new GUILayoutGroup(new RectTransform((leftWidth, 1.0f), layout.RectTransform), isHorizontal: false);
var center = new GUIFrame(new RectTransform((centerWidth, 1.0f), layout.RectTransform), style: null);
if (split)
{
new GUICustomComponent(new RectTransform(Vector2.One, center.RectTransform),
onDraw: (sb, c) =>
{
sb.DrawLine((c.Rect.Center.X, c.Rect.Top), (c.Rect.Center.X, c.Rect.Bottom), GUIStyle.TextColorDim, 2f);
});
}
GUILayoutGroup right = new GUILayoutGroup(new RectTransform((rightWidth, 1.0f), layout.RectTransform), isHorizontal: false);
return (left, center, right);
}
private void HandleDraggingAcrossModLists(GUIListBox from, GUIListBox to)
{
if (to.Rect.Contains(PlayerInput.MousePosition) && from.DraggedElement != null)
{
//move the dragged elements to the index determined previously
var draggedElement = from.DraggedElement;
var selected = from.AllSelected.ToList();
selected.Sort((a, b) => from.Content.GetChildIndex(a) - from.Content.GetChildIndex(b));
float oldCount = to.Content.CountChildren;
float newCount = oldCount + selected.Count;
var offset = draggedElement.RectTransform.AbsoluteOffset;
offset += from.Content.Rect.Location;
offset -= to.Content.Rect.Location;
for (int i = 0; i < selected.Count; i++)
{
var c = selected[i];
c.Parent.RemoveChild(c);
c.RectTransform.Parent = to.Content.RectTransform;
c.RectTransform.RepositionChildInHierarchy((int)oldCount+i);
}
from.DraggedElement = null;
from.Deselect();
from.RecalculateChildren();
from.RectTransform.RecalculateScale(true);
to.RecalculateChildren();
to.RectTransform.RecalculateScale(true);
to.Select(selected);
//recalculate the dragged element's offset so it doesn't jump around
draggedElement.RectTransform.AbsoluteOffset = offset;
to.DraggedElement = draggedElement;
to.BarScroll *= (oldCount / newCount);
}
}
private Action? currentSwapFunc = null;
private GUISoundType? swapSoundType = null;
private void PlaySwapSound()
{
SoundPlayer.PlayUISound(swapSoundType);
}
private void SetSwapFunc(GUIListBox from, GUIListBox to)
{
currentSwapFunc = () =>
{
to.Deselect();
var selected = from.AllSelected.ToArray();
foreach (var frame in selected)
{
frame.Parent.RemoveChild(frame);
frame.RectTransform.Parent = to.Content.RectTransform;
}
from.RecalculateChildren();
from.RectTransform.RecalculateScale(true);
to.RecalculateChildren();
to.RectTransform.RecalculateScale(true);
to.Select(selected);
};
if (to == enabledRegularModsList)
{
swapSoundType = GUISoundType.Increase;
}
else if (to == disabledRegularModsList)
{
swapSoundType = GUISoundType.Decrease;
}
else
{
swapSoundType = null;
}
}
private void CreateInstalledModsTab(
out GUIDropDown enabledCoreDropdown,
out GUIListBox enabledRegularModsList,
out GUIListBox disabledRegularModsList,
out Action<ItemOrPackage> onInstalledInfoButtonHit,
out GUITextBox modsListFilter,
out Dictionary<Filter, GUITickBox> modsListFilterTickboxes,
out GUIButton bulkUpdateButton)
{
GUIFrame content = CreateNewContentFrame(Tab.InstalledMods);
CreateWorkshopItemDetailContainer(
content,
out var outerContainer,
onSelected: (itemOrPackage, selectedFrame) =>
{
if (itemOrPackage.TryGet(out Steamworks.Ugc.Item item)) { PopulateFrameWithItemInfo(item, selectedFrame); }
},
onDeselected: () => PopulateInstalledModLists(),
out onInstalledInfoButtonHit, out var deselect);
GUILayoutGroup mainLayout =
new GUILayoutGroup(new RectTransform(Vector2.One, outerContainer.Content.RectTransform), childAnchor: Anchor.TopCenter);
mainLayout.RectTransform.SetAsFirstChild();
var (topLeft, _, topRight) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.13f);
topLeft.Stretch = true;
Label(topLeft, TextManager.Get("enabledcore"), GUIStyle.SubHeadingFont, heightScale: 1.0f);
enabledCoreDropdown = Dropdown<CorePackage>(topLeft,
(p) => p.Name,
ContentPackageManager.CorePackages.ToArray(),
ContentPackageManager.EnabledPackages.Core!,
(p) => { },
heightScale: 1.0f / 13.0f);
Label(topLeft, "", GUIStyle.SubHeadingFont, heightScale: 1.0f);
topRight.ChildAnchor = Anchor.CenterLeft;
var topRightButtons = new GUILayoutGroup(new RectTransform((1.0f, 0.5f), topRight.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.05f
};
void padTopRight(float width=1.0f)
{
new GUIFrame(new RectTransform((width, 1.0f), topRightButtons.RectTransform), style: null);
}
padTopRight();
//TODO: put stuff here
padTopRight(width: 3.0f);
var refreshListsButton
= new GUIButton(
new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
text: "", style: "GUIReloadButton")
{
OnClicked = (b, o) =>
{
PopulateInstalledModLists();
return false;
},
ToolTip = TextManager.Get("RefreshModLists")
};
bulkUpdateButton
= new GUIButton(
new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
text: "", style: "GUIUpdateButton")
{
OnClicked = (b, o) =>
{
BulkDownloader.PrepareUpdates();
return false;
},
Enabled = false
};
padTopRight(width: 0.1f);
var (left, center, right) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.8f);
right.ChildAnchor = Anchor.TopRight;
//enabled mods
Label(left, TextManager.Get("enabledregular"), GUIStyle.SubHeadingFont);
var enabledModsList = new GUIListBox(new RectTransform((1.0f, 0.93f), left.RectTransform))
{
CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
HideDraggedElement = true,
PlaySoundOnSelect = true,
SoundOnDragStart = GUISoundType.Select,
SoundOnDragStop = GUISoundType.Increase,
};
enabledRegularModsList = enabledModsList;
//disabled mods
Label(right, TextManager.Get("disabledregular"), GUIStyle.SubHeadingFont);
var disabledModsList = new GUIListBox(new RectTransform((1.0f, 0.93f), right.RectTransform))
{
CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
HideDraggedElement = true,
PlaySoundOnSelect = true,
SoundOnDragStart = GUISoundType.Select,
SoundOnDragStop = GUISoundType.Decrease,
};
disabledRegularModsList = disabledModsList;
var centerButton =
new GUIButton(
new RectTransform(Vector2.One * 0.95f, center.RectTransform, scaleBasis: ScaleBasis.BothWidth,
anchor: Anchor.Center),
style: "GUIButtonToggleLeft")
{
PlaySoundOnSelect = false,
Visible = false,
OnClicked = (button, o) =>
{
if (currentSwapFunc != null)
{
PlaySwapSound();
currentSwapFunc.Invoke();
}
return false;
}
};
enabledModsList.OnSelected = (frame, o) =>
{
disabledModsList.Deselect();
centerButton.Visible = true;
centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleRight"));
SetSwapFunc(enabledModsList, disabledModsList);
return true;
};
disabledModsList.OnSelected = (frame, o) =>
{
enabledModsList.Deselect();
centerButton.Visible = true;
centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleLeft"));
SetSwapFunc(disabledModsList, enabledModsList);
return true;
};
var filterContainer = new GUILayoutGroup(NewItemRectT(mainLayout, heightScale: 1.0f), isHorizontal: true)
{ Stretch = true, RelativeSpacing = 0.01f };
void padFilterContainer(float width = 0.25f)
=> new GUIFrame(new RectTransform((width, 1.0f), filterContainer!.RectTransform), style: null);
GUIButton filterLayoutButton(string style)
=> new GUIButton(
new RectTransform(Vector2.One, filterContainer!.RectTransform, scaleBasis: ScaleBasis.BothHeight),
"", style: style);
padFilterContainer(width: 0.2f);
var loadPresetBtn = filterLayoutButton("OpenButton");
loadPresetBtn.ToolTip = TextManager.Get("LoadModListPresetHeader");
loadPresetBtn.OnClicked = OpenLoadPreset;
var savePresetBtn = filterLayoutButton("SaveButton");
savePresetBtn.ToolTip = TextManager.Get("SaveModListPresetHeader");
savePresetBtn.OnClicked = OpenSavePreset;
padFilterContainer(width: 0.05f);
var searchRectT = new RectTransform((0.5f, 1.0f), filterContainer.RectTransform);
var searchBox = CreateSearchBox(searchRectT);
modsListFilter = searchBox;
var filterTickboxes = new Dictionary<Filter, GUITickBox>();
modsListFilterTickboxes = filterTickboxes;
var filterTickboxesDropdown
= filterLayoutButton("SetupVisibilityButton");
var filterTickboxesContainer
= new GUIFrame(new RectTransform((0.3f, 0.2f), content.RectTransform,
scaleBasis: ScaleBasis.BothWidth), style: "InnerFrame");
var filterTickboxesUpdater
= new GUICustomComponent(new RectTransform(Vector2.Zero, content.RectTransform),
onUpdate: (f, component) =>
{
filterTickboxesContainer.Visible = filterTickboxesDropdown.Selected;
filterTickboxesContainer.RectTransform.AbsoluteOffset
= (filterTickboxesDropdown.Rect.Location - content.Rect.Location)
+ (filterTickboxesDropdown.Rect.Width / 2, 0)
- (filterTickboxesContainer.Rect.Size.ToVector2() * (0.5f, 1.0f)).ToPoint();
filterTickboxesContainer.RectTransform.NonScaledSize
= new Point(filterTickboxes.Select(tb => (int)tb.Value.Font.MeasureString(tb.Value.GetChild<GUITextBlock>().Text).X).Max(),
filterTickboxes.Select(tb => tb.Value.Rect.Height).Aggregate((a,b) => a+b))
+(filterTickboxes.Values.First().Rect.Height * 4, filterTickboxes.Values.First().Rect.Height / 2);
if (PlayerInput.PrimaryMouseButtonClicked()
&& !GUI.IsMouseOn(filterTickboxesDropdown)
&& !GUI.IsMouseOn(filterTickboxesContainer))
{
filterTickboxesDropdown.Selected = false;
}
});
var filterTickboxesLayout
= new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, filterTickboxesContainer.RectTransform, Anchor.Center));
void addFilterTickbox(Filter filter, string? style, bool selected)
{
var tickbox = new GUITickBox(NewItemRectT(filterTickboxesLayout!, heightScale: 0.5f), "")
{
Selected = selected,
OnSelected = _ =>
{
UpdateModListItemVisibility();
return true;
}
};
filterTickboxes!.Add(filter, tickbox);
var text = new GUITextBlock(new RectTransform((1.0f, 1.0f), tickbox.RectTransform, Anchor.CenterRight)
{
AbsoluteOffset = (-tickbox.Box.Rect.Width * 2, 0),
},
TextManager.Get($"ModFilter.{filter}"))
{
CanBeFocused = false
};
var icon = new GUIFrame(
new RectTransform(Vector2.One, text.RectTransform, Anchor.CenterLeft, Pivot.CenterRight,
scaleBasis: ScaleBasis.BothHeight), style: style)
{
CanBeFocused = false
};
}
addFilterTickbox(Filter.ShowLocal, "WorkshopMenu.EditButton", selected: true);
addFilterTickbox(Filter.ShowWorkshop, "WorkshopMenu.DownloadedIcon", selected: true);
addFilterTickbox(Filter.ShowPublished, "WorkshopMenu.PublishedIcon", selected: true);
addFilterTickbox(Filter.ShowOnlySubs, null, selected: false);
addFilterTickbox(Filter.ShowOnlyItemAssemblies, null, selected: false);
padFilterContainer();
new GUICustomComponent(new RectTransform(Vector2.Zero, content.RectTransform),
onUpdate: (f, component) =>
{
HandleDraggingAcrossModLists(enabledModsList, disabledModsList);
HandleDraggingAcrossModLists(disabledModsList, enabledModsList);
UpdateDraggingSounds();
if (PlayerInput.PrimaryMouseButtonClicked()
&& !GUI.IsMouseOn(enabledModsList)
&& !GUI.IsMouseOn(disabledModsList)
&& GUIContextMenu.CurrentContextMenu is null)
{
enabledModsList.Deselect();
disabledModsList.Deselect();
}
else if (!PlayerInput.IsCtrlDown() && !PlayerInput.IsShiftDown() && PlayerInput.DoubleClicked())
{
currentSwapFunc?.Invoke();
}
},
onDraw: (spriteBatch, component) =>
{
enabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
disabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
});
void UpdateDraggingSounds()
{
if (draggedElement != null)
{
if (enabledModsList.DraggedElement == null && disabledModsList.DraggedElement == null)
{
SetDragOrigin(null);
}
CheckDragStopSound(enabledModsList);
CheckDragStopSound(disabledModsList);
}
else if (enabledModsList.DraggedElement != null)
{
SetDragOrigin(enabledModsList);
}
else if (disabledModsList.DraggedElement != null)
{
SetDragOrigin(disabledModsList);
}
void SetDragOrigin(GUIListBox? listBox)
{
draggedElement = listBox?.DraggedElement;
draggedElementOrigin = listBox;
}
void CheckDragStopSound(GUIListBox listBox)
{
listBox.PlaySoundOnDragStop = listBox.DraggedElement != null && draggedElementOrigin != listBox;
}
}
}
protected override void UpdateModListItemVisibility()
{
string str = modsListFilter.Text;
enabledRegularModsList.Content.Children.Concat(disabledRegularModsList.Content.Children)
.ForEach(c => c.Visible = !(c.UserData is ContentPackage p)
|| ModNameMatches(p, str) && ModMatchesTickboxes(p, c));
}
private bool ModMatchesTickboxes(ContentPackage p, GUIComponent guiItem)
{
var iconBtn = guiItem.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
bool matches = false;
matches |= modsListFilterTickboxes[Filter.ShowLocal].Selected
&& ContentPackageManager.LocalPackages.Contains(p);
matches |= modsListFilterTickboxes[Filter.ShowPublished].Selected
&& (ContentPackageManager.WorkshopPackages.Contains(p)
&& iconBtn?.Style?.Identifier == "WorkshopMenu.PublishedIcon");
matches |= modsListFilterTickboxes[Filter.ShowWorkshop].Selected
&& (ContentPackageManager.WorkshopPackages.Contains(p)
&& iconBtn?.Style?.Identifier != "WorkshopMenu.PublishedIcon");
if (modsListFilterTickboxes[Filter.ShowOnlySubs].Selected
&& modsListFilterTickboxes[Filter.ShowOnlyItemAssemblies].Selected
&& p.Files.All(f => f is BaseSubFile || f is ItemAssemblyFile))
{
//Both the subs-only tickbox and the item-assembly-only tickbox
//are enabled, and all files match either of them so show this mod
}
else if (modsListFilterTickboxes[Filter.ShowOnlySubs].Selected
&& p.Files.Any(f => !(f is BaseSubFile)))
{
matches = false;
}
else if (modsListFilterTickboxes[Filter.ShowOnlyItemAssemblies].Selected
&& p.Files.Any(f => !(f is ItemAssemblyFile)))
{
matches = false;
}
return matches;
}
private void PrepareToShowModInfo(ContentPackage mod)
{
TaskPool.Add($"PrepareToShow{mod.SteamWorkshopId}Info", SteamManager.Workshop.GetItem(mod.SteamWorkshopId),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item? item)) { return; }
if (item is null) { return; }
onInstalledInfoButtonHit(item.Value);
});
}
public void PopulateInstalledModLists(bool forceRefreshEnabled = false, bool refreshDisabled = true)
{
bulkUpdateButton.Enabled = false;
bulkUpdateButton.ToolTip = "";
ContentPackageManager.UpdateContentPackageList();
SwapDropdownValues<CorePackage>(enabledCoreDropdown,
(p) => p.Name,
ContentPackageManager.CorePackages.ToArray(),
ContentPackageManager.EnabledPackages.Core!,
(p) => { });
void addRegularModToList(RegularPackage mod, GUIListBox list)
{
var modFrame = new GUIFrame(new RectTransform((1.0f, 0.08f), list.Content.RectTransform),
style: "ListBoxElement")
{
UserData = mod
};
var contextMenuHandler = new GUICustomComponent(new RectTransform(Vector2.Zero, modFrame.RectTransform),
onUpdate: (f, component) =>
{
var parentList = modFrame.Parent?.Parent?.Parent as GUIListBox; //lovely jank :)
if (parentList is null) { return; }
if (GUI.MouseOn == modFrame && parentList.DraggedElement is null && PlayerInput.SecondaryMouseButtonClicked())
{
if (!parentList.AllSelected.Contains(modFrame)) { parentList.Select(parentList.Content.GetChildIndex(modFrame)); }
static void noop() { }
List<ContextMenuOption> contextMenuOptions = new List<ContextMenuOption>();
if (ContentPackageManager.WorkshopPackages.Contains(mod))
{
contextMenuOptions.Add(
new ContextMenuOption("ViewWorkshopModDetails".ToIdentifier(), isEnabled: true, onSelected: () => PrepareToShowModInfo(mod)));
}
var labelConditions
= (parentList == enabledRegularModsList, parentList.AllSelected.Count > 1);
Identifier swapLabel = (labelConditions switch
{
(true, true) => "EnableSelectedWorkshopMods",
(true, false) => "EnableWorkshopMod",
(false, true) => "DisableSelectedWorkshopMods",
(false, false) => "DisableWorkshopMod"
}).ToIdentifier();
contextMenuOptions.Add(new ContextMenuOption(swapLabel,
isEnabled: true, onSelected: currentSwapFunc ?? noop));
var selectedMods = parentList.AllSelected.Select(it => it.UserData)
.OfType<ContentPackage>().ToArray();
if (selectedMods.All(ContentPackageManager.LocalPackages.Contains) && selectedMods.Length > 1)
{
contextMenuOptions.Add(new ContextMenuOption("MergeSelectedMods".ToIdentifier(), isEnabled: true,
onSelected: () => ModMerger.AskMerge(selectedMods)));
}
GUIButton? iconBtn(GUIComponent component) => component.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
if (selectedMods.All(ContentPackageManager.WorkshopPackages.Contains)
&& parentList.AllSelected.All(c => iconBtn(c)?.Style?.Identifier == "WorkshopMenu.DownloadedIcon")
&& selectedMods.Length > 0)
{
contextMenuOptions.Add(new ContextMenuOption(
(selectedMods.Length > 1 ? "UnsubscribeFromAllSelected" : "WorkshopItemUnsubscribe").ToIdentifier(),
isEnabled: true,
onSelected: () =>
{
TaskPool.Add($"UnsubFromSelected", Task.WhenAll(selectedMods.Select(m => SteamManager.Workshop.GetItem(m.SteamWorkshopId))),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item?[] items)) { return; }
items.ForEach(it =>
{
if (!(it is { } item)) { return; }
item.Unsubscribe();
SteamManager.Workshop.Uninstall(item);
PopulateInstalledModLists();
});
});
}));
}
GUIContextMenu.CreateContextMenu(
pos: PlayerInput.MousePosition,
header: ToolBox.LimitString(mod.Name, GUIStyle.SubHeadingFont, GUI.IntScale(300f)),
headerColor: null,
contextMenuOptions.ToArray());
}
});
var frameContent = new GUILayoutGroup(new RectTransform((0.95f, 0.9f), modFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var dragIndicator = new GUIButton(new RectTransform((0.5f, 0.5f), frameContent.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: "GUIDragIndicator")
{
CanBeFocused = false
};
var modNameScissor = new GUIScissorComponent(new RectTransform((0.8f, 1.0f), frameContent.RectTransform))
{
CanBeFocused = false
};
var modName = new GUITextBlock(new RectTransform(Vector2.One, modNameScissor.Content.RectTransform),
text: mod.Name)
{
CanBeFocused = false
};
if (mod.Errors.Any())
{
CreateModErrorInfo(mod, modFrame, modName);
}
if (ContentPackageManager.LocalPackages.Contains(mod))
{
var editButton = new GUIButton(new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: "WorkshopMenu.EditButton")
{
OnClicked = (button, o) =>
{
ToolBox.OpenFileWithShell(mod.Dir);
return false;
},
ToolTip = TextManager.Get("OpenLocalModInExplorer")
};
}
else if (ContentPackageManager.WorkshopPackages.Contains(mod))
{
var infoButton = new GUIButton(
new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: null)
{
CanBeSelected = false,
OnClicked = (button, o) =>
{
PrepareToShowModInfo(mod);
return false;
}
};
if (!SteamManager.IsInitialized)
{
infoButton.Enabled = false;
}
TaskPool.Add(
$"DetermineUpdateRequired{mod.SteamWorkshopId}",
mod.IsUpToDate(),
t =>
{
if (!t.TryGetResult(out bool isUpToDate)) { return; }
if (!isUpToDate)
{
infoButton.CanBeSelected = true;
infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
bulkUpdateButton.Enabled = true;
bulkUpdateButton.ToolTip = TextManager.Get("ModUpdatesAvailable");
}
});
}
}
void addRegularModsToList(IEnumerable<RegularPackage> mods, GUIListBox list)
{
list.ClearChildren();
foreach (var mod in mods)
{
addRegularModToList(mod, list);
}
}
var enabledMods =
(forceRefreshEnabled || (enabledRegularModsList.Content.CountChildren + disabledRegularModsList.Content.CountChildren == 0)
? ContentPackageManager.EnabledPackages.Regular
: enabledRegularModsList.Content.Children
.Select(c => c.UserData)
.OfType<RegularPackage>()
.Where(p => ContentPackageManager.RegularPackages.Contains(p)))
.ToArray();
var disabledMods = ContentPackageManager.RegularPackages.Where(p => !enabledMods.Contains(p));
addRegularModsToList(enabledMods, enabledRegularModsList);
if (refreshDisabled) { addRegularModsToList(disabledMods, disabledRegularModsList); }
TaskPool.Add(
$"DetermineWorkshopModIcons",
SteamManager.Workshop.GetPublishedItems(),
t =>
{
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> items)) { return; }
var ids = items.Select(it => it.Id).ToHashSet();
foreach (var child in enabledRegularModsList.Content.Children
.Concat(disabledRegularModsList.Content.Children))
{
var mod = child.UserData as RegularPackage;
if (mod is null || !ContentPackageManager.WorkshopPackages.Contains(mod)) { continue; }
var btn = child.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
if (btn is null) { continue; }
if (btn.Style != null) { continue; }
btn.ApplyStyle(
GUIStyle.GetComponentStyle(
ids.Contains(mod.SteamWorkshopId)
? "WorkshopMenu.PublishedIcon"
: "WorkshopMenu.DownloadedIcon"));
btn.ToolTip = TextManager.Get(
ids.Contains(mod.SteamWorkshopId)
? "PublishedWorkshopMod"
: "DownloadedWorkshopMod");
btn.HoverCursor = CursorState.Default;
}
});
UpdateModListItemVisibility();
}
}
}
@@ -151,7 +151,10 @@ namespace Barotrauma.Steam
onDeselected: () => itemList?.Deselect(), onDeselected: () => itemList?.Deselect(),
out var select, out var deselect); out var select, out var deselect);
itemList = new GUIListBox(new RectTransform(Vector2.One, outerContainer.Content.RectTransform)); itemList = new GUIListBox(new RectTransform(Vector2.One, outerContainer.Content.RectTransform))
{
PlaySoundOnSelect = true,
};
itemList.RectTransform.SetAsFirstChild(); itemList.RectTransform.SetAsFirstChild();
workshopItemList = itemList; workshopItemList = itemList;
@@ -0,0 +1,256 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.IO;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
readonly struct ModListPreset
{
public const string SavePath = "ModLists";
public enum ModType
{
Vanilla,
Local,
Workshop
}
public readonly string Name;
public readonly CorePackage CorePackage;
public readonly ImmutableArray<RegularPackage> RegularPackages;
public ModListPreset(XDocument doc)
{
Name = doc.Root!.GetAttributeString("name", "");
CorePackage corePackage = ContentPackageManager.VanillaCorePackage!;
List<RegularPackage> regularPackages = new List<RegularPackage>();
void addPkg(ContentPackage pkg)
{
if (pkg is CorePackage core) { corePackage = core; }
else if (pkg is RegularPackage reg) { regularPackages.Add(reg); }
}
foreach (var element in doc.Root!.Elements())
{
ModType modType = Enum.TryParse<ModType>(element.Name.LocalName, ignoreCase: true, out var mt) ? mt : ModType.Local;
switch (modType)
{
case ModType.Vanilla:
CorePackage = ContentPackageManager.VanillaCorePackage!;
break;
case ModType.Workshop:
{
var id = element.GetAttributeUInt64("id", 0);
var pkg = ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == id);
if (id != 0 && pkg != null)
{
addPkg(pkg);
}
}
break;
case ModType.Local:
{
var name = element.GetAttributeString("name", "");
var pkg = ContentPackageManager.LocalPackages.FirstOrDefault(p => p.NameMatches(name));
if (!name.IsNullOrEmpty() && pkg != null)
{
addPkg(pkg);
}
}
break;
}
}
CorePackage = corePackage;
RegularPackages = regularPackages.ToImmutableArray();
}
public ModListPreset(string name, CorePackage corePackage, IReadOnlyList<RegularPackage> regularPackages)
{
Name = name;
CorePackage = corePackage;
RegularPackages = regularPackages.ToImmutableArray();
}
public RichString GetTooltip()
{
LocalizedString retVal = $"‖color:gui.orange‖{Name}‖end‖" //TODO: we need a RichString builder
+ "\n " + TextManager.AddPunctuation(':', TextManager.Get("CorePackage"))
+ "\n - " + CorePackage.Name;
if (RegularPackages.Any())
{
retVal += "\n " + TextManager.AddPunctuation(':', TextManager.Get("RegularPackages"))
+ "\n - "
+ LocalizedString.Join("\n - ", RegularPackages.Select(p => (LocalizedString)p.Name));
}
return RichString.Rich(retVal);
}
public void Save()
{
XDocument newDoc = new XDocument();
XElement newRoot = new XElement("mods", new XAttribute("name", Name));
newDoc.Add(newRoot);
ModType determineType(ContentPackage pkg)
{
if (pkg == ContentPackageManager.VanillaCorePackage) { return ModType.Vanilla; }
if (ContentPackageManager.WorkshopPackages.Contains(pkg)) { return ModType.Workshop; }
return ModType.Local;
}
void writePkgElem(ContentPackage pkg)
{
var pkgType = determineType(pkg);
var pkgElem = new XElement(pkgType.ToString());
switch (pkgType)
{
case ModType.Workshop:
pkgElem.SetAttributeValue("name", pkg.Name);
pkgElem.SetAttributeValue("id", pkg.SteamWorkshopId.ToString());
break;
case ModType.Local:
pkgElem.SetAttributeValue("name", pkg.Name);
break;
}
newRoot.Add(pkgElem);
}
writePkgElem(CorePackage);
RegularPackages.ForEach(writePkgElem);
if (!Directory.Exists(SavePath)) { Directory.CreateDirectory(SavePath); }
newDoc.SaveSafe(Path.Combine(SavePath, ToolBox.RemoveInvalidFileNameChars($"{Name}.xml")));
}
}
}
namespace Barotrauma.Steam
{
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
private bool OpenLoadPreset(GUIButton _, object __)
{
OpenLoadPreset();
return false;
}
private void OpenLoadPreset()
{
var msgBox = new GUIMessageBox(
TextManager.Get("LoadModListPresetHeader"),
"",
buttons: new [] { TextManager.Get("Load"), TextManager.Get("Cancel") },
relativeSize: (0.4f, 0.6f));
var presetListBox = new GUIListBox(new RectTransform((1.0f, 0.7f), msgBox.Content.RectTransform));
(string Path, XDocument? Doc) tryLoadXml(string path)
=> (path, XMLExtensions.TryLoadXml(path));
var presets = Directory.Exists(ModListPreset.SavePath)
? Directory.GetFiles(ModListPreset.SavePath)
.Select(tryLoadXml)
.Where(d => d.Doc != null)
.ToArray()
: Array.Empty<(string Path, XDocument? Doc)>();
foreach (var doc in presets)
{
ModListPreset preset = new ModListPreset(doc.Doc!);
var presetFrame = new GUIFrame(new RectTransform((1.0f, 0.09f), presetListBox.Content.RectTransform),
style: "ListBoxElement")
{
UserData = preset,
ToolTip = preset.GetTooltip()
};
new GUITextBlock(new RectTransform(Vector2.One, presetFrame.RectTransform), preset.Name)
{
CanBeFocused = false
};
var deleteBtn
= new GUIButton(new RectTransform((0.2f, 1.0f), presetFrame.RectTransform, Anchor.CenterRight),
TextManager.Get("Delete"), style: "GUIButtonSmall")
{
OnClicked = (button, o) =>
{
File.Delete(doc.Path);
presetListBox.Content.RemoveChild(presetFrame);
return false;
}
};
}
msgBox.Buttons[0].OnClicked = (button, o) =>
{
if (presetListBox.SelectedData is ModListPreset preset)
{
var allChildren = enabledRegularModsList.Content.Children
.Concat(disabledRegularModsList.Content.Children)
.ToArray();
enabledRegularModsList.ClearChildren();
disabledRegularModsList.ClearChildren();
var toEnable =
allChildren.Where(c => c.UserData is RegularPackage p
&& preset.RegularPackages.Contains(p))
.OrderBy(c => c.UserData is RegularPackage p ? preset.RegularPackages.IndexOf(p) : int.MaxValue)
.ToArray();
var toDisable = allChildren.Where(c => !toEnable.Contains(c)).ToArray();
toEnable.ForEach(c => c.RectTransform.Parent = enabledRegularModsList.Content.RectTransform);
toDisable.ForEach(c => c.RectTransform.Parent = disabledRegularModsList.Content.RectTransform);
enabledCoreDropdown.SelectItem(preset.CorePackage);
}
msgBox.Close();
return false;
};
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
private bool OpenSavePreset(GUIButton _, object __)
{
OpenSavePreset();
return false;
}
private void OpenSavePreset()
{
var msgBox = new GUIMessageBox(
TextManager.Get("SaveModListPresetHeader"),
"",
buttons: new [] { TextManager.Get("Save"), TextManager.Get("Cancel") },
relativeSize: (0.4f, 0.2f));
var nameBox = new GUITextBox(new RectTransform((1.0f, 0.3f), msgBox.Content.RectTransform), "");
msgBox.Buttons[0].OnClicked = (button, o) =>
{
if (nameBox.Text.IsNullOrEmpty())
{
nameBox.Flash(GUIStyle.Red);
return false;
}
if (enabledCoreDropdown.SelectedData is CorePackage corePackage)
{
ModListPreset preset = new ModListPreset(nameBox.Text,
corePackage,
enabledRegularModsList.Content.Children
.Select(c => c.UserData)
.OfType<RegularPackage>().ToArray());
preset.Save();
}
msgBox.Close();
return false;
};
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
}
}
@@ -3,9 +3,9 @@ using Barotrauma.Extensions;
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 System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>; using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
namespace Barotrauma.Steam namespace Barotrauma.Steam
@@ -20,20 +20,20 @@ namespace Barotrauma.Steam
Publish Publish
} }
private enum Filter
{
ShowLocal,
ShowWorkshop,
ShowPublished,
ShowOnlySubs,
ShowOnlyItemAssemblies
}
private readonly GUILayoutGroup tabber; private readonly GUILayoutGroup tabber;
private readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents; private readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
private readonly GUIFrame contentFrame; private readonly GUIFrame contentFrame;
private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected");
private readonly GUIDropDown enabledCoreDropdown;
private readonly GUIListBox enabledRegularModsList;
private readonly GUIListBox disabledRegularModsList;
private readonly Action<ItemOrPackage> onInstalledInfoButtonHit;
private readonly GUITextBox modsListFilter;
private readonly GUIButton bulkUpdateButton;
private CancellationTokenSource taskCancelSrc = new CancellationTokenSource(); private CancellationTokenSource taskCancelSrc = new CancellationTokenSource();
private readonly HashSet<SteamManager.Workshop.ItemThumbnail> itemThumbnails = new HashSet<SteamManager.Workshop.ItemThumbnail>(); private readonly HashSet<SteamManager.Workshop.ItemThumbnail> itemThumbnails = new HashSet<SteamManager.Workshop.ItemThumbnail>();
@@ -41,7 +41,7 @@ namespace Barotrauma.Steam
private readonly GUIListBox selfModsList; private readonly GUIListBox selfModsList;
private uint memSubscribedModCount = 0; private uint memSubscribedModCount = 0;
public MutableWorkshopMenu(GUIFrame parent) : base(parent) public MutableWorkshopMenu(GUIFrame parent) : base(parent)
{ {
var mainLayout var mainLayout
@@ -62,6 +62,7 @@ namespace Barotrauma.Steam
out disabledRegularModsList, out disabledRegularModsList,
out onInstalledInfoButtonHit, out onInstalledInfoButtonHit,
out modsListFilter, out modsListFilter,
out modsListFilterTickboxes,
out bulkUpdateButton); out bulkUpdateButton);
CreatePopularModsTab(out popularModsList); CreatePopularModsTab(out popularModsList);
CreatePublishTab(out selfModsList); CreatePublishTab(out selfModsList);
@@ -69,45 +70,6 @@ namespace Barotrauma.Steam
SelectTab(Tab.InstalledMods); SelectTab(Tab.InstalledMods);
} }
private void UpdateSubscribedModInstalls()
{
if (!SteamManager.IsInitialized) { return; }
uint numSubscribedMods = SteamManager.GetNumSubscribedItems();
if (numSubscribedMods == memSubscribedModCount) { return; }
memSubscribedModCount = numSubscribedMods;
var subscribedIds = SteamManager.GetSubscribedItems().ToHashSet();
var installedIds = ContentPackageManager.WorkshopPackages.Select(p => p.SteamWorkshopId).ToHashSet();
foreach (var id in subscribedIds.Where(id2 => !installedIds.Contains(id2)))
{
Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id);
if (!item.IsDownloading && !SteamManager.Workshop.IsInstalling(item))
{
SteamManager.Workshop.DownloadModThenEnqueueInstall(item);
}
}
TaskPool.Add("RemoveUnsubscribedItems", SteamManager.Workshop.GetPublishedItems(), t =>
{
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> publishedItems)) { return; }
var allRequiredInstalled = subscribedIds.Union(publishedItems.Select(it => it.Id)).ToHashSet();
bool needsRefresh = false;
foreach (var id in installedIds.Where(id2 => !allRequiredInstalled.Contains(id2)))
{
Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id);
SteamManager.Workshop.Uninstall(item);
needsRefresh = true;
}
if (needsRefresh)
{
PopulateInstalledModLists();
}
});
}
private void SwitchContent(GUIFrame newContent) private void SwitchContent(GUIFrame newContent)
{ {
contentFrame.Children.ForEach(c => c.Visible = false); contentFrame.Children.ForEach(c => c.Visible = false);
@@ -161,460 +123,6 @@ namespace Barotrauma.Steam
return content; return content;
} }
private static (GUILayoutGroup Left, GUIFrame center, GUILayoutGroup Right) CreateSidebars(
GUIComponent parent,
float leftWidth = 0.3875f,
float centerWidth = 0.025f,
float rightWidth = 0.5875f,
bool split = false,
float height = 1.0f)
{
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform((1.0f, height), parent.RectTransform), isHorizontal: true);
GUILayoutGroup left = new GUILayoutGroup(new RectTransform((leftWidth, 1.0f), layout.RectTransform), isHorizontal: false);
var center = new GUIFrame(new RectTransform((centerWidth, 1.0f), layout.RectTransform), style: null);
if (split)
{
new GUICustomComponent(new RectTransform(Vector2.One, center.RectTransform),
onDraw: (sb, c) =>
{
sb.DrawLine((c.Rect.Center.X, c.Rect.Top), (c.Rect.Center.X, c.Rect.Bottom), GUIStyle.TextColorDim, 2f);
});
}
GUILayoutGroup right = new GUILayoutGroup(new RectTransform((rightWidth, 1.0f), layout.RectTransform), isHorizontal: false);
return (left, center, right);
}
private void HandleDraggingAcrossModLists(GUIListBox from, GUIListBox to)
{
if (to.Rect.Contains(PlayerInput.MousePosition) && from.DraggedElement != null)
{
//move the dragged elements to the index determined previously
var draggedElement = from.DraggedElement;
var selected = from.AllSelected.ToList();
selected.Sort((a, b) => from.Content.GetChildIndex(a) - from.Content.GetChildIndex(b));
float oldCount = to.Content.CountChildren;
float newCount = oldCount + selected.Count;
var offset = draggedElement.RectTransform.AbsoluteOffset;
offset += from.Content.Rect.Location;
offset -= to.Content.Rect.Location;
for (int i = 0; i < selected.Count; i++)
{
var c = selected[i];
c.Parent.RemoveChild(c);
c.RectTransform.Parent = to.Content.RectTransform;
c.RectTransform.RepositionChildInHierarchy((int)oldCount+i);
}
from.DraggedElement = null;
from.Deselect();
from.RecalculateChildren();
from.RectTransform.RecalculateScale(true);
to.RecalculateChildren();
to.RectTransform.RecalculateScale(true);
to.Select(selected);
//recalculate the dragged element's offset so it doesn't jump around
draggedElement.RectTransform.AbsoluteOffset = offset;
to.DraggedElement = draggedElement;
to.BarScroll *= (oldCount / newCount);
}
}
private Action? currentSwapFunc = null;
private void SetSwapFunc(GUIListBox from, GUIListBox to)
{
currentSwapFunc = () =>
{
to.Deselect();
var selected = from.AllSelected.ToArray();
foreach (var frame in selected)
{
frame.Parent.RemoveChild(frame);
frame.RectTransform.Parent = to.Content.RectTransform;
}
from.RecalculateChildren();
from.RectTransform.RecalculateScale(true);
to.RecalculateChildren();
to.RectTransform.RecalculateScale(true);
to.Select(selected);
};
}
private void CreateInstalledModsTab(
out GUIDropDown enabledCoreDropdown,
out GUIListBox enabledRegularModsList,
out GUIListBox disabledRegularModsList,
out Action<ItemOrPackage> onInstalledInfoButtonHit,
out GUITextBox modsListFilter,
out GUIButton bulkUpdateButton)
{
GUIFrame content = CreateNewContentFrame(Tab.InstalledMods);
CreateWorkshopItemDetailContainer(
content,
out var outerContainer,
onSelected: (itemOrPackage, selectedFrame) =>
{
if (itemOrPackage.TryGet(out Steamworks.Ugc.Item item)) { PopulateFrameWithItemInfo(item, selectedFrame); }
},
onDeselected: () => PopulateInstalledModLists(),
out onInstalledInfoButtonHit, out var deselect);
GUILayoutGroup mainLayout =
new GUILayoutGroup(new RectTransform(Vector2.One, outerContainer.Content.RectTransform), childAnchor: Anchor.TopCenter);
mainLayout.RectTransform.SetAsFirstChild();
var (topLeft, _, topRight) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.13f);
topLeft.Stretch = true;
Label(topLeft, TextManager.Get("enabledcore"), GUIStyle.SubHeadingFont, heightScale: 1.0f);
enabledCoreDropdown = Dropdown<CorePackage>(topLeft,
(p) => p.Name,
ContentPackageManager.CorePackages.ToArray(),
ContentPackageManager.EnabledPackages.Core!,
(p) => { },
heightScale: 1.0f / 13.0f);
Label(topLeft, "", GUIStyle.SubHeadingFont, heightScale: 1.0f);
topRight.ChildAnchor = Anchor.CenterLeft;
var topRightButtons = new GUILayoutGroup(new RectTransform((1.0f, 0.5f), topRight.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.05f
};
void padTopRight(float width=1.0f)
{
new GUIFrame(new RectTransform((width, 1.0f), topRightButtons.RectTransform), style: null);
}
padTopRight();
//TODO: put stuff here
padTopRight(width: 3.0f);
var refreshListsButton
= new GUIButton(
new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
text: "", style: "GUIReloadButton")
{
OnClicked = (b, o) =>
{
PopulateInstalledModLists();
return false;
},
ToolTip = TextManager.Get("RefreshModLists")
};
bulkUpdateButton
= new GUIButton(
new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
text: "", style: "GUIUpdateButton")
{
OnClicked = (b, o) =>
{
BulkDownloader.PrepareUpdates();
return false;
},
Enabled = false
};
padTopRight(width: 0.1f);
var (left, center, right) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.8f);
right.ChildAnchor = Anchor.TopRight;
//enabled mods
Label(left, TextManager.Get("enabledregular"), GUIStyle.SubHeadingFont);
var enabledModsList = new GUIListBox(new RectTransform((1.0f, 0.93f), left.RectTransform))
{
CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
HideDraggedElement = true
};
enabledRegularModsList = enabledModsList;
//disabled mods
Label(right, TextManager.Get("disabledregular"), GUIStyle.SubHeadingFont);
var disabledModsList = new GUIListBox(new RectTransform((1.0f, 0.93f), right.RectTransform))
{
CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
HideDraggedElement = true
};
disabledRegularModsList = disabledModsList;
var centerButton =
new GUIButton(
new RectTransform(Vector2.One * 0.95f, center.RectTransform, scaleBasis: ScaleBasis.BothWidth,
anchor: Anchor.Center),
style: "GUIButtonToggleLeft")
{
Visible = false,
OnClicked = (button, o) =>
{
currentSwapFunc?.Invoke();
return false;
}
};
enabledModsList.OnSelected = (frame, o) =>
{
disabledModsList.Deselect();
centerButton.Visible = true;
centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleRight"));
SetSwapFunc(enabledModsList, disabledModsList);
return true;
};
disabledModsList.OnSelected = (frame, o) =>
{
enabledModsList.Deselect();
centerButton.Visible = true;
centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleLeft"));
SetSwapFunc(disabledModsList, enabledModsList);
return true;
};
var searchBox = CreateSearchBox(mainLayout, width: 0.5f);
modsListFilter = searchBox;
new GUICustomComponent(new RectTransform(Vector2.Zero, content.RectTransform),
onUpdate: (f, component) =>
{
HandleDraggingAcrossModLists(enabledModsList, disabledModsList);
HandleDraggingAcrossModLists(disabledModsList, enabledModsList);
if (PlayerInput.PrimaryMouseButtonClicked()
&& !GUI.IsMouseOn(enabledModsList)
&& !GUI.IsMouseOn(disabledModsList)
&& GUIContextMenu.CurrentContextMenu is null)
{
enabledModsList.Deselect();
disabledModsList.Deselect();
}
else if (!PlayerInput.IsCtrlDown() && !PlayerInput.IsShiftDown() && PlayerInput.DoubleClicked())
{
currentSwapFunc?.Invoke();
}
},
onDraw: (spriteBatch, component) =>
{
enabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
disabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
});
}
protected override void UpdateModListItemVisibility()
{
string str = modsListFilter.Text;
enabledRegularModsList.Content.Children.Concat(disabledRegularModsList.Content.Children)
.ForEach(c => c.Visible = str.IsNullOrWhiteSpace()
|| (c.UserData is ContentPackage p
&& p.Name.Contains(str, StringComparison.OrdinalIgnoreCase)));
}
private void PrepareToShowModInfo(ContentPackage mod)
{
TaskPool.Add($"PrepareToShow{mod.SteamWorkshopId}Info", SteamManager.Workshop.GetItem(mod.SteamWorkshopId),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item? item)) { return; }
if (item is null) { return; }
onInstalledInfoButtonHit(item.Value);
});
}
public void PopulateInstalledModLists(bool forceRefreshEnabled = false, bool refreshDisabled = true)
{
bulkUpdateButton.Enabled = false;
bulkUpdateButton.ToolTip = "";
ContentPackageManager.UpdateContentPackageList();
SwapDropdownValues<CorePackage>(enabledCoreDropdown,
(p) => p.Name,
ContentPackageManager.CorePackages.ToArray(),
ContentPackageManager.EnabledPackages.Core!,
(p) => { });
void addRegularModToList(RegularPackage mod, GUIListBox list)
{
var modFrame = new GUIFrame(new RectTransform((1.0f, 0.08f), list.Content.RectTransform),
style: "ListBoxElement")
{
UserData = mod
};
var contextMenuHandler = new GUICustomComponent(new RectTransform(Vector2.Zero, modFrame.RectTransform),
onUpdate: (f, component) =>
{
var parentList = modFrame.Parent?.Parent?.Parent as GUIListBox; //lovely jank :)
if (parentList is null) { return; }
if (GUI.MouseOn == modFrame && parentList.DraggedElement is null && PlayerInput.SecondaryMouseButtonClicked())
{
if (!parentList.AllSelected.Contains(modFrame)) { parentList.Select(parentList.Content.GetChildIndex(modFrame)); }
static void noop() { }
List<ContextMenuOption> contextMenuOptions = new List<ContextMenuOption>();
if (ContentPackageManager.WorkshopPackages.Contains(mod))
{
contextMenuOptions.Add(
new ContextMenuOption("ViewWorkshopModDetails".ToIdentifier(), isEnabled: true, onSelected: () => PrepareToShowModInfo(mod)));
}
Identifier swapLabel
= ((parentList == enabledRegularModsList ? "Disable" : "Enable")
+ (parentList.AllSelected.Count > 1 ? "SelectedWorkshopMods" : "WorkshopMod"))
.ToIdentifier();
contextMenuOptions.Add(new ContextMenuOption(swapLabel,
isEnabled: true, onSelected: currentSwapFunc ?? noop));
GUIContextMenu.CreateContextMenu(
pos: PlayerInput.MousePosition,
header: ToolBox.LimitString(mod.Name, GUIStyle.SubHeadingFont, GUI.IntScale(300f)),
headerColor: null,
contextMenuOptions.ToArray());
}
});
var frameContent = new GUILayoutGroup(new RectTransform((0.95f, 0.9f), modFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var dragIndicator = new GUIButton(new RectTransform((0.5f, 0.5f), frameContent.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: "GUIDragIndicator")
{
CanBeFocused = false
};
var modNameScissor = new GUIScissorComponent(new RectTransform((0.8f, 1.0f), frameContent.RectTransform))
{
CanBeFocused = false
};
var modName = new GUITextBlock(new RectTransform(Vector2.One, modNameScissor.Content.RectTransform),
text: mod.Name)
{
CanBeFocused = false
};
if (mod.Errors.Any())
{
CreateModErrorInfo(mod, modFrame, modName);
}
if (ContentPackageManager.LocalPackages.Contains(mod))
{
var editButton = new GUIButton(new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: "WorkshopMenu.EditButton")
{
OnClicked = (button, o) =>
{
ToolBox.OpenFileWithShell(mod.Dir);
return false;
},
ToolTip = TextManager.Get("OpenLocalModInExplorer")
};
}
else if (ContentPackageManager.WorkshopPackages.Contains(mod))
{
var infoButton = new GUIButton(
new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: null)
{
CanBeSelected = false,
OnClicked = (button, o) =>
{
PrepareToShowModInfo(mod);
return false;
}
};
if (!SteamManager.IsInitialized)
{
infoButton.Enabled = false;
}
TaskPool.Add(
$"DetermineUpdateRequired{mod.SteamWorkshopId}",
mod.IsUpToDate(),
t =>
{
if (!t.TryGetResult(out bool isUpToDate)) { return; }
if (!isUpToDate)
{
infoButton.CanBeSelected = true;
infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
bulkUpdateButton.Enabled = true;
bulkUpdateButton.ToolTip = TextManager.Get("ModUpdatesAvailable");
}
});
}
}
void addRegularModsToList(IEnumerable<RegularPackage> mods, GUIListBox list)
{
list.ClearChildren();
foreach (var mod in mods)
{
addRegularModToList(mod, list);
}
}
var enabledMods =
(forceRefreshEnabled || (enabledRegularModsList.Content.CountChildren + disabledRegularModsList.Content.CountChildren == 0)
? ContentPackageManager.EnabledPackages.Regular
: enabledRegularModsList.Content.Children
.Select(c => c.UserData)
.OfType<RegularPackage>()
.Where(p => ContentPackageManager.RegularPackages.Contains(p)))
.ToArray();
var disabledMods = ContentPackageManager.RegularPackages.Where(p => !enabledMods.Contains(p));
addRegularModsToList(enabledMods, enabledRegularModsList);
if (refreshDisabled) { addRegularModsToList(disabledMods, disabledRegularModsList); }
TaskPool.Add(
$"DetermineWorkshopModIcons",
SteamManager.Workshop.GetPublishedItems(),
t =>
{
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> items)) { return; }
var ids = items.Select(it => it.Id).ToHashSet();
foreach (var child in enabledRegularModsList.Content.Children
.Concat(disabledRegularModsList.Content.Children))
{
var mod = child.UserData as RegularPackage;
if (mod is null || !ContentPackageManager.WorkshopPackages.Contains(mod)) { continue; }
var btn = child.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
if (btn is null) { continue; }
if (btn.Style != null) { continue; }
btn.ApplyStyle(
GUIStyle.GetComponentStyle(
ids.Contains(mod.SteamWorkshopId)
? "WorkshopMenu.PublishedIcon"
: "WorkshopMenu.DownloadedIcon"));
btn.ToolTip = TextManager.Get(
ids.Contains(mod.SteamWorkshopId)
? "PublishedWorkshopMod"
: "DownloadedWorkshopMod");
btn.HoverCursor = CursorState.Default;
}
});
UpdateModListItemVisibility();
}
private void CreatePopularModsTab(out GUIListBox popularModsList) private void CreatePopularModsTab(out GUIListBox popularModsList)
{ {
GUIFrame content = CreateNewContentFrame(Tab.PopularMods); GUIFrame content = CreateNewContentFrame(Tab.PopularMods);
@@ -106,10 +106,8 @@ namespace Barotrauma.Steam
=> new GUIFrame(new RectTransform(Vector2.Zero, parent.RectTransform), style: null) => new GUIFrame(new RectTransform(Vector2.Zero, parent.RectTransform), style: null)
{ UserData = new ActionCarrier(id, action) }; { UserData = new ActionCarrier(id, action) };
protected GUITextBox CreateSearchBox(GUILayoutGroup mainLayout, float width = 1.0f, float heightScale = 1.0f) protected GUITextBox CreateSearchBox(RectTransform searchRectT)
{ {
var searchRectT = NewItemRectT(mainLayout, heightScale: heightScale);
searchRectT.RelativeSize = (width, searchRectT.RelativeSize.Y);
var searchHolder = new GUIFrame(searchRectT, style: null); var searchHolder = new GUIFrame(searchRectT, style: null);
var searchBox = new GUITextBox(new RectTransform(Vector2.One, searchHolder.RectTransform), "", createClearButton: true); var searchBox = new GUITextBox(new RectTransform(Vector2.One, searchHolder.RectTransform), "", createClearButton: true);
var searchTitle = new GUITextBlock(new RectTransform(Vector2.One, searchHolder.RectTransform) {Anchor = Anchor.TopLeft}, var searchTitle = new GUITextBlock(new RectTransform(Vector2.One, searchHolder.RectTransform) {Anchor = Anchor.TopLeft},
@@ -142,7 +140,8 @@ namespace Barotrauma.Steam
const int maxErrorsToShow = 5; const int maxErrorsToShow = 5;
nameText.TextColor = GUIStyle.Red; nameText.TextColor = GUIStyle.Red;
uiElement.ToolTip = uiElement.ToolTip =
TextManager.GetWithVariable("contentpackagehaserrors", "[packagename]", mod.Name) + '\n' + string.Join('\n', mod.Errors.Take(maxErrorsToShow).Select(e => e.error)); TextManager.GetWithVariable("contentpackagehaserrors", "[packagename]", mod.Name)
+ '\n' + string.Join('\n', mod.Errors.Take(maxErrorsToShow).Select(e => e.Message));
if (mod.Errors.Count() > maxErrorsToShow) if (mod.Errors.Count() > maxErrorsToShow)
{ {
uiElement.ToolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (mod.Errors.Count() - maxErrorsToShow).ToString()); uiElement.ToolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (mod.Errors.Count() - maxErrorsToShow).ToString());
@@ -1,3 +1,5 @@
using System;
#nullable enable #nullable enable
namespace Barotrauma.Steam namespace Barotrauma.Steam
@@ -7,5 +9,8 @@ namespace Barotrauma.Steam
public WorkshopMenu(GUIFrame parent) { } public WorkshopMenu(GUIFrame parent) { }
protected abstract void UpdateModListItemVisibility(); protected abstract void UpdateModListItemVisibility();
protected bool ModNameMatches(ContentPackage p, string query)
=> p.Name.Contains(query, StringComparison.OrdinalIgnoreCase);
} }
} }
@@ -6,12 +6,13 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.18.2.0</Version> <Version>0.18.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors> <WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
+2 -1
View File
@@ -6,12 +6,13 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.18.2.0</Version> <Version>0.18.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors> <WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
@@ -6,13 +6,14 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.18.2.0</Version> <Version>0.18.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors> <WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
@@ -6,12 +6,13 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.18.2.0</Version> <Version>0.18.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors> <WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
+2 -1
View File
@@ -6,12 +6,13 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.18.2.0</Version> <Version>0.18.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors> <WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
@@ -647,6 +647,7 @@ namespace Barotrauma
{ {
msg.Write(false); msg.Write(false);
} }
msg.Write(HumanPrefabHealthMultiplier);
msg.Write(Wallet.Balance); msg.Write(Wallet.Balance);
msg.WriteRangedInteger(Wallet.RewardDistribution, 0, 100); msg.WriteRangedInteger(Wallet.RewardDistribution, 0, 100);
msg.Write((byte)TeamID); msg.Write((byte)TeamID);
@@ -1676,7 +1676,7 @@ namespace Barotrauma
GameMain.Server.SendConsoleMessage("No campaign active.", client, Color.Red); GameMain.Server.SendConsoleMessage("No campaign active.", client, Color.Red);
return; return;
} }
mpCampaign.LastUpdateID++; mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
GameMain.GameSession.Map.AllowDebugTeleport = !GameMain.GameSession.Map.AllowDebugTeleport; GameMain.GameSession.Map.AllowDebugTeleport = !GameMain.GameSession.Map.AllowDebugTeleport;
NewMessage(client.Name + (GameMain.GameSession.Map.AllowDebugTeleport ? " enabled" : " disabled") + " teleportation on the campaign map.", Color.White); NewMessage(client.Name + (GameMain.GameSession.Map.AllowDebugTeleport ? " enabled" : " disabled") + " teleportation on the campaign map.", Color.White);
GameMain.Server.SendConsoleMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", client); GameMain.Server.SendConsoleMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", client);
@@ -2274,7 +2274,6 @@ namespace Barotrauma
Wallet wallet = targetCharacter is null ? campaign.Bank : targetCharacter.Wallet; Wallet wallet = targetCharacter is null ? campaign.Bank : targetCharacter.Wallet;
wallet.Give(money); wallet.Give(money);
GameAnalyticsManager.AddMoneyGainedEvent(money, GameAnalyticsManager.MoneySource.Cheat, "console"); GameAnalyticsManager.AddMoneyGainedEvent(money, GameAnalyticsManager.MoneySource.Cheat, "console");
campaign.LastUpdateID++;
} }
else else
{ {
@@ -37,7 +37,7 @@ namespace Barotrauma
{ {
if (forceMapUI == value) { return; } if (forceMapUI == value) { return; }
forceMapUI = value; forceMapUI = value;
LastUpdateID++; IncrementLastUpdateIdForFlag(NetFlags.MapAndMissions);
} }
} }
@@ -71,11 +71,43 @@ namespace Barotrauma
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); } get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
} }
public static void StartNewCampaign(string savePath, string subPath, string seed, CampaignSettings settings) private bool purchasedHullRepairs, purchasedLostShuttles, purchasedItemRepairs;
public override bool PurchasedHullRepairs
{
get { return purchasedHullRepairs; }
set
{
if (purchasedHullRepairs == value) { return; }
purchasedHullRepairs = value;
IncrementLastUpdateIdForFlag(NetFlags.Misc);
}
}
public override bool PurchasedLostShuttles
{
get { return purchasedLostShuttles; }
set
{
if (purchasedLostShuttles == value) { return; }
purchasedLostShuttles = value;
IncrementLastUpdateIdForFlag(NetFlags.Misc);
}
}
public override bool PurchasedItemRepairs
{
get { return purchasedItemRepairs; }
set
{
if (purchasedItemRepairs == value) { return; }
purchasedItemRepairs = value;
IncrementLastUpdateIdForFlag(NetFlags.Misc);
}
}
public static void StartNewCampaign(string savePath, string subPath, string seed, CampaignSettings startingSettings)
{ {
if (string.IsNullOrWhiteSpace(savePath)) { return; } if (string.IsNullOrWhiteSpace(savePath)) { return; }
GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), savePath, GameModePreset.MultiPlayerCampaign, settings, seed); GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), savePath, GameModePreset.MultiPlayerCampaign, startingSettings, seed);
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
@@ -158,7 +190,7 @@ namespace Barotrauma
public override void Start() public override void Start()
{ {
base.Start(); base.Start();
lastUpdateID++; IncrementAllLastUpdateIds();
} }
private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection; private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection;
@@ -274,7 +306,7 @@ namespace Barotrauma
protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults) protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults)
{ {
lastUpdateID++; IncrementAllLastUpdateIds();
switch (transitionType) switch (transitionType)
{ {
@@ -321,6 +353,7 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
LeaveUnconnectedSubs(leavingSub); LeaveUnconnectedSubs(leavingSub);
NextLevel = newLevel; NextLevel = newLevel;
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
} }
else else
@@ -329,7 +362,7 @@ namespace Barotrauma
GameMain.Server.EndGame(TransitionType.None, wasSaved: false); GameMain.Server.EndGame(TransitionType.None, wasSaved: false);
LoadCampaign(GameMain.GameSession.SavePath); LoadCampaign(GameMain.GameSession.SavePath);
LastSaveID++; LastSaveID++;
LastUpdateID++; IncrementAllLastUpdateIds();
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -360,14 +393,14 @@ namespace Barotrauma
} }
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
CargoManager.OnItemsInBuyCrateChanged += () => { LastUpdateID++; }; CargoManager.OnItemsInBuyCrateChanged += () => { IncrementLastUpdateIdForFlag(NetFlags.ItemsInBuyCrate); };
CargoManager.OnPurchasedItemsChanged += () => { LastUpdateID++; }; CargoManager.OnPurchasedItemsChanged += () => { IncrementLastUpdateIdForFlag(NetFlags.PurchasedItems); };
CargoManager.OnSoldItemsChanged += () => { LastUpdateID++; }; CargoManager.OnSoldItemsChanged += () => { IncrementLastUpdateIdForFlag(NetFlags.SoldItems); };
UpgradeManager.OnUpgradesChanged += () => { LastUpdateID++; }; UpgradeManager.OnUpgradesChanged += () => { IncrementLastUpdateIdForFlag(NetFlags.UpgradeManager); };
Map.OnLocationSelected += (loc, connection) => { LastUpdateID++; }; Map.OnLocationSelected += (loc, connection) => { IncrementLastUpdateIdForFlag(NetFlags.MapAndMissions); };
Map.OnMissionsSelected += (loc, mission) => { LastUpdateID++; }; Map.OnMissionsSelected += (loc, mission) => { IncrementLastUpdateIdForFlag(NetFlags.MapAndMissions); };
Reputation.OnAnyReputationValueChanged += () => { LastUpdateID++; }; Reputation.OnAnyReputationValueChanged += () => { IncrementLastUpdateIdForFlag(NetFlags.Reputation); };
//increment save ID so clients know they're lacking the most up-to-date save file //increment save ID so clients know they're lacking the most up-to-date save file
LastSaveID++; LastSaveID++;
@@ -388,6 +421,7 @@ namespace Barotrauma
discardedCharacters.Add(data); discardedCharacters.Add(data);
} }
characterData.Remove(data); characterData.Remove(data);
IncrementLastUpdateIdForFlag(NetFlags.CharacterInfo);
} }
} }
} }
@@ -402,6 +436,7 @@ namespace Barotrauma
characterData.RemoveAll(cd => cd.MatchesClient(client)); characterData.RemoveAll(cd => cd.MatchesClient(client));
var data = new CharacterCampaignData(client); var data = new CharacterCampaignData(client);
characterData.Add(data); characterData.Add(data);
IncrementLastUpdateIdForFlag(NetFlags.CharacterInfo);
return data; return data;
} }
@@ -413,6 +448,7 @@ namespace Barotrauma
var matchingData = GetClientCharacterData(client); var matchingData = GetClientCharacterData(client);
if (matchingData != null) { client.CharacterInfo = matchingData.CharacterInfo; } if (matchingData != null) { client.CharacterInfo = matchingData.CharacterInfo; }
} }
IncrementLastUpdateIdForFlag(NetFlags.CharacterInfo);
} }
public Dictionary<Client, Job> GetAssignedJobs(IEnumerable<Client> connectedClients) public Dictionary<Client, Job> GetAssignedJobs(IEnumerable<Client> connectedClients)
@@ -517,127 +553,187 @@ namespace Barotrauma
base.End(transitionType); base.End(transitionType);
} }
private bool IsFlagRequired(Client c, NetFlags flag)
=> !c.LastRecvCampaignUpdate.TryGetValue(flag, out var id) || NetIdUtils.IdMoreRecent(GetLastUpdateIdForFlag(flag), id);
public void ServerWrite(IWriteMessage msg, Client c) public void ServerWrite(IWriteMessage msg, Client c)
{ {
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue); System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
Reputation reputation = Map?.CurrentLocation?.Reputation; NetFlags requiredFlags = lastUpdateID.Keys.Where(k => IsFlagRequired(c, k)).Aggregate((NetFlags)0, (f1, f2) => f1 | f2);
msg.Write((UInt16)requiredFlags);
msg.Write(IsFirstRound); msg.Write(IsFirstRound);
msg.Write(CampaignID); msg.Write(CampaignID);
msg.Write(lastUpdateID);
msg.Write(lastSaveID); msg.Write(lastSaveID);
msg.Write(map.Seed); msg.Write(map.Seed);
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); if (requiredFlags.HasFlag(NetFlags.Misc))
var selectedMissionIndices = map.GetSelectedMissionIndices();
msg.Write((byte)selectedMissionIndices.Count());
foreach (int selectedMissionIndex in selectedMissionIndices)
{ {
msg.Write((byte)selectedMissionIndex); msg.Write(GetLastUpdateIdForFlag(NetFlags.Misc));
msg.Write(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles);
} }
var subList = GameMain.NetLobbyScreen.GetSubList(); if (requiredFlags.HasFlag(NetFlags.MapAndMissions))
List<int> ownedSubmarineIndices = new List<int>();
for (int i = 0; i < subList.Count; i++)
{ {
if (GameMain.GameSession.OwnedSubmarines.Any(s => s.Name == subList[i].Name)) msg.Write(GetLastUpdateIdForFlag(NetFlags.MapAndMissions));
msg.Write(ForceMapUI);
msg.Write(map.AllowDebugTeleport);
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
if (map.CurrentLocation != null)
{ {
ownedSubmarineIndices.Add(i); msg.Write((byte)map.CurrentLocation.AvailableMissions.Count());
} foreach (Mission mission in map.CurrentLocation.AvailableMissions)
}
msg.Write((ushort)ownedSubmarineIndices.Count);
foreach (int index in ownedSubmarineIndices)
{
msg.Write((ushort)index);
}
msg.Write(map.AllowDebugTeleport);
msg.Write(reputation != null);
if (reputation != null) { msg.Write(reputation.Value); }
// hopefully we'll never have more than 128 factions
msg.Write((byte)Factions.Count);
foreach (Faction faction in Factions)
{
msg.Write(faction.Prefab.Identifier);
msg.Write(faction.Reputation.Value);
}
msg.Write(ForceMapUI);
msg.Write(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles);
if (map.CurrentLocation != null)
{
msg.Write((byte)map.CurrentLocation?.AvailableMissions.Count());
foreach (Mission mission in map.CurrentLocation.AvailableMissions)
{
msg.Write(mission.Prefab.Identifier);
if (mission.Locations[0] == mission.Locations[1])
{ {
msg.Write((byte)255); msg.Write(mission.Prefab.Identifier);
} if (mission.Locations[0] == mission.Locations[1])
else {
{ msg.Write((byte)255);
Location missionDestination = mission.Locations[0] == map.CurrentLocation ? mission.Locations[1] : mission.Locations[0]; }
LocationConnection connection = map.CurrentLocation.Connections.Find(c => c.OtherLocation(map.CurrentLocation) == missionDestination); else
msg.Write((byte)map.CurrentLocation.Connections.IndexOf(connection)); {
Location missionDestination = mission.Locations[0] == map.CurrentLocation ? mission.Locations[1] : mission.Locations[0];
LocationConnection connection = map.CurrentLocation.Connections.Find(c => c.OtherLocation(map.CurrentLocation) == missionDestination);
msg.Write((byte)map.CurrentLocation.Connections.IndexOf(connection));
}
} }
} }
else
// Store balance
bool hasStores = map.CurrentLocation.Stores != null && map.CurrentLocation.Stores.Any();
msg.Write(hasStores);
if (hasStores)
{ {
msg.Write((byte)map.CurrentLocation.Stores.Count); msg.Write((byte)0);
foreach (var store in map.CurrentLocation.Stores.Values) }
var selectedMissionIndices = map.GetSelectedMissionIndices();
msg.Write((byte)selectedMissionIndices.Count());
foreach (int selectedMissionIndex in selectedMissionIndices)
{
msg.Write((byte)selectedMissionIndex);
}
WriteStores(msg);
}
if (requiredFlags.HasFlag(NetFlags.SubList))
{
msg.Write(GetLastUpdateIdForFlag(NetFlags.SubList));
var subList = GameMain.NetLobbyScreen.GetSubList();
List<int> ownedSubmarineIndices = new List<int>();
for (int i = 0; i < subList.Count; i++)
{
if (GameMain.GameSession.OwnedSubmarines.Any(s => s.Name == subList[i].Name))
{ {
msg.Write(store.Identifier); ownedSubmarineIndices.Add(i);
msg.Write((UInt16)store.Balance);
} }
} }
msg.Write((ushort)ownedSubmarineIndices.Count);
foreach (int index in ownedSubmarineIndices)
{
msg.Write((ushort)index);
}
} }
else if (requiredFlags.HasFlag(NetFlags.UpgradeManager))
{ {
msg.Write((byte)0); msg.Write(GetLastUpdateIdForFlag(NetFlags.UpgradeManager));
// Store balance msg.Write((ushort)UpgradeManager.PendingUpgrades.Count);
msg.Write(false); foreach (var (prefab, category, level) in UpgradeManager.PendingUpgrades)
{
msg.Write(prefab.Identifier);
msg.Write(category.Identifier);
msg.Write((byte)level);
}
msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count);
foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps)
{
msg.Write(itemSwap.ItemToRemove.ID);
msg.Write(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty);
}
} }
WriteItems(msg, CargoManager.ItemsInBuyCrate); if (requiredFlags.HasFlag(NetFlags.ItemsInBuyCrate))
WriteItems(msg, CargoManager.ItemsInSellFromSubCrate);
WriteItems(msg, CargoManager.PurchasedItems);
WriteItems(msg, CargoManager.SoldItems);
msg.Write((ushort)UpgradeManager.PendingUpgrades.Count);
foreach (var (prefab, category, level) in UpgradeManager.PendingUpgrades)
{ {
msg.Write(prefab.Identifier); msg.Write(GetLastUpdateIdForFlag(NetFlags.ItemsInBuyCrate));
msg.Write(category.Identifier); WriteItems(msg, CargoManager.ItemsInBuyCrate);
msg.Write((byte)level); WriteStores(msg);
} }
msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count); if (requiredFlags.HasFlag(NetFlags.ItemsInSellFromSubCrate))
foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps)
{ {
msg.Write(itemSwap.ItemToRemove.ID); msg.Write(GetLastUpdateIdForFlag(NetFlags.ItemsInSellFromSubCrate));
msg.Write(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty); WriteItems(msg, CargoManager.ItemsInSellFromSubCrate);
WriteStores(msg);
} }
var characterData = GetClientCharacterData(c); if (requiredFlags.HasFlag(NetFlags.PurchasedItems))
if (characterData?.CharacterInfo == null)
{ {
msg.Write(false); msg.Write(GetLastUpdateIdForFlag(NetFlags.PurchasedItems));
WriteItems(msg, CargoManager.PurchasedItems);
WriteStores(msg);
} }
else if (requiredFlags.HasFlag(NetFlags.SoldItems))
{ {
msg.Write(true); msg.Write(GetLastUpdateIdForFlag(NetFlags.SoldItems));
characterData.CharacterInfo.ServerWrite(msg); WriteItems(msg, CargoManager.SoldItems);
WriteStores(msg);
}
if (requiredFlags.HasFlag(NetFlags.Reputation))
{
msg.Write(GetLastUpdateIdForFlag(NetFlags.Reputation));
Reputation reputation = Map?.CurrentLocation?.Reputation;
msg.Write(reputation != null);
if (reputation != null) { msg.Write(reputation.Value); }
// hopefully we'll never have more than 128 factions
msg.Write((byte)Factions.Count);
foreach (Faction faction in Factions)
{
msg.Write(faction.Prefab.Identifier);
msg.Write(faction.Reputation.Value);
}
}
if (requiredFlags.HasFlag(NetFlags.CharacterInfo))
{
msg.Write(GetLastUpdateIdForFlag(NetFlags.CharacterInfo));
var characterData = GetClientCharacterData(c);
if (characterData?.CharacterInfo == null)
{
msg.Write(false);
}
else
{
msg.Write(true);
characterData.CharacterInfo.ServerWrite(msg);
}
}
void WriteStores(IWriteMessage msg)
{
if (map.CurrentLocation != null)
{
// Store balance
bool hasStores = map.CurrentLocation.Stores != null && map.CurrentLocation.Stores.Any();
msg.Write(hasStores);
if (hasStores)
{
msg.Write((byte)map.CurrentLocation.Stores.Count);
foreach (var store in map.CurrentLocation.Stores.Values)
{
msg.Write(store.Identifier);
msg.Write((UInt16)store.Balance);
}
}
}
else
{
msg.Write((byte)0);
// Store balance
msg.Write(false);
}
} }
} }
@@ -102,6 +102,7 @@ namespace Barotrauma.Items.Components
{ {
msg.Write(autoPilot); msg.Write(autoPilot);
msg.Write(TryExtractEventData<EventData>(extraData, out var eventData) && eventData.DockingButtonClicked); msg.Write(TryExtractEventData<EventData>(extraData, out var eventData) && eventData.DockingButtonClicked);
msg.Write(user?.ID ?? Entity.NullEntityID);
if (!autoPilot) if (!autoPilot)
{ {
@@ -21,7 +21,7 @@ namespace Barotrauma.Networking
public UInt16 LastSentEntityEventID = 0; public UInt16 LastSentEntityEventID = 0;
public UInt16 LastRecvEntityEventID = 0; public UInt16 LastRecvEntityEventID = 0;
public UInt16 LastRecvCampaignUpdate = 0; public readonly Dictionary<MultiPlayerCampaign.NetFlags, UInt16> LastRecvCampaignUpdate = new Dictionary<MultiPlayerCampaign.NetFlags, ushort>();
public UInt16 LastRecvCampaignSave = 0; public UInt16 LastRecvCampaignSave = 0;
public (UInt16 saveId, float time) LastCampaignSaveSendTime; public (UInt16 saveId, float time) LastCampaignSaveSendTime;
@@ -1,5 +1,5 @@
using System; using System;
using System.IO; using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -155,7 +155,7 @@ namespace Barotrauma.Networking
else else
{ {
Log("Using SteamP2P networking.", ServerLog.MessageType.ServerMessage); Log("Using SteamP2P networking.", ServerLog.MessageType.ServerMessage);
serverPeer = new SteamP2PServerPeer(ownerSteamId.Value, serverSettings); serverPeer = new SteamP2PServerPeer(ownerSteamId.Value, ownerKey.Value, serverSettings);
} }
serverPeer.OnInitializationComplete = OnInitializationComplete; serverPeer.OnInitializationComplete = OnInitializationComplete;
@@ -746,7 +746,7 @@ namespace Barotrauma.Networking
string seed = inc.ReadString(); string seed = inc.ReadString();
string subName = inc.ReadString(); string subName = inc.ReadString();
string subHash = inc.ReadString(); string subHash = inc.ReadString();
CampaignSettings settings = new CampaignSettings(inc); CampaignSettings settings = INetSerializableStruct.Read<CampaignSettings>(inc);
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash); var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash);
@@ -767,8 +767,7 @@ namespace Barotrauma.Networking
string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName); string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName);
if (connectedClient.HasPermission(ClientPermissions.SelectMode) || connectedClient.HasPermission(ClientPermissions.ManageCampaign)) if (connectedClient.HasPermission(ClientPermissions.SelectMode) || connectedClient.HasPermission(ClientPermissions.ManageCampaign))
{ {
ServerSettings.RadiationEnabled = settings.RadiationEnabled; ServerSettings.CampaignSettings = settings;
ServerSettings.MaxMissionCount = settings.MaxMissionCount;
ServerSettings.SaveSettings(); ServerSettings.SaveSettings();
MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings); MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings);
} }
@@ -833,6 +832,9 @@ namespace Barotrauma.Networking
case ClientPacketHeader.EVENTMANAGER_RESPONSE: case ClientPacketHeader.EVENTMANAGER_RESPONSE:
GameMain.GameSession?.EventManager.ServerRead(inc, connectedClient); GameMain.GameSession?.EventManager.ServerRead(inc, connectedClient);
break; break;
case ClientPacketHeader.UPDATE_CHARACTERINFO:
UpdateCharacterInfo(inc, connectedClient);
break;
case ClientPacketHeader.ERROR: case ClientPacketHeader.ERROR:
HandleClientError(inc, connectedClient); HandleClientError(inc, connectedClient);
break; break;
@@ -1050,9 +1052,11 @@ namespace Barotrauma.Networking
if (c.LastRecvCampaignSave > 0) if (c.LastRecvCampaignSave > 0)
{ {
byte campaignID = inc.ReadByte(); byte campaignID = inc.ReadByte();
c.LastRecvCampaignUpdate = inc.ReadUInt16(); foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
c.LastRecvCampaignUpdate[netFlag] = inc.ReadUInt16();
}
bool characterDiscarded = inc.ReadBoolean(); bool characterDiscarded = inc.ReadBoolean();
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
{ {
if (characterDiscarded) { campaign.DiscardClientCharacterData(c); } if (characterDiscarded) { campaign.DiscardClientCharacterData(c); }
@@ -1061,7 +1065,11 @@ namespace Barotrauma.Networking
if (campaign.CampaignID != campaignID) if (campaign.CampaignID != campaignID)
{ {
c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1); c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1);
c.LastRecvCampaignUpdate = (ushort)(campaign.LastUpdateID - 1); foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
c.LastRecvCampaignUpdate[netFlag] =
(UInt16)(campaign.GetLastUpdateIdForFlag(netFlag) - 1);
}
} }
} }
} }
@@ -1122,9 +1130,11 @@ namespace Barotrauma.Networking
if (c.LastRecvCampaignSave > 0) if (c.LastRecvCampaignSave > 0)
{ {
byte campaignID = inc.ReadByte(); byte campaignID = inc.ReadByte();
c.LastRecvCampaignUpdate = inc.ReadUInt16(); foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
c.LastRecvCampaignUpdate[netFlag] = inc.ReadUInt16();
}
bool characterDiscarded = inc.ReadBoolean(); bool characterDiscarded = inc.ReadBoolean();
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
{ {
if (characterDiscarded) { campaign.DiscardClientCharacterData(c); } if (characterDiscarded) { campaign.DiscardClientCharacterData(c); }
@@ -1133,7 +1143,11 @@ namespace Barotrauma.Networking
if (campaign.CampaignID != campaignID) if (campaign.CampaignID != campaignID)
{ {
c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1); c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1);
c.LastRecvCampaignUpdate = (ushort)(campaign.LastUpdateID - 1); foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
c.LastRecvCampaignUpdate[netFlag] =
(UInt16)(campaign.GetLastUpdateIdForFlag(netFlag) - 1);
}
} }
} }
} }
@@ -1370,7 +1384,7 @@ namespace Barotrauma.Networking
if (gameStarted) if (gameStarted)
{ {
Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
if (mpCampaign != null && Level.IsLoadedOutpost && save) if (mpCampaign != null && Level.IsLoadedFriendlyOutpost && save)
{ {
mpCampaign.SavePlayers(); mpCampaign.SavePlayers();
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
@@ -1672,8 +1686,7 @@ namespace Barotrauma.Networking
outmsg.Write(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server outmsg.Write(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
outmsg.Write(c.LastSentEntityEventID); outmsg.Write(c.LastSentEntityEventID);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode && if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
NetIdUtils.IdMoreRecent(campaign.LastUpdateID, c.LastRecvCampaignUpdate))
{ {
outmsg.Write(true); outmsg.Write(true);
outmsg.WritePadBits(); outmsg.WritePadBits();
@@ -1899,8 +1912,7 @@ namespace Barotrauma.Networking
int campaignBytes = outmsg.LengthBytes; int campaignBytes = outmsg.LengthBytes;
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
if (outmsg.LengthBytes < MsgConstants.MTU - 500 && if (outmsg.LengthBytes < MsgConstants.MTU - 500 &&
campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode && campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
NetIdUtils.IdMoreRecent(campaign.LastUpdateID, c.LastRecvCampaignUpdate))
{ {
outmsg.Write(true); outmsg.Write(true);
outmsg.WritePadBits(); outmsg.WritePadBits();
@@ -2049,7 +2061,10 @@ namespace Barotrauma.Networking
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
msg.Write(campaign == null ? (byte)0 : campaign.CampaignID); msg.Write(campaign == null ? (byte)0 : campaign.CampaignID);
msg.Write(campaign == null ? (UInt16)0 : campaign.LastSaveID); msg.Write(campaign == null ? (UInt16)0 : campaign.LastSaveID);
msg.Write(campaign == null ? (UInt16)0 : campaign.LastUpdateID); foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
msg.Write(campaign == null ? (UInt16)0 : campaign.GetLastUpdateIdForFlag(flag));
}
connectedClients.ForEach(c => c.ReadyToStart = false); connectedClients.ForEach(c => c.ReadyToStart = false);
@@ -2077,7 +2092,7 @@ namespace Barotrauma.Networking
} }
} }
startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(selectedSub, selectedShuttle, selectedMode, CampaignSettings.Unsure), false); startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(selectedSub, selectedShuttle, selectedMode, CampaignSettings.Empty), false);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -2195,7 +2210,7 @@ namespace Barotrauma.Networking
Level.Loaded?.SpawnNPCs(); Level.Loaded?.SpawnNPCs();
Level.Loaded?.SpawnCorpses(); Level.Loaded?.SpawnCorpses();
Level.Loaded?.PrepareBeaconStation(); Level.Loaded?.PrepareBeaconStation();
AutoItemPlacer.SpawnItems(); AutoItemPlacer.SpawnItems(campaign?.Settings.StartItemSet);
CrewManager crewManager = campaign?.CrewManager; CrewManager crewManager = campaign?.CrewManager;
@@ -3203,18 +3218,27 @@ namespace Barotrauma.Networking
if (checkActiveVote && Voting.ActiveVote != null) if (checkActiveVote && Voting.ActiveVote != null)
{ {
int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(Voting.ActiveVote.VoteType) == 2); var inGameClients = GameMain.Server.ConnectedClients.Where(c => c.InGame);
int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(Voting.ActiveVote.VoteType) == 1); if (inGameClients.Count() == 1)
int max = GameMain.Server.ConnectedClients.Count(c => c.InGame);
// Required ratio cannot be met
if (no / (float)max > 1f - serverSettings.VoteRequiredRatio)
{
Voting.ActiveVote.Finish(Voting, passed: false);
}
else if (yes / (float)max >= serverSettings.VoteRequiredRatio)
{ {
Voting.ActiveVote.Finish(Voting, passed: true); Voting.ActiveVote.Finish(Voting, passed: true);
} }
else
{
var eligibleClients = inGameClients.Where(c => c != Voting.ActiveVote.VoteStarter);
int yes = eligibleClients.Count(c => c.GetVote<int>(Voting.ActiveVote.VoteType) == 2);
int no = eligibleClients.Count(c => c.GetVote<int>(Voting.ActiveVote.VoteType) == 1);
int max = eligibleClients.Count();
// Required ratio cannot be met
if (no / (float)max > 1f - serverSettings.VoteRequiredRatio)
{
Voting.ActiveVote.Finish(Voting, passed: false);
}
else if (yes / (float)max >= serverSettings.VoteRequiredRatio)
{
Voting.ActiveVote.Finish(Voting, passed: true);
}
}
} }
Client.UpdateKickVotes(connectedClients); Client.UpdateKickVotes(connectedClients);
@@ -3295,7 +3319,7 @@ namespace Barotrauma.Networking
if (voteType != VoteType.PurchaseSub) if (voteType != VoteType.PurchaseSub)
{ {
GameMain.GameSession.SwitchSubmarine(targetSubmarine, deliveryFee, starter); GameMain.GameSession.SwitchSubmarine(targetSubmarine, subVote.TransferItems, deliveryFee, starter);
} }
Voting.StopSubmarineVote(true); Voting.StopSubmarineVote(true);
@@ -16,14 +16,21 @@ namespace Barotrauma.Networking
private set; private set;
} }
public SteamP2PServerPeer(UInt64 steamId, ServerSettings settings) private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Value);
private UInt64 ReadSteamId(IReadMessage inc)
=> inc.ReadUInt64() ^ ownerKey64;
private void WriteSteamId(IWriteMessage msg, UInt64 val)
=> msg.Write(val ^ ownerKey64);
public SteamP2PServerPeer(UInt64 steamId, int ownerKey, ServerSettings settings)
{ {
serverSettings = settings; serverSettings = settings;
connectedClients = new List<NetworkConnection>(); connectedClients = new List<NetworkConnection>();
pendingClients = new List<PendingClient>(); pendingClients = new List<PendingClient>();
ownerKey = null; this.ownerKey = ownerKey;
OwnerSteamID = steamId; OwnerSteamID = steamId;
@@ -33,7 +40,7 @@ namespace Barotrauma.Networking
public override void Start() public override void Start()
{ {
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(OwnerSteamID); WriteSteamId(outMsg, OwnerSteamID);
outMsg.Write((byte)DeliveryMethod.Reliable); outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage)); outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage));
@@ -122,8 +129,8 @@ namespace Barotrauma.Networking
{ {
if (!started) { return; } if (!started) { return; }
UInt64 senderSteamId = inc.ReadUInt64(); UInt64 senderSteamId = ReadSteamId(inc);
UInt64 ownerSteamId = inc.ReadUInt64(); UInt64 ownerSteamId = ReadSteamId(inc);
PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
@@ -264,7 +271,7 @@ namespace Barotrauma.Networking
IWriteMessage msgToSend = new WriteOnlyMessage(); IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = new byte[16]; byte[] msgData = new byte[16];
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length); msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
msgToSend.Write(conn.SteamID); WriteSteamId(msgToSend, conn.SteamID);
msgToSend.Write((byte)deliveryMethod); msgToSend.Write((byte)deliveryMethod);
msgToSend.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage)); msgToSend.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage));
msgToSend.Write((UInt16)length); msgToSend.Write((UInt16)length);
@@ -281,7 +288,7 @@ namespace Barotrauma.Networking
if (string.IsNullOrWhiteSpace(msg)) { return; } if (string.IsNullOrWhiteSpace(msg)) { return; }
IWriteMessage msgToSend = new WriteOnlyMessage(); IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.Write(steamId); WriteSteamId(msgToSend, steamId);
msgToSend.Write((byte)DeliveryMethod.Reliable); msgToSend.Write((byte)DeliveryMethod.Reliable);
msgToSend.Write((byte)(PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage)); msgToSend.Write((byte)(PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage));
msgToSend.Write(msg); msgToSend.Write(msg);
@@ -318,7 +325,7 @@ namespace Barotrauma.Networking
protected override void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg) protected override void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg)
{ {
IWriteMessage msgToSend = new WriteOnlyMessage(); IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.Write(conn.SteamID); WriteSteamId(msgToSend, conn.SteamID);
msgToSend.Write((byte)deliveryMethod); msgToSend.Write((byte)deliveryMethod);
msgToSend.Write(msg.Buffer, 0, msg.LengthBytes); msgToSend.Write(msg.Buffer, 0, msg.LengthBytes);
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone(); byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
@@ -1,11 +1,9 @@
using Barotrauma.IO; using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Barotrauma.IO;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -36,7 +34,7 @@ namespace Barotrauma.Networking
=> LastUpdateIdForFlag[flag] = (UInt16)(GameMain.NetLobbyScreen.LastUpdateID + 1); => LastUpdateIdForFlag[flag] = (UInt16)(GameMain.NetLobbyScreen.LastUpdateID + 1);
private bool IsFlagRequired(Client c, NetFlags flag) private bool IsFlagRequired(Client c, NetFlags flag)
=> LastUpdateIdForFlag[flag] > c.LastRecvLobbyUpdate; => NetIdUtils.IdMoreRecent(LastUpdateIdForFlag[flag], c.LastRecvLobbyUpdate);
public NetFlags GetRequiredFlags(Client c) public NetFlags GetRequiredFlags(Client c)
=> LastUpdateIdForFlag.Keys => LastUpdateIdForFlag.Keys
@@ -56,7 +54,7 @@ namespace Barotrauma.Networking
{ {
var property = netProperties[key]; var property = netProperties[key];
property.SyncValue(); property.SyncValue();
if (property.LastUpdateID > c.LastRecvLobbyUpdate) if (NetIdUtils.IdMoreRecent(property.LastUpdateID, c.LastRecvLobbyUpdate))
{ {
outMsg.Write(key); outMsg.Write(key);
netProperties[key].Write(outMsg); netProperties[key].Write(outMsg);
@@ -257,7 +255,7 @@ namespace Barotrauma.Networking
doc.Root.SetAttributeValue("queryport", QueryPort); doc.Root.SetAttributeValue("queryport", QueryPort);
#endif #endif
doc.Root.SetAttributeValue("password", password ?? ""); doc.Root.SetAttributeValue("password", password ?? "");
doc.Root.SetAttributeValue("enableupnp", EnableUPnP); doc.Root.SetAttributeValue("enableupnp", EnableUPnP);
doc.Root.SetAttributeValue("autorestart", autoRestart); doc.Root.SetAttributeValue("autorestart", autoRestart);
@@ -266,11 +264,12 @@ namespace Barotrauma.Networking
doc.Root.SetAttributeValue("ServerMessage", ServerMessageText); doc.Root.SetAttributeValue("ServerMessage", ServerMessageText);
doc.Root.SetAttributeValue("HiddenSubs", string.Join(",", HiddenSubs)); doc.Root.SetAttributeValue("HiddenSubs", string.Join(",", HiddenSubs));
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes)); doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => $"{c.Start}-{c.End}"))); doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => $"{c.Start}-{c.End}")));
SerializableProperty.SerializeProperties(this, doc.Root, true); SerializableProperty.SerializeProperties(this, doc.Root, true);
doc.Root.Add(CampaignSettings.Save());
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
{ {
@@ -399,7 +398,7 @@ namespace Barotrauma.Networking
ServerName = doc.Root.GetAttributeString("name", ""); ServerName = doc.Root.GetAttributeString("name", "");
if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); } if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
ServerMessageText = doc.Root.GetAttributeString("ServerMessage", ""); ServerMessageText = doc.Root.GetAttributeString("ServerMessage", "");
GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier; GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier;
//handle Random as the mission type, which is no longer a valid setting //handle Random as the mission type, which is no longer a valid setting
//MissionType.All offers equivalent functionality //MissionType.All offers equivalent functionality
@@ -410,6 +409,14 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetBotCount(BotCount); GameMain.NetLobbyScreen.SetBotCount(BotCount);
MonsterEnabled ??= CharacterPrefab.Prefabs.Select(p => (p.Identifier, true)).ToDictionary(); MonsterEnabled ??= CharacterPrefab.Prefabs.Select(p => (p.Identifier, true)).ToDictionary();
foreach (XElement element in doc.Root.Elements())
{
if (element.Name.ToIdentifier() == nameof(Barotrauma.CampaignSettings))
{
CampaignSettings = new CampaignSettings(element);
}
}
} }
public string SelectNonHiddenSubmarine(string current = null) public string SelectNonHiddenSubmarine(string current = null)
@@ -27,11 +27,13 @@ namespace Barotrauma
public VoteState State { get; set; } public VoteState State { get; set; }
public SubmarineInfo Sub; public SubmarineInfo Sub;
public bool TransferItems;
public int DeliveryFee; public int DeliveryFee;
public SubmarineVote(Client starter, SubmarineInfo subInfo, int deliveryFee, VoteType voteType) public SubmarineVote(Client starter, SubmarineInfo subInfo, bool transferItems, int deliveryFee, VoteType voteType)
{ {
Sub = subInfo; Sub = subInfo;
TransferItems = transferItems;
DeliveryFee = deliveryFee; DeliveryFee = deliveryFee;
VoteType = voteType; VoteType = voteType;
State = VoteState.Started; State = VoteState.Started;
@@ -101,15 +103,12 @@ namespace Barotrauma
private readonly Dictionary<Client, (VoteType voteType, DateTime time)> rejectedVoteTimes = new Dictionary<Client, (VoteType voteType, DateTime time)>(); private readonly Dictionary<Client, (VoteType voteType, DateTime time)> rejectedVoteTimes = new Dictionary<Client, (VoteType voteType, DateTime time)>();
private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender) private void StartSubmarineVote(SubmarineInfo subInfo, bool transferItems, VoteType voteType, Client sender)
{ {
if (ActiveVote == null)
{
sender.SetVote(voteType, 2);
}
var subVote = new SubmarineVote( var subVote = new SubmarineVote(
sender, sender,
subInfo, subInfo,
transferItems,
voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0, voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0,
voteType); voteType);
StartOrEnqueueVote(subVote); StartOrEnqueueVote(subVote);
@@ -152,10 +151,6 @@ namespace Barotrauma
{ {
return; return;
} }
if (ActiveVote == null)
{
starter.SetVote(VoteType.TransferMoney, 2);
}
StartOrEnqueueVote(new TransferVote(starter, from, transferAmount, to)); StartOrEnqueueVote(new TransferVote(starter, from, transferAmount, to));
GameMain.Server.UpdateVoteStatus(checkActiveVote: false); GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
} }
@@ -205,11 +200,19 @@ namespace Barotrauma
if (ActiveVote.Timer >= GameMain.NetworkMember.ServerSettings.VoteTimeout) if (ActiveVote.Timer >= GameMain.NetworkMember.ServerSettings.VoteTimeout)
{ {
var inGameClients = GameMain.Server.ConnectedClients.Where(c => c.InGame);
var eligibleClients = inGameClients.Where(c => c != ActiveVote.VoteStarter);
// Do not take unanswered into account for total // Do not take unanswered into account for total
int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2); int yes = eligibleClients.Count(c => c.GetVote<int>(ActiveVote.VoteType) == 2);
int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1); int no = eligibleClients.Count(c => c.GetVote<int>(ActiveVote.VoteType) == 1);
int total = Math.Max(yes + no, 1); int total = Math.Max(yes + no, 1);
ActiveVote.Finish(this, passed: yes / (float)(total) >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio);
bool passed =
yes / (float)total >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio ||
inGameClients.Count() == 1;
ActiveVote.Finish(this, passed);
} }
} }
@@ -293,12 +296,13 @@ namespace Barotrauma
{ {
string subName = inc.ReadString(); string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName); SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
bool transferItems = inc.ReadBoolean();
if (!ShouldRejectVote(sender, voteType)) if (!ShouldRejectVote(sender, voteType))
{ {
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign &&
(campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo))) (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
{ {
StartSubmarineVote(subInfo, voteType, sender); StartSubmarineVote(subInfo, transferItems, voteType, sender);
} }
} }
} }
@@ -355,22 +359,24 @@ namespace Barotrauma
{ {
msg.Write((byte)ActiveVote.VoteType); msg.Write((byte)ActiveVote.VoteType);
if (ActiveVote.State != VoteState.None && ActiveVote.VoteType != VoteType.Unknown) if (ActiveVote.State != VoteState.None && ActiveVote.VoteType != VoteType.Unknown)
{ {
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2); var eligibleClients = GameMain.Server.ConnectedClients.Where(c => c.InGame && c != ActiveVote.VoteStarter);
msg.Write((byte)yesClients.Count);
var yesClients = eligibleClients.Where(c => c.GetVote<int>(ActiveVote.VoteType) == 2);
msg.Write((byte)yesClients.Count());
foreach (Client c in yesClients) foreach (Client c in yesClients)
{ {
msg.Write(c.ID); msg.Write(c.ID);
} }
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1); var noClients = eligibleClients.Where(c => c.GetVote<int>(ActiveVote.VoteType) == 1);
msg.Write((byte)noClients.Count); msg.Write((byte)noClients.Count());
foreach (Client c in noClients) foreach (Client c in noClients)
{ {
msg.Write(c.ID); msg.Write(c.ID);
} }
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.InGame)); msg.Write((byte)eligibleClients.Count());
switch (ActiveVote.State) switch (ActiveVote.State)
{ {
@@ -384,6 +390,7 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name); msg.Write((ActiveVote as SubmarineVote).Sub.Name);
msg.Write((ActiveVote as SubmarineVote).TransferItems);
break; break;
case VoteType.TransferMoney: case VoteType.TransferMoney:
var transferVote = (ActiveVote as TransferVote); var transferVote = (ActiveVote as TransferVote);
@@ -405,8 +412,10 @@ namespace Barotrauma
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name); var subVote = ActiveVote as SubmarineVote;
msg.Write((short)(ActiveVote as SubmarineVote).DeliveryFee); msg.Write(subVote.Sub.Name);
msg.Write(subVote.TransferItems);
msg.Write((short)subVote.DeliveryFee);
break; break;
} }
break; break;
@@ -6,12 +6,13 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.18.2.0</Version> <Version>0.18.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<InvariantGlobalization>true</InvariantGlobalization>
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors> <WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<CampaignSettingPresets>
<CampaignSettingDefinitions>
<StartingBalanceAmount high="10000" medium="8500" low="6000" />
<ExtraEventManagerDifficulty easy="-15.0" normal="0.0" hard="20.0" hellish="60.0" />
<LevelDifficultyMultiplier easy="0.6" normal="1.0" hard="1.6" hellish="10.0" />
</CampaignSettingDefinitions>
<CampaignSettings
presetname="Easy"
RadiationEnabled="false"
StartingBalanceAmount="High"
StartItemSet="easy"
Difficulty="Easy"/>
<CampaignSettings
presetname="Normal"
RadiationEnabled="false"
StartingBalanceAmount="Medium"
StartItemSet="normal"
Difficulty="Medium"/>
<CampaignSettings
presetname="Hard"
RadiationEnabled="true"
StartingBalanceAmount="Low"
StartItemSet="hard"
Difficulty="Hard"/>
</CampaignSettingPresets>
@@ -34,14 +34,18 @@ namespace Barotrauma
public float SoundRange public float SoundRange
{ {
get { return soundRange; } get { return soundRange; }
set set
{ {
if (float.IsNaN(value)) if (float.IsNaN(value))
{ {
DebugConsole.ThrowError("Attempted to set the SoundRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace()); DebugConsole.ThrowError("Attempted to set the SoundRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
return; return;
} }
soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange); soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange);
if (soundRange > 0.0f && !Static && FadeOutTime > 0.0f)
{
NeedsUpdate = true;
}
} }
} }
@@ -55,7 +59,11 @@ namespace Barotrauma
DebugConsole.ThrowError("Attempted to set the SightRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace()); DebugConsole.ThrowError("Attempted to set the SightRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
return; return;
} }
sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange); sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange);
if (sightRange > 0 && !Static && FadeOutTime > 0.0f)
{
NeedsUpdate = true;
}
} }
} }
@@ -99,13 +107,33 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool InDetectable public bool InDetectable
{ {
get => inDetectable || (SoundRange <= 0 && SightRange <= 0); get
set => inDetectable = value; {
return inDetectable || (SoundRange <= 0 && SightRange <= 0);
}
set
{
inDetectable = value;
if (inDetectable)
{
NeedsUpdate = true;
}
}
} }
public float MinSoundRange, MinSightRange; public float MinSoundRange, MinSightRange;
public float MaxSoundRange = 100000, MaxSightRange = 100000; public float MaxSoundRange = 100000, MaxSightRange = 100000;
/// <summary>
/// Does the AI target do something that requires Update() to be called (e.g. static targets don't need to be updated)
/// </summary>
public bool NeedsUpdate
{
get;
private set;
} = true;
public TargetType Type { get; private set; } public TargetType Type { get; private set; }
public enum TargetType public enum TargetType
@@ -190,14 +218,22 @@ namespace Barotrauma
if (!Static && FadeOutTime > 0) if (!Static && FadeOutTime > 0)
{ {
// The aitarget goes silent/invisible if the components don't keep it active // The aitarget goes silent/invisible if the components don't keep it active
if (!StaticSight && SightRange > 0) if (!StaticSight && sightRange > 0)
{ {
DecreaseSightRange(deltaTime); DecreaseSightRange(deltaTime);
} }
if (!StaticSound && SoundRange > 0) if (!StaticSound && soundRange > 0)
{ {
DecreaseSoundRange(deltaTime); DecreaseSoundRange(deltaTime);
} }
if (sightRange <= 0 && soundRange <= 0)
{
NeedsUpdate = false;
}
}
else
{
NeedsUpdate = false;
} }
} }
@@ -1440,14 +1440,33 @@ namespace Barotrauma
} }
else if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && distance < AttackLimb.attack.Range * 5) else if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && distance < AttackLimb.attack.Range * 5)
{ {
reachTimer += deltaTime; Vector2 targetVelocity = Vector2.Zero;
if (reachTimer > reachTimeOut) Submarine targetSub = SelectedAiTarget.Entity.Submarine;
if (targetSub != null)
{ {
reachTimer = 0; targetVelocity = targetSub.Velocity;
IgnoreTarget(SelectedAiTarget); }
State = AIState.Idle; else if (targetCharacter != null)
ResetAITarget(); {
return; targetVelocity = targetCharacter.AnimController.Collider.LinearVelocity;
}
else if (SelectedAiTarget.Entity is Item i && i.body != null)
{
targetVelocity = i.body.LinearVelocity;
}
float mySpeed = Character.AnimController.Collider.LinearVelocity.LengthSquared();
float targetSpeed = targetVelocity.LengthSquared();
if (mySpeed < 0.1f || mySpeed > targetSpeed)
{
reachTimer += deltaTime;
if (reachTimer > reachTimeOut)
{
reachTimer = 0;
IgnoreTarget(SelectedAiTarget);
State = AIState.Idle;
ResetAITarget();
return;
}
} }
} }
@@ -866,8 +866,8 @@ namespace Barotrauma
var container = i.GetComponent<ItemContainer>(); var container = i.GetComponent<ItemContainer>();
if (container == null) { return 0; } if (container == null) { return 0; }
if (!container.Inventory.CanBePut(containableItem)) { return 0; } if (!container.Inventory.CanBePut(containableItem)) { return 0; }
var rootContainer = container.Item.GetRootContainer(); var rootContainer = container.Item.GetRootContainer() ?? container.Item;
if (rootContainer?.GetComponent<Fabricator>() != null || rootContainer?.GetComponent<Deconstructor>() != null) { return 0; } if (rootContainer.GetComponent<Fabricator>() != null || rootContainer.GetComponent<Deconstructor>() != null) { return 0; }
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined)) if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
{ {
if (isRestrictionsDefined) if (isRestrictionsDefined)
@@ -882,7 +882,12 @@ namespace Barotrauma
} }
else else
{ {
return isPreferencesDefined ? 0 : 1; if (isPreferencesDefined)
{
// Use any valid locker as a fall back container.
return container.Item.HasTag("locker") ? 0.5f : 0;
}
return 1;
} }
} }
} }
@@ -1950,11 +1955,10 @@ namespace Barotrauma
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
} }
float dangerousItemsFactor = 1f; float dangerousItemsFactor = 1f;
foreach (Item item in Item.ItemList) foreach (Item item in Item.DangerousItems)
{ {
if (item.CurrentHull != hull) { continue; } if (item.CurrentHull == hull)
if (item.Prefab != null && item.Prefab.IsDangerous) {
{
dangerousItemsFactor = 0; dangerousItemsFactor = 0;
break; break;
} }
@@ -245,7 +245,7 @@ namespace Barotrauma
{ {
get get
{ {
if (IgnoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC) if (IgnoreAtOutpost && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
{ {
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID)) if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
{ {
@@ -48,16 +48,29 @@ namespace Barotrauma
} }
else else
{ {
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float reduction = isPriority ? 1 : 2; float reduction = isPriority ? 1 : 2;
float max = AIObjectiveManager.LowestOrderPriority - reduction; float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
float devotion = CumulatedDevotion / 100; if (operateObjective != null && objectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1)); {
// Prioritize leaks that we are already fixing
Priority = maxPriority;
}
else
{
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
{
// Double the distance when the leak can be accessed from the current hull.
distanceFactor *= 2;
}
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
} }
return Priority; return Priority;
} }
@@ -202,7 +215,7 @@ namespace Barotrauma
// This is an approximation, because we don't know the exact reach until the pose is taken. // This is an approximation, because we don't know the exact reach until the pose is taken.
// And even then the actual range depends on the direction we are aiming to. // And even then the actual range depends on the direction we are aiming to.
// Found out that without any multiplier the value (209) is often too short. // Found out that without any multiplier the value (209) is often too short.
return repairTool.Range + armLength * 1.3f; return repairTool.Range + armLength * 2;
} }
} }
} }
@@ -185,6 +185,11 @@ namespace Barotrauma
{ {
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null); PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
} }
else
{
PathSteering.ResetPath();
PathSteering.Reset();
}
} }
else else
{ {
@@ -290,12 +295,25 @@ namespace Barotrauma
{ {
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null); PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
} }
else
{
PathSteering.ResetPath();
PathSteering.Reset();
}
} }
} }
public void Wander(float deltaTime) public void Wander(float deltaTime)
{ {
if (character.IsClimbing) { return; } if (character.IsClimbing)
{
if (character.AnimController.GetHeightFromFloor() < 0.1f)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
}
return;
}
var currentHull = character.CurrentHull; var currentHull = character.CurrentHull;
if (!character.AnimController.InWater && currentHull != null) if (!character.AnimController.InWater && currentHull != null)
{ {
@@ -142,7 +142,7 @@ namespace Barotrauma
} }
var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character); var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
if (order == null) { continue; } if (order == null) { continue; }
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC) if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
{ {
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID)) if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
{ {
@@ -238,7 +238,7 @@ namespace Barotrauma
}; };
if (repairTool != null) if (repairTool != null)
{ {
objective.CloseEnough = repairTool.Range * 0.75f; objective.CloseEnough = AIObjectiveFixLeak.CalculateReach(repairTool, character);
} }
return objective; return objective;
}, },
@@ -1,8 +1,6 @@
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using FarseerPhysics;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -90,6 +88,10 @@ namespace Barotrauma
{ {
steering = Vector2.Normalize(steering) * Math.Abs(speed); steering = Vector2.Normalize(steering) * Math.Abs(speed);
} }
if (host is AIController aiController && aiController?.Character.CharacterHealth.GetAfflictionOfType("invertcontrols".ToIdentifier()) != null)
{
steering = -steering;
}
host.Steering = steering; host.Steering = steering;
} }

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