Unstable 0.17.5.0

This commit is contained in:
Markus Isberg
2022-03-30 01:20:59 +09:00
parent c1b8e5a341
commit 44ded0225a
88 changed files with 2033 additions and 1430 deletions

View File

@@ -1151,15 +1151,7 @@ namespace Barotrauma
}
}
partial void OnMoneyChanged(int prevAmount, int newAmount)
{
if (newAmount > prevAmount)
{
int increase = newAmount - prevAmount;
AddMessage("+" + TextManager.GetWithVariable("currencyformat", "[credits]", "[value]").Value,
GUIStyle.Yellow, playSound: this == Controlled, "money".ToIdentifier(), increase);
}
}
partial void OnMoneyChanged(int prevAmount, int newAmount) { }
partial void OnTalentGiven(TalentPrefab talentPrefab)
{

View File

@@ -0,0 +1,225 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.Steam;
using Microsoft.Xna.Framework;
using Directory = Barotrauma.IO.Directory;
using File = Barotrauma.IO.File;
using Path = Barotrauma.IO.Path;
namespace Barotrauma.Transition
{
/// <summary>
/// Class dedicated to transitioning away from the old, shitty
/// Mods + Submarines folders to the new LocalMods folder
/// </summary>
public static class UgcTransition
{
private const string readmeName = "LOCALMODS_README.txt";
public static void Prepare()
{
TaskPool.Add("UgcTransition.Prepare", DetermineItemsToTransition(), t =>
{
if (!t.TryGetResult(out (OldSubs, OldMods) pair)) { return; }
var (subs, mods) = pair;
if (!subs.FilePaths.Any() && !mods.Mods.Any()) { return; }
var msgBox = new GUIMessageBox(TextManager.Get("Ugc.TransferTitle"), "", relativeSize: (0.5f, 0.8f),
buttons: new LocalizedString[] { TextManager.Get("Ugc.TransferButton") });
var desc = new GUITextBlock(new RectTransform((1.0f, 0.24f), msgBox.Content.RectTransform),
text: TextManager.Get("Ugc.TransferDesc"), wrap: true, textAlignment: Alignment.CenterLeft);
var modsList = new GUIListBox(new RectTransform((1.0f, 0.6f), msgBox.Content.RectTransform))
{
HoverCursor = CursorState.Default
};
Dictionary<string, GUITickBox> pathTickboxMap = new Dictionary<string, GUITickBox>();
void addHeader(LocalizedString str)
{
var itemFrame = new GUITextBlock(new RectTransform((1.0f, 0.08f), modsList.Content.RectTransform),
text: str, font: GUIStyle.SubHeadingFont)
{
CanBeFocused = false
};
}
void addTickbox(string dir, string name, bool ticked)
{
var itemFrame = new GUIFrame(new RectTransform((1.0f, 0.07f), modsList.Content.RectTransform),
style: null)
{
CanBeFocused = false
};
var tickbox = new GUITickBox(new RectTransform(Vector2.One, itemFrame.RectTransform), name)
{
Selected = ticked
};
pathTickboxMap.Add(dir, tickbox);
}
addHeader(TextManager.Get("WorkshopLabelSubmarines"));
foreach (var sub in subs.FilePaths)
{
var subName = Path.GetFileNameWithoutExtension(sub);
addTickbox(sub, subName, ticked: !ContentPackageManager.LocalPackages.Any(p => p.NameMatches(subName)));
}
addHeader("");
addHeader(TextManager.Get("SubscribedMods"));
foreach (var mod in mods.Mods)
{
addTickbox(mod.Dir, mod.Name, ticked: !ContentPackageManager.LocalPackages.Any(p => p.SteamWorkshopId != 0 && p.SteamWorkshopId == mod.Item?.Id));
}
GUIMessageBox? subMsgBox = null;
void createSubMsgBox(LocalizedString str, bool closable)
{
subMsgBox?.Close();
subMsgBox = new GUIMessageBox(headerText: "", text: str,
buttons: closable ? new[] { TextManager.Get("Close") } : Array.Empty<LocalizedString>());
if (closable)
{
subMsgBox.Buttons[0].OnClicked = subMsgBox.Close;
}
}
msgBox.Buttons[0].OnClicked = (b, o) =>
{
TaskPool.Add("TransferMods", TransferMods(pathTickboxMap), t2 =>
{
if (t2.Exception != null)
{
DebugConsole.ThrowError("There was an error transferring mods", t2.Exception.GetInnermost());
}
ContentPackageManager.LocalPackages.Refresh();
createSubMsgBox(TextManager.Get("Ugc.TransferComplete"), closable: true);
});
msgBox.Close();
createSubMsgBox(TextManager.Get("Ugc.Transferring"), closable: false);
return false;
};
});
}
private struct OldSubs
{
public readonly IReadOnlyList<string> FilePaths;
public OldSubs(IReadOnlyList<string> filePaths)
{
FilePaths = filePaths;
}
}
private struct OldMods
{
public readonly IReadOnlyList<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)> Mods;
public OldMods(IReadOnlyList<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)> mods)
{
Mods = mods;
}
}
private const string oldSubsPath = "Submarines";
private const string oldModsPath = "Mods";
private static async Task<(OldSubs Subs, OldMods Mods)> DetermineItemsToTransition()
{
string[] subs = Array.Empty<string>();
List<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)> mods
= new List<(string Dir, string Name, Steamworks.Ugc.Item? Item, DateTime InstallTime)>();
if (FolderShouldBeTransitioned(oldModsPath))
{
subs = Directory.GetFiles(oldSubsPath, "*.sub", SearchOption.TopDirectoryOnly);
string[] allOldMods = Directory.GetDirectories(oldModsPath, "*", SearchOption.TopDirectoryOnly);
var publishedItems = await SteamManager.Workshop.GetPublishedItems();
foreach (var modDir in allOldMods)
{
var fileList = XMLExtensions.TryLoadXml(Path.Combine(modDir, ContentPackage.FileListFileName), out _);
if (fileList?.Root is null) { continue; }
var oldId = fileList.Root.GetAttributeUInt64("steamworkshopid", 0);
var updateTime = File.GetLastWriteTime(modDir).ToUniversalTime();
var oldName = fileList.Root.GetAttributeString("name", "");
var item = oldId != 0 ? publishedItems.FirstOrNull(it => it.Id == oldId) : null;
if (oldId == 0 || item.HasValue)
{
mods.Add((modDir, oldName, item, updateTime));
}
}
}
while (!(Screen.Selected is MainMenuScreen)) { await Task.Delay(500); }
return (new OldSubs(subs), new OldMods(mods));
}
private static bool FolderShouldBeTransitioned(string folderName)
{
return Directory.Exists(folderName)
&& !File.Exists(Path.Combine(folderName, readmeName));
}
private static async Task TransferMods(Dictionary<string, GUITickBox> pathTickboxMap)
{
//WriteReadme(oldSubsPath); //can't do this because the submarine discovery code is borked
WriteReadme(oldModsPath);
foreach (var (path, tickbox) in pathTickboxMap)
{
if (!tickbox.Selected) { continue; }
string dirName = Path.GetFileNameWithoutExtension(path);
string destPath = Path.Combine(ContentPackage.LocalModsDir, dirName);
//find unique path to save in
for (int i = 0;;i++)
{
if (!Directory.Exists(destPath)) { break; }
destPath = Path.Combine(ContentPackage.LocalModsDir, $"{dirName}.{i}");
}
if (path.StartsWith(oldSubsPath, StringComparison.OrdinalIgnoreCase))
{
//copying a sub: manually create filelist.xml
ModProject modProject = new ModProject
{
Name = dirName,
ModVersion = ContentPackage.DefaultModVersion
};
modProject.AddFile(ModProject.File.FromPath<SubmarineFile>(Path.Combine(ContentPath.ModDirStr, $"{dirName}.sub")));
Directory.CreateDirectory(destPath);
File.Copy(path, Path.Combine(destPath, $"{dirName}.sub"));
modProject.Save(Path.Combine(destPath, ContentPackage.FileListFileName));
await Task.Yield();
}
else
{
//copying a mod: we have a neat method for that!
await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath);
}
}
}
private static void WriteReadme(string folderName)
{
if (!Directory.Exists(folderName)) { return; }
File.WriteAllText(path: Path.Combine(folderName, readmeName),
contents: "This folder is no longer used by Barotrauma;\n" +
"your mods and submarines should have been transferred\n" +
"to LocalMods. If they are not being found, delete this\n" +
"readme and relaunch the game.", encoding: Encoding.UTF8);
}
}
}

View File

@@ -1101,58 +1101,6 @@ namespace Barotrauma
}
}, isCheat: true));
commands.Add(new Command("save|savesub", "save [submarine name]: Save the currently loaded submarine using the specified name.", (string[] args) =>
{
if (args.Length < 1) { return; }
GameMain.SubEditorScreen.SetMode(SubEditorScreen.Mode.Default);
string fileName = string.Join(" ", args);
if (fileName.Contains("../"))
{
ThrowError("Illegal symbols in filename (../)");
return;
}
if (Submarine.MainSub.TrySaveAs(Barotrauma.IO.Path.Combine(SubmarineInfo.SavePath, fileName + ".sub")))
{
NewMessage("Sub saved", Color.Green);
}
}));
commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) =>
{
if (args.Length == 0) { return; }
if (GameMain.GameSession != null)
{
ThrowError("The loadsub command cannot be used when a round is running. You should probably be using spawnsub instead.");
return;
}
string name = string.Join(" ", args);
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => name.Equals(s.Name, StringComparison.OrdinalIgnoreCase));
if (subInfo == null)
{
string path = Path.Combine(SubmarineInfo.SavePath, name);
if (!File.Exists(path))
{
ThrowError($"Could not find a submarine with the name \"{name}\" or in the path {path}.");
return;
}
subInfo = new SubmarineInfo(path);
}
Submarine.Load(subInfo, true);
},
() =>
{
return new string[][]
{
SubmarineInfo.SavedSubmarines.Select(s => s.Name).ToArray()
};
}));
commands.Add(new Command("cleansub", "", (string[] args) =>
{
for (int i = MapEntity.mapEntityList.Count - 1; i >= 0; i--)

View File

@@ -22,7 +22,7 @@ namespace Barotrauma
private GUIButton clearAllButton;
private List<CharacterInfo> PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires;
private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign();
private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(ClientPermissions.ManageHires);
private Point resolutionWhenCreated;
@@ -433,7 +433,7 @@ namespace Barotrauma
else if (!btn.Enabled)
{
btn.ToolTip = string.Empty;
btn.Enabled = true;
btn.Enabled = HasPermission;
}
};
@@ -632,7 +632,7 @@ namespace Barotrauma
totalBlock.Text = TextManager.FormatCurrency(total);
bool enoughMoney = campaign == null || campaign.Wallet.CanAfford(total);
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any();
validateHiresButton.Enabled = enoughMoney && HasPermission && pendingList.Content.RectTransform.Children.Any();
}
public bool ValidateHires(List<CharacterInfo> hires, bool createNetworkEvent = false)

View File

@@ -316,26 +316,27 @@ namespace Barotrauma
if (GameMain.ShowPerf)
{
int x = 400;
int y = 10;
DrawString(spriteBatch, new Vector2(300, y),
DrawString(spriteBatch, new Vector2(x, y),
"Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" +
" Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms",
GUIStyle.Green, Color.Black * 0.8f, font: GUIStyle.SmallFont);
y += 15;
GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: GUIStyle.Green);
GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(x, y, 170, 50), color: GUIStyle.Green);
y += 50;
DrawString(spriteBatch, new Vector2(300, y),
DrawString(spriteBatch, new Vector2(x, y),
"Update - Avg: " + GameMain.PerformanceCounter.UpdateTimeGraph.Average().ToString("0.00") + " ms" +
" Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms",
Color.LightBlue, Color.Black * 0.8f, font: GUIStyle.SmallFont);
y += 15;
GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Color.LightBlue);
GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(x, y, 170, 50), color: Color.LightBlue);
y += 50;
foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers)
{
float elapsedMillisecs = GameMain.PerformanceCounter.GetAverageElapsedMillisecs(key);
DrawString(spriteBatch, new Vector2(300, y),
DrawString(spriteBatch, new Vector2(x, y),
key + ": " + elapsedMillisecs.ToString("0.00"),
Color.Lerp(Color.LightGreen, GUIStyle.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15;
@@ -351,18 +352,19 @@ namespace Barotrauma
if (Powered.Grids != null)
{
DrawString(spriteBatch, new Vector2(300, y), "Grids: " + Powered.Grids.Count, Color.LightGreen, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y), "Grids: " + Powered.Grids.Count, Color.LightGreen, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15;
}
if (Settings.EnableDiagnostics)
{
DrawString(spriteBatch, new Vector2(320, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(320, y + 15), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(320, y + 30), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(320, y + 45), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(320, y + 60), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(320, y + 75), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
x += 20;
DrawString(spriteBatch, new Vector2(x, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 15), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 30), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 45), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 60), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
DrawString(spriteBatch, new Vector2(x, y + 75), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUIStyle.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
}
}

View File

@@ -534,7 +534,10 @@ namespace Barotrauma
}
}
Vector2 finalBottomRight = characterPositions[endIndex];
finalBottomRight += Font.MeasureChar(Text[endIndex]) * TextBlock.TextScale;
if (Text.Length > endIndex)
{
finalBottomRight += Font.MeasureChar(Text[endIndex]) * TextBlock.TextScale;
}
drawRect(topLeft, finalBottomRight);
}

View File

@@ -113,71 +113,40 @@ namespace Barotrauma
#region Permissions
private bool hadPermissions, hadBuyPermissions, hadSellInventoryPermissions, hadSellSubPermissions;
private bool hadBuyPermissions, hadSellInventoryPermissions, hadSellSubPermissions;
private bool HasPermissions
{
get => GetPermissions();
set => hadPermissions = value;
}
private bool HasBuyPermissions
{
get => HasPermissions || GetPermissions(StoreTab.Buy);
get => HasPermissionToUseTab(StoreTab.Buy);
set => hadBuyPermissions = value;
}
private bool HasSellInventoryPermissions
{
get => HasPermissions || GetPermissions(StoreTab.Sell);
get => HasPermissionToUseTab(StoreTab.Sell);
set => hadSellInventoryPermissions = value;
}
private bool HasSellSubPermissions
{
get => HasPermissions || GetPermissions(StoreTab.SellSub);
get => HasPermissionToUseTab(StoreTab.SellSub);
set => hadSellSubPermissions = value;
}
private bool GetPermissions(StoreTab? tab = null)
private bool HasPermissionToUseTab(StoreTab tab)
{
if (!tab.HasValue)
return tab switch
{
return campaignUI.Campaign.AllowedToManageCampaign() || campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.CampaignStore);
}
else
{
return tab.Value switch
{
StoreTab.Buy => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.BuyItems),
StoreTab.Sell => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems),
StoreTab.SellSub => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems),
_ => false,
};
}
StoreTab.Buy => true,
StoreTab.Sell => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems),
StoreTab.SellSub => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems),
_ => false,
};
}
private void UpdatePermissions(StoreTab? tab = null)
private void UpdatePermissions()
{
HasPermissions = GetPermissions();
if (!tab.HasValue)
{
HasBuyPermissions = GetPermissions(StoreTab.Buy);
HasSellInventoryPermissions = GetPermissions(StoreTab.Sell);
HasSellSubPermissions = GetPermissions(StoreTab.SellSub);
}
else
{
switch (tab.Value)
{
case StoreTab.Buy:
HasBuyPermissions = GetPermissions(tab.Value);
break;
case StoreTab.Sell:
HasSellInventoryPermissions = GetPermissions(tab.Value);
break;
case StoreTab.SellSub:
HasSellSubPermissions = GetPermissions(tab.Value);
break;
}
}
HasBuyPermissions = HasPermissionToUseTab(StoreTab.Buy);
HasSellInventoryPermissions = HasPermissionToUseTab(StoreTab.Sell);
HasSellSubPermissions = HasPermissionToUseTab(StoreTab.SellSub);
}
private bool HasTabPermissions(StoreTab tab)
@@ -196,23 +165,16 @@ namespace Barotrauma
return HasTabPermissions(activeTab);
}
private bool HavePermissionsChanged(StoreTab? tab = null)
private bool HavePermissionsChanged(StoreTab tab)
{
if (!tab.HasValue)
bool hadTabPermissions = tab switch
{
return hadPermissions != HasPermissions;
}
else
{
bool hadTabPermissions = tab.Value switch
{
StoreTab.Buy => hadBuyPermissions,
StoreTab.Sell => hadSellInventoryPermissions,
StoreTab.SellSub => hadSellSubPermissions,
_ => false
};
return hadTabPermissions != HasTabPermissions(tab.Value);
}
StoreTab.Buy => hadBuyPermissions,
StoreTab.Sell => hadSellInventoryPermissions,
StoreTab.SellSub => hadSellSubPermissions,
_ => false
};
return hadTabPermissions != HasTabPermissions(tab);
}
#endregion
@@ -2202,10 +2164,6 @@ namespace Barotrauma
{
RefreshItemsToSellFromSub();
}
if (needsRefresh || HavePermissionsChanged())
{
Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
}
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy))
{
RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);

View File

@@ -43,6 +43,7 @@ namespace Barotrauma
private GUIButton transferMenuButton;
private float transferMenuOpenState;
private bool transferMenuStateCompleted;
private readonly HashSet<Identifier> registeredEvents = new HashSet<Identifier>();
private class LinkedGUI
{
@@ -218,7 +219,7 @@ namespace Barotrauma
public void AddToGUIUpdateList()
{
infoFrame?.AddToGUIUpdateList(order: 1);
infoFrame?.AddToGUIUpdateList();
NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList();
}
@@ -298,11 +299,13 @@ namespace Barotrauma
}
SetBalanceText(balanceText, campaignMode.Bank.Balance);
campaignMode.OnMoneyChanged.RegisterOverwriteExisting(nameof(CreateInfoFrame).ToIdentifier(), e =>
Identifier eventIdentifier = nameof(CreateInfoFrame).ToIdentifier();
campaignMode.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{
if (e.Wallet != campaignMode.Bank) { return; }
if (!e.Owner.IsNone()) { return; }
SetBalanceText(balanceText, e.Wallet.Balance);
});
registeredEvents.Add(eventIdentifier);
static void SetBalanceText(GUITextBlock text, int balance)
{
@@ -371,15 +374,16 @@ namespace Barotrauma
}
}
private const float jobColumnWidthPercentage = 0.138f;
private const float characterColumnWidthPercentage = 0.656f;
private const float pingColumnWidthPercentage = 0.206f;
private const float jobColumnWidthPercentage = 0.138f,
characterColumnWidthPercentage = 0.45f,
pingColumnWidthPercentage = 0.206f,
walletColumnWidthPercentage = 0.206f;
private int jobColumnWidth, characterColumnWidth, pingColumnWidth;
private int jobColumnWidth, characterColumnWidth, pingColumnWidth, walletColumnWidth;
private void CreateCrewListFrame(GUIFrame crewFrame)
{
crew = GameMain.GameSession?.CrewManager?.GetCharacters() ?? Array.Empty<Character>();
crew = GameMain.GameSession?.CrewManager?.GetCharacters() ?? new List<Character>() { TestScreen.dummyCharacter};
teamIDs = crew.Select(c => c.TeamID).Distinct().ToList();
// Show own team first when there's more than one team
@@ -553,20 +557,23 @@ namespace Barotrauma
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
GUIButton pingButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("serverlistping"), style: "GUIButtonSmallFreeScale");
GUIButton walletButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("crewwallet.wallet"), style: "GUIButtonSmallFreeScale");
sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f);
pingButton.RectTransform.RelativeSize = new Vector2(pingColumnWidthPercentage * sizeMultiplier, 1f);
walletButton.RectTransform.RelativeSize = new Vector2(walletColumnWidthPercentage * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = pingButton.TextBlock.Font = GUIStyle.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = pingButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = pingButton.ForceUpperCase = ForceUpperCase.Yes;
jobButton.TextBlock.Font = characterButton.TextBlock.Font = pingButton.TextBlock.Font = walletButton.TextBlock.Font = GUIStyle.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = pingButton.CanBeFocused = walletButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = pingButton.ForceUpperCase = walletButton.ForceUpperCase = ForceUpperCase.Yes;
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
pingColumnWidth = pingButton.Rect.Width;
walletColumnWidth = walletButton.Rect.Width;
}
private void CreateMultiPlayerList(bool refresh)
@@ -651,6 +658,8 @@ namespace Barotrauma
};
}
}
CreateWalletCrewFrame(character, paddedFrame);
}
private void CreateMultiPlayerClientElement(Client client)
@@ -678,6 +687,10 @@ namespace Barotrauma
};
CreateNameWithPermissionIcon(client, paddedFrame);
if (client.Character is { } character)
{
CreateWalletCrewFrame(character, paddedFrame);
}
linkedGUIList.Add(new LinkedGUI(client, frame, false, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center)));
}
@@ -714,6 +727,47 @@ namespace Barotrauma
return 0;
}
private void CreateWalletCrewFrame(Character character, GUILayoutGroup paddedFrame)
{
GUILayoutGroup walletLayout = new GUILayoutGroup(new RectTransform(new Point(walletColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.Center)
{
CanBeFocused = false
};
if (character.IsBot) { return; }
GUILayoutGroup paddedLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1f), walletLayout.RectTransform, Anchor.Center), isHorizontal: true)
{
Stretch = true
};
GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "StoreTradingIcon", scaleToFit: true);
GUITextBlock walletBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), string.Empty, textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont);
SetWalletText(walletBlock, character.Wallet);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
{
Identifier eventIdentifier = new Identifier($"{nameof(CreateWalletCrewFrame)}.{character.ID}");
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{
if (!(e.Owner is Some<Character> { Value: var owner }) || owner != character) { return; }
SetWalletText(walletBlock, e.Wallet);
});
registeredEvents.Add(eventIdentifier);
}
static void SetWalletText(GUITextBlock block, Wallet wallet)
{
block.Text = TextManager.FormatCurrency(wallet.Balance);
block.ToolTip = string.Empty;
if (block.TextSize.X + block.Padding.X + block.Padding.Z > block.Rect.Width)
{
block.ToolTip = block.Text;
block.Text = TextManager.Get("crewwallet.balance.toomuchtoshow");
}
}
}
private void CreateNameWithPermissionIcon(Client client, GUILayoutGroup paddedFrame)
{
GUITextBlock characterNameBlock;
@@ -795,7 +849,7 @@ namespace Barotrauma
GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter");
if (existingPreview != null) { infoFrameHolder.RemoveChild(existingPreview); }
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.145f, 0) })
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.69f), infoFrameHolder.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) })
{
UserData = "SelectedCharacter"
};
@@ -810,28 +864,29 @@ namespace Barotrauma
{
GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character)));
GameMain.Client.SelectCrewCharacter(character, preview);
CreateWalletFrame(background, character);
if (!character.IsBot && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { CreateWalletFrame(background, character, mpCampaign); }
}
}
else if (client != null)
{
GUIComponent preview = CreateClientInfoFrame(background, client, GetPermissionIcon(client));
GameMain.Client?.SelectCrewClient(client, preview);
if (client.Character != null)
if (client.Character != null && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)
{
CreateWalletFrame(background, client.Character);
CreateWalletFrame(background, client.Character, mpCampaign);
}
}
return true;
}
private void CreateWalletFrame(GUIComponent parent, Character character)
private void CreateWalletFrame(GUIComponent parent, Character character, MultiPlayerCampaign campaign)
{
if (campaign is null) { throw new ArgumentNullException(nameof(campaign), "Tried to create a wallet frame when campaign was null"); }
if (character is null) { throw new ArgumentNullException(nameof(character), "Tried to create a wallet frame for a null character");}
isTransferMenuOpen = false;
transferMenuOpenState = 1f;
ImmutableArray<Character> salaryCrew = Mission.GetSalaryEligibleCrew().Where(c => c != character).ToImmutableArray();
ImmutableHashSet<Character> salaryCrew = GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != character).ToImmutableHashSet();
Wallet targetWallet = character.Wallet;
@@ -905,8 +960,8 @@ namespace Barotrauma
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center);
GUILayoutGroup centerButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1f), buttonLayout.RectTransform), isHorizontal: true);
GUIButton confirmButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("confirm"), style: "GUIButtonFreeScale") { Enabled = false };
GUIButton resetButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("reset"), style: "GUIButtonFreeScale") { Enabled = false };
GUIButton confirmButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("confirm"), style: "GUIButtonFreeScale") { Enabled = false };
// @formatter:on
ImmutableArray<GUILayoutGroup> layoutGroups = ImmutableArray.Create(transferMenuLayout, paddedTransferMenuLayout, mainLayout, leftLayout, rightLayout);
MedicalClinicUI.EnsureTextDoesntOverflow(character.Name, leftName, leftLayout.Rect, layoutGroups);
@@ -927,137 +982,146 @@ namespace Barotrauma
ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen);
ToggleCenterButton(centerButton, isSending);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
if (!(Character.Controlled is { } myCharacter))
{
if (!(Character.Controlled is { } myCharacter))
{
salarySlider.Enabled = false;
transferAmountInput.Enabled = false;
centerButton.Enabled = false;
confirmButton.Enabled = false;
return;
}
salarySlider.Enabled = false;
transferAmountInput.Enabled = false;
centerButton.Enabled = false;
confirmButton.Enabled = false;
return;
}
bool hasPermissions = campaign.AllowedToManageCampaign();
salarySlider.Enabled = hasPermissions;
Wallet otherWallet;
bool hasMoneyPermissions = campaign.AllowedToManageCampaign(ClientPermissions.ManageMoney);
salarySlider.Enabled = hasMoneyPermissions;
Wallet otherWallet;
switch (hasPermissions)
{
case true:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
break;
case false when character == myCharacter:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
isSending = true;
ToggleCenterButton(centerButton, isSending);
break;
default:
rightName.Text = myCharacter.Name;
otherWallet = campaign.PersonalWallet;
break;
}
switch (hasMoneyPermissions)
{
case true:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
break;
case false when character == myCharacter:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
isSending = true;
ToggleCenterButton(centerButton, isSending);
break;
default:
rightName.Text = myCharacter.Name;
otherWallet = campaign.PersonalWallet;
break;
}
MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups);
if (!hasPermissions)
MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups);
updateButtonText();
if (!hasMoneyPermissions)
{
if (character != Character.Controlled)
{
centerButton.Enabled = centerButton.CanBeFocused = false;
salarySlider.Enabled = salarySlider.CanBeFocused = false;
}
salarySlider.Enabled = salarySlider.CanBeFocused = false;
}
leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
UpdateAllInputs();
centerButton.OnClicked = (btn, o) =>
{
isSending = !isSending;
updateButtonText();
ToggleCenterButton(btn, isSending);
UpdateAllInputs();
return true;
};
centerButton.OnClicked = (btn, o) =>
void updateButtonText()
{
confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney");
}
transferAmountInput.OnValueChanged = input =>
{
UpdateInputs();
};
transferAmountInput.OnValueEntered = input =>
{
UpdateAllInputs();
};
Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier();
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{
if (e.Wallet == targetWallet)
{
isSending = !isSending;
ToggleCenterButton(btn, isSending);
UpdateAllInputs();
return true;
};
transferAmountInput.OnValueChanged = input =>
{
UpdateInputs();
};
transferAmountInput.OnValueEntered = input =>
{
UpdateAllInputs();
};
campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(CreateWalletFrame).ToIdentifier(), e =>
{
if (e.Wallet == targetWallet)
{
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance);
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f;
}
UpdateAllInputs();
});
resetButton.OnClicked = (button, o) =>
{
transferAmountInput.IntValue = 0;
UpdateAllInputs();
return true;
};
confirmButton.OnClicked = (button, o) =>
{
int amount = transferAmountInput.IntValue;
if (amount == 0) { return false; }
Option<Character> target1 = Option<Character>.Some(character),
target2 = otherWallet == campaign.Bank ? Option<Character>.None() : Option<Character>.Some(myCharacter);
if (isSending) { (target1, target2) = (target2, target1); }
SendTransaction(target1, target2, amount);
isTransferMenuOpen = false;
ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen);
return true;
};
void UpdateAllInputs()
{
UpdateInputs();
UpdateMaxInput();
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance);
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f;
}
UpdateAllInputs();
});
registeredEvents.Add(eventIdentifier);
void UpdateInputs()
{
confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0;
if (transferAmountInput.IntValue == 0)
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
rightBalance.TextColor = GUIStyle.TextColorNormal;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance);
leftBalance.TextColor = GUIStyle.TextColorNormal;
}
else if (isSending)
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Blue;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Red;
}
else
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Red;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Blue;
}
}
resetButton.OnClicked = (button, o) =>
{
transferAmountInput.IntValue = 0;
UpdateAllInputs();
return true;
};
void UpdateMaxInput()
confirmButton.OnClicked = (button, o) =>
{
int amount = transferAmountInput.IntValue;
if (amount == 0) { return false; }
Option<Character> target1 = Option<Character>.Some(character),
target2 = otherWallet == campaign.Bank ? Option<Character>.None() : Option<Character>.Some(myCharacter);
if (isSending) { (target1, target2) = (target2, target1); }
SendTransaction(target1, target2, amount);
isTransferMenuOpen = false;
ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen);
return true;
};
void UpdateAllInputs()
{
UpdateInputs();
UpdateMaxInput();
}
void UpdateInputs()
{
confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0;
if (transferAmountInput.IntValue == 0)
{
transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance;
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance);
rightBalance.TextColor = GUIStyle.TextColorNormal;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance);
leftBalance.TextColor = GUIStyle.TextColorNormal;
}
else if (isSending)
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Blue;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Red;
}
else
{
rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Red;
leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Blue;
}
}
void UpdateMaxInput()
{
transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance;
}
static void ToggleTransferMenuIcon(GUIButton btn, bool open)
@@ -1173,7 +1237,7 @@ namespace Barotrauma
private void CreateMultiPlayerLogContent(GUIFrame crewFrame)
{
var logContainer = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), crewFrame.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) });
var logContainer = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.145f, 0) });
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.900f, 0.900f), logContainer.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, 0.0475f) }, style: null);
var content = new GUILayoutGroup(new RectTransform(Vector2.One, innerFrame.RectTransform))
{
@@ -1188,22 +1252,22 @@ namespace Barotrauma
Spacing = (int)(5 * GUI.Scale)
};
foreach (Pair<string, PlayerConnectionChangeType> pair in storedMessages)
foreach ((string message, PlayerConnectionChangeType type) in storedMessages)
{
AddLineToLog(pair.First, pair.Second);
AddLineToLog(message, type);
}
logList.BarScroll = 1f;
}
private static readonly List<Pair<string, PlayerConnectionChangeType>> storedMessages = new List<Pair<string, PlayerConnectionChangeType>>();
private static readonly List<(string message, PlayerConnectionChangeType type)> storedMessages = new List<(string message, PlayerConnectionChangeType type)>();
public static void StorePlayerConnectionChangeMessage(ChatMessage message)
{
if (!GameMain.GameSession?.IsRunning ?? true) { return; }
string msg = ChatMessage.GetTimeStamp() + message.TextWithSender;
storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType));
storedMessages.Add((msg, message.ChangeType));
if (GameSession.IsTabMenuOpen && SelectedTab == InfoFrameTab.Crew)
{
@@ -2026,5 +2090,14 @@ namespace Barotrauma
if (character != Character.Controlled) { return; }
UpdateTalentInfo();
}
public void OnClose()
{
if (!(GameMain.GameSession?.Campaign is { } campaign)) { return; }
foreach (Identifier identifier in registeredEvents)
{
campaign.OnMoneyChanged.TryDeregister(identifier);
}
}
}
}

View File

@@ -73,6 +73,8 @@ namespace Barotrauma
private Point screenResolution;
private bool needsRefresh = true;
/// <summary>
/// While set to true any call to <see cref="RefreshUpgradeList"/> will cause the buy button to be disabled and to not update the prices.
/// This is to prevent us from buying another upgrade before the server has given us the new prices and causing potential syncing issues.
@@ -102,13 +104,18 @@ namespace Barotrauma
CreateUI(upgradeFrame);
if (Campaign == null) { return; }
Campaign.UpgradeManager.OnUpgradesChanged += RefreshAll;
Campaign.CargoManager.OnPurchasedItemsChanged += RefreshAll;
Campaign.CargoManager.OnSoldItemsChanged += RefreshAll;
Campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(UpgradeStore).ToIdentifier(), e => { RefreshAll(); } );
Campaign.UpgradeManager.OnUpgradesChanged += RequestRefresh;
Campaign.CargoManager.OnPurchasedItemsChanged += RequestRefresh;
Campaign.CargoManager.OnSoldItemsChanged += RequestRefresh;
Campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(UpgradeStore).ToIdentifier(), e => { RequestRefresh(); } );
}
public void RefreshAll()
public void RequestRefresh()
{
needsRefresh = true;
}
private void RefreshAll()
{
switch (selectedUpgradeTab)
{
@@ -131,6 +138,7 @@ namespace Barotrauma
}
break;
}
needsRefresh = false;
}
private void RefreshUpgradeList()
@@ -1295,7 +1303,9 @@ namespace Barotrauma
{
if (Campaign == null) { return; }
if (!parent.Children.Any() || Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine || GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y)
if (!parent.Children.Any() ||
Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine ||
GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y)
{
GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind();
drawnSubmarine = Submarine.MainSub;
@@ -1313,6 +1323,10 @@ namespace Barotrauma
// we also need this when we first load in so we know which category entries to disable since the CampaignUI is created before the submarine is loaded in.
RefreshAll();
}
if (needsRefresh)
{
RefreshAll();
}
// accept an active confirmation popup if any
if (PlayerInput.KeyHit(Keys.Enter) && GUIMessageBox.MessageBoxes.Any())
@@ -1588,7 +1602,7 @@ namespace Barotrauma
if (button != null)
{
button.Enabled = currentLevel < prefab.MaxLevel;
if (WaitForServerUpdate || !campaign.AllowedToManageCampaign() || !campaign.Wallet.CanAfford(price))
if (WaitForServerUpdate || !campaign.Wallet.CanAfford(price))
{
button.Enabled = false;
}
@@ -1693,7 +1707,7 @@ namespace Barotrauma
return frames.ToArray();
}
private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign();
private bool HasPermission => true;
// just a shortcut to create new RectTransforms since all the new RectTransform and new Vector2 confuses my IDE (and me)
private static RectTransform rectT(float x, float y, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft, ScaleBasis scaleBasis = ScaleBasis.Normal)

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
@@ -22,27 +23,50 @@ namespace Barotrauma
private float votingTime = 100f;
private float timer;
private VoteType currentVoteType;
private Color submarineColor => GUIStyle.Orange;
private Color SubmarineColor => GUIStyle.Orange;
private Point createdForResolution;
public VotingInterface(Client starter, SubmarineInfo info, VoteType type, float votingTime)
public static VotingInterface CreateSubmarineVotingInterface(Client starter, SubmarineInfo info, VoteType type, float votingTime)
{
if (starter == null || info == null) return;
SetSubmarineVotingText(starter, info, type);
this.votingTime = votingTime;
getYesVotes = SubmarineYesVotes;
getNoVotes = SubmarineNoVotes;
getMaxVotes = SubmarineMaxVotes;
onVoteEnd = () => SendSubmarineVoteEndMessage(info, type);
if (starter == null || info == null) { return null; }
Initialize(starter, type);
var subVoting = new VotingInterface()
{
votingTime = votingTime,
getYesVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountYes(type) ?? 0,
getNoVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountNo(type) ?? 0,
getMaxVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountMax(type) ?? 0,
};
subVoting.onVoteEnd = () => subVoting.SendSubmarineVoteEndMessage(info, type);
subVoting.SetSubmarineVotingText(starter, info, type);
subVoting.Initialize(starter, type);
return subVoting;
}
public static VotingInterface CreateMoneyTransferVotingInterface(Client starter, Client from, Client to, int amount, float votingTime)
{
if (starter == null) { return null; }
if (from == null && to == null) { return null; }
var transferVoting = new VotingInterface()
{
votingTime = votingTime,
getYesVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountYes(VoteType.TransferMoney) ?? 0,
getNoVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountNo(VoteType.TransferMoney) ?? 0,
getMaxVotes = () => GameMain.NetworkMember?.Voting?.GetVoteCountMax(VoteType.TransferMoney) ?? 0,
};
transferVoting.onVoteEnd = () => transferVoting.SendMoneyTransferVoteEndMessage(from, to, amount);
transferVoting.SetMoneyTransferVotingText(starter, from, to, amount);
transferVoting.Initialize(starter, VoteType.TransferMoney);
return transferVoting;
}
private void Initialize(Client starter, VoteType type)
{
currentVoteType = type;
CreateVotingGUI();
if (starter.ID == GameMain.Client.ID) SetGUIToVotedState(2);
if (starter.ID == GameMain.Client.ID) { SetGUIToVotedState(2); }
VoteRunning = true;
}
@@ -50,7 +74,7 @@ namespace Barotrauma
{
createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
if (frame != null) frame.Parent.RemoveChild(frame);
frame?.Parent.RemoveChild(frame);
frame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.VotingArea, GameMain.Client.InGameHUD.RectTransform), style: "");
int padding = HUDLayoutSettings.Padding * 2;
@@ -116,8 +140,8 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (!VoteRunning) return;
if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) CreateVotingGUI();
if (!VoteRunning) { return; }
if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) { CreateVotingGUI(); }
yesVotes = getYesVotes();
noVotes = getNoVotes();
maxVotes = getMaxVotes();
@@ -126,7 +150,6 @@ namespace Barotrauma
votingTimer.BarSize = timer / votingTime;
}
public void EndVote(bool passed, int yesVoteFinal, int noVoteFinal)
{
VoteRunning = false;
@@ -143,19 +166,20 @@ namespace Barotrauma
JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab;
Color nameColor = prefab != null ? prefab.UIColor : Color.White;
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‖";
LocalizedString text = string.Empty;
switch (type)
{
case VoteType.PurchaseAndSwitchSub:
votingOnText = TextManager.GetWithVariables("submarinepurchaseandswitchvote",
text = TextManager.GetWithVariables("submarinepurchaseandswitchvote",
("[playername]", characterRichString),
("[submarinename]", submarineRichString),
("[amount]", info.Price.ToString()),
("[currencyname]", TextManager.Get("credit").ToLower()));
break;
case VoteType.PurchaseSub:
votingOnText = TextManager.GetWithVariables("submarinepurchasevote",
text = TextManager.GetWithVariables("submarinepurchasevote",
("[playername]", characterRichString),
("[submarinename]", submarineRichString),
("[amount]", info.Price.ToString()),
@@ -163,10 +187,9 @@ namespace Barotrauma
break;
case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
if (deliveryFee > 0)
{
votingOnText = TextManager.GetWithVariables("submarineswitchfeevote",
text = TextManager.GetWithVariables("submarineswitchfeevote",
("[playername]", characterRichString),
("[submarinename]", submarineRichString),
("[locationname]", endLocation.Name),
@@ -175,37 +198,22 @@ namespace Barotrauma
}
else
{
votingOnText = TextManager.GetWithVariables("submarineswitchnofeevote",
text = TextManager.GetWithVariables("submarineswitchnofeevote",
("[playername]", characterRichString),
("[submarinename]", submarineRichString));
}
break;
}
votingOnText = RichString.Rich(votingOnText);
}
private int SubmarineYesVotes()
{
return GameMain.NetworkMember.SubmarineVoteYesCount;
}
private int SubmarineNoVotes()
{
return GameMain.NetworkMember.SubmarineVoteNoCount;
}
private int SubmarineMaxVotes()
{
return GameMain.NetworkMember.SubmarineVoteMax;
votingOnText = RichString.Rich(text);
}
private void SendSubmarineVoteEndMessage(SubmarineInfo info, VoteType type)
{
GameMain.NetworkMember.AddChatMessage(GetSubmarineVoteResultMessage(info, type, yesVotes.ToString(), noVotes.ToString(), votePassed).Value, ChatMessageType.Server);
GameMain.NetworkMember.AddChatMessage(GetSubmarineVoteResultMessage(info, type, yesVotes, noVotes, votePassed).Value, ChatMessageType.Server);
}
public static LocalizedString GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, string yesVoteString, string noVoteString, bool votePassed)
private LocalizedString GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, int yesVoteCount, int noVoteCount, bool votePassed)
{
LocalizedString result = string.Empty;
@@ -214,18 +222,18 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchaseandswitchvotepassed" : "submarinepurchaseandswitchvotefailed",
("[submarinename]", info.DisplayName),
("[amount]", info.Price.ToString()),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", info.Price)),
("[currencyname]", TextManager.Get("credit").ToLower()),
("[yesvotecount]", yesVoteString),
("[novotecount]" , noVoteString));
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]" , noVoteCount.ToString()));
break;
case VoteType.PurchaseSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchasevotepassed" : "submarinepurchasevotefailed",
("[submarinename]", info.DisplayName),
("[amount]", info.Price.ToString()),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", info.Price)),
("[currencyname]", TextManager.Get("credit").ToLower()),
("[yesvotecount]", yesVoteString),
("[novotecount]", noVoteString));
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
break;
case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
@@ -235,17 +243,17 @@ namespace Barotrauma
result = TextManager.GetWithVariables(votePassed ? "submarineswitchfeevotepassed" : "submarineswitchfeevotefailed",
("[submarinename]", info.DisplayName),
("[locationname]", endLocation.Name),
("[amount]", deliveryFee.ToString()),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", deliveryFee)),
("[currencyname]", TextManager.Get("credit").ToLower()),
("[yesvotecount]", yesVoteString),
("[novotecount]", noVoteString));
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
}
else
{
result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed",
("[submarinename]", info.DisplayName),
("[yesvotecount]", yesVoteString),
("[novotecount]", noVoteString));
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
}
break;
default:
@@ -255,6 +263,58 @@ namespace Barotrauma
}
#endregion
private void SetMoneyTransferVotingText(Client starter, Client from, Client to, int amount)
{
string name = starter.Name;
JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab;
Color nameColor = prefab != null ? prefab.UIColor : Color.White;
string characterRichString = $"‖color:{nameColor.R},{nameColor.G},{nameColor.B}‖{name}‖color:end‖";
LocalizedString text = string.Empty;
if (from == null && to != null)
{
text = TextManager.GetWithVariables("crewwallet.requestbanktoselfvote",
("[requester]", characterRichString),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", amount)));
}
else if (from != null && to == null)
{
text = TextManager.GetWithVariables("crewwallet.requestselftobankvote",
("[requester]", characterRichString),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", amount)));
}
else
{
//not supported atm: clients can only requests transfers between their own wallet and the bank
LocalizedString bankName = TextManager.Get("crewwallet.bank");
text = TextManager.GetWithVariables("crewwallet.requesttransfervote",
("[requester]", characterRichString),
("[player1]", from?.Character == null ? bankName : from.Character.Name),
("[player2]", to?.Character == null ? bankName : to.Character.Name),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", amount)));
}
votingOnText = RichString.Rich(text);
}
private void SendMoneyTransferVoteEndMessage(Client from, Client to, int amount)
{
GameMain.NetworkMember.AddChatMessage(GetMoneyTransferVoteResultMessage(from, to, amount, yesVotes, noVotes, votePassed).Value, ChatMessageType.Server);
}
public static LocalizedString GetMoneyTransferVoteResultMessage(Client from, Client to, int transferAmount, int yesVoteCount, int noVoteCount, bool votePassed)
{
LocalizedString result = string.Empty;
if (from != null)
{
result = TextManager.GetWithVariables(votePassed ? "crewwallet.banktoplayer.votepassed" : "crewwallet.banktoplayer.votefailed",
("[playername]", from.Name),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", transferAmount)),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
}
return result;
}
public void Remove()
{
if (frame != null)

View File

@@ -17,6 +17,7 @@ using Barotrauma.Tutorials;
using Barotrauma.Media;
using Barotrauma.Extensions;
using System.Threading.Tasks;
using Barotrauma.Transition;
namespace Barotrauma
{
@@ -463,6 +464,7 @@ namespace Barotrauma
yield return CoroutineStatus.Running;
UgcTransition.Prepare();
var contentPackageLoadRoutine = ContentPackageManager.Init();
foreach (var progress in contentPackageLoadRoutine)
{
@@ -691,14 +693,12 @@ namespace Barotrauma
}
else if (GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled)
{
IEnumerable<string> saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
var saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
if (saveFiles.Count() > 0)
{
saveFiles = saveFiles.OrderBy(file => File.GetLastWriteTime(file));
try
{
SaveUtil.LoadGame(saveFiles.Last());
SaveUtil.LoadGame(saveFiles.OrderBy(file => file.SaveTime).Last().FilePath);
}
catch (Exception e)
{

View File

@@ -67,6 +67,8 @@ namespace Barotrauma
public void SetSoldItems(Dictionary<Identifier, List<SoldItem>> items)
{
if (SoldItems.Count == 0 && items.Count == 0) { return; }
SoldItems.Clear();
foreach (var entry in items)
{

View File

@@ -86,30 +86,14 @@ namespace Barotrauma
/// <summary>
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
/// </summary>
public bool AllowedToEndRound()
{
//allow ending the round if the client has permissions, is the owner, the only client in the server
//or if no-one has management permissions
if (GameMain.Client == null) { return true; }
return
GameMain.Client.HasPermission(ClientPermissions.ManageRound) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner ||
GameMain.Client.ConnectedClients.None(c =>
c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign)));
}
/// <summary>
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
/// </summary>
public bool AllowedToManageCampaign(ClientPermissions permissions = ClientPermissions.ManageCampaign)
public bool AllowedToManageCampaign(ClientPermissions permissions)
{
//allow managing the round if the client has permissions, is the owner, the only client in the server,
//or if no-one has management permissions
if (GameMain.Client == null) { return true; }
return
GameMain.Client.HasPermission(permissions) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner ||
GameMain.Client.ConnectedClients.None(c => c.InGame && (c.IsOwner || c.HasPermission(permissions)));
@@ -210,7 +194,7 @@ namespace Barotrauma
if (endRoundButton.Visible)
{
if (!AllowedToEndRound())
if (!AllowedToManageCampaign(ClientPermissions.ManageMap))
{
buttonText = TextManager.Get("map");
}
@@ -306,7 +290,7 @@ namespace Barotrauma
default:
ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType, storeIdentifier: npc.MerchantIdentifier);
CampaignUI.UpgradeStore?.RefreshAll();
CampaignUI.UpgradeStore?.RequestRefresh();
break;
}
}

View File

@@ -42,7 +42,7 @@ namespace Barotrauma
return PersonalWallet;
}
public static void StartCampaignSetup(IEnumerable<string> saveFiles)
public static void StartCampaignSetup(List<SaveInfo> saveFiles)
{
var parent = GameMain.NetLobbyScreen.CampaignSetupFrame;
parent.ClearChildren();
@@ -746,7 +746,7 @@ namespace Barotrauma
if (reputation.HasValue)
{
campaign.Map.CurrentLocation.Reputation.SetReputation(reputation.Value);
campaign?.CampaignUI?.UpgradeStore?.RefreshAll();
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
foreach (var availableMission in availableMissions)
@@ -786,7 +786,7 @@ namespace Barotrauma
if (shouldRefresh)
{
campaign?.CampaignUI?.UpgradeStore?.RefreshAll();
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
if (myCharacterInfo != null)

View File

@@ -110,7 +110,7 @@ namespace Barotrauma
petsElement = subElement;
break;
case Wallet.LowerCaseSaveElementName:
Bank = new Wallet(subElement);
Bank = new Wallet(Option<Character>.None(), subElement);
break;
case "stats":
LoadStats(subElement);
@@ -129,7 +129,7 @@ namespace Barotrauma
int oldMoney = element.GetAttributeInt("money", 0);
if (oldMoney > 0)
{
Bank = new Wallet
Bank = new Wallet(Option<Character>.None())
{
Balance = oldMoney
};

View File

@@ -32,6 +32,7 @@ namespace Barotrauma
}
else
{
tabMenu?.OnClose();
tabMenu = null;
NetLobbyScreen.JobInfoFrame = null;
}

View File

@@ -318,7 +318,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub)));
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled)
{
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, Mission.GetSalaryEligibleCrew().Where(c => c != controlled), Option<int>.Some(reward));
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(reward));
if (share > 0)
{
string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share);

View File

@@ -38,11 +38,14 @@ namespace Barotrauma.Items.Components
public readonly bool Loop;
public ItemSound(RoundSound sound, ActionType type, bool loop = false)
public readonly bool OnlyPlayInSameSub;
public ItemSound(RoundSound sound, ActionType type, bool loop = false, bool onlyPlayInSameSub = false)
{
this.RoundSound = sound;
this.Type = type;
this.Loop = loop;
this.OnlyPlayInSameSub = onlyPlayInSameSub;
}
}
@@ -339,6 +342,11 @@ namespace Barotrauma.Items.Components
return;
}
if (itemSound.OnlyPlayInSameSub && item.Submarine != null && Character.Controlled != null)
{
if (Character.Controlled.Submarine == null || !Character.Controlled.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return; }
}
if (itemSound.Loop)
{
if (loopingSoundChannel != null && loopingSoundChannel.Sound != itemSound.RoundSound.Sound)
@@ -500,7 +508,9 @@ namespace Barotrauma.Items.Components
RoundSound sound = RoundSound.Load(subElement);
if (sound == null) { break; }
ItemSound itemSound = new ItemSound(sound, type, subElement.GetAttributeBool("loop", false))
ItemSound itemSound = new ItemSound(sound, type,
subElement.GetAttributeBool("loop", false),
subElement.GetAttributeBool("onlyinsamesub", false))
{
VolumeProperty = subElement.GetAttributeIdentifier("volumeproperty", "")
};

View File

@@ -50,6 +50,9 @@ namespace Barotrauma.Items.Components
[Serialize("FabricatorCreate", IsPropertySaveable.Yes)]
public string CreateButtonText { get; set; }
[Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)]
public string FabricationLimitReachedText { get; set; }
partial void InitProjSpecific()
{
//CreateGUI();
@@ -195,7 +198,7 @@ namespace Barotrauma.Items.Components
foreach (FabricationRecipe fi in fabricationRecipes.Values)
{
var frame = new GUIFrame(new RectTransform(new Point(itemList.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null)
var frame = new GUIFrame(new RectTransform(new Point(itemList.Content.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null)
{
UserData = fi,
HoverColor = Color.Gold * 0.2f,
@@ -223,6 +226,13 @@ namespace Barotrauma.Items.Components
AutoScaleVertical = true,
ToolTip = fi.TargetItem.Description
};
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), frame.RectTransform, Anchor.BottomRight),
TextManager.Get(FabricationLimitReachedText), font: GUIStyle.SmallFont, textAlignment: Alignment.BottomRight)
{
UserData = nameof(FabricationLimitReachedText),
Visible = false
};
}
}
@@ -297,7 +307,8 @@ namespace Barotrauma.Items.Components
}
else
{
sufficientSkillsText.Visible = false;
sufficientSkillsText.Visible = insufficientSkillsText.Visible = false;
sufficientSkillsText.Enabled = insufficientSkillsText.Enabled = false;
}
var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
@@ -493,14 +504,15 @@ namespace Barotrauma.Items.Components
if (string.IsNullOrWhiteSpace(filter))
{
itemList.Content.Children.ForEach(c => c.Visible = true);
return true;
}
foreach (GUIComponent child in itemList.Content.Children)
else
{
FabricationRecipe recipe = child.UserData as FabricationRecipe;
if (recipe?.DisplayName == null) { continue; }
child.Visible = recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase);
foreach (GUIComponent child in itemList.Content.Children)
{
FabricationRecipe recipe = child.UserData as FabricationRecipe;
if (recipe?.DisplayName == null) { continue; }
child.Visible = recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase);
}
}
HideEmptyItemListCategories();
@@ -516,7 +528,10 @@ namespace Barotrauma.Items.Components
{
if (!(child.UserData is FabricationRecipe recipe))
{
child.Visible = recipeVisible;
if (child.Enabled)
{
child.Visible = recipeVisible;
}
recipeVisible = false;
}
else
@@ -719,24 +734,26 @@ namespace Barotrauma.Items.Components
{
foreach (GUIComponent child in itemList.Content.Children)
{
if (!(child.UserData is FabricationRecipe itemPrefab)) { continue; }
if (!(child.UserData is FabricationRecipe recipe)) { continue; }
if (itemPrefab != selectedItem &&
if (recipe != selectedItem &&
(child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y))
{
continue;
}
bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients, character);
if (itemPrefab == selectedItem)
bool canBeFabricated = CanBeFabricated(recipe, availableIngredients, character);
if (recipe == selectedItem)
{
activateButton.Enabled = canBeFabricated;
}
var childContainer = child.GetChild<GUILayoutGroup>();
childContainer.GetChild<GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
childContainer.GetChild<GUIImage>().Color = itemPrefab.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f);
childContainer.GetChild<GUIImage>().Color = recipe.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f);
var limitReachedText = child.FindChild(nameof(FabricationLimitReachedText));
limitReachedText.Visible = !canBeFabricated && fabricationLimits.TryGetValue(recipe.RecipeHash, out int amount) && amount <= 0;
}
}
}

View File

@@ -396,7 +396,7 @@ namespace Barotrauma.Items.Components
private bool VisibleOnItemFinder(Item it)
{
if (it.Submarine != item.Submarine) { return false; }
if (!item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
if (it.NonInteractable || it.HiddenInGame) { return false; }
if (it.GetComponent<Pickable>() == null) { return false; }
@@ -432,10 +432,10 @@ namespace Barotrauma.Items.Components
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
ImmutableHashSet<Item> hullPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent<Door>() != null || it.GetComponent<Turret>() != null)).ToImmutableHashSet();
ImmutableHashSet<Item> hullPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent<Door>() != null || it.GetComponent<Turret>() != null)).ToImmutableHashSet();
miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents);
IEnumerable<Item> electrialPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null);
IEnumerable<Item> electrialPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null);
electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electrialPointsOfInterest, out electricalMapComponents);
Dictionary<MiniMapGUIComponent, GUIComponent> electricChildren = new Dictionary<MiniMapGUIComponent, GUIComponent>();

View File

@@ -412,7 +412,7 @@ namespace Barotrauma
new GUIMessageBox(string.Empty, TextManager.Get("LockedPathTooltip"));
}
//clients aren't allowed to select the location without a permission
else if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign() ?? false)
else if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap) ?? false)
{
connectionHighlightState = 0.0f;
SelectedConnection = connection;

View File

@@ -271,6 +271,7 @@ namespace Barotrauma.Networking
otherClients = new List<Client>();
serverSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false);
Voting = new Voting();
if (steamId == 0)
{
@@ -637,7 +638,7 @@ namespace Barotrauma.Networking
if (gameStarted && Screen.Selected == GameMain.GameScreen)
{
EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode);
EndVoteTickBox.Visible = ServerSettings.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode);
respawnManager?.Update(deltaTime);
@@ -898,13 +899,13 @@ namespace Barotrauma.Networking
GUI.SetSavingIndicatorState(save);
break;
case ServerPacketHeader.CAMPAIGN_SETUP_INFO:
UInt16 saveCount = inc.ReadUInt16();
List<string> saveFiles = new List<string>();
byte saveCount = inc.ReadByte();
List<CampaignMode.SaveInfo> saveInfos = new List<CampaignMode.SaveInfo>();
for (int i = 0; i < saveCount; i++)
{
saveFiles.Add(inc.ReadString());
saveInfos.Add(INetSerializableStruct.Read<CampaignMode.SaveInfo>(inc));
}
MultiPlayerCampaign.StartCampaignSetup(saveFiles);
MultiPlayerCampaign.StartCampaignSetup(saveInfos);
break;
case ServerPacketHeader.PERMISSIONS:
ReadPermissions(inc);
@@ -1458,7 +1459,7 @@ namespace Barotrauma.Networking
{
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
campaign.CampaignUI?.UpgradeStore?.RefreshAll();
campaign.CampaignUI?.UpgradeStore?.RequestRefresh();
campaign.CampaignUI?.CrewManagement?.RefreshPermissions();
}
}
@@ -1666,10 +1667,7 @@ namespace Barotrauma.Networking
isOutpost = levelData.Type == LevelData.LevelType.Outpost;
}
if (GameMain.Client?.ServerSettings?.Voting != null)
{
GameMain.Client.ServerSettings.Voting.ResetVotes(GameMain.Client.ConnectedClients);
}
Voting?.ResetVotes(GameMain.Client.ConnectedClients);
if (loadTask != null)
{
@@ -1882,7 +1880,7 @@ namespace Barotrauma.Networking
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash);
if (matchingSub == null)
{
matchingSub = new SubmarineInfo(Path.Combine(SubmarineInfo.SavePath, subName) + ".sub", subHash, tryLoad: false)
matchingSub = new SubmarineInfo(Path.Combine(SaveUtil.SubmarineDownloadFolder, subName) + ".sub", subHash, tryLoad: false)
{
SubmarineClass = (SubmarineClass)subClass
};
@@ -2086,7 +2084,7 @@ namespace Barotrauma.Networking
{
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
campaign.CampaignUI?.UpgradeStore?.RefreshAll();
campaign.CampaignUI?.UpgradeStore?.RequestRefresh();
campaign.CampaignUI?.CrewManagement?.RefreshPermissions();
}
}
@@ -2208,8 +2206,8 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer);
serverSettings.VoiceChatEnabled = voiceChatEnabled;
serverSettings.Voting.AllowSubVoting = allowSubVoting;
serverSettings.Voting.AllowModeVoting = allowModeVoting;
serverSettings.AllowSubVoting = allowSubVoting;
serverSettings.AllowModeVoting = allowModeVoting;
if (clientPeer is SteamP2POwnerPeer)
{
@@ -2240,7 +2238,7 @@ namespace Barotrauma.Networking
ChatMessage.ClientRead(inc);
break;
case ServerNetObject.VOTE:
serverSettings.Voting.ClientRead(inc);
Voting.ClientRead(inc);
break;
}
}
@@ -2826,12 +2824,12 @@ namespace Barotrauma.Networking
public void Vote(VoteType voteType, object data)
{
if (clientPeer == null) return;
if (clientPeer == null) { return; }
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ClientNetObject.VOTE);
serverSettings.Voting.ClientWrite(msg, voteType, data);
Voting.ClientWrite(msg, voteType, data);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer.Send(msg, DeliveryMethod.Reliable);
@@ -2847,19 +2845,27 @@ namespace Barotrauma.Networking
#region Submarine Change Voting
public void InitiateSubmarineChange(SubmarineInfo sub, VoteType voteType)
{
if (sub == null) return;
if (serverSettings.Voting.VoteRunning)
{
new GUIMessageBox(TextManager.Get("unabletoinitiateavoteheader"), TextManager.Get("votealreadyactivetext"));
return;
}
if (sub == null) { return; }
Vote(voteType, sub);
}
public void ShowSubmarineChangeVoteInterface(Client starter, SubmarineInfo info, VoteType type, float timeOut)
{
if (info == null || votingInterface != null) return;
votingInterface = new VotingInterface(starter, info, type, timeOut);
if (info == null || votingInterface != null) { return; }
votingInterface = VotingInterface.CreateSubmarineVotingInterface(starter, info, type, timeOut);
}
#endregion
#region Money Transfer Voting
public void ShowMoneyTransferVoteInterface(Client starter, Client from, int amount, Client to, float timeOut)
{
if (votingInterface != null) { return; }
if (from == null && to == null)
{
DebugConsole.ThrowError("Tried to initiate a vote for transferring from null to null!");
return;
}
votingInterface = VotingInterface.CreateMoneyTransferVotingInterface(starter, from, to, amount, timeOut);
}
#endregion
@@ -3103,7 +3109,7 @@ namespace Barotrauma.Networking
{
if (!gameStarted) return false;
if (!serverSettings.Voting.AllowEndVoting || !HasSpawned)
if (!serverSettings.AllowEndVoting || !HasSpawned)
{
tickBox.Visible = false;
return false;
@@ -3322,15 +3328,17 @@ namespace Barotrauma.Networking
inGameHUD.DrawManually(spriteBatch);
if (EndVoteCount > 0)
int endVoteCount = Voting.GetVoteCountYes(VoteType.EndRound);
int endVoteMax = Voting.GetVoteCountMax(VoteType.EndRound);
if (endVoteCount > 0)
{
if (EndVoteTickBox.Visible)
{
EndVoteTickBox.Text = $"{endRoundVoteText} {EndVoteCount}/{EndVoteMax}";
EndVoteTickBox.Text = $"{endRoundVoteText} {endVoteCount}/{endVoteMax}";
}
else
{
LocalizedString endVoteText = TextManager.GetWithVariables("EndRoundVotes", ("[votes]", EndVoteCount.ToString()), ("[max]", EndVoteMax.ToString()));
LocalizedString endVoteText = TextManager.GetWithVariables("EndRoundVotes", ("[votes]", endVoteCount.ToString()), ("[max]", endVoteMax.ToString()));
GUI.DrawString(spriteBatch, EndVoteTickBox.Rect.Center.ToVector2() - GUIStyle.SmallFont.MeasureString(endVoteText) / 2,
endVoteText.Value,
Color.White,
@@ -3498,7 +3506,7 @@ namespace Barotrauma.Networking
OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.KickPlayer(client); return false; }
};
}
else if (serverSettings.Voting.AllowVoteKick && client.AllowKicking)
else if (serverSettings.AllowVoteKick && client.AllowKicking)
{
var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform),
TextManager.Get("VoteToKick"), style: "GUIButtonSmall")

View File

@@ -2,56 +2,42 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
partial class Voting
{
public bool AllowSubVoting
{
get { return allowSubVoting; }
set
{
if (value == allowSubVoting) return;
allowSubVoting = value;
GameMain.NetLobbyScreen.SubList.Enabled = value ||
(GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub));
var subVotesLabel = GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true) as GUITextBlock;
subVotesLabel.Visible = value;
var subVisButton = GameMain.NetLobbyScreen.SubVisibilityButton;
subVisButton.RectTransform.AbsoluteOffset
= new Point(value ? (int)(subVotesLabel.TextSize.X + subVisButton.Rect.Width) : 0, 0);
private readonly Dictionary<VoteType, int>
voteCountYes = new Dictionary<VoteType, int>(),
voteCountNo = new Dictionary<VoteType, int>(),
voteCountMax = new Dictionary<VoteType, int>();
UpdateVoteTexts(null, VoteType.Sub);
GameMain.NetLobbyScreen.SubList.Deselect();
}
public int GetVoteCountYes(VoteType voteType)
{
voteCountYes.TryGetValue(voteType, out int value);
return value;
}
public bool AllowModeVoting
public int GetVoteCountNo(VoteType voteType)
{
get { return allowModeVoting; }
set
{
if (value == allowModeVoting) return;
allowModeVoting = value;
GameMain.NetLobbyScreen.ModeList.Enabled =
value ||
(GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectMode));
GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value;
// Disable modes that cannot be voted on
foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children)
{
if (guiComponent is GUIFrame frame)
{
frame.CanBeFocused = !allowModeVoting || ((GameModePreset) frame.UserData).Votable;
}
}
UpdateVoteTexts(null, VoteType.Mode);
GameMain.NetLobbyScreen.ModeList.Deselect();
}
voteCountNo.TryGetValue(voteType, out int value);
return value;
}
public int GetVoteCountMax(VoteType voteType)
{
voteCountMax.TryGetValue(voteType, out int value);
return value;
}
public void SetVoteCountYes(VoteType voteType, int value)
{
voteCountYes[voteType] = value;
}
public void SetVoteCountNo(VoteType voteType, int value)
{
voteCountNo[voteType] = value;
}
public void SetVoteCountMax(VoteType voteType, int value)
{
voteCountMax[voteType] = value;
}
public void UpdateVoteTexts(List<Client> clients, VoteType voteType)
@@ -139,17 +125,15 @@ namespace Barotrauma
msg.Write(votedClient.ID);
break;
case VoteType.StartRound:
if (!(data is bool)) return;
if (!(data is bool)) { return; }
msg.Write((bool)data);
break;
case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub:
case VoteType.SwitchSub:
if (!VoteRunning)
{
SubmarineInfo voteSub = data as SubmarineInfo;
if (voteSub == null) return;
if (data is SubmarineInfo voteSub)
{
//initiate sub vote
msg.Write(true);
msg.Write(voteSub.Name);
}
@@ -159,7 +143,11 @@ namespace Barotrauma
msg.Write(false);
msg.Write((int)data);
}
break;
case VoteType.TransferMoney:
if (!(data is int)) { return; }
msg.Write(false); //not initiating a vote
msg.Write((int)data);
break;
}
@@ -168,8 +156,8 @@ namespace Barotrauma
public void ClientRead(IReadMessage inc)
{
AllowSubVoting = inc.ReadBoolean();
if (allowSubVoting)
GameMain.Client.ServerSettings.AllowSubVoting = inc.ReadBoolean();
if (GameMain.Client.ServerSettings.AllowSubVoting)
{
UpdateVoteTexts(null, VoteType.Sub);
int votableCount = inc.ReadByte();
@@ -186,8 +174,8 @@ namespace Barotrauma
SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes);
}
}
AllowModeVoting = inc.ReadBoolean();
if (allowModeVoting)
GameMain.Client.ServerSettings.AllowModeVoting = inc.ReadBoolean();
if (GameMain.Client.ServerSettings.AllowModeVoting)
{
UpdateVoteTexts(null, VoteType.Mode);
int votableCount = inc.ReadByte();
@@ -199,135 +187,136 @@ namespace Barotrauma
SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes);
}
}
AllowEndVoting = inc.ReadBoolean();
if (AllowEndVoting)
GameMain.Client.ServerSettings.AllowEndVoting = inc.ReadBoolean();
if (GameMain.Client.ServerSettings.AllowEndVoting)
{
GameMain.NetworkMember.EndVoteCount = inc.ReadByte();
GameMain.NetworkMember.EndVoteMax = inc.ReadByte();
SetVoteCountYes(VoteType.EndRound, inc.ReadByte());
SetVoteCountMax(VoteType.EndRound, inc.ReadByte());
}
AllowVoteKick = inc.ReadBoolean();
GameMain.Client.ServerSettings.AllowVoteKick = inc.ReadBoolean();
byte subVoteStateByte = inc.ReadByte();
VoteState subVoteState = VoteState.None;
try
{
subVoteState = (VoteState)subVoteStateByte;
}
byte activeVoteStateByte = inc.ReadByte();
VoteState activeVoteState = VoteState.None;
try { activeVoteState = (VoteState)activeVoteStateByte; }
catch (System.Exception e)
{
DebugConsole.ThrowError("Failed to cast vote type \"" + subVoteStateByte + "\"", e);
DebugConsole.ThrowError("Failed to cast vote type \"" + activeVoteStateByte + "\"", e);
}
if (subVoteState != VoteState.None)
if (activeVoteState != VoteState.None)
{
byte voteTypeByte = inc.ReadByte();
VoteType voteType = VoteType.Unknown;
try
{
voteType = (VoteType)voteTypeByte;
}
try { voteType = (VoteType)voteTypeByte; }
catch (System.Exception e)
{
DebugConsole.ThrowError("Failed to cast vote type \"" + voteTypeByte + "\"", e);
}
if (voteType != VoteType.Unknown)
byte yesClientCount = inc.ReadByte();
for (int i = 0; i < yesClientCount; i++)
{
byte yesClientCount = inc.ReadByte();
for (int i = 0; i < yesClientCount; i++)
{
byte clientID = inc.ReadByte();
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID);
matchingClient?.SetVote(voteType, 2);
}
byte noClientCount = inc.ReadByte();
for (int i = 0; i < noClientCount; i++)
{
byte clientID = inc.ReadByte();
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID);
matchingClient?.SetVote(voteType, 1);
}
GameMain.NetworkMember.SubmarineVoteYesCount = yesClientCount;
GameMain.NetworkMember.SubmarineVoteNoCount = noClientCount;
GameMain.NetworkMember.SubmarineVoteMax = inc.ReadByte();
switch (subVoteState)
{
case VoteState.Started:
Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID);
if (!myClient.InGame)
{
VoteRunning = true;
return;
}
string subName1 = inc.ReadString();
SubmarineInfo info = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName1);
if (info == null)
{
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted");
return;
}
VoteRunning = true;
byte starterID = inc.ReadByte();
Client starterClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == starterID);
float timeOut = inc.ReadByte();
GameMain.Client.ShowSubmarineChangeVoteInterface(starterClient, info, voteType, timeOut);
break;
case VoteState.Running:
// Nothing specific
break;
case VoteState.Passed:
case VoteState.Failed:
VoteRunning = false;
bool passed = inc.ReadBoolean();
string subName2 = inc.ReadString();
SubmarineInfo subInfo = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2);
if (subInfo == null)
{
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted");
return;
}
if (GameMain.Client.VotingInterface != null)
{
GameMain.Client.VotingInterface.EndVote(passed, yesClientCount, noClientCount);
}
else if (GameMain.Client.ConnectedClients.Count > 1)
{
GameMain.NetworkMember.AddChatMessage(VotingInterface.GetSubmarineVoteResultMessage(subInfo, voteType, yesClientCount.ToString(), noClientCount.ToString(), passed).Value, ChatMessageType.Server);
}
if (passed)
{
int deliveryFee = inc.ReadInt16();
switch (voteType)
{
case VoteType.PurchaseAndSwitchSub:
GameMain.GameSession.PurchaseSubmarine(subInfo);
GameMain.GameSession.SwitchSubmarine(subInfo, 0);
break;
case VoteType.PurchaseSub:
GameMain.GameSession.PurchaseSubmarine(subInfo);
break;
case VoteType.SwitchSub:
GameMain.GameSession.SwitchSubmarine(subInfo, deliveryFee);
break;
}
SubmarineSelection.ContentRefreshRequired = true;
}
break;
}
byte clientID = inc.ReadByte();
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID);
matchingClient?.SetVote(voteType, 2);
}
}
byte noClientCount = inc.ReadByte();
for (int i = 0; i < noClientCount; i++)
{
byte clientID = inc.ReadByte();
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID);
matchingClient?.SetVote(voteType, 1);
}
byte maxClientCount = inc.ReadByte();
SetVoteCountYes(voteType, yesClientCount);
SetVoteCountNo(voteType, noClientCount);
SetVoteCountMax(voteType, maxClientCount);
switch (activeVoteState)
{
case VoteState.Started:
byte starterID = inc.ReadByte();
Client starterClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == starterID);
float timeOut = inc.ReadByte();
Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID);
if (!myClient.InGame) { return; }
switch (voteType)
{
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
string subName1 = inc.ReadString();
SubmarineInfo info = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName1);
if (info == null)
{
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted");
return;
}
GameMain.Client.ShowSubmarineChangeVoteInterface(starterClient, info, voteType, timeOut);
break;
case VoteType.TransferMoney:
byte fromClientId = inc.ReadByte();
byte toClientId = inc.ReadByte();
int transferAmount = inc.ReadInt32();
Client fromClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == fromClientId);
Client toClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == toClientId);
GameMain.Client.ShowMoneyTransferVoteInterface(starterClient, fromClient, transferAmount, toClient, timeOut);
break;
}
break;
case VoteState.Running:
// Nothing specific
break;
case VoteState.Passed:
case VoteState.Failed:
bool passed = inc.ReadBoolean();
SubmarineInfo subInfo = null;
switch (voteType)
{
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
string subName2 = inc.ReadString();
subInfo = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2);
if (subInfo == null)
{
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted");
return;
}
break;
}
GameMain.Client.VotingInterface?.EndVote(passed, yesClientCount, noClientCount);
if (passed && subInfo != null)
{
int deliveryFee = inc.ReadInt16();
switch (voteType)
{
case VoteType.PurchaseAndSwitchSub:
GameMain.GameSession.PurchaseSubmarine(subInfo);
GameMain.GameSession.SwitchSubmarine(subInfo, 0);
break;
case VoteType.PurchaseSub:
GameMain.GameSession.PurchaseSubmarine(subInfo);
break;
case VoteType.SwitchSub:
GameMain.GameSession.SwitchSubmarine(subInfo, deliveryFee);
break;
}
SubmarineSelection.ContentRefreshRequired = true;
}
break;
}
}
GameMain.NetworkMember.ConnectedClients.ForEach(c => c.SetVote(VoteType.StartRound, false));
byte readyClientCount = inc.ReadByte();

View File

@@ -1,5 +1,8 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Barotrauma
{
@@ -44,5 +47,60 @@ namespace Barotrauma
this.newGameContainer = newGameContainer;
this.loadGameContainer = loadGameContainer;
}
protected List<CampaignMode.SaveInfo> prevSaveFiles;
protected GUIComponent CreateSaveElement(CampaignMode.SaveInfo saveInfo)
{
if (string.IsNullOrEmpty(saveInfo.FilePath))
{
DebugConsole.AddWarning("Error when updating campaign load menu: path to a save file was empty.\n" + Environment.StackTrace);
return null;
}
var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement")
{
UserData = saveInfo.FilePath
};
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath))
{
CanBeFocused = false
};
if (saveInfo.EnabledContentPackageNames != null && saveInfo.EnabledContentPackageNames.Any())
{
if (!GameSession.IsCompatibleWithEnabledContentPackages(saveInfo.EnabledContentPackageNames, out LocalizedString errorMsg))
{
nameText.TextColor = GUIStyle.Red;
saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning"));
}
}
prevSaveFiles ??= new List<CampaignMode.SaveInfo>();
prevSaveFiles.Add(saveInfo);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft),
text: saveInfo.SubmarineName, font: GUIStyle.SmallFont)
{
CanBeFocused = false,
UserData = saveInfo.FilePath
};
string saveTimeStr = string.Empty;
if (saveInfo.SaveTime > 0)
{
DateTime time = ToolBox.Epoch.ToDateTime(saveInfo.SaveTime);
saveTimeStr = time.ToString();
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
text: saveTimeStr, textAlignment: Alignment.Right, font: GUIStyle.SmallFont)
{
CanBeFocused = false,
UserData = saveInfo.FilePath
};
return saveFrame;
}
}
}

View File

@@ -12,7 +12,7 @@ namespace Barotrauma
{
private GUIButton deleteMpSaveButton;
public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<string> saveFiles = null)
public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, List<CampaignMode.SaveInfo> saveFiles = null)
: base(newGameContainer, loadGameContainer)
{
var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: false)
@@ -219,8 +219,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
private List<string> prevSaveFiles;
public void UpdateLoadMenu(IEnumerable<string> saveFiles = null)
public void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
{
prevSaveFiles?.Clear();
prevSaveFiles = null;
@@ -242,73 +241,9 @@ namespace Barotrauma
OnSelected = SelectSaveFile
};
foreach (string saveFile in saveFiles)
foreach (CampaignMode.SaveInfo saveInfo in saveFiles)
{
if (string.IsNullOrEmpty(saveFile))
{
DebugConsole.AddWarning("Error when updating campaign load menu: path to a save file was empty.\n" + Environment.StackTrace);
continue;
}
string fileName = saveFile;
string subName = "";
string saveTime = "";
string contentPackageStr = "";
var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement")
{
UserData = saveFile
};
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), "")
{
CanBeFocused = false
};
bool isCompatible = true;
prevSaveFiles ??= new List<string>();
prevSaveFiles?.Add(saveFile);
string[] splitSaveFile = saveFile.Split(';');
saveFrame.UserData = splitSaveFile[0];
fileName = Path.GetFileNameWithoutExtension(splitSaveFile[0]);
nameText.Text = fileName;
if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; }
if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; }
if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; }
if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime))
{
DateTime time = ToolBox.Epoch.ToDateTime(unixTime);
saveTime = time.ToString();
}
if (!string.IsNullOrEmpty(contentPackageStr))
{
List<string> contentPackagePaths = contentPackageStr.Split('|').ToList();
if (!GameSession.IsCompatibleWithEnabledContentPackages(contentPackagePaths, out LocalizedString errorMsg))
{
nameText.TextColor = GUIStyle.Red;
saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning"));
}
}
if (!isCompatible)
{
nameText.TextColor = GUIStyle.Red;
saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave");
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft),
text: subName, font: GUIStyle.SmallFont)
{
CanBeFocused = false,
UserData = fileName
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
text: saveTime, textAlignment: Alignment.Right, font: GUIStyle.SmallFont)
{
CanBeFocused = false,
UserData = fileName
};
CreateSaveElement(saveInfo);
}
saveList.Content.RectTransform.SortChildren((c1, c2) =>
@@ -380,7 +315,7 @@ namespace Barotrauma
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.StartsWith(saveFile));
prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});

View File

@@ -21,7 +21,7 @@ namespace Barotrauma
private GUIButton nextButton;
private GUILayoutGroup characterInfoColumns;
public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null)
public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
: base(newGameContainer, loadGameContainer)
{
UpdateNewGameMenu(submarines);
@@ -606,8 +606,7 @@ namespace Barotrauma
}
}
private List<string> prevSaveFiles;
public void UpdateLoadMenu(IEnumerable<string> saveFiles = null)
public void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
{
prevSaveFiles?.Clear();
prevSaveFiles = null;
@@ -647,32 +646,16 @@ namespace Barotrauma
}
};
foreach (string saveFile in saveFiles)
foreach (var saveInfo in saveFiles)
{
string fileName = saveFile;
string subName = "";
string saveTime = "";
string contentPackageStr = "";
var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement")
{
UserData = saveFile
};
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), "")
{
CanBeFocused = false
};
bool isCompatible = true;
prevSaveFiles ??= new List<string>();
nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
var saveFrame = CreateSaveElement(saveInfo);
if (saveFrame == null) { continue; }
XDocument doc = SaveUtil.LoadGameSessionDoc(saveInfo.FilePath);
if (doc?.Root == null)
{
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.TextColor = GUIStyle.Red;
DebugConsole.ThrowError("Error loading save file \"" + saveInfo.FilePath + "\". The file may be corrupted.");
saveFrame.GetChild<GUITextBlock>().TextColor = GUIStyle.Red;
continue;
}
if (doc.Root.GetChildElement("multiplayercampaign") != null)
@@ -681,44 +664,11 @@ namespace Barotrauma
saveList.Content.RemoveChild(saveFrame);
continue;
}
subName = doc.Root.GetAttributeString("submarine", "");
saveTime = doc.Root.GetAttributeString("savetime", "");
isCompatible = SaveUtil.IsSaveFileCompatible(doc);
contentPackageStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", "");
prevSaveFiles?.Add(saveFile);
if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime))
if (!SaveUtil.IsSaveFileCompatible(doc))
{
DateTime time = ToolBox.Epoch.ToDateTime(unixTime);
saveTime = time.ToString();
}
if (!string.IsNullOrEmpty(contentPackageStr))
{
List<string> contentPackagePaths = contentPackageStr.Split('|').ToList();
if (!GameSession.IsCompatibleWithEnabledContentPackages(contentPackagePaths, out LocalizedString errorMsg))
{
nameText.TextColor = GUIStyle.Red;
saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning"));
}
}
if (!isCompatible)
{
nameText.TextColor = GUIStyle.Red;
saveFrame.GetChild<GUITextBlock>().TextColor = GUIStyle.Red;
saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave");
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft),
text: subName, font: GUIStyle.SmallFont)
{
CanBeFocused = false,
UserData = fileName
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
text: saveTime, textAlignment: Alignment.Right, font: GUIStyle.SmallFont)
{
CanBeFocused = false,
UserData = fileName
};
}
saveList.Content.RectTransform.SortChildren((c1, c2) =>
@@ -830,7 +780,7 @@ namespace Barotrauma
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.StartsWith(saveFile));
prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});

View File

@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -332,7 +333,7 @@ namespace Barotrauma
foreach (GUITickBox tickBox in missionTickBoxes)
{
bool disable = hasMaxMissions && !tickBox.Selected;
tickBox.Enabled = Campaign.AllowedToManageCampaign() && !disable;
tickBox.Enabled = Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap) && !disable;
tickBox.Box.DisabledColor = disable ? tickBox.Box.Color * 0.5f : tickBox.Box.Color * 0.8f;
foreach (GUIComponent child in tickBox.Parent.Parent.Children)
{
@@ -480,7 +481,7 @@ namespace Barotrauma
if (GUI.MouseOn == tickBox) { return false; }
if (tickBox != null)
{
if (Campaign.AllowedToManageCampaign() && tickBox.Enabled)
if (Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap) && tickBox.Enabled)
{
tickBox.Selected = !tickBox.Selected;
}
@@ -521,10 +522,10 @@ namespace Barotrauma
};
tickBox.RectTransform.MinSize = new Point(tickBox.Rect.Height, 0);
tickBox.RectTransform.IsFixedSize = true;
tickBox.Enabled = Campaign.AllowedToManageCampaign();
tickBox.Enabled = Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap);
tickBox.OnSelected += (GUITickBox tb) =>
{
if (!Campaign.AllowedToManageCampaign()) { return false; }
if (!Campaign.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap)) { return false; }
if (tb.Selected)
{
@@ -544,7 +545,7 @@ namespace Barotrauma
UpdateMaxMissions(connection.OtherLocation(currentDisplayLocation));
if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending &&
Campaign.AllowedToManageCampaign())
Campaign.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap))
{
GameMain.Client?.SendCampaignState();
}
@@ -665,7 +666,7 @@ namespace Barotrauma
return true;
},
Enabled = true,
Visible = Campaign.AllowedToEndRound()
Visible = Campaign.AllowedToManageCampaign(ClientPermissions.ManageMap)
};
buttonArea.RectTransform.MinSize = new Point(0, StartButton.RectTransform.MinSize.Y);
@@ -702,12 +703,10 @@ namespace Barotrauma
{
case CampaignMode.InteractionType.Repair:
repairHullsButton.Enabled =
(Campaign.PurchasedHullRepairs || Campaign.Wallet.CanAfford(CampaignMode.HullRepairCost)) &&
Campaign.AllowedToManageCampaign();
(Campaign.PurchasedHullRepairs || Campaign.Wallet.CanAfford(CampaignMode.HullRepairCost));
repairHullsButton.GetChild<GUITickBox>().Selected = Campaign.PurchasedHullRepairs;
repairItemsButton.Enabled =
(Campaign.PurchasedItemRepairs || Campaign.Wallet.CanAfford(CampaignMode.ItemRepairCost)) &&
Campaign.AllowedToManageCampaign();
(Campaign.PurchasedItemRepairs || Campaign.Wallet.CanAfford(CampaignMode.ItemRepairCost));
repairItemsButton.GetChild<GUITickBox>().Selected = Campaign.PurchasedItemRepairs;
if (GameMain.GameSession?.SubmarineInfo == null || !GameMain.GameSession.SubmarineInfo.SubsLeftBehind)
@@ -718,8 +717,7 @@ namespace Barotrauma
else
{
replaceShuttlesButton.Enabled =
(Campaign.PurchasedLostShuttles || Campaign.Wallet.CanAfford(CampaignMode.ShuttleReplaceCost)) &&
Campaign.AllowedToManageCampaign();
(Campaign.PurchasedLostShuttles || Campaign.Wallet.CanAfford(CampaignMode.ShuttleReplaceCost));
replaceShuttlesButton.GetChild<GUITickBox>().Selected = Campaign.PurchasedLostShuttles;
}
break;

View File

@@ -387,11 +387,14 @@ namespace Barotrauma.CharacterEditor
contentPackageDropDown.Flash();
return false;
}
if (!File.Exists(TexturePath))
if (SourceCharacter?.SpeciesName != CharacterPrefab.HumanSpeciesName)
{
GUI.AddMessage(GetCharacterEditorTranslation("TextureDoesNotExist"), GUIStyle.Red);
texturePathElement.Flash(GUIStyle.Red);
return false;
if (!File.Exists(TexturePath))
{
GUI.AddMessage(GetCharacterEditorTranslation("TextureDoesNotExist"), GUIStyle.Red);
texturePathElement.Flash(GUIStyle.Red);
return false;
}
}
var path = Path.GetFileName(TexturePath);
if (!path.EndsWith(".png", StringComparison.OrdinalIgnoreCase))

View File

@@ -10,11 +10,13 @@ namespace Barotrauma
public override sealed void Deselect()
{
DeselectEditorSpecific();
#if !DEBUG
//reset cheats the player might have used in the editor
GameMain.LightManager.LightingEnabled = true;
GameMain.LightManager.LosEnabled = true;
Hull.EditFire = false;
Hull.EditWater = false;
#endif
}
protected virtual void DeselectEditorSpecific() { }

View File

@@ -1006,13 +1006,12 @@ namespace Barotrauma
spriteBatch.End();
}
private void StartGame(SubmarineInfo selectedSub, string saveName, string mapSeed, CampaignSettings settings)
private void StartGame(SubmarineInfo selectedSub, string savePath, string mapSeed, CampaignSettings settings)
{
if (string.IsNullOrEmpty(saveName)) return;
if (string.IsNullOrEmpty(savePath)) { return; }
var existingSaveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
if (existingSaveFiles.Any(s => s == saveName))
if (existingSaveFiles.Any(s => s.FilePath == savePath))
{
new GUIMessageBox(TextManager.Get("SaveNameInUseHeader"), TextManager.Get("SaveNameInUseText"));
return;
@@ -1045,7 +1044,7 @@ namespace Barotrauma
selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, settings, mapSeed);
GameMain.GameSession = new GameSession(selectedSub, savePath, GameModePreset.SinglePlayerCampaign, settings, mapSeed);
GameMain.GameSession.CrewManager.CharacterInfos.Clear();
foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo))
{

View File

@@ -1299,7 +1299,7 @@ namespace Barotrauma
if (GameMain.Client != null)
{
GameMain.Client.ServerSettings.Voting.ResetVotes(GameMain.Client.ConnectedClients);
GameMain.Client.Voting.ResetVotes(GameMain.Client.ConnectedClients);
spectateButton.OnClicked = GameMain.Client.SpectateClicked;
ReadyToStartBox.OnSelected = GameMain.Client.SetReadyToStart;
}
@@ -1307,9 +1307,6 @@ namespace Barotrauma
roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible);
roundControlsHolder.Recalculate();
GameMain.NetworkMember.EndVoteCount = 0;
GameMain.NetworkMember.EndVoteMax = 1;
base.Select();
}
@@ -1348,9 +1345,9 @@ namespace Barotrauma
ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
ServerMessage.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
shuttleTickBox.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings) && !GameMain.Client.GameStarted;
SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub));
SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub));
ShuttleList.Enabled = ShuttleList.ButtonEnabled = GameMain.Client.HasPermission(ClientPermissions.SelectSub) && !GameMain.Client.GameStarted;
ModeList.Enabled = GameMain.Client.ServerSettings.Voting.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode);
ModeList.Enabled = GameMain.Client.ServerSettings.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode);
LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible);
@@ -1889,7 +1886,7 @@ namespace Barotrauma
}
else
{
var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) },
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), parent.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) },
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont)
{
UserData = "classtext",
@@ -1907,7 +1904,7 @@ namespace Barotrauma
VoteType voteType;
if (component.Parent == GameMain.NetLobbyScreen.SubList.Content)
{
if (!GameMain.Client.ServerSettings.Voting.AllowSubVoting)
if (!GameMain.Client.ServerSettings.AllowSubVoting)
{
var selectedSub = component.UserData as SubmarineInfo;
if (!selectedSub.RequiredContentPackagesInstalled)
@@ -1942,7 +1939,7 @@ namespace Barotrauma
}
else if (component.Parent == GameMain.NetLobbyScreen.ModeList.Content)
{
if (!GameMain.Client.ServerSettings.Voting.AllowModeVoting)
if (!GameMain.Client.ServerSettings.AllowModeVoting)
{
if (GameMain.Client.HasPermission(ClientPermissions.SelectMode))
{
@@ -2384,6 +2381,7 @@ namespace Barotrauma
return true;
}
};
permissionTick.ToolTip = permissionTick.TextBlock.ToolTip = TextManager.Get("ClientPermission." + permission + ".description");
}
var listBoxContainerRight = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), permissionContainer.RectTransform))
@@ -2478,7 +2476,7 @@ namespace Barotrauma
if (GameMain.Client != null && GameMain.Client.ConnectedClients.Contains(selectedClient))
{
if (GameMain.Client.ServerSettings.Voting.AllowVoteKick &&
if (GameMain.Client.ServerSettings.AllowVoteKick &&
selectedClient != null && selectedClient.AllowKicking)
{
var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaLower.RectTransform),
@@ -3564,22 +3562,7 @@ namespace Barotrauma
if (GameMain.Client.ServerSettings.AllowFileTransfers)
{
errorMsg += TextManager.Get("DownloadSubQuestion");
var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg,
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "request" + subName
};
requestFileBox.Buttons[0].UserData = new string[] { subName, md5Hash };
requestFileBox.Buttons[0].OnClicked += requestFileBox.Close;
requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) =>
{
string[] fileInfo = (string[])userdata;
GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo[0], fileInfo[1]);
return true;
};
requestFileBox.Buttons[1].OnClicked += requestFileBox.Close;
GameMain.Client?.RequestFile(FileTransferType.Submarine, subName, md5Hash);
}
else
{
@@ -3596,7 +3579,7 @@ namespace Barotrauma
public bool CheckIfCampaignSubMatches(SubmarineInfo serverSubmarine, SubmarineDeliveryData deliveryData)
{
if (GameMain.Client == null) return false;
if (GameMain.Client == null) { return false; }
//already downloading the selected sub file
if (GameMain.Client.FileReceiver.ActiveTransfers.Any(t => t.FileName == serverSubmarine.Name + ".sub"))
@@ -3610,59 +3593,19 @@ namespace Barotrauma
return true;
}
purchasableSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSubmarine.Name);
FailedSubInfo fileInfo = new FailedSubInfo(serverSubmarine.Name, serverSubmarine.MD5Hash.StringRepresentation);
LocalizedString errorMsg = "";
if (purchasableSub == null)
switch (deliveryData)
{
errorMsg = TextManager.GetWithVariable("SubNotFoundError", "[subname]", serverSubmarine.Name) + " ";
}
else if (purchasableSub.MD5Hash?.StringRepresentation == null)
{
errorMsg = TextManager.GetWithVariable("SubLoadError", "[subname]", serverSubmarine.Name) + " ";
/*GUITextBlock textBlock = subList.Content.GetChildByUserData(sub)?.GetChild<GUITextBlock>();
if (textBlock != null) { textBlock.TextColor = GUIStyle.Red; }*/
}
else
{
errorMsg = TextManager.GetWithVariables("SubDoesntMatchError",
("[subname]", purchasableSub.Name),
("[myhash]", purchasableSub.MD5Hash.ShortRepresentation),
("[serverhash]", Md5Hash.GetShortHash(serverSubmarine.MD5Hash.StringRepresentation))) + " ";
}
errorMsg += TextManager.Get("DownloadSubQuestion");
//already showing a message about the same sub
if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "request" + serverSubmarine.Name))
{
return false;
}
var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg,
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "request" + serverSubmarine.Name
};
requestFileBox.Buttons[0].UserData = new FailedSubInfo(serverSubmarine.Name, serverSubmarine.MD5Hash.StringRepresentation);
requestFileBox.Buttons[0].OnClicked += requestFileBox.Close;
requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) =>
{
FailedSubInfo fileInfo = (FailedSubInfo)userdata;
if (deliveryData == SubmarineDeliveryData.Owned)
{
case SubmarineDeliveryData.Owned:
FailedOwnedSubs.Add(fileInfo);
}
else if (deliveryData == SubmarineDeliveryData.Campaign)
{
break;
case SubmarineDeliveryData.Campaign:
FailedCampaignSubs.Add(fileInfo);
}
break;
}
GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo.Name, fileInfo.Hash);
return true;
};
requestFileBox.Buttons[1].OnClicked += requestFileBox.Close;
GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo.Name, fileInfo.Hash);
return false;
}

View File

@@ -1905,27 +1905,27 @@ namespace Barotrauma
toolTip = TextManager.GetWithVariable("ServerListIncompatibleVersion", "[version]", serverInfo.GameVersion);
}
int maxIncompatibleToList = 10;
List<LocalizedString> incompatibleModNames = new List<LocalizedString>();
for (int i = 0; i < serverInfo.ContentPackageNames.Count; i++)
{
bool listAsIncompatible = false;
if (serverInfo.ContentPackageWorkshopIds[i] == 0)
{
listAsIncompatible = !ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.Hash.StringRepresentation == serverInfo.ContentPackageHashes[i]);
}
else
{
listAsIncompatible = ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.Hash.StringRepresentation != serverInfo.ContentPackageHashes[i] &&
contentPackage.SteamWorkshopId == serverInfo.ContentPackageWorkshopIds[i]);
}
bool listAsIncompatible = !ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.Hash.StringRepresentation == serverInfo.ContentPackageHashes[i]);
if (listAsIncompatible)
{
if (toolTip != "") toolTip += "\n";
toolTip += TextManager.GetWithVariables("ServerListIncompatibleContentPackage",
("[contentpackage]", serverInfo.ContentPackageNames[i]),
("[hash]", Md5Hash.GetShortHash(serverInfo.ContentPackageHashes[i])));
incompatibleModNames.Add(TextManager.GetWithVariables("ModNameAndHashFormat",
("[name]", serverInfo.ContentPackageNames[i]),
("[hash]", Md5Hash.GetShortHash(serverInfo.ContentPackageHashes[i]))));
}
}
if (incompatibleModNames.Any())
{
toolTip += '\n' + TextManager.Get("ModDownloadHeader") + "\n" + string.Join(", ", incompatibleModNames.Take(maxIncompatibleToList));
if (incompatibleModNames.Count > maxIncompatibleToList)
{
toolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (incompatibleModNames.Count - maxIncompatibleToList).ToString());
}
}
serverContent.Children.ForEach(c => c.ToolTip = toolTip);
serverName.TextColor *= 0.5f;

View File

@@ -237,7 +237,7 @@ namespace Barotrauma
public override Camera Cam => cam;
public static XDocument AutoSaveInfo;
private static readonly string autoSavePath = Path.Combine(SubmarineInfo.SavePath, ".AutoSaves");
private static readonly string autoSavePath = Path.Combine("Submarines", ".AutoSaves");
private static readonly string autoSaveInfoPath = Path.Combine(autoSavePath, "autosaves.xml");
private static string GetSubDescription()
@@ -678,7 +678,7 @@ namespace Barotrauma
//-----------------------------------------------
showEntitiesPanel = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.5f), GUI.Canvas)
showEntitiesPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.5f), GUI.Canvas)
{
MinSize = new Point(190, 0)
})
@@ -771,22 +771,26 @@ namespace Barotrauma
}
foreach (string subcategory in availableSubcategories)
{
var tb = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), subcategoryList.Content.RectTransform),
var tb = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.15f), subcategoryList.Content.RectTransform),
TextManager.Get("subcategory." + subcategory).Fallback(subcategory), font: GUIStyle.SmallFont)
{
UserData = subcategory,
Selected = !IsSubcategoryHidden(subcategory),
OnSelected = (GUITickBox obj) => { hiddenSubCategories[(string)obj.UserData] = !obj.Selected; return true; },
};
if (tb.TextBlock.TextSize.X > tb.TextBlock.Rect.Width * 1.25f)
tb.TextBlock.Wrap = true;
}
GUITextBlock.AutoScaleAndNormalize(subcategoryList.Content.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock));
foreach (GUIComponent child in subcategoryList.Content.Children)
{
if (child is GUITickBox tb && tb.TextBlock.TextSize.X > tb.TextBlock.Rect.Width * 1.25f)
{
tb.ToolTip = tb.Text;
tb.Text = ToolBox.LimitString(tb.Text.Value, tb.Font, (int)(tb.TextBlock.Rect.Width * 1.25f));
}
}
GUITextBlock.AutoScaleAndNormalize(subcategoryList.Content.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock));
showEntitiesPanel.RectTransform.NonScaledSize =
new Point(
(int)(paddedShowEntitiesPanel.RectTransform.Children.Max(c => (int)((c.GUIComponent as GUITickBox)?.TextBlock.TextSize.X ?? 0)) / paddedShowEntitiesPanel.RectTransform.RelativeSize.X),
@@ -2933,7 +2937,7 @@ namespace Barotrauma
if (deleteButtonHolder.FindChild("delete") is GUIButton deleteBtn)
{
deleteBtn.Enabled = userData is SubmarineInfo subInfo
&& (GetContentPackageIntrinsicallyTiedToSub(subInfo) != null || Path.GetDirectoryName(subInfo.FilePath) == SubmarineInfo.SavePath);
&& GetContentPackageIntrinsicallyTiedToSub(subInfo) != null;
}
return true;
}
@@ -3102,8 +3106,9 @@ namespace Barotrauma
var loadedSub = Submarine.Load(new SubmarineInfo(filePath), true);
// set the submarine file path to the "default" value
loadedSub.Info.FilePath = Path.Combine(SubmarineInfo.SavePath, $"{TextManager.Get("UnspecifiedSubFileName")}.sub");
loadedSub.Info.Name = TextManager.Get("UnspecifiedSubFileName").Value;
var unspecifiedFileName = TextManager.Get("UnspecifiedSubFileName");
loadedSub.Info.FilePath = Path.Combine(ContentPackage.LocalModsDir, unspecifiedFileName.Value, $"{unspecifiedFileName}.sub");
loadedSub.Info.Name = unspecifiedFileName.Value;
try
{
loadedSub.Info.Name = loadedSub.Info.SubmarineElement.GetAttributeString("name", loadedSub.Info.Name);
@@ -3199,8 +3204,7 @@ namespace Barotrauma
//check that it's a local content package and only allow deletion if it is.
//(deleting from the Submarines folder is also currently allowed, but this is temporary)
var subPackage = GetContentPackageIntrinsicallyTiedToSub(sub);
bool isInOldSavePath = Path.GetDirectoryName(sub.FilePath) == SubmarineInfo.SavePath;
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage) && !isInOldSavePath) { return; }
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { return; }
var msgBox = new GUIMessageBox(
TextManager.Get("DeleteDialogLabel"),
@@ -3216,10 +3220,6 @@ namespace Barotrauma
ContentPackageManager.LocalPackages.Refresh();
ContentPackageManager.EnabledPackages.DisableRemovedMods();
}
else if (isInOldSavePath && File.Exists(sub.FilePath))
{
File.Delete(sub.FilePath);
}
sub.Dispose();
SubmarineInfo.RefreshSavedSubs();

View File

@@ -13,7 +13,7 @@ using OpenAL;
namespace Barotrauma
{
public class SettingsMenu
class SettingsMenu
{
public static SettingsMenu? Instance { get; private set; }
@@ -709,7 +709,9 @@ namespace Barotrauma
GUIFrame content = CreateNewContentFrame(Tab.Mods);
content.RectTransform.RelativeSize = Vector2.One;
workshopMenu = new WorkshopMenu(content);
workshopMenu = Screen.Selected is MainMenuScreen
? (WorkshopMenu)new MutableWorkshopMenu(content)
: (WorkshopMenu)new ImmutableWorkshopMenu(content);
}
private void CreateBottomButtons()
@@ -729,7 +731,7 @@ namespace Barotrauma
OnClicked = (btn, obj) =>
{
GameSettings.SetCurrentConfig(unsavedConfig);
WorkshopMenu.Apply();
if (WorkshopMenu is MutableWorkshopMenu mutableWorkshopMenu) { mutableWorkshopMenu.Apply(); }
GameSettings.SaveCurrentConfig();
mainFrame.Flash(color: GUIStyle.Green);
return false;

View File

@@ -8,9 +8,9 @@ using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Steam
{
public partial class WorkshopMenu
abstract partial class WorkshopMenu
{
private readonly struct BBWord
protected readonly struct BBWord
{
[Flags]
public enum TagType
@@ -42,10 +42,10 @@ namespace Barotrauma.Steam
}
}
private static readonly Regex bbTagRegex = new Regex(@"\[(.+?)\]",
protected static readonly Regex bbTagRegex = new Regex(@"\[(.+?)\]",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static GUICustomComponent CreateBBCodeElement(string bbCode, GUIListBox container)
protected static GUICustomComponent CreateBBCodeElement(string bbCode, GUIListBox container)
{
Point cachedContainerSize = Point.Zero;
List<BBWord> bbWords = new List<BBWord>();

View File

@@ -0,0 +1,41 @@
#nullable enable
using Microsoft.Xna.Framework;
namespace Barotrauma.Steam
{
sealed class ImmutableWorkshopMenu : WorkshopMenu
{
public ImmutableWorkshopMenu(GUIFrame parent) : base(parent)
{
var mainLayout
= new GUILayoutGroup(new RectTransform((0.5f, 1.0f), parent.RectTransform, Anchor.Center), isHorizontal: false);
Label(mainLayout, TextManager.Get("enabledcore"), GUIStyle.SubHeadingFont);
var coreBox = new GUIButton(
NewItemRectT(mainLayout), style: "GUITextBoxNoIcon", text: ContentPackageManager.EnabledPackages.Core!.Name, textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false,
CanBeSelected = false
};
coreBox.TextBlock.Padding = new Vector4(10.0f, 0.0f, 10.0f, 0.0f);
Label(mainLayout, TextManager.Get("enabledregular"), GUIStyle.SubHeadingFont);
var regularList = new GUIListBox(
NewItemRectT(mainLayout, heightScale: 12f))
{
OnSelected = (component, o) => false,
HoverCursor = CursorState.Default
};
foreach (var p in ContentPackageManager.EnabledPackages.Regular)
{
var regularBox = new GUITextBlock(
new RectTransform((1.0f, 0.07f), regularList.Content.RectTransform), text: p.Name)
{
CanBeFocused = false
};
}
Label(mainLayout, TextManager.Get("CannotChangeMods"), GUIStyle.Font);
}
}
}

View File

@@ -12,7 +12,7 @@ using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentP
namespace Barotrauma.Steam
{
public partial class WorkshopMenu
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
private string ExtractTitle(ItemOrPackage itemOrPackage)
=> itemOrPackage.TryGet(out ContentPackage package)

View File

@@ -10,7 +10,7 @@ using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentP
namespace Barotrauma.Steam
{
public partial class WorkshopMenu
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
public enum Tab
{
@@ -20,10 +20,10 @@ namespace Barotrauma.Steam
Publish
}
private readonly GUILayoutGroup tabber;
private readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
protected readonly GUILayoutGroup tabber;
protected readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
private readonly GUIFrame contentFrame;
protected readonly GUIFrame contentFrame;
private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected");
@@ -39,15 +39,17 @@ namespace Barotrauma.Steam
private readonly GUIListBox popularModsList;
private readonly GUIListBox selfModsList;
public WorkshopMenu(GUIFrame parent)
public MutableWorkshopMenu(GUIFrame parent) : base(parent)
{
var mainLayout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: false);
var mainLayout
= new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: false);
tabber = new GUILayoutGroup(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), isHorizontal: true) { Stretch = true };
tabber = new GUILayoutGroup(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), isHorizontal: true)
{ Stretch = true };
tabContents = new Dictionary<Tab, (GUIButton Button, GUIFrame Content)>();
contentFrame = new GUIFrame(new RectTransform((1.0f, 0.95f), mainLayout.RectTransform), style: null);
CreateInstalledModsTab(
out enabledCoreDropdown,
out enabledRegularModsList,

View File

@@ -15,7 +15,7 @@ using Path = Barotrauma.IO.Path;
namespace Barotrauma.Steam
{
public partial class WorkshopMenu
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
private class LocalThumbnail : IDisposable
{

View File

@@ -7,22 +7,22 @@ using Microsoft.Xna.Framework;
namespace Barotrauma.Steam
{
public partial class WorkshopMenu
abstract partial class WorkshopMenu
{
private static RectTransform NewItemRectT(GUILayoutGroup parent, float heightScale = 1.0f)
protected static RectTransform NewItemRectT(GUILayoutGroup parent, float heightScale = 1.0f)
=> new RectTransform((1.0f, 0.06f * heightScale), parent.RectTransform, Anchor.CenterLeft);
private static void Spacer(GUILayoutGroup parent, float height = 0.03f)
protected static void Spacer(GUILayoutGroup parent, float height = 0.03f)
{
new GUIFrame(new RectTransform((1.0f, height), parent.RectTransform, Anchor.CenterLeft), style: null);
}
private static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font, float heightScale = 1.0f)
protected static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font, float heightScale = 1.0f)
{
return new GUITextBlock(NewItemRectT(parent, heightScale), str, font: font);
}
private static GUITextBox ScrollableTextBox(GUILayoutGroup parent, float heightScale, string text)
protected static GUITextBox ScrollableTextBox(GUILayoutGroup parent, float heightScale, string text)
{
var containingListBox = new GUIListBox(NewItemRectT(parent, heightScale));
var textBox = new GUITextBox(
@@ -53,12 +53,12 @@ namespace Barotrauma.Steam
return textBox;
}
private static GUIDropDown DropdownEnum<T>(
protected static GUIDropDown DropdownEnum<T>(
GUILayoutGroup parent, Func<T, LocalizedString> textFunc, T currentValue,
Action<T> setter) where T : Enum
=> Dropdown(parent, textFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter);
private static GUIDropDown Dropdown<T>(
protected static GUIDropDown Dropdown<T>(
GUILayoutGroup parent, Func<T, LocalizedString> textFunc, IReadOnlyList<T> values, T currentValue,
Action<T> setter, float heightScale = 1.0f)
{
@@ -67,7 +67,7 @@ namespace Barotrauma.Steam
return dropdown;
}
private static void SwapDropdownValues<T>(
protected static void SwapDropdownValues<T>(
GUIDropDown dropdown, Func<T, LocalizedString> textFunc, IReadOnlyList<T> values, T currentValue,
Action<T> setter)
{
@@ -88,10 +88,10 @@ namespace Barotrauma.Steam
};
}
private static int Round(float v) => (int)MathF.Round(v);
private static string Percentage(float v) => $"{Round(v * 100)}";
protected static int Round(float v) => (int)MathF.Round(v);
protected static string Percentage(float v) => $"{Round(v * 100)}";
private struct ActionCarrier
protected struct ActionCarrier
{
public readonly Identifier Id;
public readonly Action Action;
@@ -102,7 +102,7 @@ namespace Barotrauma.Steam
}
}
private GUIComponent CreateActionCarrier(GUIComponent parent, Identifier id, Action action)
protected GUIComponent CreateActionCarrier(GUIComponent parent, Identifier id, Action action)
=> new GUIFrame(new RectTransform(Vector2.Zero, parent.RectTransform), style: null)
{ UserData = new ActionCarrier(id, action) };
}

View File

@@ -0,0 +1,12 @@
#nullable enable
namespace Barotrauma.Steam
{
abstract partial class WorkshopMenu
{
public WorkshopMenu(GUIFrame parent)
{
}
}
}

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.17.4.0</Version>
<Version>0.17.5.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.17.4.0</Version>
<Version>0.17.5.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.17.4.0</Version>
<Version>0.17.5.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

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

View File

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

View File

@@ -129,7 +129,7 @@ namespace Barotrauma
public void ApplyWalletData(Character character)
{
character.Wallet = new Wallet(WalletData);
character.Wallet = new Wallet(Option<Character>.Some(character), WalletData);
}
public XElement Save()

View File

@@ -135,7 +135,7 @@ namespace Barotrauma
DebugConsole.NewMessage("Saved campaigns:", Color.White);
for (int i = 0; i < saveFiles.Length; i++)
{
DebugConsole.NewMessage(" " + i + ". " + saveFiles[i], Color.White);
DebugConsole.NewMessage(" " + i + ". " + saveFiles[i].FilePath, Color.White);
}
DebugConsole.ShowQuestionPrompt("Select a save file to load (0 - " + (saveFiles.Length - 1) + "):", (string selectedSave) =>
{
@@ -148,7 +148,7 @@ namespace Barotrauma
}
else
{
LoadCampaign(saveFiles[saveIndex]);
LoadCampaign(saveFiles[saveIndex].FilePath);
}
});
}
@@ -166,28 +166,13 @@ namespace Barotrauma
/// <summary>
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
/// </summary>
public bool AllowedToEndRound(Client client)
{
//allow ending the round if the client has permissions, is the owner, the only client in the server,
//or if no-one has permissions
return
client.HasPermission(ClientPermissions.ManageRound) ||
client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Server.ConnectedClients.Count == 1 ||
IsOwner(client) ||
GameMain.Server.ConnectedClients.None(c =>
c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign)));
}
/// <summary>
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
/// </summary>
public bool AllowedToManageCampaign(Client client, ClientPermissions permissions = ClientPermissions.ManageCampaign)
public bool AllowedToManageCampaign(Client client, ClientPermissions permissions)
{
//allow managing the campaign if the client has permissions, is the owner, or the only client in the server,
//or if no-one has management permissions
return
client.HasPermission(permissions) ||
client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Server.ConnectedClients.Count == 1 ||
IsOwner(client) ||
GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions)));
@@ -519,7 +504,7 @@ namespace Barotrauma
walletsToCheck.Clear();
walletsToCheck.Add(0, Bank);
foreach (Character character in Mission.GetSalaryEligibleCrew())
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Player))
{
walletsToCheck.Add(character.ID, character.Wallet);
}
@@ -715,107 +700,102 @@ namespace Barotrauma
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
}
bool allowedToManageCampaign = AllowedToManageCampaign(sender);
if (AllowedToManageCampaign(sender))
Location location = Map.CurrentLocation;
int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost;
int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost;
int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost;
Wallet personalWallet = GetWallet(sender);
if (purchasedHullRepairs != PurchasedHullRepairs)
{
Location location = Map.CurrentLocation;
int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost;
int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost;
int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost;
Wallet personalWallet = GetWallet(sender);
if (purchasedHullRepairs != PurchasedHullRepairs)
switch (purchasedHullRepairs)
{
switch (purchasedHullRepairs)
{
case true when personalWallet.CanAfford(hullRepairCost):
personalWallet.Deduct(hullRepairCost);
PurchasedHullRepairs = true;
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
break;
case false:
PurchasedHullRepairs = false;
personalWallet.Refund(hullRepairCost);
break;
}
case true when personalWallet.CanAfford(hullRepairCost):
personalWallet.Deduct(hullRepairCost);
PurchasedHullRepairs = true;
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
break;
case false:
PurchasedHullRepairs = false;
personalWallet.Refund(hullRepairCost);
break;
}
}
if (purchasedItemRepairs != PurchasedItemRepairs)
if (purchasedItemRepairs != PurchasedItemRepairs)
{
switch (purchasedItemRepairs)
{
switch (purchasedItemRepairs)
{
case true when personalWallet.CanAfford(itemRepairCost):
personalWallet.Deduct(itemRepairCost);
PurchasedItemRepairs = true;
GameAnalyticsManager.AddMoneySpentEvent(itemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
break;
case false:
PurchasedItemRepairs = false;
personalWallet.Refund(itemRepairCost);
break;
}
case true when personalWallet.CanAfford(itemRepairCost):
personalWallet.Deduct(itemRepairCost);
PurchasedItemRepairs = true;
GameAnalyticsManager.AddMoneySpentEvent(itemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
break;
case false:
PurchasedItemRepairs = false;
personalWallet.Refund(itemRepairCost);
break;
}
}
if (purchasedLostShuttles != PurchasedLostShuttles)
if (purchasedLostShuttles != PurchasedLostShuttles)
{
if (GameMain.GameSession?.SubmarineInfo != null && GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied)
{
if (GameMain.GameSession?.SubmarineInfo != null && GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied)
{
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox);
}
else if (purchasedLostShuttles && personalWallet.TryDeduct(shuttleRetrieveCost))
{
PurchasedLostShuttles = true;
GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
}
else if (!purchasedItemRepairs)
{
PurchasedLostShuttles = false;
personalWallet.Refund(shuttleRetrieveCost);
}
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox);
}
if (currentLocIndex < Map.Locations.Count && Map.AllowDebugTeleport)
else if (purchasedLostShuttles && personalWallet.TryDeduct(shuttleRetrieveCost))
{
Map.SetLocation(currentLocIndex);
PurchasedLostShuttles = true;
GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
}
else if (!purchasedItemRepairs)
{
PurchasedLostShuttles = false;
personalWallet.Refund(shuttleRetrieveCost);
}
}
if (currentLocIndex < Map.Locations.Count && Map.AllowDebugTeleport)
{
Map.SetLocation(currentLocIndex);
}
if (AllowedToManageCampaign(sender, ClientPermissions.ManageMap))
{
Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
if (Map.SelectedLocation == null) { Map.SelectRandomLocation(preferUndiscovered: true); }
if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndices); }
CheckTooManyMissions(Map.CurrentLocation, sender);
}
bool allowedToUseStore = AllowedToManageCampaign(sender, ClientPermissions.CampaignStore);
if (allowedToManageCampaign || allowedToUseStore || AllowedToManageCampaign(sender, ClientPermissions.BuyItems))
var prevBuyCrateItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.ItemsInBuyCrate);
foreach (var store in prevBuyCrateItems)
{
var prevBuyCrateItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.ItemsInBuyCrate);
foreach (var store in prevBuyCrateItems)
foreach (var item in store.Value)
{
foreach (var item in store.Value)
{
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
}
}
foreach (var store in buyCrateItems)
{
foreach (var item in store.Value)
{
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
}
}
var prevPurchasedItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.PurchasedItems);
foreach (var store in prevPurchasedItems)
{
CargoManager.SellBackPurchasedItems(store.Key, store.Value);
}
foreach (var store in purchasedItems)
{
CargoManager.PurchaseItems(store.Key, store.Value, false, sender);
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
}
}
foreach (var store in buyCrateItems)
{
foreach (var item in store.Value)
{
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
}
}
var prevPurchasedItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.PurchasedItems);
foreach (var store in prevPurchasedItems)
{
CargoManager.SellBackPurchasedItems(store.Key, store.Value);
}
foreach (var store in purchasedItems)
{
CargoManager.PurchaseItems(store.Key, store.Value, false, sender);
}
bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems);
if (allowedToManageCampaign || allowedToUseStore || allowedToSellSubItems)
if (allowedToSellSubItems)
{
var prevSubSellCrateItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.ItemsInSellFromSubCrate);
foreach (var store in prevSubSellCrateItems)
@@ -834,7 +814,7 @@ namespace Barotrauma
}
}
bool allowedToSellInventoryItems = AllowedToManageCampaign(sender, ClientPermissions.SellInventoryItems);
if (allowedToManageCampaign || allowedToUseStore || (allowedToSellInventoryItems && allowedToSellSubItems))
if (allowedToSellInventoryItems && allowedToSellSubItems)
{
// for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all
// sold items that are removed so they should be discarded on the next message
@@ -867,37 +847,34 @@ namespace Barotrauma
bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character);
}
if (allowedToManageCampaign)
foreach (var (prefab, category, _) in purchasedUpgrades)
{
foreach (var (prefab, category, _) in purchasedUpgrades)
{
UpgradeManager.PurchaseUpgrade(prefab, category, client: sender);
UpgradeManager.PurchaseUpgrade(prefab, category, client: sender);
// unstable logging
int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation);
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
}
foreach (var purchasedItemSwap in purchasedItemSwaps)
// unstable logging
int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation);
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
}
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
if (purchasedItemSwap.ItemToInstall == null)
{
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove);
}
else
{
UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender);
}
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove);
}
foreach (Item item in Item.ItemList)
else
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
UpgradeManager.CancelItemSwap(item);
item.PendingItemSwap = null;
}
UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
UpgradeManager.CancelItemSwap(item);
item.PendingItemSwap = null;
}
}
}
public void ServerReadMoney(IReadMessage msg, Client sender)
@@ -907,19 +884,26 @@ namespace Barotrauma
switch (transfer.Sender)
{
case Some<ushort> { Value: var id }:
if (id != sender.CharacterID && !AllowedToManageCampaign(sender)) { return; }
if (id != sender.CharacterID && !AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; }
Wallet wallet = GetWalletByID(id);
if (wallet is InvalidWallet) { return; }
TransferMoney(wallet);
break;
case None<ushort> _:
if (!AllowedToManageCampaign(sender)) { return; }
TransferMoney(Bank);
if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney))
{
if (transfer.Receiver is Some<ushort> { Value: var receiverId } && receiverId == sender.CharacterID)
{
GameMain.Server?.Voting.StartTransferVote(sender, null, transfer.Amount, sender);
}
return;
}
else
{
TransferMoney(Bank);
}
break;
}
@@ -952,10 +936,10 @@ namespace Barotrauma
{
NetWalletSetSalaryUpdate update = INetSerializableStruct.Read<NetWalletSetSalaryUpdate>(msg);
if (!AllowedToManageCampaign(sender)) { return; }
if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; }
Character targetCharacter = Character.CharacterList.FirstOrDefault(c => c.ID == update.Target);
targetCharacter?.Wallet.SetRewardDistrubiton(update.NewRewardDistribution);
targetCharacter?.Wallet.SetRewardDistribution(update.NewRewardDistribution);
}
public void ServerReadCrew(IReadMessage msg, Client sender)
@@ -994,7 +978,7 @@ namespace Barotrauma
List<CharacterInfo> hiredCharacters = new List<CharacterInfo>();
CharacterInfo firedCharacter = null;
if (location != null && AllowedToManageCampaign(sender))
if (location != null && AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
{
if (fireCharacter)
{

View File

@@ -18,7 +18,7 @@ namespace Barotrauma
private struct RateLimitInfo
{
public int Requests;
public const int MaxRequests = 5;
public const int MaxRequests = 10;
public DateTimeOffset Expiry;
}

View File

@@ -130,6 +130,8 @@ namespace Barotrauma.Networking
KarmaManager.SelectPreset(serverSettings.KarmaPreset);
serverSettings.SetPassword(password);
Voting = new Voting();
ownerKey = ownKey;
ownerSteamId = steamId;
@@ -383,23 +385,7 @@ namespace Barotrauma.Networking
TraitorManager?.Update(deltaTime);
if (serverSettings.Voting.VoteRunning)
{
Voting.SubVote.Timer += deltaTime;
if (Voting.SubVote.Timer >= serverSettings.SubmarineVoteTimeout)
{
// Do not take unanswered into account for total
if (SubmarineVoteYesCount / (float)(SubmarineVoteYesCount + SubmarineVoteNoCount) >= serverSettings.SubmarineVoteRequiredRatio)
{
SwitchSubmarine();
}
else
{
serverSettings.Voting.StopSubmarineVote(false);
}
}
}
Voting.Update(deltaTime);
bool isCrewDead =
connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated);
@@ -1073,7 +1059,7 @@ namespace Barotrauma.Networking
ChatMessage.ServerRead(inc, c);
break;
case ClientNetObject.VOTE:
serverSettings.Voting.ServerRead(inc, c);
Voting.ServerRead(inc, c);
break;
default:
return;
@@ -1220,7 +1206,7 @@ namespace Barotrauma.Networking
entityEventManager.Read(inc, c);
break;
case ClientNetObject.VOTE:
serverSettings.Voting.ServerRead(inc, c);
Voting.ServerRead(inc, c);
break;
case ClientNetObject.SPECTATING_POS:
c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
@@ -1309,17 +1295,11 @@ namespace Barotrauma.Networking
var mpCampaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
if (command == ClientPermissions.ManageRound && mpCampaign != null)
{
if (!mpCampaign.AllowedToEndRound(sender))
{
return;
}
//do nothing, ending campaign rounds is checked in more detail below
}
else if (command == ClientPermissions.ManageCampaign && mpCampaign != null)
{
if (!mpCampaign.AllowedToManageCampaign(sender))
{
return;
}
//do nothing, campaign permissions are checked in more detail in MultiplayerCampaign.ServerRead
}
else if (!sender.HasPermission(command))
{
@@ -1381,21 +1361,26 @@ namespace Barotrauma.Networking
bool end = inc.ReadBoolean();
if (end)
{
bool save = inc.ReadBoolean();
if (gameStarted)
if (mpCampaign == null ||
mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageRound) ||
mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign))
{
Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
if (mpCampaign != null && Level.IsLoadedOutpost && save)
bool save = inc.ReadBoolean();
if (gameStarted)
{
mpCampaign.SavePlayers();
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
if (mpCampaign != null && Level.IsLoadedOutpost && save)
{
mpCampaign.SavePlayers();
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
}
else
{
save = false;
}
EndGame(wasSaved: save);
}
else
{
save = false;
}
EndGame(wasSaved: save);
}
}
else
@@ -1403,14 +1388,17 @@ namespace Barotrauma.Networking
bool continueCampaign = inc.ReadBoolean();
if (mpCampaign != null && mpCampaign.GameOver || continueCampaign)
{
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath);
if (mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageMap))
{
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath);
}
}
else if (!gameStarted && !initiatedStartGame)
{
Log("Client \"" + ClientLogName(sender) + "\" started the round.", ServerLog.MessageType.ServerMessage);
StartGame();
}
else if (mpCampaign != null)
else if (mpCampaign != null && (mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || mpCampaign.AllowedToManageCampaign(sender, ClientPermissions.ManageMap)))
{
var availableTransition = mpCampaign.GetAvailableTransition(out _, out _);
//don't force location if we've teleported
@@ -1473,34 +1461,20 @@ namespace Barotrauma.Networking
if (GameMain.NetLobbyScreen.GameModes[modeIndex].Identifier == "multiplayercampaign")
{
string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false).ToArray();
for (int i = 0; i < saveFiles.Length; i++)
{
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFiles[i]);
if (doc?.Root != null)
{
saveFiles[i] =
string.Join(";",
saveFiles[i].Replace(';', ' '),
doc.Root.GetAttributeStringUnrestricted("submarine", ""),
doc.Root.GetAttributeStringUnrestricted("savetime", ""),
doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", ""));
}
}
const int MaxSaves = 255;
var saveInfos = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false);
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO);
msg.Write((UInt16)saveFiles.Count());
foreach (string saveFile in saveFiles)
msg.Write((byte)Math.Min(saveInfos.Count, MaxSaves));
for (int i = 0; i < saveInfos.Count && i < MaxSaves; i++)
{
msg.Write(saveFile);
msg.Write(saveInfos[i]);
}
serverPeer.Send(msg, sender.Connection, DeliveryMethod.Reliable);
}
break;
case ClientPermissions.ManageCampaign:
(GameMain.GameSession.GameMode as MultiPlayerCampaign)?.ServerRead(inc, sender);
mpCampaign?.ServerRead(inc, sender);
break;
case ClientPermissions.ConsoleCommands:
{
@@ -1900,8 +1874,8 @@ namespace Barotrauma.Networking
outmsg.Write(selectedShuttle.Name);
outmsg.Write(selectedShuttle.MD5Hash.ToString());
outmsg.Write(serverSettings.Voting.AllowSubVoting);
outmsg.Write(serverSettings.Voting.AllowModeVoting);
outmsg.Write(serverSettings.AllowSubVoting);
outmsg.Write(serverSettings.AllowModeVoting);
outmsg.Write(serverSettings.VoiceChatEnabled);
@@ -2036,9 +2010,9 @@ namespace Barotrauma.Networking
SubmarineInfo selectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle;
SubmarineInfo selectedSub;
if (serverSettings.Voting.AllowSubVoting)
if (serverSettings.AllowSubVoting)
{
selectedSub = serverSettings.Voting.HighestVoted<SubmarineInfo>(VoteType.Sub, connectedClients);
selectedSub = Voting.HighestVoted<SubmarineInfo>(VoteType.Sub, connectedClients);
if (selectedSub == null) { selectedSub = GameMain.NetLobbyScreen.SelectedSub; }
}
else
@@ -2051,7 +2025,7 @@ namespace Barotrauma.Networking
return false;
}
GameModePreset selectedMode = serverSettings.Voting.HighestVoted<GameModePreset>(VoteType.Mode, connectedClients);
GameModePreset selectedMode = Voting.HighestVoted<GameModePreset>(VoteType.Mode, connectedClients);
if (selectedMode == null) { selectedMode = GameMain.NetLobbyScreen.SelectedMode; }
if (selectedMode == null)
@@ -2455,11 +2429,8 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Running;
if (GameMain.Server?.ServerSettings?.Voting != null)
{
GameMain.Server.ServerSettings.Voting.ResetVotes(GameMain.Server.ConnectedClients);
}
Voting?.ResetVotes(GameMain.Server.ConnectedClients);
GameMain.GameScreen.Select();
Log("Round started.", ServerLog.MessageType.ServerMessage);
@@ -3233,23 +3204,24 @@ namespace Barotrauma.Networking
serverPeer.Send(msg, transfer.Connection, DeliveryMethod.ReliableOrdered);
}
public void UpdateVoteStatus()
public void UpdateVoteStatus(bool checkActiveVote = true)
{
if (connectedClients.Count == 0) return;
if (connectedClients.Count == 0) { return; }
if (serverSettings.Voting.VoteRunning)
if (checkActiveVote && Voting.ActiveVote != null)
{
int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(Voting.ActiveVote.VoteType) == 2);
int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(Voting.ActiveVote.VoteType) == 1);
int max = GameMain.Server.ConnectedClients.Count(c => c.InGame);
// Required ratio cannot be met
if (SubmarineVoteNoCount / (float)SubmarineVoteMax > 1f - serverSettings.SubmarineVoteRequiredRatio)
if (no / (float)max > 1f - serverSettings.VoteRequiredRatio)
{
serverSettings.Voting.StopSubmarineVote(false);
return; // Update will be re-sent via StopSubmarineVote
Voting.ActiveVote.Finish(Voting, passed: false);
}
else if (SubmarineVoteYesCount / (float)SubmarineVoteMax >= serverSettings.SubmarineVoteRequiredRatio)
else if (yes / max >= serverSettings.VoteRequiredRatio)
{
SwitchSubmarine();
return; // Update will be re-sent via StopSubmarineVote
}
Voting.ActiveVote.Finish(Voting, passed: true);
}
}
Client.UpdateKickVotes(connectedClients);
@@ -3279,10 +3251,12 @@ namespace Barotrauma.Networking
SendVoteStatus(connectedClients);
if (serverSettings.Voting.AllowEndVoting && EndVoteMax > 0 &&
((float)EndVoteCount / (float)EndVoteMax) >= serverSettings.EndVoteRequiredRatio)
int endVoteCount = ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound));
int endVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned);
if (serverSettings.AllowEndVoting && endVoteMax > 0 &&
((float)endVoteCount / (float)endVoteMax) >= serverSettings.EndVoteRequiredRatio)
{
Log("Ending round by votes (" + EndVoteCount + "/" + (EndVoteMax - EndVoteCount) + ")", ServerLog.MessageType.ServerMessage);
Log("Ending round by votes (" + endVoteCount + "/" + (endVoteMax - endVoteCount) + ")", ServerLog.MessageType.ServerMessage);
EndGame(wasSaved: false);
}
}
@@ -3294,7 +3268,7 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ServerNetObject.VOTE);
serverSettings.Voting.ServerWrite(msg);
Voting.ServerWrite(msg);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
foreach (var c in recipients)
@@ -3303,11 +3277,13 @@ namespace Barotrauma.Networking
}
}
private void SwitchSubmarine()
public void SwitchSubmarine()
{
SubmarineInfo targetSubmarine = Voting.SubVote.Sub;
VoteType voteType = Voting.SubVote.VoteType;
Client starter = Voting.SubVote.VoteStarter;
if (!(Voting.ActiveVote is Voting.SubmarineVote subVote)) { return; }
SubmarineInfo targetSubmarine = subVote.Sub;
VoteType voteType = Voting.ActiveVote.VoteType;
Client starter = Voting.ActiveVote.VoteStarter;
int deliveryFee = 0;
switch (voteType)
@@ -3318,7 +3294,7 @@ namespace Barotrauma.Networking
GameMain.GameSession.PurchaseSubmarine(targetSubmarine, starter);
break;
case VoteType.SwitchSub:
deliveryFee = Voting.SubVote.DeliveryFee;
deliveryFee = subVote.DeliveryFee;
break;
default:
return;
@@ -3326,10 +3302,10 @@ namespace Barotrauma.Networking
if (voteType != VoteType.PurchaseSub)
{
SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(targetSubmarine, deliveryFee, starter);
GameMain.GameSession.SwitchSubmarine(targetSubmarine, deliveryFee, starter);
}
serverSettings.Voting.StopSubmarineVote(true);
Voting.StopSubmarineVote(true);
}
public void UpdateClientPermissions(Client client)

View File

@@ -312,8 +312,8 @@ namespace Barotrauma.Networking
AutoRestart = doc.Root.GetAttributeBool("autorestart", false);
Voting.AllowSubVoting = SubSelectionMode == SelectionMode.Vote;
Voting.AllowModeVoting = ModeSelectionMode == SelectionMode.Vote;
AllowSubVoting = SubSelectionMode == SelectionMode.Vote;
AllowModeVoting = ModeSelectionMode == SelectionMode.Vote;
selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f);
GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty);

View File

@@ -7,59 +7,161 @@ namespace Barotrauma
{
partial class Voting
{
public bool AllowSubVoting
public interface IVote
{
get { return allowSubVoting; }
set { allowSubVoting = value; }
}
public bool AllowModeVoting
{
get { return allowModeVoting; }
set { allowModeVoting = value; }
}
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public struct SubmarineVote
public VoteState State { get; set; }
public void Finish(Voting voting, bool passed);
}
public class SubmarineVote : IVote
{
public Client VoteStarter;
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public VoteState State { get; set; }
public SubmarineInfo Sub;
public VoteType VoteType;
public float Timer;
public int DeliveryFee;
public VoteState State;
public SubmarineVote(Client starter, SubmarineInfo subInfo, int deliveryFee, VoteType voteType)
{
Sub = subInfo;
DeliveryFee = deliveryFee;
VoteType = voteType;
State = VoteState.Started;
VoteStarter = starter;
}
public void Finish(Voting voting, bool passed)
{
if (passed)
{
GameMain.Server?.SwitchSubmarine();
}
voting.StopSubmarineVote(passed);
}
}
public static SubmarineVote SubVote;
public static IVote ActiveVote;
public class TransferVote : IVote
{
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public VoteState State { get; set; }
//null = bank
public readonly Client From, To;
public readonly int TransferAmount;
public TransferVote(Client starter, Client from, int transferAmount, Client to)
{
VoteStarter = starter;
From = from;
To = to;
TransferAmount = transferAmount;
State = VoteState.Started;
VoteType = VoteType.TransferMoney;
}
public void Finish(Voting voting, bool passed)
{
if (passed)
{
Wallet fromWallet = From == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : From.Character?.Wallet;
if (fromWallet.TryDeduct(TransferAmount))
{
Wallet toWallet = To == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : To.Character?.Wallet;
toWallet.Give(TransferAmount);
}
}
voting.StopMoneyTransferVote(passed);
}
}
private static readonly Queue<IVote> pendingVotes = new Queue<IVote>();
private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender)
{
SubVote.Sub = subInfo;
SubVote.DeliveryFee = voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0;
SubVote.VoteType = voteType;
SubVote.State = VoteState.Started;
SubVote.VoteStarter = sender;
VoteRunning = true;
var subVote = new SubmarineVote(
sender,
subInfo,
voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0,
voteType);
StartOrEnqueueVote(subVote);
sender.SetVote(voteType, 2);
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
}
public void StopSubmarineVote(bool passed)
{
VoteRunning = false;
SubVote.State = passed ? VoteState.Passed : VoteState.Failed;
if (!(ActiveVote is SubmarineVote)) { return; }
StopActiveVote(passed);
}
GameMain.Server.UpdateVoteStatus();
public void StopMoneyTransferVote(bool passed)
{
if (!(ActiveVote is TransferVote)) { return; }
StopActiveVote(passed);
}
public void StopActiveVote(bool passed)
{
ActiveVote.State = passed ? VoteState.Passed : VoteState.Failed;
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
GameMain.NetworkMember.SubmarineVoteYesCount = GameMain.NetworkMember.SubmarineVoteNoCount = GameMain.NetworkMember.SubmarineVoteMax = 0;
for (int i = 0; i < GameMain.NetworkMember.ConnectedClients.Count; i++)
{
GameMain.NetworkMember.ConnectedClients[i].SetVote(SubVote.VoteType, 0);
GameMain.NetworkMember.ConnectedClients[i].SetVote(ActiveVote.VoteType, 0);
}
SubVote.Sub = null;
SubVote.DeliveryFee = 0;
SubVote.VoteType = VoteType.Unknown;
SubVote.Timer = 0.0f;
SubVote.State = VoteState.None;
SubVote.VoteStarter = null;
ActiveVote = null;
if (pendingVotes.Any())
{
ActiveVote = pendingVotes.Dequeue();
}
}
public void StartTransferVote(Client starter, Client from, int transferAmount, Client to)
{
StartOrEnqueueVote(new TransferVote(starter, from, transferAmount, to));
starter.SetVote(VoteType.TransferMoney, 2);
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
}
private void StartOrEnqueueVote(IVote vote)
{
if (ActiveVote == null)
{
ActiveVote = vote;
}
else
{
pendingVotes.Enqueue(vote);
}
}
public void Update(float deltaTime)
{
if (ActiveVote == null) { return; }
ActiveVote.Timer += deltaTime;
if (ActiveVote.Timer >= GameMain.NetworkMember.ServerSettings.VoteTimeout)
{
// Do not take unanswered into account for total
int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2);
int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1);
ActiveVote.Finish(this, passed: yes / (float)(yes + no) >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio);
}
}
public void ServerRead(IReadMessage inc, Client sender)
@@ -97,10 +199,6 @@ namespace Barotrauma
case VoteType.EndRound:
if (!sender.HasSpawned) { return; }
sender.SetVote(voteType, inc.ReadBoolean());
GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound));
GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned);
break;
case VoteType.Kick:
byte kickedClientID = inc.ReadByte();
@@ -126,24 +224,34 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub:
case VoteType.SwitchSub:
case VoteType.TransferMoney:
bool startVote = inc.ReadBoolean();
if (startVote)
{
string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
if (voteType == VoteType.TransferMoney)
{
StartSubmarineVote(subInfo, voteType, sender);
int amount = inc.ReadInt32();
int fromClientId = inc.ReadByte();
int toClientId = inc.ReadByte();
pendingVotes.Enqueue(new TransferVote(sender,
GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId),
amount,
GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId)));
}
else
{
string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
{
StartSubmarineVote(subInfo, voteType, sender);
}
}
}
else
{
sender.SetVote(voteType, (int)inc.ReadByte());
}
GameMain.Server.SubmarineVoteYesCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 2);
GameMain.Server.SubmarineVoteNoCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 1);
GameMain.Server.SubmarineVoteMax = GameMain.Server.ConnectedClients.Count(c => c.InGame);
break;
}
@@ -154,10 +262,10 @@ namespace Barotrauma
public void ServerWrite(IWriteMessage msg)
{
if (GameMain.Server == null) return;
if (GameMain.Server == null) { return; }
msg.Write(allowSubVoting);
if (allowSubVoting)
msg.Write(GameMain.Server.ServerSettings.AllowSubVoting);
if (GameMain.Server.ServerSettings.AllowSubVoting)
{
IReadOnlyDictionary<SubmarineInfo, int> voteList = GetVoteCounts<SubmarineInfo>(VoteType.Sub, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
@@ -167,8 +275,8 @@ namespace Barotrauma
msg.Write(vote.Key.Name);
}
}
msg.Write(AllowModeVoting);
if (allowModeVoting)
msg.Write(GameMain.Server.ServerSettings.AllowModeVoting);
if (GameMain.Server.ServerSettings.AllowModeVoting)
{
IReadOnlyDictionary<GameModePreset, int> voteList = GetVoteCounts<GameModePreset>(VoteType.Mode, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
@@ -178,60 +286,78 @@ namespace Barotrauma
msg.Write(vote.Key.Identifier);
}
}
msg.Write(AllowEndVoting);
if (AllowEndVoting)
msg.Write(GameMain.Server.ServerSettings.AllowEndVoting);
if (GameMain.Server.ServerSettings.AllowEndVoting)
{
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound)));
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned));
}
msg.Write(AllowVoteKick);
msg.Write(GameMain.Server.ServerSettings.AllowVoteKick);
msg.Write((byte)SubVote.State);
if (SubVote.State != VoteState.None)
msg.Write((byte)(ActiveVote?.State ?? VoteState.None));
if (ActiveVote != null)
{
msg.Write((byte)SubVote.VoteType);
if (SubVote.VoteType != VoteType.Unknown)
{
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 2);
msg.Write((byte)ActiveVote.VoteType);
if (ActiveVote.State != VoteState.None && ActiveVote.VoteType != VoteType.Unknown)
{
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2);
msg.Write((byte)yesClients.Count);
foreach (Client c in yesClients)
{
msg.Write(c.ID);
}
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 1);
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1);
msg.Write((byte)noClients.Count);
foreach (Client c in noClients)
{
msg.Write(c.ID);
}
msg.Write((byte)GameMain.Server.SubmarineVoteMax);
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.InGame));
switch (SubVote.State)
switch (ActiveVote.State)
{
case VoteState.Started:
msg.Write(SubVote.Sub.Name);
msg.Write(SubVote.VoteStarter.ID);
msg.Write((byte)GameMain.Server.ServerSettings.SubmarineVoteTimeout);
msg.Write(ActiveVote.VoteStarter.ID);
msg.Write((byte)GameMain.Server.ServerSettings.VoteTimeout);
switch (ActiveVote.VoteType)
{
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name);
break;
case VoteType.TransferMoney:
var transferVote = (ActiveVote as TransferVote);
msg.Write(transferVote.From?.ID ?? 0);
msg.Write(transferVote.To?.ID ?? 0);
msg.Write(transferVote.TransferAmount);
break;
}
break;
case VoteState.Running:
// Nothing specific
break;
case VoteState.Passed:
case VoteState.Failed:
msg.Write(SubVote.State == VoteState.Passed);
msg.Write(SubVote.Sub.Name);
if (SubVote.State == VoteState.Passed)
msg.Write(ActiveVote.State == VoteState.Passed);
switch (ActiveVote.VoteType)
{
msg.Write((short)SubVote.DeliveryFee);
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name);
msg.Write((short)(ActiveVote as SubmarineVote).DeliveryFee);
break;
}
break;
}
}
}
}
}
var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<bool>(VoteType.StartRound));
msg.Write((byte)readyClients.Count);

View File

@@ -192,7 +192,7 @@ namespace Barotrauma
public override void Select()
{
base.Select();
GameMain.Server.ServerSettings.Voting.ResetVotes(GameMain.Server.ConnectedClients);
GameMain.Server.Voting.ResetVotes(GameMain.Server.ConnectedClients);
if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this)
{
GameMain.GameSession = null;

View File

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

View File

@@ -8,7 +8,7 @@
<Preset
name="Moderator"
description="Allowed to manage round settings, kick players and access server logs."
permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog">
permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog,ManageSettings,ManageMoney">
<Command name="clientlist"/>
<Command name="readycheck"/>
<Command name="autorestart"/>

View File

@@ -130,7 +130,7 @@ namespace Barotrauma
}
}
private Wallet wallet = new Wallet();
private Wallet wallet;
public Wallet Wallet
{
@@ -1055,8 +1055,13 @@ namespace Barotrauma
return newCharacter;
}
private Character(Submarine submarine, ushort id): base(submarine, id)
{
wallet = new Wallet(Option<Character>.Some(this));
}
protected Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, RagdollParams ragdollParams = null)
: base(null, id)
: this(null, id)
{
this.Seed = seed;
this.Prefab = prefab;

View File

@@ -172,11 +172,21 @@ namespace Barotrauma
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.SteamWorkshopId == Core.SteamWorkshopId) ??
ContentPackageManager.CorePackages.First());
}
SetRegular(Regular
.Select(p => ContentPackageManager.RegularPackages.Contains(p)
? p
: ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2 => p2.SteamWorkshopId == p.SteamWorkshopId))
.ToArray());
List<RegularPackage> newRegular = new List<RegularPackage>();
foreach (var p in Regular)
{
if (ContentPackageManager.RegularPackages.Contains(p))
{
newRegular.Add(p);
}
else if (ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2
=> p2.SteamWorkshopId == p.SteamWorkshopId) is { } newP)
{
newRegular.Add(newP);
}
}
SetRegular(newRegular);
}
public static void BackUp()

View File

@@ -115,6 +115,24 @@ namespace Barotrauma
}
public void Remove() => Element.Remove();
public override bool Equals(object? obj)
{
return obj is ContentXElement element && this == element;
}
public override int GetHashCode()
{
return HashCode.Combine(ContentPackage, Element);
}
public static bool operator ==(in ContentXElement? a, in ContentXElement? b)
{
return a?.ContentPackage == b?.ContentPackage && a?.Element == b?.Element;
}
public static bool operator !=(in ContentXElement? a, in ContentXElement? b) =>
!(a == b);
}
public static class ContentXElementExtensions

View File

@@ -1,4 +1,6 @@
namespace Barotrauma
using System;
namespace Barotrauma
{
public enum TransitionMode
{
@@ -146,4 +148,11 @@
AlwaysStayConscious,
}
[Flags]
public enum CharacterType
{
Bot = 0b01,
Player = 0b10,
Both = Bot | Player
}
}

View File

@@ -350,7 +350,7 @@ namespace Barotrauma
float difficultyMultiplier = 1 + level.Difficulty / 100f;
baseExperienceGain *= difficultyMultiplier;
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters();
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
// use multipliers here so that we can easily add them together without introducing multiplicative XP stacking
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
@@ -380,7 +380,7 @@ namespace Barotrauma
GameAnalyticsManager.AddMoneyGainedEvent(totalReward, GameAnalyticsManager.MoneySource.MissionReward, Prefab.Identifier.Value);
#if SERVER
totalReward = DistributeRewardsToCrew(GetSalaryEligibleCrew(), totalReward);
totalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(CharacterType.Player), totalReward);
#endif
if (totalReward > 0)
{
@@ -436,18 +436,6 @@ namespace Barotrauma
}
#endif
public static IEnumerable<Character> GetSalaryEligibleCrew()
{
if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return Array.Empty<Character>(); }
IEnumerable<Character> characters = crewManager.GetCharacters();
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead).Concat(characters);
#elif CLIENT
return characters;
#endif
}
public static int GetRewardDistibutionSum(IEnumerable<Character> crew, int rewardDistribution = 0) => crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution;

View File

@@ -95,7 +95,7 @@ namespace Barotrauma
public readonly bool IsSideObjective;
public readonly bool RequireWreck;
public readonly bool RequireWreck, RequireRuin;
/// <summary>
/// The mission can only be received when travelling from a location of the first type to a location of the second type
@@ -152,6 +152,7 @@ namespace Barotrauma
AllowRetry = element.GetAttributeBool("allowretry", false);
IsSideObjective = element.GetAttributeBool("sideobjective", false);
RequireWreck = element.GetAttributeBool("requirewreck", false);
RequireRuin = element.GetAttributeBool("requireruin", false);
Commonness = element.GetAttributeInt("commonness", 1);
if (element.GetAttribute("difficulty") != null)
{

View File

@@ -226,6 +226,7 @@ namespace Barotrauma
public void SetPurchasedItems(Dictionary<Identifier, List<PurchasedItem>> purchasedItems)
{
if (purchasedItems.Count == 0 && PurchasedItems.Count == 0) { return; }
PurchasedItems.Clear();
foreach (var entry in purchasedItems)
{

View File

@@ -64,7 +64,7 @@ namespace Barotrauma
if (reputationChange > 0f)
{
float reputationGainMultiplier = 1f;
foreach (Character character in GameSession.GetSessionCrewCharacters())
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier);
}

View File

@@ -6,6 +6,7 @@ namespace Barotrauma
{
internal readonly struct WalletChangedEvent
{
public readonly Option<Character> Owner;
public readonly Wallet Wallet;
public readonly WalletInfo Info;
public readonly WalletChangedData ChangedData;
@@ -15,6 +16,7 @@ namespace Barotrauma
Wallet = wallet;
Info = info;
ChangedData = changedData;
Owner = wallet.Owner;
}
}
@@ -109,6 +111,8 @@ namespace Barotrauma
// ReSharper disable ValueParameterNotUsed
internal sealed class InvalidWallet : Wallet
{
public InvalidWallet(): base(Option<Character>.None()) { }
public override int Balance
{
get => 0;
@@ -132,6 +136,8 @@ namespace Barotrauma
AttrubuteNameRewardDistribution = "rewarddistribution",
SaveElementName = "Wallet";
public readonly Option<Character> Owner;
private int balance;
public virtual int Balance
@@ -148,9 +154,12 @@ namespace Barotrauma
set => rewardDistribution = ClampRewardDistribution(value);
}
public Wallet() { }
public Wallet(Option<Character> owner)
{
Owner = owner;
}
public Wallet(XElement element)
public Wallet(Option<Character> owner, XElement element): this(owner)
{
balance = ClampBalance(element.GetAttributeInt(AttributeNameBalance, 0));
rewardDistribution = ClampBalance(element.GetAttributeInt(AttrubuteNameRewardDistribution, 0));
@@ -185,7 +194,7 @@ namespace Barotrauma
SettingsChanged(balanceChanged: Option<int>.Some(-price), rewardChanged: Option<int>.None());
}
public void SetRewardDistrubiton(int value)
public void SetRewardDistribution(int value)
{
int oldValue = RewardDistribution;
RewardDistribution = value;

View File

@@ -53,7 +53,7 @@ namespace Barotrauma
public int GetAddedMissionCount()
{
int count = 0;
foreach (Character character in GameSession.GetSessionCrewCharacters())
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
}
@@ -68,6 +68,15 @@ namespace Barotrauma
abstract partial class CampaignMode : GameMode
{
[NetworkSerialize]
public struct SaveInfo : INetSerializableStruct
{
public string FilePath;
public int SaveTime;
public string SubmarineName;
public string[] EnabledContentPackageNames;
}
public const int MaxMoney = int.MaxValue / 2; //about 1 billion
public const int InitialMoney = 8500;
@@ -180,13 +189,37 @@ namespace Barotrauma
protected CampaignMode(GameModePreset preset)
: base(preset)
{
Bank = new Wallet
Bank = new Wallet(Option<Character>.None())
{
Balance = InitialMoney
};
CargoManager = new CargoManager(this);
MedicalClinic = new MedicalClinic(this);
Identifier messageIdentifier = new Identifier("money");
#if CLIENT
OnMoneyChanged.RegisterOverwriteExisting(new Identifier("CampaignMoneyChangeNotification"), e =>
{
if (!(e.ChangedData.BalanceChanged is Some<int> { Value: var changed })) { return; }
bool isGain = changed > 0;
Color clr = isGain ? GUIStyle.Yellow : GUIStyle.Red;
switch (e.Owner)
{
case Some<Character> { Value: var owner}:
owner.AddMessage(FormatMessage(), clr, playSound: Character.Controlled == owner, messageIdentifier, changed);
break;
case None<Character> _ when IsSinglePlayer:
Character.Controlled?.AddMessage(FormatMessage(), clr, playSound: true, messageIdentifier, changed);
break;
}
string FormatMessage() => TextManager.GetWithVariable(isGain ? "moneygainformat" : "moneyloseformat", "[money]", TextManager.FormatCurrency(Math.Abs(changed))).ToString();
});
#endif
}
public virtual Wallet GetWallet(Client client = null)

View File

@@ -167,7 +167,7 @@ namespace Barotrauma
LoadStats(subElement);
break;
case Wallet.LowerCaseSaveElementName:
Bank = new Wallet(subElement);
Bank = new Wallet(Option<Character>.None(), subElement);
break;
#if SERVER
case "savedexperiencepoints":
@@ -183,7 +183,7 @@ namespace Barotrauma
int oldMoney = element.GetAttributeInt("money", 0);
if (oldMoney > 0)
{
Bank = new Wallet
Bank = new Wallet(Option<Character>.None())
{
Balance = oldMoney
};

View File

@@ -735,14 +735,40 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime);
public static IEnumerable<Character> GetSessionCrewCharacters()
/// <summary>
/// Returns a list of crew characters currently in the game with a given filter.
/// </summary>
/// <param name="type">Character type filter</param>
/// <returns></returns>
/// <remarks>
/// In singleplayer mode the CharacterType.Player returns the currently controlled player.
/// </remarks>
public static ImmutableHashSet<Character> GetSessionCrewCharacters(CharacterType type)
{
if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return ImmutableHashSet<Character>.Empty; }
IEnumerable<Character> players;
IEnumerable<Character> bots;
HashSet<Character> characters = new HashSet<Character>();
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead);
#else
if (GameMain.GameSession?.CrewManager is null) { return Enumerable.Empty<Character>(); }
return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null && !c.IsDead);
players = GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead);
bots = crewManager.GetCharacters().Where(c => !c.IsRemotePlayer);
#elif CLIENT
players = crewManager.GetCharacters().Where(c => c.IsPlayer);
bots = crewManager.GetCharacters().Where(c => c.IsBot);
#endif
if (type.HasFlag(CharacterType.Bot))
{
foreach (Character bot in bots) { characters.Add(bot); }
}
if (type.HasFlag(CharacterType.Player))
{
foreach (Character player in players) { characters.Add(player); }
}
return characters.ToImmutableHashSet();
}
public void EndRound(string endMessage, List<TraitorMissionResult>? traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
@@ -754,7 +780,7 @@ namespace Barotrauma
try
{
ImmutableArray<Character> crewCharacters = GetSessionCrewCharacters().ToImmutableArray();
ImmutableHashSet<Character> crewCharacters = GetSessionCrewCharacters(CharacterType.Both);
int prevMoney = GetAmountOfMoney(crewCharacters);
@@ -898,7 +924,7 @@ namespace Barotrauma
}
}
foreach (Character c in GetSessionCrewCharacters())
foreach (Character c in GetSessionCrewCharacters(CharacterType.Both))
{
foreach (var itemSelectedDuration in c.ItemSelectedDurations)
{
@@ -948,25 +974,25 @@ namespace Barotrauma
#endif
}
public static bool IsCompatibleWithEnabledContentPackages(IList<string> contentPackagePaths, out LocalizedString errorMsg)
public static bool IsCompatibleWithEnabledContentPackages(IList<string> contentPackageNames, out LocalizedString errorMsg)
{
errorMsg = "";
//no known content packages, must be an older save file
if (!contentPackagePaths.Any()) { return true; }
if (!contentPackageNames.Any()) { return true; }
List<string> missingPackages = new List<string>();
foreach (string packagePath in contentPackagePaths)
foreach (string packageName in contentPackageNames)
{
if (!ContentPackageManager.EnabledPackages.All.Any(cp => cp.Path == packagePath))
if (!ContentPackageManager.EnabledPackages.All.Any(cp => cp.NameMatches(packageName)))
{
missingPackages.Add(packagePath);
missingPackages.Add(packageName);
}
}
List<string> excessPackages = new List<string>();
foreach (ContentPackage cp in ContentPackageManager.EnabledPackages.All)
{
if (!cp.HasMultiplayerSyncedContent) { continue; }
if (!contentPackagePaths.Any(p => p == cp.Path))
if (!contentPackageNames.Any(p => cp.NameMatches(p)))
{
excessPackages.Add(cp.Name);
}
@@ -976,9 +1002,9 @@ namespace Barotrauma
if (missingPackages.Count == 0 && missingPackages.Count == 0)
{
var enabledPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).ToImmutableArray();
for (int i = 0; i < contentPackagePaths.Count && i < enabledPackages.Length; i++)
for (int i = 0; i < contentPackageNames.Count && i < enabledPackages.Length; i++)
{
if (contentPackagePaths[i] != enabledPackages[i].Path)
if (!enabledPackages[i].NameMatches(contentPackageNames[i]))
{
orderMismatch = true;
break;
@@ -1009,7 +1035,7 @@ namespace Barotrauma
if (orderMismatch)
{
if (!errorMsg.IsNullOrEmpty()) { errorMsg += "\n"; }
errorMsg += TextManager.GetWithVariable("campaignmode.contentpackageordermismatch", "[loadorder]", string.Join(", ", contentPackagePaths));
errorMsg += TextManager.GetWithVariable("campaignmode.contentpackageordermismatch", "[loadorder]", string.Join(", ", contentPackageNames));
}
return false;
@@ -1040,8 +1066,8 @@ namespace Barotrauma
}
}
if (Map != null) { rootElement.Add(new XAttribute("mapseed", Map.Seed)); }
rootElement.Add(new XAttribute("selectedcontentpackages",
string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Path))));
rootElement.Add(new XAttribute("selectedcontentpackagenames",
string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Name.Replace("|", @"\|")))));
((CampaignMode)GameMode).Save(doc.Root);

View File

@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components
{
@@ -13,6 +12,7 @@ namespace Barotrauma.Items.Components
{
[Editable]
public LimbType LimbType { get; set; }
[Editable]
public Vector2 Position { get; set; }
@@ -33,7 +33,7 @@ namespace Barotrauma.Items.Components
partial class Controller : ItemComponent, IServerSerializable
{
//where the limbs of the user should be positioned when using the controller
private readonly List<LimbPos> limbPositions;
private readonly List<LimbPos> limbPositions = new List<LimbPos>();
private Direction dir;
@@ -117,38 +117,9 @@ namespace Barotrauma.Items.Components
public Controller(Item item, ContentXElement element)
: base(item, element)
{
limbPositions = new List<LimbPos>();
userPos = element.GetAttributeVector2("UserPos", Vector2.Zero);
Enum.TryParse(element.GetAttributeString("direction", "None"), out dir);
foreach (var subElement in element.Elements())
{
if (subElement.Name != "limbposition") { continue; }
string limbStr = subElement.GetAttributeString("limb", "");
if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType))
{
DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type.");
}
else
{
LimbPos limbPos = new LimbPos(limbType,
subElement.GetAttributeVector2("position", Vector2.Zero),
subElement.GetAttributeBool("allowusinglimb", false));
limbPositions.Add(limbPos);
if (!limbPos.AllowUsingLimb)
{
if (limbType == LimbType.RightHand || limbType == LimbType.RightForearm || limbType == LimbType.RightArm ||
limbType == LimbType.LeftHand || limbType == LimbType.LeftForearm || limbType == LimbType.LeftArm)
{
AllowAiming = false;
}
}
}
}
LoadLimbPositions(element);
IsActive = true;
}
@@ -529,5 +500,63 @@ namespace Barotrauma.Items.Components
}
partial void HideHUDs(bool value);
public override XElement Save(XElement parentElement)
{
return SaveLimbPositions(base.Save(parentElement));
}
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues, idRemap);
if (GameMain.GameSession?.GameMode?.Preset == GameModePreset.TestMode)
{
LoadLimbPositions(componentElement);
}
}
private XElement SaveLimbPositions(XElement element)
{
if (Screen.Selected == GameMain.SubEditorScreen)
{
foreach (var limbPos in limbPositions)
{
element.Add(new XElement("limbposition",
new XAttribute("limb", limbPos.LimbType),
new XAttribute("position", XMLExtensions.Vector2ToString(limbPos.Position)),
new XAttribute("allowusinglimb", limbPos.AllowUsingLimb)));
}
}
return element;
}
private void LoadLimbPositions(XElement element)
{
limbPositions.Clear();
foreach (var subElement in element.Elements())
{
if (subElement.Name != "limbposition") { continue; }
string limbStr = subElement.GetAttributeString("limb", "");
if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType))
{
DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type.");
}
else
{
LimbPos limbPos = new LimbPos(limbType,
subElement.GetAttributeVector2("position", Vector2.Zero),
subElement.GetAttributeBool("allowusinglimb", false));
limbPositions.Add(limbPos);
if (!limbPos.AllowUsingLimb)
{
if (limbType == LimbType.RightHand || limbType == LimbType.RightForearm || limbType == LimbType.RightArm ||
limbType == LimbType.LeftHand || limbType == LimbType.LeftForearm || limbType == LimbType.LeftArm)
{
AllowAiming = false;
}
}
}
}
}
}
}

View File

@@ -80,6 +80,8 @@ namespace Barotrauma.Items.Components
private float progressState;
private readonly Dictionary<uint, int> fabricationLimits = new Dictionary<uint, int>();
public Fabricator(Item item, ContentXElement element)
: base(item, element)
{
@@ -105,6 +107,10 @@ namespace Barotrauma.Items.Components
}
}
fabricationRecipes.Add(recipe.RecipeHash, recipe);
if (recipe.FabricationLimitMax >= 0)
{
fabricationLimits.Add(recipe.RecipeHash, Rand.Range(recipe.FabricationLimitMin, recipe.FabricationLimitMax + 1));
}
}
}
this.fabricationRecipes = fabricationRecipes.ToImmutableDictionary();
@@ -244,7 +250,7 @@ namespace Barotrauma.Items.Components
itemList.Enabled = true;
if (activateButton != null)
{
activateButton.Text = TextManager.Get("FabricatorCreate");
activateButton.Text = TextManager.Get(CreateButtonText);
}
#endif
fabricatedItem = null;
@@ -400,8 +406,22 @@ namespace Barotrauma.Items.Components
quality = GetFabricatedItemQuality(fabricatedItem, user);
}
int amount = (int)fabricationitemAmount.Value;
if (fabricationLimits.ContainsKey(fabricatedItem.RecipeHash))
{
if (amount > fabricationLimits[fabricatedItem.RecipeHash])
{
amount = fabricationLimits[fabricatedItem.RecipeHash];
fabricationLimits[fabricatedItem.RecipeHash] = 0;
}
else
{
fabricationLimits[fabricatedItem.RecipeHash] -= amount;
}
}
var tempUser = user;
for (int i = 0; i < (int)fabricationitemAmount.Value; i++)
for (int i = 0; i < amount; i++)
{
float outCondition = fabricatedItem.OutCondition;
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
@@ -535,6 +555,11 @@ namespace Barotrauma.Items.Components
}
}
if (fabricationLimits.TryGetValue(fabricableItem.RecipeHash, out int amount) && amount <= 0)
{
return false;
}
return fabricableItem.RequiredItems.All(requiredItem =>
{
int availablePrefabsAmount = 0;
@@ -698,7 +723,6 @@ namespace Barotrauma.Items.Components
componentElement.Add(new XAttribute("fabricateditemidentifier", fabricatedItem.TargetItem.Identifier));
componentElement.Add(new XAttribute("savedtimeuntilready", timeUntilReady.ToString("G", CultureInfo.InvariantCulture)));
componentElement.Add(new XAttribute("savedrequiredtime", requiredTime.ToString("G", CultureInfo.InvariantCulture)));
}
return componentElement;
}

View File

@@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -114,7 +115,7 @@ namespace Barotrauma
private readonly Quality qualityComponent;
private readonly Queue<float> impactQueue = new Queue<float>();
private readonly ConcurrentQueue<float> impactQueue = new ConcurrentQueue<float>();
//a dictionary containing lists of the status effects in all the components of the item
private readonly bool[] hasStatusEffectsOfType;
@@ -1695,9 +1696,8 @@ namespace Barotrauma
public override void Update(float deltaTime, Camera cam)
{
while (impactQueue.Count > 0)
while (impactQueue.TryDequeue(out float impact))
{
float impact = impactQueue.Dequeue();
HandleCollision(impact);
}
@@ -1933,10 +1933,7 @@ namespace Barotrauma
if (contact.FixtureA.Body == f1.Body) { normal = -normal; }
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
lock (impactQueue)
{
impactQueue.Enqueue(impact);
}
impactQueue.Enqueue(impact);
return true;
}

View File

@@ -119,6 +119,11 @@ namespace Barotrauma
public readonly uint RecipeHash;
public readonly int Amount;
/// <summary>
/// How many of this item the fabricator can create (< 0 = unlimited)
/// </summary>
public readonly int FabricationLimitMin, FabricationLimitMax;
public FabricationRecipe(XElement element, Identifier itemPrefab)
{
TargetItemPrefabIdentifier = itemPrefab;
@@ -141,6 +146,10 @@ namespace Barotrauma
RequiresRecipe = element.GetAttributeBool("requiresrecipe", false);
Amount = element.GetAttributeInt("amount", 1);
int limitDefault = element.GetAttributeInt("fabricationlimit", -1);
FabricationLimitMin = element.GetAttributeInt(nameof(FabricationLimitMin), limitDefault);
FabricationLimitMax = element.GetAttributeInt(nameof(FabricationLimitMax), limitDefault);
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -973,7 +982,11 @@ namespace Barotrauma
public int? GetMinPrice()
{
int? minPrice = StorePrices.Values.Min(p => p.Price);
int? minPrice = null;
if (StorePrices != null && StorePrices.Any())
{
minPrice = StorePrices.Values.Min(p => p.Price);
}
if (minPrice.HasValue)
{
if (DefaultPrice != null)

View File

@@ -831,7 +831,13 @@ namespace Barotrauma
}
List<Point> ruinPositions = new List<Point>();
for (int i = 0; i < GenerationParams.RuinCount; i++)
int ruinCount = GenerationParams.RuinCount;
if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireRuin) ?? false)
{
ruinCount = Math.Max(ruinCount, 1);
}
for (int i = 0; i < ruinCount; i++)
{
Point ruinSize = new Point(5000);
int limitLeft = Math.Max(startPosition.X, ruinSize.X / 2);

View File

@@ -387,14 +387,14 @@ namespace Barotrauma
set;
}
[Serialize(3, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
[Serialize(10, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int AbyssResourceClustersMin
{
get;
set;
}
[Serialize(20, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
[Serialize(50, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int AbyssResourceClustersMax
{
get;

View File

@@ -257,7 +257,7 @@ namespace Barotrauma
}
DailySpecials.Clear();
int extraSpecialSalesCount = Location.GetExtraSpecialSalesCount();
for (int i = 0; i < DailySpecialsCount + extraSpecialSalesCount; i++)
for (int i = 0; i < Location.DailySpecialsCount + extraSpecialSalesCount; i++)
{
if (availableStock.None()) { break; }
var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced);
@@ -266,7 +266,7 @@ namespace Barotrauma
availableStock.Remove(item);
}
RequestedGoods.Clear();
for (int i = 0; i < RequestedGoodsCount; i++)
for (int i = 0; i < Location.RequestedGoodsCount; i++)
{
var item = ItemPrefab.Prefabs.GetRandom(p =>
p.CanBeSold && !RequestedGoods.Contains(p) &&
@@ -359,8 +359,8 @@ namespace Barotrauma
/// How many map progress steps it takes before the discounts should be updated.
/// </summary>
private const int SpecialsUpdateInterval = 3;
private const int DailySpecialsCount = 3;
private const int RequestedGoodsCount = 3;
private int DailySpecialsCount => Type.DailySpecialsCount;
private int RequestedGoodsCount => Type.RequestedGoodsCount;
private int StepsSinceSpecialsUpdated { get; set; }
public HashSet<Identifier> StoreIdentifiers { get; } = new HashSet<Identifier>();
@@ -1226,7 +1226,7 @@ namespace Barotrauma
public int GetExtraSpecialSalesCount()
{
var characters = GameSession.GetSessionCrewCharacters();
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
if (!characters.Any()) { return 0; }
return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
}
@@ -1252,7 +1252,7 @@ namespace Barotrauma
Discovered = true;
if (checkTalents)
{
GameSession.GetSessionCrewCharacters().ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
}
}

View File

@@ -1,13 +1,11 @@
using Microsoft.Xna.Framework;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using System.Collections.Immutable;
namespace Barotrauma
{
@@ -21,8 +19,9 @@ namespace Barotrauma
//<name, commonness>
private readonly ImmutableArray<(Identifier Name, float Commonness)> hireableJobs;
private readonly float totalHireableWeight;
public Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
public readonly Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
public readonly LocalizedString Name;
@@ -65,7 +64,7 @@ namespace Barotrauma
get;
private set;
}
public string ReplaceInRadiation { get; }
public Sprite Sprite { get; private set; }
@@ -86,6 +85,8 @@ namespace Barotrauma
/// In percentages
/// </summary>
public int StorePriceModifierRange { get; } = 5;
public int DailySpecialsCount { get; } = 1;
public int RequestedGoodsCount { get; } = 1;
public List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>()
{
@@ -144,7 +145,7 @@ namespace Barotrauma
names = new List<string>() { "Name file not found" };
}
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", new string[] { "" });
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty<string>());
foreach (string commonnessPerZoneStr in commonnessPerZoneStrs)
{
string[] splitCommonnessPerZone = commonnessPerZoneStr.Split(':');
@@ -152,12 +153,26 @@ namespace Barotrauma
!int.TryParse(splitCommonnessPerZone[0].Trim(), out int zoneIndex) ||
!float.TryParse(splitCommonnessPerZone[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float zoneCommonness))
{
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone0index: zone0commonness, zone1index: zone1commonness\"");
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone1index: zone1commonness, zone2index: zone2commonness\"");
break;
}
CommonnessPerZone[zoneIndex] = zoneCommonness;
}
string[] minCountPerZoneStrs = element.GetAttributeStringArray("mincountperzone", Array.Empty<string>());
foreach (string minCountPerZoneStr in minCountPerZoneStrs)
{
string[] splitMinCountPerZone = minCountPerZoneStr.Split(':');
if (splitMinCountPerZone.Length != 2 ||
!int.TryParse(splitMinCountPerZone[0].Trim(), out int zoneIndex) ||
!int.TryParse(splitMinCountPerZone[1].Trim(), out int minCount))
{
DebugConsole.ThrowError("Failed to read minimum count values for location type \"" + Identifier + "\" - minimum counts should be given in the format \"zone1index: zone1mincount, zone2index: zone2mincount\"");
break;
}
MinCountPerZone[zoneIndex] = minCount;
}
var hireableJobs = new List<(Identifier, float)>();
foreach (var subElement in element.Elements())
{
@@ -205,6 +220,8 @@ namespace Barotrauma
StoreBalanceStatuses.Add(new StoreBalanceStatus(percentage, modifier, color));
}
}
DailySpecialsCount = subElement.GetAttributeInt("dailyspecialscount", DailySpecialsCount);
RequestedGoodsCount = subElement.GetAttributeInt("requestedgoodscount", RequestedGoodsCount);
break;
}
}

View File

@@ -218,16 +218,24 @@ namespace Barotrauma
foreach (Location location in Locations)
{
if (location.Type.Identifier != "city" &&
location.Type.Identifier != "outpost")
{
continue;
}
if (location.Type.Identifier != "outpost") { continue; }
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
}
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
if (CurrentLocation == null)
{
foreach (Location location in Locations)
{
if (!location.Type.HasOutpost) { continue; }
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
}
}
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
CurrentLocation.Discover(true);
@@ -273,6 +281,7 @@ namespace Barotrauma
}
voronoiSites.Clear();
Dictionary<int, List<Location>> locationsPerZone = new Dictionary<int, List<Location>>();
foreach (GraphEdge edge in edges)
{
if (edge.Point1 == edge.Point2) { continue; }
@@ -301,7 +310,24 @@ namespace Barotrauma
Vector2 position = points[positionIndex];
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; }
int zone = GetZoneIndex(position.X);
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.ServerAndClient), requireOutpost: false, existingLocations: Locations);
if (!locationsPerZone.ContainsKey(zone))
{
locationsPerZone[zone] = new List<Location>();
}
LocationType forceLocationType = null;
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
{
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
{
forceLocationType = locationType;
break;
}
}
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.ServerAndClient),
requireOutpost: false, forceLocationType: forceLocationType, existingLocations: Locations);
locationsPerZone[zone].Add(newLocations[i]);
Locations.Add(newLocations[i]);
}
@@ -448,8 +474,7 @@ namespace Barotrauma
Connections[i].Locations[1];
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned")
{
#warning TODO: determinism?
leftMostLocation.ChangeType(LocationType.Prefabs.First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
}
leftMostLocation.IsGateBetweenBiomes = true;
Connections[i].Locked = true;

View File

@@ -752,6 +752,7 @@ namespace Barotrauma
bool solutionFound = false;
foreach (PlacedModule module in movableModules)
{
if (module.ThisGap.ConnectedDoor == null && module.PreviousGap.ConnectedDoor == null) { continue; }
Vector2 moveDir = GetMoveDir(module.ThisGapPosition);
Vector2 moveStep = moveDir * 50.0f;
Vector2 currentMove = Vector2.Zero;
@@ -1093,6 +1094,10 @@ namespace Barotrauma
}
thisWayPoint.Remove();
}
else
{
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\".");
}
gapToRemove.ConnectedDoor?.Item.Remove();
if (hallwayLength <= 1.0f) { gapToRemove?.Remove(); }

View File

@@ -28,8 +28,6 @@ namespace Barotrauma
partial class SubmarineInfo : IDisposable
{
public const string SavePath = "Submarines";
private static List<SubmarineInfo> savedSubmarines = new List<SubmarineInfo>();
public static IEnumerable<SubmarineInfo> SavedSubmarines => savedSubmarines;
@@ -578,58 +576,14 @@ namespace Barotrauma
if (File.Exists(savedSubmarines[i].FilePath))
{
bool isDownloadedSub = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
bool isInSubmarinesFolder = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SavePath);
bool isInContentPackage = contentPackageSubs.Any(f => f.Path == savedSubmarines[i].FilePath);
if (isDownloadedSub) { continue; }
if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && (isInSubmarinesFolder || isInContentPackage)) { continue; }
if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && isInContentPackage) { continue; }
}
savedSubmarines[i].Dispose();
}
if (!Directory.Exists(SavePath))
{
try
{
Directory.CreateDirectory(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Directory \"" + SavePath + "\" not found and creating the directory failed.", e);
return;
}
}
List<string> filePaths;
string[] subDirectories;
try
{
filePaths = Directory.GetFiles(SavePath).ToList();
subDirectories = Directory.GetDirectories(SavePath).Where(s =>
{
DirectoryInfo dir = new DirectoryInfo(s);
return !dir.Attributes.HasFlag(System.IO.FileAttributes.Hidden) && !dir.Name.StartsWith(".");
}).ToArray();
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open directory \"" + SavePath + "\"!", e);
return;
}
foreach (string subDirectory in subDirectories)
{
try
{
filePaths.AddRange(Directory.GetFiles(subDirectory).ToList());
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open subdirectory \"" + subDirectory + "\"!", e);
return;
}
}
List<string> filePaths = new List<string>();
foreach (BaseSubFile subFile in contentPackageSubs)
{
if (!filePaths.Any(fp => fp == subFile.Path))
@@ -643,34 +597,7 @@ namespace Barotrauma
foreach (string path in filePaths)
{
var subInfo = new SubmarineInfo(path);
if (subInfo.IsFileCorrupted)
{
#if CLIENT
if (DebugConsole.IsOpen) { DebugConsole.Toggle(); }
var deleteSubPrompt = new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariable("SubLoadError", "[subname]", subInfo.Name) + "\n" +
TextManager.GetWithVariable("DeleteFileVerification", "[filename]", subInfo.Name),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
string filePath = path;
deleteSubPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
try
{
File.Delete(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to delete file \"{filePath}\".", e);
}
deleteSubPrompt.Close();
return true;
};
deleteSubPrompt.Buttons[1].OnClicked += deleteSubPrompt.Close;
#endif
}
else
if (!subInfo.IsFileCorrupted)
{
savedSubmarines.Add(subInfo);
}

View File

@@ -22,11 +22,12 @@ namespace Barotrauma.Networking
ManageSettings = 0x200,
ManagePermissions = 0x400,
KarmaImmunity = 0x800,
BuyItems = 0x1000,
ManageMoney = 0x1000,
SellInventoryItems = 0x2000,
SellSubItems = 0x4000,
CampaignStore = 0x8000,
All = 0xFFFF
ManageMap = 0x8000,
ManageHires = 0x10000,
All = 0x1FFFF
}
class PermissionPreset

View File

@@ -116,7 +116,8 @@ namespace Barotrauma.Networking
StartRound,
PurchaseAndSwitchSub,
PurchaseSub,
SwitchSub
SwitchSub,
TransferMoney
}
public enum ReadyCheckState
@@ -179,11 +180,11 @@ namespace Barotrauma.Networking
protected ServerSettings serverSettings;
public Voting Voting { get; protected set; }
protected TimeSpan updateInterval;
protected DateTime updateTimer;
public int EndVoteCount, EndVoteMax, SubmarineVoteYesCount, SubmarineVoteNoCount, SubmarineVoteMax;
protected bool gameStarted;
protected RespawnManager respawnManager;

View File

@@ -278,8 +278,6 @@ namespace Barotrauma.Networking
{
ServerLog = new ServerLog(serverName);
Voting = new Voting();
Whitelist = new WhiteList();
BanList = new BanList();
@@ -378,8 +376,6 @@ namespace Barotrauma.Networking
public ServerLog ServerLog;
public Voting Voting;
public Dictionary<Identifier, bool> MonsterEnabled { get; private set; }
public const int MaxExtraCargoItemsOfType = 10;
@@ -577,34 +573,20 @@ namespace Barotrauma.Networking
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowVoteKick
{
get
{
return Voting.AllowVoteKick;
}
set
{
Voting.AllowVoteKick = value;
}
get; set;
}
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowEndVoting
{
get
{
return Voting.AllowEndVoting;
}
set
{
Voting.AllowEndVoting = value;
}
get; set;
}
private bool allowRespawn;
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowRespawn
{
get { return allowRespawn; ; }
get { return allowRespawn; }
set
{
if (allowRespawn == value) { return; }
@@ -779,7 +761,7 @@ namespace Barotrauma.Networking
set
{
subSelectionMode = value;
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
AllowSubVoting = subSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
@@ -792,7 +774,7 @@ namespace Barotrauma.Networking
set
{
modeSelectionMode = value;
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
@@ -807,14 +789,14 @@ namespace Barotrauma.Networking
}
[Serialize(0.6f, IsPropertySaveable.Yes)]
public float SubmarineVoteRequiredRatio
public float VoteRequiredRatio
{
get;
private set;
}
[Serialize(30f, IsPropertySaveable.Yes)]
public float SubmarineVoteTimeout
public float VoteTimeout
{
get;
private set;
@@ -928,6 +910,59 @@ namespace Barotrauma.Networking
set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); }
}
private bool allowSubVoting;
//Don't serialize: the value is set based on SubSelectionMode
public bool AllowSubVoting
{
get { return allowSubVoting; }
set
{
if (value == allowSubVoting) { return; }
allowSubVoting = value;
#if CLIENT
GameMain.NetLobbyScreen.SubList.Enabled = value ||
(GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectSub));
var subVotesLabel = GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true) as GUITextBlock;
subVotesLabel.Visible = value;
var subVisButton = GameMain.NetLobbyScreen.SubVisibilityButton;
subVisButton.RectTransform.AbsoluteOffset
= new Point(value ? (int)(subVotesLabel.TextSize.X + subVisButton.Rect.Width) : 0, 0);
GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Sub);
GameMain.NetLobbyScreen.SubList.Deselect();
#endif
}
}
private bool allowModeVoting;
//Don't serialize: the value is set based on ModeSelectionMode
public bool AllowModeVoting
{
get { return allowModeVoting; }
set
{
if (value == allowModeVoting) { return; }
allowModeVoting = value;
#if CLIENT
GameMain.NetLobbyScreen.ModeList.Enabled =
value ||
(GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectMode));
GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value;
// Disable modes that cannot be voted on
foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children)
{
if (guiComponent is GUIFrame frame)
{
frame.CanBeFocused = !allowModeVoting || ((GameModePreset)frame.UserData).Votable;
}
}
GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Mode);
GameMain.NetLobbyScreen.ModeList.Deselect();
#endif
}
}
public void SetPassword(string password)
{
if (string.IsNullOrEmpty(password))

View File

@@ -1,19 +1,11 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Voting
{
private bool allowSubVoting, allowModeVoting;
public bool AllowVoteKick = true;
public bool AllowEndVoting = true;
public bool VoteRunning = false;
public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 };
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, List<Client> voters)
@@ -39,12 +31,12 @@ namespace Barotrauma
public T HighestVoted<T>(VoteType voteType, List<Client> voters)
{
if (voteType == VoteType.Sub && !AllowSubVoting) return default(T);
if (voteType == VoteType.Mode && !AllowModeVoting) return default(T);
if (voteType == VoteType.Sub && !GameMain.NetworkMember.ServerSettings.AllowSubVoting) { return default; }
if (voteType == VoteType.Mode && !GameMain.NetworkMember.ServerSettings.AllowModeVoting) { return default; }
IReadOnlyDictionary<T, int> voteList = GetVoteCounts<T>(voteType, voters);
T selected = default(T);
T selected = default;
int highestVotes = 0;
foreach (KeyValuePair<T, int> votable in voteList)
{
@@ -71,11 +63,13 @@ namespace Barotrauma
{
client.ResetVotes();
}
GameMain.NetworkMember.EndVoteCount = 0;
GameMain.NetworkMember.EndVoteMax = 0;
#if CLIENT
foreach (VoteType voteType in Enum.GetValues(typeof(VoteType)))
{
SetVoteCountYes(voteType, 0);
SetVoteCountNo(voteType, 0);
SetVoteCountMax(voteType, 0);
}
UpdateVoteTexts(connectedClients, VoteType.Mode);
UpdateVoteTexts(connectedClients, VoteType.Sub);
#endif

View File

@@ -348,6 +348,14 @@ namespace Barotrauma.Steam
string val = attribute.Value.CleanUpPathCrossPlatform(correctFilenameCase: false);
//Handle mods that have been mangled by pre-modding-refactor
//copying of post-modding-refactor mods (what a clusterfuck)
int modDirStrIndex = val.IndexOf(ContentPath.ModDirStr, StringComparison.OrdinalIgnoreCase);
if (modDirStrIndex >= 0)
{
val = val[modDirStrIndex..];
}
//Handle really old mods (0.9.0.4-era) that might be structured as
//%ModDir%/Mods/[NAME]/[RESOURCE]
string fullSrcPath = Path.Combine(fileListDir, val).CleanUpPath();
@@ -418,7 +426,7 @@ namespace Barotrauma.Steam
File.Copy(from, to, overwrite: true);
}
private static async Task CopyDirectory(string fileListDir, string modName, string from, string to)
public static async Task CopyDirectory(string fileListDir, string modName, string from, string to)
{
from = Path.GetFullPath(from); to = Path.GetFullPath(to);
Directory.CreateDirectory(to);

View File

@@ -79,10 +79,10 @@ namespace Barotrauma
{
roundData.EnteredCrushDepth.Add(c);
}
else if (Level.Loaded.GetRealWorldDepth(c.WorldPosition.Y) < Level.Loaded.RealWorldCrushDepth * 0.5f)
else if (Level.Loaded.GetRealWorldDepth(c.WorldPosition.Y) < Level.Loaded.RealWorldCrushDepth - 500.0f)
{
//all characters that have entered crush depth and are still alive get an achievement
if (roundData.EnteredCrushDepth.Contains(c)) UnlockAchievement(c, "survivecrushdepth".ToIdentifier());
if (roundData.EnteredCrushDepth.Contains(c)) { UnlockAchievement(c, "survivecrushdepth".ToIdentifier()); }
}
}
}
@@ -426,7 +426,7 @@ namespace Barotrauma
!c.IsDead &&
c.TeamID != CharacterTeamType.FriendlyNPC &&
!(c.AIController is EnemyAIController) &&
(c.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost)));
(c.Submarine == gameSession.Submarine || gameSession.Submarine.GetConnectedSubs().Contains(c.Submarine) || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost)));
if (charactersInSub.Count == 1)
{
@@ -454,6 +454,10 @@ namespace Barotrauma
}
foreach (Character character in charactersInSub)
{
if (roundData.EnteredCrushDepth.Contains(character))
{
UnlockAchievement(character, "survivecrushdepth".ToIdentifier());
}
if (character.Info.Job == null) { continue; }
UnlockAchievement(character, $"{character.Info.Job.Prefab.Identifier}round".ToIdentifier());
}

View File

@@ -37,6 +37,12 @@ namespace Barotrauma
events.Remove(identifier);
}
public void TryDeregister(Identifier identifier)
{
if (!HasEvent(identifier)) { return; }
Deregister(identifier);
}
public bool HasEvent(Identifier identifier) => events.ContainsKey(identifier);
public void Invoke(T data)

View File

@@ -9,6 +9,7 @@ using System.Threading;
using System.Xml.Linq;
using Steamworks.Data;
using Color = Microsoft.Xna.Framework.Color;
using System.Text.RegularExpressions;
namespace Barotrauma
{
@@ -227,7 +228,7 @@ namespace Barotrauma
return Path.Combine(folder, saveName);
}
public static IEnumerable<string> GetSaveFiles(SaveType saveType, bool includeInCompatible = true)
public static IReadOnlyList<CampaignMode.SaveInfo> GetSaveFiles(SaveType saveType, bool includeInCompatible = true)
{
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
if (!Directory.Exists(folder))
@@ -250,18 +251,61 @@ namespace Barotrauma
files.AddRange(Directory.GetFiles(legacyFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly));
}
if (!includeInCompatible)
List<CampaignMode.SaveInfo> saveInfos = new List<CampaignMode.SaveInfo>();
foreach (string file in files)
{
for (int i = files.Count - 1; i >= 0; i--)
XDocument doc = LoadGameSessionDoc(file);
if (!includeInCompatible && !IsSaveFileCompatible(doc))
{
XDocument doc = LoadGameSessionDoc(files[i]);
if (!IsSaveFileCompatible(doc))
continue;
}
if (doc?.Root == null)
{
saveInfos.Add(new CampaignMode.SaveInfo()
{
files.RemoveAt(i);
FilePath = file
});
}
else
{
List<string> enabledContentPackageNames = new List<string>();
//backwards compatibility
string enabledContentPackagePathsStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", string.Empty);
foreach (string packagePath in enabledContentPackagePathsStr.Split('|'))
{
if (string.IsNullOrEmpty(packagePath)) { continue; }
//change paths to names
string fileName = Path.GetFileNameWithoutExtension(packagePath);
if (fileName == "filelist")
{
enabledContentPackageNames.Add(Path.GetFileName(Path.GetDirectoryName(packagePath)));
}
else
{
enabledContentPackageNames.Add(fileName);
}
}
string enabledContentPackageNamesStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackagenames", string.Empty);
//split on pipes, excluding pipes preceded by \
foreach (string packageName in Regex.Split(enabledContentPackageNamesStr, @"(?<!(?<!\\)*\\)\|"))
{
if (string.IsNullOrEmpty(packageName)) { continue; }
enabledContentPackageNames.Add(packageName.Replace(@"\|", "|"));
}
saveInfos.Add(new CampaignMode.SaveInfo()
{
FilePath = file,
SubmarineName = doc?.Root?.GetAttributeStringUnrestricted("submarine", ""),
SaveTime = doc.Root.GetAttributeInt("savetime", 0),
EnabledContentPackageNames = enabledContentPackageNames.ToArray(),
});
}
}
return files;
return saveInfos;
}
public static string CreateSavePath(SaveType saveType, string fileName = "Save_Default")

View File

@@ -1,3 +1,53 @@
---------------------------------------------------------------------------------------------------------
v0.17.5.0
---------------------------------------------------------------------------------------------------------
Changes:
- Changed server browser tooltips: list all missing mods instead of just missing ones that aren't available from the workshop.
- Players without the permission to access the bank can request money from the bank (approved by vote).
- Modified Selkie emergency hatch: can only use it if the shuttle is flooded.
- Added limits to how many items a vending machine can dispense and adjusted what's available in them (unstable only).
- Reworked campaign permissions: removed BuyItems and CampaignStore permissions (no longer needed, everyone can buy), added ManageMap and ManageHires permissions, ManageCampaign allows gives you all the other campaign-related permissions.
- Adjusted the amount of colonies in cold caverns.
- Added a prompt to allow modders to automatically transfer their mods and submarines from before v0.17 to the new LocalMods folder.
- Disabled loading submarines from the Submarines folder, as it has been rendered obsolete by this change.
- Removed submarine download confirmation prompt. All subs that are required to play in a server will be downloaded automatically, which shouldn't be a problem since they're only stored temporarily.
- Swapped the sides of server log and character info in multiplayer tab menu.
- Doubled the rate limit for medical clinic which should reduce "No response from server" errors.
- Rebalance mudraptors: slightly more health, less damage at head.
- Reduce the probability for the coilgun to dismember limbs (or break armor).
- Crawler: adjust the vitality multipliers of hands and tail from 50% to 75%.
Fixes:
- Fixed multiplayer campaign saves with semicolons or pipes in their name causing "path to a save file was empty" errors in the server lobby.
- Fixed performance drops in multiplayer when someone attacks the outpost NPCs and causes the reputation to drop.
- Moved the showperf view to the right to prevent it from overlapping with the crew list.
- Fixed status monitor's electrical view and item finder not showing items in docked subs.
- The sound of crowbaring a door open can't be heard from other subs.
- Fixed "a gaze into the abyss" achievement working unreliably. The achievement didn't unlock until you returned to 50% of the crush depth, which isn't possible in some levels (e.g. if the level starts at 3500 m and crush depth at 5000 m). Now it's unlocked if you get to 500 m above the crush depth or to the end of the level.
- Fixed certain achievements (last man standing, lone sailor, finishing a round with a specific job) not unlocking if you're in a docked sub instead of the main sub at the end of the round.
- Readjusted all sitting animations so that they characters shouldn't twitch anymore while sitting.
Fixes (unstable only):
- Fixed mods list being configurable outside of the main menu.
- Misc fixes to colonies (added vents, wired docking ports, adjusted misaligned stairs, fixed a bunch of gap and draw order issues).
- Fixed outpost generator sometimes placing a hallway between modules connected by stairs in colonies.
- Fixed crashing when saving a sub.
- Fixed cargo missions sometimes displaying an incorrect number of crates in the description.
- Hide the "sufficient skills to fabricate"/"insufficient skills to fabricate" labels on the vending machines.
- Decrease the number of daily specials and requested goods for merchants to account for the multiple vendors.
- Fixed crash in Mission.GetSalaryEligibleCrew when taking control of a bot in the multiplayer campaign.
- Fixed tab menu blocking all other UI elements.
- Readjusted amount of materials in abyss islands.
- Fixed wallet UI being accessible in all multiplayer game modes.
- Fixed flashlights emitting sparks.
- Flipped the confirm and reset buttons in wallet UI.
- Added a new column to multiplayer tab menu that shows the amount of money the player has in their wallet.
Modding:
- Option to make missions force a ruin in the level if there isn't one.
- Character editor: don't check the validity of the texture path when copying humans (because the path is not valid and will be parsed later). Allows creating custom human characters by copying the vanilla human (even though they are not fully supported).
---------------------------------------------------------------------------------------------------------
v0.17.4.0
---------------------------------------------------------------------------------------------------------