Unstable v0.1300.0.1

This commit is contained in:
Markus Isberg
2021-03-05 17:00:56 +02:00
parent 64cdb32078
commit cb969c959f
199 changed files with 6043 additions and 3911 deletions
@@ -257,7 +257,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool Freeze { get; set; } public bool Freeze { get; set; }
public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true) public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true, Rectangle? overrideMouseOn = null)
{ {
prevPosition = position; prevPosition = position;
prevZoom = zoom; prevZoom = zoom;
@@ -294,7 +294,7 @@ namespace Barotrauma
} }
} }
if (allowZoom && GUI.MouseOn == null) if (allowZoom && (GUI.MouseOn == null || (overrideMouseOn?.Contains(PlayerInput.MousePosition) ?? false)))
{ {
Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition); Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition);
Vector2 diffViewCenter; Vector2 diffViewCenter;
@@ -529,6 +529,7 @@ namespace Barotrauma
{ {
ushort infoID = inc.ReadUInt16(); ushort infoID = inc.ReadUInt16();
string newName = inc.ReadString(); string newName = inc.ReadString();
string originalName = inc.ReadString();
int gender = inc.ReadByte(); int gender = inc.ReadByte();
int race = inc.ReadByte(); int race = inc.ReadByte();
int headSpriteID = inc.ReadByte(); int headSpriteID = inc.ReadByte();
@@ -556,7 +557,7 @@ namespace Barotrauma
} }
// TODO: animations // TODO: animations
CharacterInfo ch = new CharacterInfo(speciesName, newName, jobPrefab, ragdollFile, variant) CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant)
{ {
ID = infoID, ID = infoID,
}; };
@@ -240,6 +240,8 @@ namespace Barotrauma
Character.Controlled.SelectedConstruction = null; Character.Controlled.SelectedConstruction = null;
} }
} }
HintManager.OnShowHealthInterface();
} }
} }
@@ -718,6 +720,7 @@ namespace Barotrauma
int dmgPerSecond = Math.Sign(a2.DamagePerSecond - a1.DamagePerSecond); int dmgPerSecond = Math.Sign(a2.DamagePerSecond - a1.DamagePerSecond);
return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(a1.Strength - a1.Strength); return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(a1.Strength - a1.Strength);
}); });
HintManager.OnAfflictionDisplayed(Character, currentDisplayedAfflictions.FirstOrDefault());
updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval; updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval;
} }
@@ -1188,7 +1191,7 @@ namespace Barotrauma
} }
} }
private Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction) public static Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction)
{ {
// No specific colors, use generic // No specific colors, use generic
if (prefab.IconColors == null) if (prefab.IconColors == null)
@@ -1208,6 +1211,8 @@ namespace Barotrauma
} }
} }
public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
private void UpdateAfflictionContainer(LimbHealth selectedLimb) private void UpdateAfflictionContainer(LimbHealth selectedLimb)
{ {
selectedLimbText.Text = selectedLimb == null ? "" : selectedLimb.Name; selectedLimbText.Text = selectedLimb == null ? "" : selectedLimb.Name;
@@ -1275,7 +1280,7 @@ namespace Barotrauma
var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, button.RectTransform, Anchor.Center), affliction.Prefab.Icon, scaleToFit: true) var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, button.RectTransform, Anchor.Center), affliction.Prefab.Icon, scaleToFit: true)
{ {
Color = GetAfflictionIconColor(affliction.Prefab, affliction), Color = GetAfflictionIconColor(affliction),
CanBeFocused = false CanBeFocused = false
}; };
afflictionIcon.PressedColor = afflictionIcon.Color; afflictionIcon.PressedColor = afflictionIcon.Color;
@@ -1911,7 +1916,7 @@ namespace Barotrauma
float alpha = MathHelper.Lerp(0.3f, 1.0f, float alpha = MathHelper.Lerp(0.3f, 1.0f,
(affliction.Strength - showIconThreshold) / Math.Min(affliction.Prefab.MaxStrength - showIconThreshold, 10.0f)); (affliction.Strength - showIconThreshold) / Math.Min(affliction.Prefab.MaxStrength - showIconThreshold, 10.0f));
affliction.Prefab.Icon.Draw(spriteBatch, iconPos - iconSize / 2.0f, GetAfflictionIconColor(affliction.Prefab, affliction) * alpha, 0, iconScale); affliction.Prefab.Icon.Draw(spriteBatch, iconPos - iconSize / 2.0f, GetAfflictionIconColor(affliction) * alpha, 0, iconScale);
iconPos += new Vector2(10.0f, 20.0f) * iconScale; iconPos += new Vector2(10.0f, 20.0f) * iconScale;
} }
@@ -1534,7 +1534,9 @@ namespace Barotrauma
List<string> lines = missingTags.Select(t => "\"" + t.Key + "\"\n missing from " + string.Join(", ", t.Value)).ToList(); List<string> lines = missingTags.Select(t => "\"" + t.Key + "\"\n missing from " + string.Join(", ", t.Value)).ToList();
string filePath = "missingloca.txt"; string filePath = "missingloca.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
TextManager.Language = "English"; TextManager.Language = "English";
})); }));
@@ -1543,7 +1545,9 @@ namespace Barotrauma
{ {
var debugLines = EventSet.GetDebugStatistics(); var debugLines = EventSet.GetDebugStatistics();
string filePath = "eventstats.txt"; string filePath = "eventstats.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, debugLines); File.WriteAllLines(filePath, debugLines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
})); }));
@@ -1824,7 +1828,9 @@ namespace Barotrauma
lines.Add("<EntityName." + me.Identifier + ">" + me.Name + "</EntityName." + me.Identifier + ">"); lines.Add("<EntityName." + me.Identifier + ">" + me.Name + "</EntityName." + me.Identifier + ">");
lines.Add("<EntityDescription." + me.Identifier + ">" + me.Description + "</EntityDescription." + me.Identifier + ">"); lines.Add("<EntityDescription." + me.Identifier + ">" + me.Description + "</EntityDescription." + me.Identifier + ">");
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
})); }));
commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) => commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) =>
@@ -1843,8 +1849,8 @@ namespace Barotrauma
docs.Add(eventPrefab.ConfigElement.Document); docs.Add(eventPrefab.ConfigElement.Document);
getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier); getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier);
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
@@ -1852,7 +1858,6 @@ namespace Barotrauma
Indent = true, Indent = true,
NewLineOnAttributes = false NewLineOnAttributes = false
}; };
foreach (XDocument doc in docs) foreach (XDocument doc in docs)
{ {
using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings)) using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings))
@@ -1861,6 +1866,7 @@ namespace Barotrauma
writer.Flush(); writer.Flush();
} }
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
void getTextsFromElement(XElement element, List<string> list, string parentName) void getTextsFromElement(XElement element, List<string> list, string parentName)
{ {
@@ -2007,7 +2013,9 @@ namespace Barotrauma
lines.Add("[/table]"); lines.Add("[/table]");
lines.Add(""); lines.Add("");
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
})); }));
#if DEBUG #if DEBUG
@@ -2049,7 +2057,9 @@ namespace Barotrauma
commands.Add(new Command("printproperties", "Goes through the currently collected property list for missing localizations and writes them to a file.", (string[] args) => commands.Add(new Command("printproperties", "Goes through the currently collected property list for missing localizations and writes them to a file.", (string[] args) =>
{ {
string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\propertylocalization.txt"; string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\propertylocalization.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(path, SerializableEntityEditor.MissingLocalizations); File.WriteAllLines(path, SerializableEntityEditor.MissingLocalizations);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
})); }));
commands.Add(new Command("getproperties", "Goes through the MapEntity prefabs and checks their serializable properties for localization issues.", (string[] args) => commands.Add(new Command("getproperties", "Goes through the MapEntity prefabs and checks their serializable properties for localization issues.", (string[] args) =>
@@ -560,6 +560,22 @@ namespace Barotrauma
}; };
} }
break; break;
case NetworkEventType.UNLOCKPATH:
UInt16 connectionIndex = msg.ReadUInt16();
if (GameMain.GameSession?.Map?.Connections != null)
{
if (connectionIndex >= GameMain.GameSession.Map.Connections.Count)
{
DebugConsole.ThrowError($"Failed to unlock a path on the campaign map. Connection index out of bounds (index: {connectionIndex}, number of connections: {GameMain.GameSession.Map.Connections.Count})");
}
else
{
GameMain.GameSession.Map.Connections[connectionIndex].Locked = false;
new GUIMessageBox(string.Empty, TextManager.Get("pathunlockedgeneric"),
new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "UnlockPathIcon", relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128));
}
}
break;
} }
} }
} }
@@ -10,7 +10,16 @@ namespace Barotrauma
for (int i = 0; i < characterCount; i++) for (int i = 0; i < characterCount; i++)
{ {
characters.Add(Character.ReadSpawnData(msg)); Character character = Character.ReadSpawnData(msg);
characters.Add(character);
if (msg.ReadBoolean()) { requireKill.Add(character); }
if (msg.ReadBoolean())
{
requireRescue.Add(character);
#if CLIENT
GameMain.GameSession.CrewManager.AddCharacterToCrewList(character);
#endif
}
ushort itemCount = msg.ReadUInt16(); ushort itemCount = msg.ReadUInt16();
for (int j = 0; j < itemCount; j++) for (int j = 0; j < itemCount; j++)
{ {
@@ -23,7 +23,7 @@ namespace Barotrauma
{ {
yield return new WaitForSeconds(1.0f); yield return new WaitForSeconds(1.0f);
} }
new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon) new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon, parseRichText: true)
{ {
IconColor = Prefab.IconColor IconColor = Prefab.IconColor
}; };
@@ -6,7 +6,7 @@
{ {
foreach (Mission mission in missions) foreach (Mission mission in missions)
{ {
new GUIMessageBox(mission.Name, mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon) new GUIMessageBox(mission.Name, mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon, parseRichText: true)
{ {
IconColor = mission.Prefab.IconColor, IconColor = mission.Prefab.IconColor,
UserData = "missionstartmessage" UserData = "missionstartmessage"
@@ -422,12 +422,12 @@ namespace Barotrauma
} }
} }
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData) public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData, int rtdOffset = 0)
{ {
DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, richTextData); DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, richTextData, rtdOffset);
} }
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData) public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData, int rtdOffset = 0)
{ {
if (textures.Count == 0 && !DynamicLoading) { return; } if (textures.Count == 0 && !DynamicLoading) { return; }
@@ -457,13 +457,13 @@ namespace Barotrauma
Color currentTextColor; Color currentTextColor;
if (currentRichTextData != null && i > currentRichTextData.EndIndex + lineNum) while (currentRichTextData != null && i + rtdOffset > currentRichTextData.EndIndex + lineNum)
{ {
richTextDataIndex++; richTextDataIndex++;
currentRichTextData = richTextDataIndex < richTextData.Count ? richTextData[richTextDataIndex] : null; currentRichTextData = richTextDataIndex < richTextData.Count ? richTextData[richTextDataIndex] : null;
} }
if (currentRichTextData != null && currentRichTextData.StartIndex + lineNum <= i && i <= currentRichTextData.EndIndex + lineNum) if (currentRichTextData != null && currentRichTextData.StartIndex + lineNum <= i + rtdOffset && i + rtdOffset <= currentRichTextData.EndIndex + lineNum)
{ {
currentTextColor = currentRichTextData.Color ?? color; currentTextColor = currentRichTextData.Color ?? color;
if (!string.IsNullOrEmpty(currentRichTextData.Metadata)) if (!string.IsNullOrEmpty(currentRichTextData.Metadata))
@@ -321,6 +321,10 @@ namespace Barotrauma
float prevSize = chatBox.BarSize; float prevSize = chatBox.BarSize;
string displayedText = message.TranslatedText; string displayedText = message.TranslatedText;
if (message.Type == ChatMessageType.Server)
{
RichTextData.GetRichTextData(displayedText, out displayedText);
}
string senderName = ""; string senderName = "";
Color senderColor = Color.White; Color senderColor = Color.White;
if (!string.IsNullOrWhiteSpace(message.SenderName)) if (!string.IsNullOrWhiteSpace(message.SenderName))
@@ -1,10 +1,10 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
@@ -189,7 +189,7 @@ namespace Barotrauma
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUI.SubHeadingFont);
crewList = new GUIListBox(new RectTransform(new Vector2(1.0f, (8)* height), pendingAndCrewGroup.RectTransform)) crewList = new GUIListBox(new RectTransform(new Vector2(1.0f, 8 * height), pendingAndCrewGroup.RectTransform))
{ {
Spacing = 1 Spacing = 1
}; };
@@ -208,7 +208,7 @@ namespace Barotrauma
{ {
ClickSound = GUISoundType.HireRepairClick, ClickSound = GUISoundType.HireRepairClick,
ForceUpperCase = true, ForceUpperCase = true,
OnClicked = (b, o) => ValidatePendingHires(true) OnClicked = (b, o) => ValidateHires(PendingHires, true)
}; };
clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall")) clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall"))
{ {
@@ -338,7 +338,7 @@ namespace Barotrauma
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, (int)(GUI.yScale * 55)), parent: listBox.Content.RectTransform), "ListBoxElement") GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, (int)(GUI.yScale * 55)), parent: listBox.Content.RectTransform), "ListBoxElement")
{ {
UserData = new Tuple<CharacterInfo, float>(characterInfo, skill != null ? skill.Level : 0.0f) UserData = new Tuple<CharacterInfo, float>(characterInfo, skill?.Level ?? 0.0f)
}; };
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{ {
@@ -346,19 +346,22 @@ namespace Barotrauma
}; };
float portraitWidth = (0.8f * mainGroup.Rect.Height) / mainGroup.Rect.Width; float portraitWidth = (0.8f * mainGroup.Rect.Height) / mainGroup.Rect.Width;
new GUICustomComponent(new RectTransform(new Vector2(portraitWidth, 0.8f), mainGroup.RectTransform), var icon = new GUICustomComponent(new RectTransform(new Vector2(portraitWidth, 0.8f), mainGroup.RectTransform),
onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())) onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
{ {
CanBeFocused = false CanBeFocused = false
}; };
GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)); GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)) { CanBeFocused = false };
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), GUILayoutGroup nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { CanBeFocused = false };
characterInfo.Name, textColor: jobColor, textAlignment: Alignment.BottomLeft) GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameGroup.RectTransform),
listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name,
textColor: jobColor, textAlignment: Alignment.BottomLeft)
{ {
CanBeFocused = false CanBeFocused = false
}; };
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width); nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Job.Name, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft) characterInfo.Job.Name, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft)
{ {
@@ -384,11 +387,18 @@ namespace Barotrauma
if (listBox != crewList) if (listBox != crewList)
{ {
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), FormatCurrency(characterInfo.Salary), textAlignment: Alignment.Center) new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
FormatCurrency(characterInfo.Salary),
textAlignment: Alignment.Center)
{ {
CanBeFocused = false CanBeFocused = false
}; };
} }
else
{
// Just a bit of padding to make list layouts similar
new GUIFrame(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
}
if (listBox == hireableList) if (listBox == hireableList)
{ {
@@ -447,6 +457,23 @@ namespace Barotrauma
} }
}; };
} }
if (listBox == pendingList || listBox == crewList)
{
nameBlock.RectTransform.Resize(new Point(nameBlock.Rect.Width - nameBlock.Rect.Height, nameBlock.Rect.Height));
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.RectTransform.Resize(new Point((int)(nameBlock.Padding.X + nameBlock.TextSize.X + nameBlock.Padding.Z), nameBlock.Rect.Height));
Point size = new Point((int)(0.7f * nameBlock.Rect.Height));
new GUIImage(new RectTransform(size, nameGroup.RectTransform), "EditIcon") { CanBeFocused = false };
size = new Point(3 * mainGroup.AbsoluteSpacing + icon.Rect.Width + nameAndJobGroup.Rect.Width, mainGroup.Rect.Height);
new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null)
{
Enabled = HasPermission,
ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", TextManager.Get($"input.{(PlayerInput.MouseButtonsSwapped() ? "rightmouse" : "leftmouse")}")),
UserData = characterInfo,
OnClicked = CreateRenamingComponent
};
}
} }
private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo) private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
@@ -479,7 +506,10 @@ namespace Barotrauma
GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true }; GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
float blockHeight = 1.0f / 4; float blockHeight = 1.0f / 4;
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name")); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), characterInfo.Name); GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), "");
string name = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
nameBlock.Text = ToolBox.LimitString(name, nameBlock.Font, nameBlock.Rect.Width);
if (characterInfo.HasGenders) if (characterInfo.HasGenders)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("gender")); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("gender"));
@@ -554,9 +584,18 @@ namespace Barotrauma
if (PendingHires.Contains(characterInfo)) { PendingHires.Remove(characterInfo); } if (PendingHires.Contains(characterInfo)) { PendingHires.Remove(characterInfo); }
pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo)); pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo));
pendingList.UpdateScrollBarSize(); pendingList.UpdateScrollBarSize();
CreateCharacterFrame(characterInfo, hireableList);
SortCharacters(hireableList, (SortingMethod)sortingDropDown.SelectedItemData); // Server will reset the names to originals in multiplayer
hireableList.UpdateScrollBarSize(); if (!GameMain.IsMultiplayer) { characterInfo?.ResetName(); }
if (campaign.Map.CurrentLocation.HireManager.AvailableCharacters.Any(info => info.GetIdentifierUsingOriginalName() == characterInfo.GetIdentifierUsingOriginalName()) &&
hireableList.Content.Children.None(c => c.UserData is Tuple<CharacterInfo, float> userData && userData.Item1.GetIdentifierUsingOriginalName() == characterInfo.GetIdentifierUsingOriginalName()))
{
CreateCharacterFrame(characterInfo, hireableList);
SortCharacters(hireableList, (SortingMethod)sortingDropDown.SelectedItemData);
hireableList.UpdateScrollBarSize();
}
if (setTotalHireCost) { SetTotalHireCost(); } if (setTotalHireCost) { SetTotalHireCost(); }
if (createNetworkMessage) { SendCrewState(true); } if (createNetworkMessage) { SendCrewState(true); }
return true; return true;
@@ -573,36 +612,41 @@ namespace Barotrauma
{ {
if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; } if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; }
int total = 0; int total = 0;
pendingList.Content.Children.ForEach(c => total += (c.UserData as Tuple<CharacterInfo, float>).Item1.Salary); pendingList.Content.Children.ForEach(c =>
{
total += (c.UserData as Tuple<CharacterInfo, float>).Item1.Salary;
});
totalBlock.Text = FormatCurrency(total); totalBlock.Text = FormatCurrency(total);
bool enoughMoney = campaign != null ? total <= campaign.Money : true; bool enoughMoney = campaign != null ? total <= campaign.Money : true;
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red; totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any(); validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any();
} }
public bool ValidatePendingHires(bool createNetworkEvent = false) public bool ValidateHires(List<CharacterInfo> hires, bool createNetworkEvent = false)
{ {
List<CharacterInfo> hires = new List<CharacterInfo>(); if (hires == null || hires.None()) { return false; }
int total = 0;
foreach (GUIComponent c in pendingList.Content.Children.ToList())
{
if (c.UserData is Tuple<CharacterInfo, float> info)
{
hires.Add(info.Item1);
total += info.Item1.Salary;
}
}
if (hires.None() || total > campaign.Money) { return false; } List<CharacterInfo> nonDuplicateHires = new List<CharacterInfo>();
hires.ForEach(hireInfo =>
{
if(campaign.CrewManager.GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
{
nonDuplicateHires.Add(hireInfo);
}
});
if (nonDuplicateHires.None()) { return false; }
int total = nonDuplicateHires.Aggregate(0, (total, info) => total + info.Salary);
if (total > campaign.Money) { return false; }
bool atLeastOneHired = false; bool atLeastOneHired = false;
foreach (CharacterInfo ci in hires) foreach (CharacterInfo ci in nonDuplicateHires)
{ {
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci)) if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci))
{ {
atLeastOneHired = true; atLeastOneHired = true;
PendingHires.Remove(ci);
pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == ci));
} }
else else
{ {
@@ -629,6 +673,93 @@ namespace Barotrauma
return false; return false;
} }
private bool CreateRenamingComponent(GUIButton button, object userData)
{
if (!HasPermission || !(userData is CharacterInfo characterInfo)) { return false; }
var outerGlowFrame = new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black * 0.7f);
var frame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.4f), outerGlowFrame.RectTransform, anchor: Anchor.Center)
{
MaxSize = new Point(400, 300).Multiply(GUI.Scale)
});
var layoutGroup = new GUILayoutGroup(new RectTransform((frame.Rect.Size - GUIStyle.ItemFrameMargin).Multiply(new Vector2(0.75f, 1.0f)), frame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
RelativeSpacing = 0.02f,
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), layoutGroup.RectTransform), TextManager.Get("campaigncrew.givenickname"), font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
var groupElementSize = new Vector2(1.0f, 0.25f);
var nameBox = new GUITextBox(new RectTransform(groupElementSize, layoutGroup.RectTransform))
{
MaxTextLength = Client.MaxNameLength
};
new GUIButton(new RectTransform(groupElementSize, layoutGroup.RectTransform), text: TextManager.Get("confirm"))
{
OnClicked = (button, userData) =>
{
if (RenameCharacter(characterInfo, nameBox.Text?.Trim()))
{
parentComponent.RemoveChild(outerGlowFrame);
return true;
}
else
{
nameBox.Flash(color: Color.Red);
return false;
}
}
};
new GUIButton(new RectTransform(groupElementSize, layoutGroup.RectTransform), text: TextManager.Get("cancel"))
{
OnClicked = (button, userData) =>
{
parentComponent.RemoveChild(outerGlowFrame);
return true;
}
};
layoutGroup.Recalculate();
return true;
}
public bool RenameCharacter(CharacterInfo characterInfo, string newName)
{
if (characterInfo == null || string.IsNullOrEmpty(newName)) { return false; }
if (newName == characterInfo.Name) { return false; }
if (GameMain.IsMultiplayer)
{
SendCrewState(false, renameCharacter: (characterInfo, newName));
}
else
{
var crewComponent = crewList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo);
if (crewComponent != null)
{
crewList.Content.RemoveChild(crewComponent);
campaign.CrewManager.RenameCharacter(characterInfo, newName);
CreateCharacterFrame(characterInfo, crewList);
SortCharacters(crewList, SortingMethod.JobAsc);
}
else
{
var pendingComponent = pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo);
if (pendingComponent != null)
{
pendingList.Content.RemoveChild(pendingComponent);
campaign.Map.CurrentLocation.HireManager.RenameCharacter(characterInfo, newName);
CreateCharacterFrame(characterInfo, pendingList);
SortCharacters(pendingList, SortingMethod.JobAsc);
SetTotalHireCost();
}
else
{
return false;
}
}
}
return true;
}
private bool FireCharacter(GUIButton button, object selection) private bool FireCharacter(GUIButton button, object selection)
{ {
if (!(selection is CharacterInfo characterInfo)) { return false; } if (!(selection is CharacterInfo characterInfo)) { return false; }
@@ -649,9 +780,10 @@ namespace Barotrauma
UpdateLocationView(campaign.Map.CurrentLocation, false); UpdateLocationView(campaign.Map.CurrentLocation, false);
} }
if ((GUI.MouseOn?.UserData as Tuple<CharacterInfo, float>)?.Item1 is CharacterInfo characterInfo) (GUIComponent highlightedFrame, CharacterInfo highlightedInfo) = FindHighlightedCharacter(GUI.MouseOn);
if (highlightedFrame != null && highlightedInfo != null)
{ {
if (characterPreviewFrame == null || characterInfo != characterPreviewFrame.UserData) if (characterPreviewFrame == null || highlightedInfo != characterPreviewFrame.UserData)
{ {
GUIComponent component = GUI.MouseOn; GUIComponent component = GUI.MouseOn;
GUIListBox listBox = null; GUIListBox listBox = null;
@@ -674,7 +806,7 @@ namespace Barotrauma
if (listBox != null) if (listBox != null)
{ {
SelectCharacter(listBox, GUI.MouseOn as GUIFrame, characterInfo); SelectCharacter(listBox, highlightedFrame as GUIFrame, highlightedInfo);
} }
} }
else else
@@ -688,6 +820,27 @@ namespace Barotrauma
characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame); characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
characterPreviewFrame = null; characterPreviewFrame = null;
} }
static (GUIComponent, CharacterInfo) FindHighlightedCharacter(GUIComponent c)
{
if (c == null)
{
return default;
}
if (c.UserData is Tuple<CharacterInfo, float> highlightedData)
{
return (c, highlightedData.Item1);
}
if (c.Parent != null)
{
if (c.Parent is GUIListBox)
{
return default;
}
return FindHighlightedCharacter(c.Parent);
}
return default;
}
} }
public void SetPendingHires(List<int> characterInfos, Location location) public void SetPendingHires(List<int> characterInfos, Location location)
@@ -700,7 +853,7 @@ namespace Barotrauma
PendingHires.Clear(); PendingHires.Clear();
foreach (int identifier in characterInfos) foreach (int identifier in characterInfos)
{ {
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifier() == identifier); CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifierUsingOriginalName() == identifier);
if (match != null) if (match != null)
{ {
PendingHires.Add(match); PendingHires.Add(match);
@@ -717,9 +870,10 @@ namespace Barotrauma
/// Notify the server of crew changes /// Notify the server of crew changes
/// </summary> /// </summary>
/// <param name="updatePending">When set to true will tell the server to update the pending hires</param> /// <param name="updatePending">When set to true will tell the server to update the pending hires</param>
/// <param name="renameCharacter">When not null tell the server to rename this character. Item1 is the character to rename, Item2 is the new name, Item3 indicates whether the renamed character is already a part of the crew.</param>
/// <param name="firedCharacter">When not null tell the server to fire this character</param> /// <param name="firedCharacter">When not null tell the server to fire this character</param>
/// <param name="validateHires">When set to true will tell the server to validate pending hires</param> /// <param name="validateHires">When set to true will tell the server to validate pending hires</param>
public void SendCrewState(bool updatePending, CharacterInfo firedCharacter = null, bool validateHires = false) public void SendCrewState(bool updatePending, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
{ {
if (campaign is MultiPlayerCampaign) if (campaign is MultiPlayerCampaign)
{ {
@@ -732,12 +886,23 @@ namespace Barotrauma
msg.Write((ushort)PendingHires.Count); msg.Write((ushort)PendingHires.Count);
foreach (CharacterInfo pendingHire in PendingHires) foreach (CharacterInfo pendingHire in PendingHires)
{ {
msg.Write(pendingHire.GetIdentifier()); msg.Write(pendingHire.GetIdentifierUsingOriginalName());
} }
} }
msg.Write(validateHires); msg.Write(validateHires);
bool validRenaming = renameCharacter.info != null && !string.IsNullOrEmpty(renameCharacter.newName);
msg.Write(validRenaming);
if (validRenaming)
{
int identifier = renameCharacter.info.GetIdentifierUsingOriginalName();
msg.Write(identifier);
msg.Write(renameCharacter.newName);
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.GetIdentifierUsingOriginalName() == identifier) ?? false;
msg.Write(existingCrewMember);
}
msg.Write(firedCharacter != null); msg.Write(firedCharacter != null);
if (firedCharacter != null) if (firedCharacter != null)
{ {
@@ -1338,9 +1338,9 @@ namespace Barotrauma
} }
} }
public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, int width = 1) public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, float width = 1)
{ {
DrawLine(sb, t, start, end, clr, depth, width); DrawLine(sb, t, start, end, clr, depth, (int)width);
} }
public static void DrawLine(SpriteBatch sb, Sprite sprite, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, int width = 1) public static void DrawLine(SpriteBatch sb, Sprite sprite, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, int width = 1)
@@ -1407,7 +1407,7 @@ namespace Barotrauma
font.DrawStringWithColors(sb, text, pos, color, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, depth, richTextData); font.DrawStringWithColors(sb, text, pos, color, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, depth, richTextData);
} }
public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, bool isFilled = false, float depth = 0.0f, float thickness = 1)
{ {
if (size.X < 0) if (size.X < 0)
{ {
@@ -1422,7 +1422,7 @@ namespace Barotrauma
DrawRectangle(sb, new Rectangle((int)start.X, (int)start.Y, (int)size.X, (int)size.Y), clr, isFilled, depth, thickness); DrawRectangle(sb, new Rectangle((int)start.X, (int)start.Y, (int)size.X, (int)size.Y), clr, isFilled, depth, thickness);
} }
public static void DrawRectangle(SpriteBatch sb, Rectangle rect, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Rectangle rect, Color clr, bool isFilled = false, float depth = 0.0f, float thickness = 1)
{ {
if (isFilled) if (isFilled)
{ {
@@ -1430,15 +1430,15 @@ namespace Barotrauma
} }
else else
{ {
sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); Rectangle srcRect = new Rectangle(0, 0, 1, 1);
sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y + rect.Height - thickness, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); sb.Draw(t, new Vector2(rect.X, rect.Y), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(thickness, rect.Height), SpriteEffects.None, depth);
sb.Draw(t, new Vector2(rect.X + thickness, rect.Y), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(rect.Width - thickness, thickness), SpriteEffects.None, depth);
sb.Draw(t, new Rectangle(rect.X, rect.Y, thickness, rect.Height), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); sb.Draw(t, new Vector2(rect.X + thickness, rect.Bottom - thickness), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(rect.Width - thickness, thickness), SpriteEffects.None, depth);
sb.Draw(t, new Rectangle(rect.X + rect.Width - thickness, rect.Y, thickness, rect.Height), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); sb.Draw(t, new Vector2(rect.Right - thickness, rect.Y + thickness), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(thickness, rect.Height - thickness * 2f), SpriteEffects.None, depth);
} }
} }
public static void DrawRectangle(SpriteBatch sb, Vector2 center, float width, float height, float rotation, Color clr, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Vector2 center, float width, float height, float rotation, Color clr, float depth = 0.0f, float thickness = 1)
{ {
Matrix rotate = Matrix.CreateRotationZ(rotation); Matrix rotate = Matrix.CreateRotationZ(rotation);
@@ -1455,7 +1455,7 @@ namespace Barotrauma
DrawLine(sb, bottomLeft, topLeft, clr, depth, thickness); DrawLine(sb, bottomLeft, topLeft, clr, depth, thickness);
} }
public static void DrawRectangle(SpriteBatch sb, Vector2[] corners, Color clr, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Vector2[] corners, Color clr, float depth = 0.0f, float thickness = 1)
{ {
if (corners.Length != 4) if (corners.Length != 4)
{ {
@@ -706,6 +706,29 @@ namespace Barotrauma
DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData); DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData);
} }
public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Vector2 pos, List<RichTextData> richTextData = null)
{
if (Tutorials.Tutorial.ContentRunning) { return; }
int width = (int)(400 * GUI.Scale);
int height = (int)(18 * GUI.Scale);
Point padding = new Point((int)(10 * GUI.Scale));
if (toolTipBlock == null || (string)toolTipBlock.userData != toolTip)
{
toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), richTextData, toolTip, font: GUI.SmallFont, wrap: true, style: "GUIToolTip");
toolTipBlock.RectTransform.NonScaledSize = new Point(
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z),
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W));
toolTipBlock.userData = toolTip;
}
toolTipBlock.RectTransform.AbsoluteOffset = pos.ToPoint();
toolTipBlock.SetTextPos();
toolTipBlock.DrawManually(spriteBatch);
}
public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle targetElement, List<RichTextData> richTextData = null) public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle targetElement, List<RichTextData> richTextData = null)
{ {
if (Tutorials.Tutorial.ContentRunning) { return; } if (Tutorials.Tutorial.ContentRunning) { return; }
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -22,11 +23,11 @@ namespace Barotrauma
{ {
Default, Default,
InGame, InGame,
Vote Vote,
Hint
} }
public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>(); public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>();
//public GUIFrame BackgroundFrame { get; private set; }
public GUILayoutGroup Content { get; private set; } public GUILayoutGroup Content { get; private set; }
public GUIFrame InnerFrame { get; private set; } public GUIFrame InnerFrame { get; private set; }
public GUITextBlock Header { get; private set; } public GUITextBlock Header { get; private set; }
@@ -58,8 +59,6 @@ namespace Barotrauma
public bool AutoClose; public bool AutoClose;
private readonly bool alwaysVisible;
private float openState; private float openState;
private float iconState; private float iconState;
private bool iconSwitching; private bool iconSwitching;
@@ -77,10 +76,17 @@ namespace Barotrauma
this.Buttons[0].OnClicked = Close; this.Buttons[0].OnClicked = Close;
} }
public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null) public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null, bool parseRichText = false)
: base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox") : base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox")
{ {
int width = (int)(DefaultWidth * (type == Type.Default ? 1.0f : 1.5f)), height = 0; int width = (int)(DefaultWidth * type switch
{
Type.Default => 1.0f,
Type.Hint => 1.25f,
_ => 1.5f
});
int height = 0;
if (relativeSize.HasValue) if (relativeSize.HasValue)
{ {
width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X); width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X);
@@ -107,6 +113,7 @@ namespace Barotrauma
Anchor anchor = type switch Anchor anchor = type switch
{ {
Type.InGame => Anchor.TopCenter, Type.InGame => Anchor.TopCenter,
Type.Hint => Anchor.TopRight,
Type.Vote => Anchor.TopRight, Type.Vote => Anchor.TopRight,
_ => Anchor.Center _ => Anchor.Center
}; };
@@ -127,13 +134,13 @@ namespace Barotrauma
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 }; Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
headerText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true); headerText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Header, "", this); GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text)) if (!string.IsNullOrWhiteSpace(text))
{ {
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true); Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Text, "", this); GUI.Style.Apply(Text, "", this);
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize = Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height); new Point(Text.Rect.Width, Text.Rect.Height);
@@ -180,7 +187,6 @@ namespace Barotrauma
else if (type == Type.InGame) else if (type == Type.InGame)
{ {
InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight); InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight);
alwaysVisible = true;
CanBeFocused = false; CanBeFocused = false;
AutoClose = true; AutoClose = true;
GUI.Style.Apply(InnerFrame, "", this); GUI.Style.Apply(InnerFrame, "", this);
@@ -235,13 +241,13 @@ namespace Barotrauma
}; };
} }
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true); Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Header, "", this); GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text)) if (!string.IsNullOrWhiteSpace(text))
{ {
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true); Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Text, "", this); GUI.Style.Apply(Text, "", this);
Content.Recalculate(); Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize = Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
@@ -266,30 +272,204 @@ namespace Barotrauma
} }
Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y); Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y);
} }
else if (type == Type.Hint)
{
CanBeFocused = false;
GUI.Style.Apply(InnerFrame, "", this);
Point absoluteSpacing = GUIStyle.ItemFrameMargin.Multiply(1.0f / 5.0f);
var verticalLayoutGroup = new GUILayoutGroup(new RectTransform(GetVerticalLayoutGroupSize(), parent: InnerFrame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = absoluteSpacing.Y,
Stretch = true
};
var topHorizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.7f), verticalLayoutGroup.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
int iconMaxHeight = 0;
if (icon != null)
{
Icon = new GUIImage(new RectTransform(new Vector2(0.15f, 0.95f), topHorizontalLayoutGroup.RectTransform), icon, scaleToFit: true);
iconMaxHeight = (int)Icon.Sprite.size.Y;
}
else
{
bool iconStyleDefined = !string.IsNullOrEmpty(iconStyle);
Icon = new GUIImage(new RectTransform(new Vector2(0.15f, 0.95f), topHorizontalLayoutGroup.RectTransform),
iconStyleDefined ? iconStyle : "GUIButtonInfo", scaleToFit: true);
if (!iconStyleDefined)
{
Icon.Color = Color.Orange;
}
iconMaxHeight = (int)(Icon.Style.GetDefaultSprite()?.size.Y ?? GUI.yScale * 40);
}
iconMaxHeight = Math.Min((int)(GUI.yScale * 40), iconMaxHeight);
int iconMinHeight = Math.Min((int)(GUI.yScale * 40), iconMaxHeight);
Icon.RectTransform.MinSize = new Point(Icon.Rect.Width, iconMinHeight);
Icon.RectTransform.MaxSize = new Point(Icon.Rect.Width, iconMaxHeight);
Content = new GUILayoutGroup(new RectTransform(new Vector2(Icon != null ? 0.85f : 1.0f, 1.0f), topHorizontalLayoutGroup.RectTransform))
{
AbsoluteSpacing = absoluteSpacing.Y,
};
var bottomContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.3f), verticalLayoutGroup.RectTransform), style: null);
var tickBoxLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.67f, 1.0f), bottomContainer.RectTransform, anchor: Anchor.CenterLeft),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var dontShowAgainTickBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickBoxLayoutGroup.RectTransform),
TextManager.Get("hintmessagebox.dontshowagain"))
{
ToolTip = TextManager.Get("hintmessagebox.dontshowagaintooltip"),
UserData = "dontshowagain"
};
//var disableHintsTickBox = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), tickBoxLayoutGroup.RectTransform),
// TextManager.Get("hintmessagebox.disablehints"))
//{
// ToolTip = TextManager.Get("hintmessagebox.disablehintstooltip"),
// UserData = "disablehints"
//};
Buttons = new List<GUIButton>(1)
{
new GUIButton(new RectTransform(new Vector2(0.33f, 1.0f), bottomContainer.RectTransform, Anchor.CenterRight),
text: TextManager.Get("hintmessagebox.dismiss"), style: "GUIButtonSmall")
{
OnClicked = Close
}
};
InputType? closeInput = null;
if (GameMain.Config.KeyBind(InputType.Use).MouseButton == MouseButton.None)
{
closeInput = InputType.Use;
}
else if (GameMain.Config.KeyBind(InputType.Select).MouseButton == MouseButton.None)
{
closeInput = InputType.Select;
}
if (closeInput.HasValue)
{
Buttons[0].ToolTip = TextManager.ParseInputTypes($"{TextManager.Get("hintmessagebox.dismiss")} ([InputType.{closeInput.Value}])");
Buttons[0].OnAddedToGUIUpdateList += (GUIComponent component) =>
{
if (!closing && openState >= 1.0f && PlayerInput.KeyHit(closeInput.Value))
{
GUIButton btn = component as GUIButton;
btn?.OnClicked(btn, btn.UserData);
btn?.Flash(GUI.Style.Green);
}
};
}
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true);
GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text))
{
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
GUI.Style.Apply(Text, "", this);
Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height);
Text.RectTransform.IsFixedSize = true;
if (string.IsNullOrWhiteSpace(headerText))
{
Header.RectTransform.Parent = null;
Content.ChildAnchor = Anchor.Center;
}
}
if (height == 0)
{
height = absoluteSpacing.Y;
int upperContainerHeight = absoluteSpacing.Y;
if (Header.Rect.Height > 0) { upperContainerHeight += Header.Rect.Height + Content.AbsoluteSpacing; }
if (Text != null) { upperContainerHeight += Text.Rect.Height + Content.AbsoluteSpacing; }
upperContainerHeight = Math.Max(upperContainerHeight, Icon.Rect.Height);
height += upperContainerHeight;
height += absoluteSpacing.Y;
height += (int)((bottomContainer.RectTransform.RelativeSize.Y / topHorizontalLayoutGroup.RectTransform.RelativeSize.Y) * upperContainerHeight);
height += absoluteSpacing.Y;
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
InnerFrame.RectTransform.NonScaledSize = new Point(InnerFrame.Rect.Width, height);
verticalLayoutGroup.RectTransform.NonScaledSize = GetVerticalLayoutGroupSize();
verticalLayoutGroup.Recalculate();
topHorizontalLayoutGroup.Recalculate();
Content.Recalculate();
tickBoxLayoutGroup.Recalculate();
}
InnerFrame.RectTransform.AbsoluteOffset = new Point(GUI.IntScale(64), -InnerFrame.Rect.Height);
Point GetVerticalLayoutGroupSize()
{
return InnerFrame.Rect.Size - absoluteSpacing.Multiply(2);
}
}
MessageBoxes.Add(this); MessageBoxes.Add(this);
} }
/// <summary>
/// Use to create a message box of Hint type
/// </summary>
public GUIMessageBox(string hintIdentifier, string text, Sprite icon) : this("", text, new string[0], textAlignment: Alignment.CenterLeft, type: Type.Hint, icon: icon)
{
if (InnerFrame.FindChild("dontshowagain", recursive: true) is GUITickBox dontShowAgainTickBox)
{
dontShowAgainTickBox.OnSelected = HintManager.OnDontShowAgain;
dontShowAgainTickBox.UserData = hintIdentifier;
}
if (InnerFrame.FindChild("disablehints", recursive: true) is GUITickBox disableHintsTickBox)
{
disableHintsTickBox.OnSelected = HintManager.OnDisableHints;
disableHintsTickBox.UserData = hintIdentifier;
}
}
private static Type[] messageBoxTypes;
public static void AddActiveToGUIUpdateList() public static void AddActiveToGUIUpdateList()
{ {
for (int i = 0; i < MessageBoxes.Count; i++) messageBoxTypes ??= (Type[])Enum.GetValues(typeof(Type));
foreach (var type in messageBoxTypes)
{ {
if (MessageBoxes[i] is GUIMessageBox alwaysVisibleMsgBox && alwaysVisibleMsgBox.alwaysVisible) for (int i = 0; i < MessageBoxes.Count; i++)
{ {
alwaysVisibleMsgBox.AddToGUIUpdateList(); if (MessageBoxes[i] == null) { continue; }
break; if (!(MessageBoxes[i] is GUIMessageBox messageBox))
} {
} if (type == Type.Default)
for (int i = MessageBoxes.Count - 1; i >= 0; i--) {
{ // Message box not of type GUIMessageBox is likely the round summary
if (MessageBoxes[i].UserData as string == "verificationprompt" || MessageBoxes[i].AddToGUIUpdateList();
MessageBoxes[i].UserData as string == "bugreporter") break;
{ }
continue; continue;
} }
if (!(MessageBoxes[i] is GUIMessageBox msgBox) || !msgBox.alwaysVisible) if (messageBox.type != type) { continue; }
{
MessageBoxes[i].AddToGUIUpdateList(); // These are handled separately in GUI.HandlePersistingElements()
if (MessageBoxes[i].UserData as string == "verificationprompt") { continue; }
if (MessageBoxes[i].UserData as string == "bugreporter") { continue; }
messageBox.AddToGUIUpdateList();
break; break;
} }
} }
@@ -337,11 +517,21 @@ namespace Barotrauma
} }
} }
if (type == Type.InGame) if (type == Type.InGame || type == Type.Hint)
{ {
Vector2 initialPos = new Vector2(0.0f, GameMain.GraphicsHeight); Vector2 initialPos, defaultPos, endPos;
Vector2 defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale); if (type == Type.InGame)
Vector2 endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y); {
initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y);
}
else
{
initialPos = new Vector2(GUI.IntScale(64), -InnerFrame.Rect.Height);
defaultPos = new Vector2(initialPos.X, HUDLayoutSettings.MessageAreaTop.Height + GUI.IntScale(64));
endPos = new Vector2(-InnerFrame.Rect.Width, defaultPos.Y);
}
if (!closing) if (!closing)
{ {
@@ -428,7 +618,7 @@ namespace Barotrauma
public void Close() public void Close()
{ {
if (type == Type.InGame) if (type == Type.InGame || type == Type.Hint)
{ {
closing = true; closing = true;
} }
@@ -35,6 +35,7 @@ namespace Barotrauma
public readonly Sprite[] CursorSprite = new Sprite[7]; public readonly Sprite[] CursorSprite = new Sprite[7];
public UISprite RadiationSprite { get; private set; } public UISprite RadiationSprite { get; private set; }
public SpriteSheet RadiationAnimSpriteSheet { get; private set; }
public UISprite UIGlow { get; private set; } public UISprite UIGlow { get; private set; }
public UISprite UIGlowCircular { get; private set; } public UISprite UIGlowCircular { get; private set; }
@@ -214,6 +215,9 @@ namespace Barotrauma
case "radiation": case "radiation":
RadiationSprite = new UISprite(subElement); RadiationSprite = new UISprite(subElement);
break; break;
case "radiationanimspritesheet":
RadiationAnimSpriteSheet = new SpriteSheet(subElement);
break;
case "uiglowcircular": case "uiglowcircular":
UIGlowCircular = new UISprite(subElement); UIGlowCircular = new UISprite(subElement);
break; break;
@@ -279,7 +279,8 @@ namespace Barotrauma
/// If the rectT height is set 0, the height is calculated from the text. /// If the rectT height is set 0, the height is calculated from the text.
/// </summary> /// </summary>
public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null, public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false) Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null,
bool playerInput = false, bool parseRichText = false)
: base(style, rectT) : base(style, rectT)
{ {
if (color.HasValue) if (color.HasValue)
@@ -291,6 +292,12 @@ namespace Barotrauma
OverrideTextColor(textColor.Value); OverrideTextColor(textColor.Value);
} }
if (parseRichText)
{
richTextData = RichTextData.GetRichTextData(text, out text);
hasColorHighlight = richTextData != null;
}
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font, //if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
//use the default CJK font as a fallback //use the default CJK font as a fallback
var selectedFont = originalFont = font ?? GUI.Font; var selectedFont = originalFont = font ?? GUI.Font;
@@ -70,6 +70,14 @@ namespace Barotrauma
} }
private string selectedTip; private string selectedTip;
private List<RichTextData> selectedTipRichTextData;
private bool selectedTipRichTextUnparsed;
private void SetSelectedTip(string tip)
{
selectedTip = tip;
selectedTipRichTextData = null;
selectedTipRichTextUnparsed = true;
}
private readonly object loadMutex = new object(); private readonly object loadMutex = new object();
private float? loadState; private float? loadState;
@@ -115,7 +123,7 @@ namespace Barotrauma
overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png"); overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png");
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero); noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true; DrawLoadingText = true;
selectedTip = TextManager.Get("LoadingScreenTip", true); SetSelectedTip(TextManager.Get("LoadingScreenTip", true));
} }
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime) public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
@@ -215,14 +223,34 @@ namespace Barotrauma
if (GUI.Font != null && selectedTip != null) if (GUI.Font != null && selectedTip != null)
{ {
if (selectedTipRichTextUnparsed)
{
selectedTipRichTextData = RichTextData.GetRichTextData(selectedTip, out selectedTip);
selectedTipRichTextUnparsed = false;
}
string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font); string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font);
string[] lines = wrappedTip.Split('\n'); string[] lines = wrappedTip.Split('\n');
float lineHeight = GUI.Font.MeasureString(selectedTip).Y; float lineHeight = GUI.Font.MeasureString(selectedTip).Y;
for (int i = 0; i < lines.Length; i++) if (selectedTipRichTextData != null)
{ {
GUI.Font.DrawString(spriteBatch, lines[i], int rtdOffset = 0;
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White); for (int i = 0; i < lines.Length; i++)
{
GUI.Font.DrawStringWithColors(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White,
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTipRichTextData, rtdOffset);
rtdOffset += lines[i].Length;
}
}
else
{
for (int i = 0; i < lines.Length; i++)
{
GUI.Font.DrawString(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White);
}
} }
} }
@@ -302,7 +330,7 @@ namespace Barotrauma
{ {
GameMain.Config.Language = language; GameMain.Config.Language = language;
//reload tip in the selected language //reload tip in the selected language
selectedTip = TextManager.Get("LoadingScreenTip", true); SetSelectedTip(TextManager.Get("LoadingScreenTip", true));
GameMain.Config.SetDefaultBindings(legacy: false); GameMain.Config.SetDefaultBindings(legacy: false);
GameMain.Config.CheckBindings(useDefaults: true); GameMain.Config.CheckBindings(useDefaults: true);
WaitForLanguageSelection = false; WaitForLanguageSelection = false;
@@ -364,7 +392,7 @@ namespace Barotrauma
{ {
drawn = false; drawn = false;
LoadState = null; LoadState = null;
selectedTip = TextManager.Get("LoadingScreenTip", true); SetSelectedTip(TextManager.Get("LoadingScreenTip", true));
currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture; currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture;
while (!drawn) while (!drawn)
@@ -1284,7 +1284,8 @@ namespace Barotrauma
// Add items on the sub(s) // Add items on the sub(s)
Submarine.MainSub?.GetItems(true) Submarine.MainSub?.GetItems(true)
.Where(i => i.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached) && .Where(i => i.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached) &&
i.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) i.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null)) &&
ItemAndAllContainersInteractable(i))
.ForEach(i => AddToOwnedItems(i.Prefab)); .ForEach(i => AddToOwnedItems(i.Prefab));
// Add items in character inventories // Add items in character inventories
@@ -1302,6 +1303,16 @@ namespace Barotrauma
ownedItemsUpdateTimer = 0.0f; ownedItemsUpdateTimer = 0.0f;
static bool ItemAndAllContainersInteractable(Item item)
{
do
{
if (!item.IsPlayerTeamInteractable) { return false; }
item = item.Container;
} while (item != null);
return true;
}
void AddToOwnedItems(ItemPrefab itemPrefab, int amount = 1) void AddToOwnedItems(ItemPrefab itemPrefab, int amount = 1)
{ {
if (OwnedItems.ContainsKey(itemPrefab)) if (OwnedItems.ContainsKey(itemPrefab))
@@ -882,10 +882,15 @@ namespace Barotrauma
GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null); GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null);
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.225f, 0f) }, false, childAnchor: Anchor.TopLeft); GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.225f, 0f) }, false, childAnchor: Anchor.TopLeft);
string missionNameString = ToolBox.WrapText(mission.Name, missionTextGroup.Rect.Width, GUI.LargeFont);
string missionDescriptionString = ToolBox.WrapText(mission.Description, missionTextGroup.Rect.Width, GUI.Font);
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward)); string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward));
string missionRewardString = ToolBox.WrapText(TextManager.GetWithVariable("MissionReward", "[reward]", rewardText), missionTextGroup.Rect.Width, GUI.Font);
var missionNameRichTextData = RichTextData.GetRichTextData(mission.Name, out string missionNameString);
var missionDescriptionRichTextData = RichTextData.GetRichTextData(mission.Description, out string missionDescriptionString);
var missionRewardRichTextData = RichTextData.GetRichTextData(TextManager.GetWithVariable("MissionReward", "[reward]", rewardText), out string missionRewardString);
missionNameString = ToolBox.WrapText(missionNameString, missionTextGroup.Rect.Width, GUI.LargeFont);
missionDescriptionString = ToolBox.WrapText(missionDescriptionString, missionTextGroup.Rect.Width, GUI.Font);
missionRewardString = ToolBox.WrapText(missionRewardString, missionTextGroup.Rect.Width, GUI.Font);
Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString); Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString); Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString);
@@ -901,11 +906,11 @@ namespace Barotrauma
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight); Point iconSize = new Point(iconWidth, iconHeight);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor, HoverColor = mission.Prefab.IconColor }; new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor };
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameRichTextData, missionNameString, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardString); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardRichTextData, missionRewardString);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionRichTextData, missionDescriptionString);
} }
} }
else else
@@ -466,6 +466,27 @@ namespace Barotrauma
while (Config.WaitingForAutoUpdate) { yield return CoroutineStatus.Running; } while (Config.WaitingForAutoUpdate) { yield return CoroutineStatus.Running; }
} }
#if DEBUG
if (Config.ModBreakerMode)
{
Config.SelectCorePackage(ContentPackage.CorePackages.GetRandom());
foreach (var regularPackage in ContentPackage.RegularPackages)
{
if (Rand.Range(0.0, 1.0) <= 0.5)
{
Config.EnableRegularPackage(regularPackage);
}
else
{
Config.DisableRegularPackage(regularPackage);
}
}
ContentPackage.SortContentPackages(p =>
{
return Rand.Int(int.MaxValue);
});
}
#endif
if (Config.AllEnabledPackages.None()) if (Config.AllEnabledPackages.None())
{ {
@@ -535,6 +556,7 @@ namespace Barotrauma
Order.Init(); Order.Init();
EventManagerSettings.Init(); EventManagerSettings.Init();
BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature)); BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature));
HintManager.Init();
TitleScreen.LoadState = 50.0f; TitleScreen.LoadState = 50.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -920,6 +942,8 @@ namespace Barotrauma
Client.AddToGUIUpdateList(); Client.AddToGUIUpdateList();
} }
SubmarinePreview.AddToGUIUpdateList();
FileSelection.AddToGUIUpdateList(); FileSelection.AddToGUIUpdateList();
DebugConsole.AddToGUIUpdateList(); DebugConsole.AddToGUIUpdateList();
@@ -330,7 +330,10 @@ namespace Barotrauma
if (removeInfo) { characterInfos.Remove(character.Info); } if (removeInfo) { characterInfos.Remove(character.Info); }
} }
private void AddCharacterToCrewList(Character character) /// <summary>
/// Add character to the list without actually adding it to the crew
/// </summary>
public void AddCharacterToCrewList(Character character)
{ {
if (character == null) { return; } if (character == null) { return; }
@@ -416,7 +419,8 @@ namespace Barotrauma
font: font, font: font,
textColor: character.Info?.Job?.Prefab?.UIColor) textColor: character.Info?.Job?.Prefab?.UIColor)
{ {
CanBeFocused = false CanBeFocused = false,
UserData = "name"
}; };
nameBlock.Text = ToolBox.LimitString(character.Name, font, (int)nameBlock.Rect.Width); nameBlock.Text = ToolBox.LimitString(character.Name, font, (int)nameBlock.Rect.Width);
@@ -429,22 +433,7 @@ namespace Barotrauma
{ {
UserData = character UserData = character
}; };
SetCharacterButtonTooltip(characterButton);
// Only create a tooltip if the name doesn't fit the name block
if (nameBlock.Text.EndsWith("..."))
{
var characterTooltip = character.Name;
if (character.Info?.Job?.Name != null) { characterTooltip += " (" + character.Info.Job.Name + ")"; };
characterButton.ToolTip = characterTooltip;
if (character.Info?.Job?.Prefab != null)
{
characterButton.TooltipRichTextData = new List<RichTextData>() { new RichTextData()
{
Color = character.Info.Job.Prefab.UIColor,
EndIndex = characterTooltip.Length - 1
}};
}
}
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
@@ -482,6 +471,14 @@ namespace Barotrauma
UserData = character UserData = character
}; };
currentOrderList.RectTransform.IsFixedSize = true; currentOrderList.RectTransform.IsFixedSize = true;
currentOrderList.OnAddedToGUIUpdateList += (component) =>
{
if (component is GUIListBox list)
{
list.CanBeFocused = CanIssueOrders;
list.CanDragElements = CanIssueOrders && list.Content.CountChildren > 1;
}
};
// Previous orders // Previous orders
new GUILayoutGroup(new RectTransform(Vector2.One, parent: orderGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) new GUILayoutGroup(new RectTransform(Vector2.One, parent: orderGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
@@ -526,14 +523,26 @@ namespace Barotrauma
}; };
} }
private void SetCharacterButtonTooltip(GUIButton characterButton)
{
var character = (Character)characterButton.UserData;
if (character?.Info?.Job?.Prefab == null) { return; }
string color = XMLExtensions.ColorToString(character.Info.Job.Prefab.UIColor);
string tooltip = $"‖color:{color}‖{character.Name} ({character.Info.Job.Name})‖color:end‖";
var richTextData = RichTextData.GetRichTextData(tooltip, out string sanitizedTooltip);
characterButton.ToolTip = sanitizedTooltip;
characterButton.TooltipRichTextData = richTextData;
}
/// <summary> /// <summary>
/// Sets which character is selected in the crew UI (highlight effect etc) /// Sets which character is selected in the crew UI (highlight effect etc)
/// </summary> /// </summary>
public bool CharacterClicked(GUIComponent component, object selection) public bool CharacterClicked(GUIComponent component, object selection)
{ {
if (!AllowCharacterSwitch) { return false; } if (!AllowCharacterSwitch) { return false; }
Character character = selection as Character; if (!(selection is Character character) || character.IsDead || character.IsUnconscious) { return false; }
if (character == null || character.IsDead || character.IsUnconscious) { return false; } if (!character.IsOnPlayerTeam) { return false; }
SelectCharacter(character); SelectCharacter(character);
if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber = null; } if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber = null; }
return true; return true;
@@ -588,6 +597,15 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
partial void RenameCharacterProjSpecific(CharacterInfo characterInfo)
{
if (!(crewList.Content.GetChildByUserData(characterInfo?.Character) is GUIComponent characterComponent)) { return; }
if (!(characterComponent.FindChild("name", recursive: true) is GUITextBlock nameBlock)) { return; }
nameBlock.Text = ToolBox.LimitString(characterInfo.Name, nameBlock.Font, nameBlock.Rect.Width);
if (!(characterComponent.FindChild(c => c is GUIButton && c.UserData == characterInfo?.Character) is GUIButton characterButton)) { return; }
SetCharacterButtonTooltip(characterButton);
}
#endregion #endregion
#region Dialog #region Dialog
@@ -835,7 +853,6 @@ namespace Barotrauma
if (order == null || order.Identifier == dismissedOrderPrefab.Identifier || updatedExistingIcon) if (order == null || order.Identifier == dismissedOrderPrefab.Identifier || updatedExistingIcon)
{ {
currentOrderIconList.CanDragElements = currentOrderIconList.Content.CountChildren > 1;
RearrangeIcons(); RearrangeIcons();
return; return;
} }
@@ -874,7 +891,6 @@ namespace Barotrauma
nodeIcon.RectTransform.RepositionChildInHierarchy(hierarchyIndex); nodeIcon.RectTransform.RepositionChildInHierarchy(hierarchyIndex);
} }
currentOrderIconList.CanDragElements = currentOrderIconList.Content.CountChildren > 1;
RearrangeIcons(); RearrangeIcons();
void RearrangeIcons() void RearrangeIcons()
@@ -1276,6 +1292,7 @@ namespace Barotrauma
} }
DisableCommandUI(); DisableCommandUI();
Character.Controlled = character; Character.Controlled = character;
HintManager.OnChangeCharacter();
} }
private int TryAdjustIndex(int amount) private int TryAdjustIndex(int amount)
@@ -1537,6 +1554,11 @@ namespace Barotrauma
if (characterComponent.UserData is Character character) if (characterComponent.UserData is Character character)
{ {
characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID; characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID;
if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null &&
(character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
{
characterComponent.Visible = true;
}
if (characterComponent.Visible) if (characterComponent.Visible)
{ {
if (character == Character.Controlled && characterComponent.State != GUIComponent.ComponentState.Selected) if (character == Character.Controlled && characterComponent.State != GUIComponent.ComponentState.Selected)
@@ -1817,6 +1839,8 @@ namespace Barotrauma
{ {
Character.Controlled.dontFollowCursor = true; Character.Controlled.dontFollowCursor = true;
} }
HintManager.OnShowCommandInterface();
} }
private void ToggleCommandUI() private void ToggleCommandUI()
@@ -3218,14 +3242,14 @@ namespace Barotrauma
#endif #endif
if (order.Identifier == dismissedOrderPrefab.Identifier) if (order.Identifier == dismissedOrderPrefab.Identifier)
{ {
return characters.FindAll(c => !c.IsDismissed).OrderBy(c => c.Info.DisplayName).ToList(); return characters.Union(GetOrderableFriendlyNPCs()).Where(c => !c.IsDismissed).OrderBy(c => c.Info.DisplayName).ToList();
} }
return GetCharactersSortedForOrder(order, order.Identifier != "follow").ToList(); return GetCharactersSortedForOrder(order, order.Identifier != "follow").ToList();
} }
private IEnumerable<Character> GetCharactersSortedForOrder(Order order, bool includeSelf) private IEnumerable<Character> GetCharactersSortedForOrder(Order order, bool includeSelf)
{ {
return characters.FindAll(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID)) return characters.Where(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID)).Union(GetOrderableFriendlyNPCs())
// 1. Prioritize those who are on the same submarine than the controlled character // 1. Prioritize those who are on the same submarine than the controlled character
.OrderByDescending(c => Character.Controlled == null || c.Submarine == Character.Controlled.Submarine) .OrderByDescending(c => Character.Controlled == null || c.Submarine == Character.Controlled.Submarine)
// 2. Prioritize those who are already ordered to operate the item target of the new 'operate' order, or given the same maintenance order as now issued // 2. Prioritize those who are already ordered to operate the item target of the new 'operate' order, or given the same maintenance order as now issued
@@ -3242,6 +3266,12 @@ namespace Barotrauma
.ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill)); .ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill));
} }
private IEnumerable<Character> GetOrderableFriendlyNPCs()
{
return crewList.Content.Children.Where(c => c.UserData is Character character && character.TeamID == CharacterTeamType.FriendlyNPC).Select(c => (Character)c.UserData);
}
#endregion #endregion
#endregion #endregion
@@ -63,7 +63,7 @@ namespace Barotrauma
{ {
new GUIMessageBox( new GUIMessageBox(
mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name, mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name,
mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon) mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon, parseRichText: true)
{ {
IconColor = mission.Prefab.IconColor, IconColor = mission.Prefab.IconColor,
UserData = "missionstartmessage" UserData = "missionstartmessage"
@@ -437,8 +437,10 @@ namespace Barotrauma
{ {
ShowCampaignUI = false; ShowCampaignUI = false;
} }
HintManager.OnAvailableTransition(transitionType);
} }
} }
public override void End(TransitionType transitionType = TransitionType.None) public override void End(TransitionType transitionType = TransitionType.None)
{ {
base.End(transitionType); base.End(transitionType);
@@ -649,7 +651,7 @@ namespace Barotrauma
{ {
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, mapSeed); GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Unsure, mapSeed);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID; campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -778,15 +780,28 @@ namespace Barotrauma
pendingHires.Add(msg.ReadInt32()); pendingHires.Add(msg.ReadInt32());
} }
bool validateHires = msg.ReadBoolean(); ushort hiredLength = msg.ReadUInt16();
List<CharacterInfo> hiredCharacters = new List<CharacterInfo>();
for (int i = 0; i < hiredLength; i++)
{
CharacterInfo hired = CharacterInfo.ClientRead("human", msg);
hired.Salary = msg.ReadInt32();
hiredCharacters.Add(hired);
}
bool renameCrewMember = msg.ReadBoolean();
if (renameCrewMember)
{
int renamedIdentifier = msg.ReadInt32();
string newName = msg.ReadString();
CharacterInfo renamedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
if (renamedCharacter != null) { CrewManager.RenameCharacter(renamedCharacter, newName); }
}
bool fireCharacter = msg.ReadBoolean(); bool fireCharacter = msg.ReadBoolean();
int firedIdentifier = -1;
if (fireCharacter) { firedIdentifier = msg.ReadInt32(); }
if (fireCharacter) if (fireCharacter)
{ {
int firedIdentifier = msg.ReadInt32();
CharacterInfo firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier); CharacterInfo firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier);
// this one might and is allowed to be null since the character is already fired on the original sender's game // this one might and is allowed to be null since the character is already fired on the original sender's game
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); } if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
@@ -794,10 +809,10 @@ namespace Barotrauma
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null) if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null)
{ {
CampaignUI?.CrewManagement?.SetHireables(map.CurrentLocation, availableHires); CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires);
if (validateHires) { CampaignUI?.CrewManagement.ValidatePendingHires(); } if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); }
CampaignUI?.CrewManagement?.SetPendingHires(pendingHires, map?.CurrentLocation); CampaignUI.CrewManagement.SetPendingHires(pendingHires, map.CurrentLocation);
if (fireCharacter) { CampaignUI?.CrewManagement.UpdateCrew(); } if (renameCrewMember || fireCharacter) { CampaignUI.CrewManagement.UpdateCrew(); }
} }
} }
@@ -57,11 +57,12 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Instantiates a new single player campaign /// Instantiates a new single player campaign
/// </summary> /// </summary>
private SinglePlayerCampaign(string mapSeed) : base(GameModePreset.SinglePlayerCampaign) private SinglePlayerCampaign(string mapSeed, CampaignSettings settings) : base(GameModePreset.SinglePlayerCampaign)
{ {
CampaignMetadata = new CampaignMetadata(this); CampaignMetadata = new CampaignMetadata(this);
UpgradeManager = new UpgradeManager(this); UpgradeManager = new UpgradeManager(this);
map = new Map(this, mapSeed); map = new Map(this, mapSeed, settings);
Settings = settings;
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{ {
for (int i = 0; i < jobPrefab.InitialCount; i++) for (int i = 0; i < jobPrefab.InitialCount; i++)
@@ -85,11 +86,14 @@ namespace Barotrauma
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "campaignsettings":
Settings = new CampaignSettings(subElement);
break;
case "crew": case "crew":
GameMain.GameSession.CrewManager = new CrewManager(subElement, true); GameMain.GameSession.CrewManager = new CrewManager(subElement, true);
break; break;
case "map": case "map":
map = Map.Load(this, subElement); map = Map.Load(this, subElement, Settings);
break; break;
case "metadata": case "metadata":
CampaignMetadata = new CampaignMetadata(this, subElement); CampaignMetadata = new CampaignMetadata(this, subElement);
@@ -141,9 +145,9 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Start a completely new single player campaign /// Start a completely new single player campaign
/// </summary> /// </summary>
public static SinglePlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub) public static SinglePlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub, CampaignSettings settings)
{ {
var campaign = new SinglePlayerCampaign(mapSeed); var campaign = new SinglePlayerCampaign(mapSeed, settings);
return campaign; return campaign;
} }
@@ -608,6 +612,7 @@ namespace Barotrauma
{ {
ShowCampaignUI = false; ShowCampaignUI = false;
} }
HintManager.OnAvailableTransition(transitionType);
} }
if (!crewDead) if (!crewDead)
@@ -629,9 +634,9 @@ namespace Barotrauma
if (nextLevel == null) if (nextLevel == null)
{ {
//no level selected -> force the player to select one //no level selected -> force the player to select one
ForceMapUI = true;
CampaignUI.SelectTab(InteractionType.Map); CampaignUI.SelectTab(InteractionType.Map);
map.SelectLocation(-1); map.SelectLocation(-1);
ForceMapUI = true;
return false; return false;
} }
else if (transitionType == TransitionType.ProgressToNextEmptyLocation) else if (transitionType == TransitionType.ProgressToNextEmptyLocation)
@@ -704,6 +709,7 @@ namespace Barotrauma
new XAttribute("purchasedhullrepairs", PurchasedHullRepairs), new XAttribute("purchasedhullrepairs", PurchasedHullRepairs),
new XAttribute("purchaseditemrepairs", PurchasedItemRepairs), new XAttribute("purchaseditemrepairs", PurchasedItemRepairs),
new XAttribute("cheatsenabled", CheatsEnabled)); new XAttribute("cheatsenabled", CheatsEnabled));
modeElement.Add(Settings.Save());
//save and remove all items that are in someone's inventory so they don't get included in the sub file as well //save and remove all items that are in someone's inventory so they don't get included in the sub file as well
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
@@ -99,7 +99,7 @@ namespace Barotrauma.Tutorials
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition; captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>(); tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>(); tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor")); var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("medicaldoctor"));
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor"); captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
captain_medic.TeamID = CharacterTeamType.Team1; captain_medic.TeamID = CharacterTeamType.Team1;
captain_medic.GiveJobItems(null); captain_medic.GiveJobItems(null);
@@ -122,17 +122,17 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_lockedDoor_1, null, false); SetDoorAccess(tutorial_lockedDoor_1, null, false);
SetDoorAccess(tutorial_lockedDoor_2, null, false); SetDoorAccess(tutorial_lockedDoor_2, null, false);
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic")); var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("mechanic"));
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic"); captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.TeamID = CharacterTeamType.Team1; captain_mechanic.TeamID = CharacterTeamType.Team1;
captain_mechanic.GiveJobItems(); captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer"); captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.TeamID = CharacterTeamType.Team1; captain_security.TeamID = CharacterTeamType.Team1;
captain_security.GiveJobItems(); captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer"); captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.TeamID = CharacterTeamType.Team1; captain_engineer.TeamID = CharacterTeamType.Team1;
captain_engineer.GiveJobItems(); captain_engineer.GiveJobItems();
@@ -78,7 +78,7 @@ namespace Barotrauma.Tutorials
var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull; var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull;
medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull; medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull;
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("assistant"));
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1"); patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
patient1.TeamID = CharacterTeamType.Team1; patient1.TeamID = CharacterTeamType.Team1;
patient1.GiveJobItems(null); patient1.GiveJobItems(null);
@@ -86,26 +86,26 @@ namespace Barotrauma.Tutorials
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false); patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false);
patient1.AIController.Enabled = false; patient1.AIController.Enabled = false;
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("assistant"));
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2"); patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
patient2.TeamID = CharacterTeamType.Team1; patient2.TeamID = CharacterTeamType.Team1;
patient2.GiveJobItems(null); patient2.GiveJobItems(null);
patient2.CanSpeak = false; patient2.CanSpeak = false;
patient2.AIController.Enabled = false; patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient1.TeamID = CharacterTeamType.Team1; subPatient1.TeamID = CharacterTeamType.Team1;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1); subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient2.TeamID = CharacterTeamType.Team1; subPatient2.TeamID = CharacterTeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2); subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient3.TeamID = CharacterTeamType.Team1; subPatient3.TeamID = CharacterTeamType.Team1;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
@@ -110,7 +110,7 @@ namespace Barotrauma.Tutorials
} }
CharacterInfo charInfo = configElement.Element("Character") == null ? CharacterInfo charInfo = configElement.Element("Character") == null ?
new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) : new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")) :
new CharacterInfo(configElement.Element("Character")); new CharacterInfo(configElement.Element("Character"));
WayPoint wayPoint = GetSpawnPoint(charInfo); WayPoint wayPoint = GetSpawnPoint(charInfo);
@@ -535,15 +535,15 @@ namespace Barotrauma.Tutorials
titleBlock.RectTransform.IsFixedSize = true; titleBlock.RectTransform.IsFixedSize = true;
} }
List<RichTextData> richTextData = RichTextData.GetRichTextData(text, out text); List<RichTextData> richTextData = RichTextData.GetRichTextData(" " + text, out text);
GUITextBlock textBlock; GUITextBlock textBlock;
if (richTextData == null) if (richTextData == null)
{ {
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true); textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
} }
else else
{ {
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), richTextData, " " + text, wrap: true); textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), richTextData, text, wrap: true);
} }
textBlock.RectTransform.IsFixedSize = true; textBlock.RectTransform.IsFixedSize = true;
@@ -88,6 +88,8 @@ namespace Barotrauma
} }
} }
} }
HintManager.Update();
} }
public void Draw(SpriteBatch spriteBatch) public void Draw(SpriteBatch spriteBatch)
@@ -336,7 +336,7 @@ namespace Barotrauma
{ {
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward)); string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.GetWithVariable("MissionReward", "[reward]", rewardText)); TextManager.GetWithVariable("MissionReward", "[reward]", rewardText), parseRichText: true);
} }
if (displayedMission != missionsToDisplay.Last()) if (displayedMission != missionsToDisplay.Last())
@@ -20,6 +20,7 @@ namespace Barotrauma
Audio, Audio,
VoiceChat, VoiceChat,
Controls, Controls,
Gameplay,
#if DEBUG #if DEBUG
Debug Debug
#endif #endif
@@ -532,29 +533,19 @@ namespace Barotrauma
UserData = tab UserData = tab
}; };
float tabWidth = 0.25f; float tabWidth = 1.0f / tabs.Length;
#if DEBUG #if DEBUG
tabWidth = 0.2f; string buttonText = tab != Tab.Debug ? TextManager.Get("SettingsTab." + tab.ToString()) : "Debug";
if (tab != Tab.Debug) #else
{ string buttonText = TextManager.Get("SettingsTab." + tab.ToString());
#endif #endif
tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform),
TextManager.Get("SettingsTab." + tab.ToString()), style: "GUITabButton") tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform), style: "GUITabButton")
{
UserData = tab,
OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; }
};
#if DEBUG
}
else
{ {
tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform), "Debug", style: "GUITabButton") UserData = tab,
{ OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; }
UserData = tab, };
OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; } tabButtons[(int)tab].Text = ToolBox.LimitString(buttonText, tabButtons[(int)tab].Font, (int)(0.75f * tabWidth * tabButtonHolder.Rect.Width));
};
}
#endif
} }
new GUIButton(new RectTransform(new Vector2(0.05f, 0.75f), tabButtonHolder.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.2f) }, style: "GUIBugButton") new GUIButton(new RectTransform(new Vector2(0.05f, 0.75f), tabButtonHolder.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.2f) }, style: "GUIBugButton")
@@ -669,19 +660,6 @@ namespace Barotrauma
Selected = TextureCompressionEnabled Selected = TextureCompressionEnabled
}; };
GUITickBox pauseOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, leftColumn.RectTransform),
TextManager.Get("PauseOnFocusLost"))
{
Selected = PauseOnFocusLost,
ToolTip = TextManager.Get("PauseOnFocusLostToolTip"),
OnSelected = (tickBox) =>
{
PauseOnFocusLost = tickBox.Selected;
UnsavedSettings = true;
return true;
}
};
GUITextBlock particleLimitText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ParticleLimit"), font: GUI.SubHeadingFont, wrap: true); GUITextBlock particleLimitText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ParticleLimit"), font: GUI.SubHeadingFont, wrap: true);
GUIScrollBar particleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: "GUISlider", GUIScrollBar particleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: "GUISlider",
barSize: 0.1f) barSize: 0.1f)
@@ -773,56 +751,6 @@ namespace Barotrauma
} }
}; };
GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont, wrap: true);
GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = HUDScaleText,
BarScroll = (HUDScale - MinHUDScale) / (MaxHUDScale - MinHUDScale),
OnMoved = (scrollBar, scroll) =>
{
HUDScale = MathHelper.Lerp(MinHUDScale, MaxHUDScale, scroll);
ChangeSliderText(scrollBar, HUDScale);
OnHUDScaleChanged?.Invoke();
return true;
},
Step = 0.02f
};
HUDScaleScrollBar.OnMoved(HUDScaleScrollBar, HUDScaleScrollBar.BarScroll);
GUITextBlock inventoryScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("InventoryScale"), font: GUI.SubHeadingFont);
GUIScrollBar inventoryScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = inventoryScaleText,
BarScroll = (InventoryScale - MinInventoryScale) / (MaxInventoryScale - MinInventoryScale),
OnMoved = (scrollBar, scroll) =>
{
InventoryScale = MathHelper.Lerp(MinInventoryScale, MaxInventoryScale, scroll);
ChangeSliderText(scrollBar, InventoryScale);
return true;
},
Step = 0.02f
};
inventoryScaleScrollBar.OnMoved(inventoryScaleScrollBar, inventoryScaleScrollBar.BarScroll);
GUITextBlock textScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("TextScale"), font: GUI.SubHeadingFont);
GUIScrollBar textScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = textScaleText,
BarScroll = (TextScale - MinTextScale) / (MaxTextScale - MinTextScale),
OnMoved = (scrollBar, scroll) =>
{
TextScale = MathHelper.Lerp(MinTextScale, MaxTextScale, scroll);
textScaleDirty = true;
ChangeSliderText(scrollBar, TextScale);
return true;
},
Step = 0.01f
};
textScaleScrollBar.OnMoved(textScaleScrollBar, textScaleScrollBar.BarScroll);
/// Audio tab ---------------------------------------------------------------- /// Audio tab ----------------------------------------------------------------
var audioContent = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.97f), tabs[(int)Tab.Audio].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) var audioContent = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.97f), tabs[(int)Tab.Audio].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
@@ -1380,6 +1308,88 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(defaultBindingsButton.TextBlock, legacyBindingsButton.TextBlock); GUITextBlock.AutoScaleAndNormalize(defaultBindingsButton.TextBlock, legacyBindingsButton.TextBlock);
}; };
/// Gameplay tab -------------------------------------------------------------
var gameplaySettingsGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.46f, 0.95f), tabs[(int)Tab.Gameplay].RectTransform, Anchor.TopLeft)
{ RelativeOffset = new Vector2(0.025f, 0.02f) })
{ RelativeSpacing = 0.01f };
GUITickBox pauseOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform),
TextManager.Get("PauseOnFocusLost"))
{
Selected = PauseOnFocusLost,
ToolTip = TextManager.Get("PauseOnFocusLostToolTip"),
OnSelected = (tickBox) =>
{
PauseOnFocusLost = tickBox.Selected;
UnsavedSettings = true;
return true;
}
};
GUITickBox disableInGameHintsBox = new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform),
TextManager.Get("DisableInGameHints"))
{
Selected = DisableInGameHints,
ToolTip = TextManager.Get("DisableInGameHintsToolTip"),
OnSelected = (tickBox) =>
{
DisableInGameHints = tickBox.Selected;
UnsavedSettings = true;
return true;
}
};
GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont, wrap: true);
GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = HUDScaleText,
BarScroll = (HUDScale - MinHUDScale) / (MaxHUDScale - MinHUDScale),
OnMoved = (scrollBar, scroll) =>
{
HUDScale = MathHelper.Lerp(MinHUDScale, MaxHUDScale, scroll);
ChangeSliderText(scrollBar, HUDScale);
OnHUDScaleChanged?.Invoke();
return true;
},
Step = 0.02f
};
HUDScaleScrollBar.OnMoved(HUDScaleScrollBar, HUDScaleScrollBar.BarScroll);
GUITextBlock inventoryScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("InventoryScale"), font: GUI.SubHeadingFont);
GUIScrollBar inventoryScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = inventoryScaleText,
BarScroll = (InventoryScale - MinInventoryScale) / (MaxInventoryScale - MinInventoryScale),
OnMoved = (scrollBar, scroll) =>
{
InventoryScale = MathHelper.Lerp(MinInventoryScale, MaxInventoryScale, scroll);
ChangeSliderText(scrollBar, InventoryScale);
return true;
},
Step = 0.02f
};
inventoryScaleScrollBar.OnMoved(inventoryScaleScrollBar, inventoryScaleScrollBar.BarScroll);
GUITextBlock textScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("TextScale"), font: GUI.SubHeadingFont);
GUIScrollBar textScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = textScaleText,
BarScroll = (TextScale - MinTextScale) / (MaxTextScale - MinTextScale),
OnMoved = (scrollBar, scroll) =>
{
TextScale = MathHelper.Lerp(MinTextScale, MaxTextScale, scroll);
textScaleDirty = true;
ChangeSliderText(scrollBar, TextScale);
return true;
},
Step = 0.01f
};
textScaleScrollBar.OnMoved(textScaleScrollBar, textScaleScrollBar.BarScroll);
/// Bottom buttons -------------------------------------------------------------
new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft), new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft),
TextManager.Get("Cancel")) TextManager.Get("Cancel"))
{ {
@@ -1465,55 +1475,54 @@ namespace Barotrauma
{ RelativeOffset = new Vector2(0.02f, 0.02f) }) { RelativeOffset = new Vector2(0.02f, 0.02f) })
{ RelativeSpacing = 0.01f }; { RelativeSpacing = 0.01f };
var automaticQuickStartTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Automatic quickstart enabled", style: "GUITickBox"); void addDebugTickBox(bool initialValue, Action<bool> set, string label, string tooltip)
automaticQuickStartTickBox.Selected = AutomaticQuickStartEnabled;
automaticQuickStartTickBox.ToolTip = "Will the game automatically move on to Quickstart when the game is launched";
automaticQuickStartTickBox.OnSelected = (tickBox) =>
{ {
AutomaticQuickStartEnabled = tickBox.Selected; var tickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), label, style: "GUITickBox");
UnsavedSettings = true; tickBox.Selected = initialValue;
return true; tickBox.ToolTip = tooltip;
}; tickBox.OnSelected = (tickBox) =>
{
set(tickBox.Selected);
UnsavedSettings = true;
return true;
};
}
var automaticCampaignLoadTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Automatic campaign load enabled", style: "GUITickBox"); addDebugTickBox(
automaticCampaignLoadTickBox.Selected = AutomaticCampaignLoadEnabled; AutomaticQuickStartEnabled,
automaticCampaignLoadTickBox.ToolTip = "Will the game automatically load the latest campaign save when the game is launched"; (b) => AutomaticQuickStartEnabled = b,
automaticCampaignLoadTickBox.OnSelected = (tickBox) => "Automatic quickstart enabled",
{ "Will the game automatically move on to Quickstart when the game is launched");
AutomaticCampaignLoadEnabled = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var showSplashScreenTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Splash screen enabled", style: "GUITickBox"); addDebugTickBox(
showSplashScreenTickBox.Selected = EnableSplashScreen; AutomaticCampaignLoadEnabled,
showSplashScreenTickBox.ToolTip = "Are the splash screens shown when the game is launched"; (b) => AutomaticCampaignLoadEnabled = b,
showSplashScreenTickBox.OnSelected = (tickBox) => "Automatic campaign load enabled",
{ "Will the game automatically load the latest campaign save when the game is launched");
EnableSplashScreen = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var verboseLoggingTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Verbose logging enabled", style: "GUITickBox"); addDebugTickBox(
verboseLoggingTickBox.Selected = VerboseLogging; EnableSplashScreen,
verboseLoggingTickBox.ToolTip = "Should verbose logging be used"; (b) => EnableSplashScreen = b,
verboseLoggingTickBox.OnSelected = (tickBox) => "Splash screen enabled",
{ "Are the splash screens shown when the game is launched");
VerboseLogging = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var textManagerDebugModeTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "TextManager debug mode enabled", style: "GUITickBox"); addDebugTickBox(
textManagerDebugModeTickBox.Selected = TextManagerDebugModeEnabled; VerboseLogging,
textManagerDebugModeTickBox.ToolTip = "Does the TextManager return the text tags for debug purposes?"; (b) => VerboseLogging = b,
textManagerDebugModeTickBox.OnSelected = (tickBox) => "Verbose logging enabled",
{ "Should verbose logging be used");
TextManagerDebugModeEnabled = tickBox.Selected;
UnsavedSettings = true; addDebugTickBox(
return true; TextManagerDebugModeEnabled,
}; (b) => TextManagerDebugModeEnabled = b,
"TextManager debug mode enabled",
"Does the TextManager return the text tags for debug purposes?");
addDebugTickBox(
ModBreakerMode,
(b) => ModBreakerMode = b,
"Mod breaker mode enabled",
"Do horrible things when loading mods to see if it breaks?");
#endif #endif
UnsavedSettings = false; // Reset unsaved settings to false once the UI has been created UnsavedSettings = false; // Reset unsaved settings to false once the UI has been created
@@ -639,7 +639,13 @@ namespace Barotrauma
foreach (Item doubleClickedItem in doubleClickedItems) foreach (Item doubleClickedItem in doubleClickedItems)
{ {
QuickUseItem(doubleClickedItem, true, true, true, quickUseAction, playSound: doubleClickedItem == doubleClickedItems.First()); QuickUseItem(doubleClickedItem, true, true, true, quickUseAction, playSound: doubleClickedItem == doubleClickedItems.First());
if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment || !IsInLimbSlot(doubleClickedItem, InvSlotType.Any)) //only use one item if we're equipping or using it as a treatment
if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment)
{
break;
}
//if the item was put in a limb slot, only put one item from the stack
if (doubleClickedItem.ParentInventory == this && !IsInLimbSlot(doubleClickedItem, InvSlotType.Any))
{ {
break; break;
} }
@@ -810,6 +816,8 @@ namespace Barotrauma
highlightedSubInventorySlot.Inventory.HideTimer = 0.0f; highlightedSubInventorySlot.Inventory.HideTimer = 0.0f;
} }
} }
HintManager.OnShowSubInventory(slotRef?.Item);
} }
public void AssignQuickUseNumKeys() public void AssignQuickUseNumKeys()
@@ -39,6 +39,8 @@ namespace Barotrauma.Items.Components
private Pair<Rectangle, string> tooltip; private Pair<Rectangle, string> tooltip;
private GUITextBlock requiredTimeBlock;
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
CreateGUI(); CreateGUI();
@@ -522,7 +524,7 @@ namespace Barotrauma.Items.Components
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), ToolBox.SecondsToReadableTime(requiredTime), requiredTimeBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), ToolBox.SecondsToReadableTime(requiredTime),
font: GUI.SmallFont); font: GUI.SmallFont);
return true; return true;
} }
@@ -606,6 +608,12 @@ namespace Barotrauma.Items.Components
} }
} }
partial void UpdateRequiredTimeProjSpecific()
{
if (requiredTimeBlock == null) { return; }
requiredTimeBlock.Text = ToolBox.SecondsToReadableTime(timeUntilReady > 0.0f ? timeUntilReady : requiredTime);
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
int itemIndex = pendingFabricatedItem == null ? -1 : fabricationRecipes.IndexOf(pendingFabricatedItem); int itemIndex = pendingFabricatedItem == null ? -1 : fabricationRecipes.IndexOf(pendingFabricatedItem);
@@ -1380,6 +1380,7 @@ namespace Barotrauma.Items.Components
MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f)); MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f));
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; } if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
sonarBlips.Add(blip); sonarBlips.Add(blip);
HintManager.OnSonarSpottedCharacter(Item, c);
} }
continue; continue;
} }
@@ -1399,6 +1400,7 @@ namespace Barotrauma.Items.Components
MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f)); MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f));
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; } if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
sonarBlips.Add(blip); sonarBlips.Add(blip);
HintManager.OnSonarSpottedCharacter(Item, c);
} }
} }
} }
@@ -357,8 +357,8 @@ namespace Barotrauma.Items.Components
DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false))) DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false)))
{ {
// Undocking from an outpost // Undocking from an outpost
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
campaign.ShowCampaignUI = true; campaign.ShowCampaignUI = true;
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
return false; return false;
} }
else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null && else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null &&
@@ -748,7 +748,7 @@ namespace Barotrauma.Items.Components
} }
if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null) if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null)
{ {
steeringAdjustSpeed = character == null ? 0.2f : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f); steeringAdjustSpeed = character == null ? DefaultSteeringAdjustSpeed : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f);
Vector2 input = Vector2.Zero; Vector2 input = Vector2.Zero;
if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; } if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; } if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; }
@@ -98,8 +98,11 @@ namespace Barotrauma.Items.Components
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
float chargeRatio = charge / capacity; if (chargeIndicator != null)
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green); {
float chargeRatio = charge / capacity;
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
}
} }
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
@@ -167,11 +167,11 @@ namespace Barotrauma.Items.Components
{ {
if (DockingTarget.joint != null) if (DockingTarget.joint != null)
{ {
DockingTarget.Lock(isNetworkMessage: true, forcePosition: true); DockingTarget.Lock(isNetworkMessage: true);
} }
else else
{ {
Lock(isNetworkMessage: true, forcePosition: true); Lock(isNetworkMessage: true);
} }
} }
} }
@@ -327,10 +327,11 @@ namespace Barotrauma
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
if (!rect.Contains(pos)) { continue; } if (!rect.Contains(pos)) { continue; }
float iconScale = generationParams.LocationIconSize / location.Type.Sprite.size.X; Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite;
float iconScale = generationParams.LocationIconSize / locationSprite.size.X;
if (location == CurrentDisplayLocation) { iconScale *= 1.2f; } if (location == CurrentDisplayLocation) { iconScale *= 1.2f; }
Rectangle drawRect = location.Type.Sprite.SourceRect; Rectangle drawRect = locationSprite.SourceRect;
drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f); drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f);
drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f); drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f);
drawRect.X = (int)pos.X - drawRect.Width / 2; drawRect.X = (int)pos.X - drawRect.Width / 2;
@@ -375,13 +376,18 @@ namespace Barotrauma
foreach (LocationConnection connection in Connections) foreach (LocationConnection connection in Connections)
{ {
if (HighlightedLocation != CurrentDisplayLocation && if (HighlightedLocation != CurrentDisplayLocation &&
connection.Locations.Contains(HighlightedLocation) && connection.Locations.Contains(CurrentDisplayLocation)) connection.Locations.Contains(HighlightedLocation) &&
connection.Locations.Contains(CurrentDisplayLocation))
{ {
if (PlayerInput.PrimaryMouseButtonClicked() && if (PlayerInput.PrimaryMouseButtonClicked() &&
SelectedLocation != HighlightedLocation && HighlightedLocation != null) SelectedLocation != HighlightedLocation && HighlightedLocation != null)
{ {
if (connection.Locked)
{
new GUIMessageBox(string.Empty, TextManager.Get("LockedPathTooltip"));
}
//clients aren't allowed to select the location without a permission //clients aren't allowed to select the location without a permission
if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign() ?? false) else if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign() ?? false)
{ {
connectionHighlightState = 0.0f; connectionHighlightState = 0.0f;
SelectedConnection = connection; SelectedConnection = connection;
@@ -517,8 +523,6 @@ namespace Barotrauma
float rawNoiseScale = 1.0f + PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1)); float rawNoiseScale = 1.0f + PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1));
cameraNoiseStrength = PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1)); cameraNoiseStrength = PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1));
Radiation.Draw(spriteBatch, rect, zoom);
noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(), noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(),
startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)), startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)),
color : Color.White * cameraNoiseStrength * 0.1f, color : Color.White * cameraNoiseStrength * 0.1f,
@@ -534,6 +538,8 @@ namespace Barotrauma
color: Color.White * cameraNoiseStrength * 0.1f, color: Color.White * cameraNoiseStrength * 0.1f,
textureScale: Vector2.One * noiseScale); textureScale: Vector2.One * noiseScale);
Radiation.Draw(spriteBatch, rect, zoom);
Pair<Rectangle, string> tooltip = null; Pair<Rectangle, string> tooltip = null;
if (generationParams.ShowLocations) if (generationParams.ShowLocations)
{ {
@@ -549,7 +555,9 @@ namespace Barotrauma
if (IsInFogOfWar(location)) { continue; } if (IsInFogOfWar(location)) { continue; }
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
Rectangle drawRect = location.Type.Sprite.SourceRect; Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite;
Rectangle drawRect = locationSprite.SourceRect;
drawRect.X = (int)pos.X - drawRect.Width / 2; drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2; drawRect.Y = (int)pos.Y - drawRect.Width / 2;
@@ -562,20 +570,14 @@ namespace Barotrauma
color *= 0.5f; color *= 0.5f;
} }
// TODO proper visualization of this
if (location.Type.HasOutpost && !location.HasOutpost())
{
color = GUI.Style.Red;
}
float iconScale = location == CurrentDisplayLocation ? 1.2f : 1.0f; float iconScale = location == CurrentDisplayLocation ? 1.2f : 1.0f;
if (location == HighlightedLocation) if (location == HighlightedLocation)
{ {
iconScale *= 1.2f; iconScale *= 1.2f;
} }
location.Type.Sprite.Draw(spriteBatch, pos, color, locationSprite.Draw(spriteBatch, pos, color,
scale: generationParams.LocationIconSize / location.Type.Sprite.size.X * iconScale * zoom); scale: generationParams.LocationIconSize / locationSprite.size.X * iconScale * zoom);
if (location == CurrentDisplayLocation) if (location == CurrentDisplayLocation)
{ {
@@ -657,8 +659,11 @@ namespace Barotrauma
DrawDecorativeHUD(spriteBatch, rect); DrawDecorativeHUD(spriteBatch, rect);
bool drawRadiationTooltip = true;
if (HighlightedLocation != null) if (HighlightedLocation != null)
{ {
drawRadiationTooltip = false;
Vector2 pos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom; Vector2 pos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom;
pos.X += 50 * zoom; pos.X += 50 * zoom;
Vector2 nameSize = GUI.LargeFont.MeasureString(HighlightedLocation.Name); Vector2 nameSize = GUI.LargeFont.MeasureString(HighlightedLocation.Name);
@@ -693,14 +698,23 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + 4, repBarRect.Top), repValueText, GUI.Style.TextColor); GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + 4, repBarRect.Top), repValueText, GUI.Style.TextColor);
} }
} }
if (tooltip != null) if (tooltip != null)
{ {
GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First); GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First);
drawRadiationTooltip = false;
} }
if (connectionTooltip != null) if (connectionTooltip != null)
{ {
GUIComponent.DrawToolTip(spriteBatch, connectionTooltip.Second, connectionTooltip.First); GUIComponent.DrawToolTip(spriteBatch, connectionTooltip.Second, connectionTooltip.First);
drawRadiationTooltip = false;
} }
if (drawRadiationTooltip)
{
Radiation.DrawFront(spriteBatch);
}
spriteBatch.End(); spriteBatch.End();
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect; GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
@@ -827,6 +841,7 @@ namespace Barotrauma
{ {
if (connection.LevelData.HasBeaconStation) { iconCount++; } if (connection.LevelData.HasBeaconStation) { iconCount++; }
if (connection.LevelData.HasHuntingGrounds) { iconCount++; } if (connection.LevelData.HasHuntingGrounds) { iconCount++; }
if (connection.Locked) { iconCount++; }
string tooltip = null; string tooltip = null;
var subCrushDepth = Submarine.MainSub?.RealWorldCrushDepth ?? Level.DefaultRealWorldCrushDepth; var subCrushDepth = Submarine.MainSub?.RealWorldCrushDepth ?? Level.DefaultRealWorldCrushDepth;
if (GameMain.GameSession?.Campaign?.UpgradeManager != null) if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
@@ -867,6 +882,11 @@ namespace Barotrauma
DrawIcon(beaconStationIconStyle, (int)(28 * zoom), TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip")); DrawIcon(beaconStationIconStyle, (int)(28 * zoom), TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip"));
} }
if (connection.Locked)
{
DrawIcon("LockIcon", (int)(28 * zoom), TextManager.Get("LockedPathTooltip"));
}
if (connection.LevelData.HasHuntingGrounds) if (connection.LevelData.HasHuntingGrounds)
{ {
DrawIcon("HuntingGrounds", (int)(28 * zoom), TextManager.Get("HuntingGroundsTooltip")); DrawIcon("HuntingGrounds", (int)(28 * zoom), TextManager.Get("HuntingGroundsTooltip"));
@@ -1,4 +1,5 @@
#nullable enable #nullable enable
using System;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -6,6 +7,13 @@ namespace Barotrauma
{ {
internal partial class Radiation internal partial class Radiation
{ {
private static readonly string radiationTooltip = TextManager.Get("RadiationTooltip");
private static float spriteIndex;
private readonly SpriteSheet sheet = GUI.Style.RadiationAnimSpriteSheet;
private int maxFrames => sheet.FrameCount + 1;
private bool isHovingOver;
public void Draw(SpriteBatch spriteBatch, Rectangle container, float zoom) public void Draw(SpriteBatch spriteBatch, Rectangle container, float zoom)
{ {
if (!Enabled) { return; } if (!Enabled) { return; }
@@ -19,16 +27,38 @@ namespace Barotrauma
Vector2 size = new Vector2((Amount - increasedAmount) * zoom + halfSizeX, viewBottom - topLeft.Y); Vector2 size = new Vector2((Amount - increasedAmount) * zoom + halfSizeX, viewBottom - topLeft.Y);
if (size.X < 0) { return; } if (size.X < 0) { return; }
uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, GUI.Style.Red * 0.33f, Vector2.Zero, textureScale: new Vector2(zoom)); Vector2 spriteScale = new Vector2(zoom);
if (container.Contains(PlayerInput.MousePosition) && PlayerInput.MousePosition.X < topLeft.X + size.X) uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, Params.RadiationAreaColor, Vector2.Zero, textureScale: spriteScale);
Vector2 topRight = topLeft + Vector2.UnitX * size.X;
int index = 0;
for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom)
{ {
// TODO tooltip? bool isEven = ++index % 2 == 0;
Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X;
// every other sprite's animation is reversed to make it seem more chaotic
int sprite = (int) MathF.Floor(isEven ? spriteIndex : maxFrames - spriteIndex);
sheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
}
isHovingOver = container.Contains(PlayerInput.MousePosition) && PlayerInput.MousePosition.X < topLeft.X + size.X;
}
public void DrawFront(SpriteBatch spriteBatch)
{
if (isHovingOver)
{
GUIComponent.DrawToolTip(spriteBatch, radiationTooltip, PlayerInput.MousePosition + new Vector2(18 * GUI.Scale));
} }
} }
public void MapUpdate(float deltaTime) public void MapUpdate(float deltaTime)
{ {
float spriteStep = Params.BorderAnimationSpeed * deltaTime;
spriteIndex = (spriteIndex + spriteStep) % maxFrames;
if (increasedAmount > 0) if (increasedAmount > 0)
{ {
increasedAmount -= (lastIncrease / Params.AnimationSpeed) * deltaTime; increasedAmount -= (lastIncrease / Params.AnimationSpeed) * deltaTime;
@@ -805,7 +805,7 @@ namespace Barotrauma
} }
if (selectionPos != null && selectionPos != Vector2.Zero) if (selectionPos != null && selectionPos != Vector2.Zero)
{ {
GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed, false, 0, (int)Math.Max(1.5f / GameScreen.Selected.Cam.Zoom, 1.0f)); GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed, false, 0, 2f / GameScreen.Selected.Cam.Zoom);
} }
} }
@@ -35,7 +35,10 @@ namespace Barotrauma
public void CreatePreviewWindow(GUIComponent parent) public void CreatePreviewWindow(GUIComponent parent)
{ {
var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null); var content = new GUIButton(new RectTransform(Vector2.One, parent.RectTransform), style: null)
{
OnClicked = (btn, obj) => { SubmarinePreview.Create(this); return false; }
};
if (PreviewImage == null) if (PreviewImage == null)
{ {
@@ -2064,6 +2064,8 @@ namespace Barotrauma.Networking
bool autoRestartEnabled = inc.ReadBoolean(); bool autoRestartEnabled = inc.ReadBoolean();
float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f; float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f;
bool radiationEnabled = inc.ReadBoolean();
//ignore the message if we already a more up-to-date one //ignore the message if we already a more up-to-date one
//or if we're still waiting for the initial update //or if we're still waiting for the initial update
if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) && if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) &&
@@ -2119,7 +2121,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
GameMain.NetLobbyScreen.LevelSeed = levelSeed; GameMain.NetLobbyScreen.LevelSeed = levelSeed;
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty); GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
GameMain.NetLobbyScreen.SetBotCount(botCount); GameMain.NetLobbyScreen.SetRadiationEnabled(radiationEnabled);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer); GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer);
@@ -2564,7 +2566,7 @@ namespace Barotrauma.Networking
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.CampaignID != campaignID) if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.CampaignID != campaignID)
{ {
string savePath = transfer.FilePath; string savePath = transfer.FilePath;
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign); GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Unsure);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID; campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -2912,7 +2914,7 @@ namespace Barotrauma.Networking
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
public void SetupNewCampaign(SubmarineInfo sub, string saveName, string mapSeed) public void SetupNewCampaign(SubmarineInfo sub, string saveName, string mapSeed, CampaignSettings settings)
{ {
GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false; GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false;
GameMain.NetLobbyScreen.CampaignFrame.Visible = false; GameMain.NetLobbyScreen.CampaignFrame.Visible = false;
@@ -2927,6 +2929,7 @@ namespace Barotrauma.Networking
msg.Write(mapSeed); msg.Write(mapSeed);
msg.Write(sub.Name); msg.Write(sub.Name);
msg.Write(sub.MD5Hash.Hash); msg.Write(sub.MD5Hash.Hash);
settings.Serialize(msg);
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
@@ -147,13 +147,36 @@ namespace Barotrauma.Networking
if (missingPackages.Count > 0) if (missingPackages.Count > 0)
{ {
var nonDownloadable = missingPackages.Where(p => p.WorkshopId == 0); var nonDownloadable = missingPackages.Where(p => p.WorkshopId == 0);
var mismatchedButDownloaded = missingPackages.Where(p =>
{
var localMatching = ContentPackage.RegularPackages.Find(l => l.SteamWorkshopId != 0 && p.WorkshopId == l.SteamWorkshopId);
localMatching ??= ContentPackage.CorePackages.Find(l => l.SteamWorkshopId != 0 && p.WorkshopId == l.SteamWorkshopId);
if (nonDownloadable.Any()) return localMatching != null;
});
if (mismatchedButDownloaded.Any())
{
string disconnectMsg;
if (mismatchedButDownloaded.Count() == 1)
{
disconnectMsg = $"DisconnectMessage.MismatchedWorkshopMod~[incompatiblecontentpackage]={GetPackageStr(mismatchedButDownloaded.First())}";
}
else
{
List<string> packageStrs = new List<string>();
mismatchedButDownloaded.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
disconnectMsg = $"DisconnectMessage.MismatchedWorkshopMods~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}";
}
Close(disconnectMsg, disableReconnect: true);
OnDisconnectMessageReceived?.Invoke(DisconnectReason.MissingContentPackage + "/" + disconnectMsg);
}
else if (nonDownloadable.Any())
{ {
string disconnectMsg; string disconnectMsg;
if (nonDownloadable.Count() == 1) if (nonDownloadable.Count() == 1)
{ {
disconnectMsg = $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}"; disconnectMsg = $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(nonDownloadable.First())}";
} }
else else
{ {
@@ -56,6 +56,7 @@ namespace Barotrauma.Networking
Steamworks.SteamNetworking.AllowP2PPacketRelay(true); Steamworks.SteamNetworking.AllowP2PPacketRelay(true);
ServerConnection = new SteamP2PConnection("Server", hostSteamId); ServerConnection = new SteamP2PConnection("Server", hostSteamId);
ServerConnection.SetOwnerSteamIDIfUnknown(hostSteamId);
incomingInitializationMessages = new List<IReadMessage>(); incomingInitializationMessages = new List<IReadMessage>();
incomingDataMessages = new List<IReadMessage>(); incomingDataMessages = new List<IReadMessage>();
@@ -17,6 +17,7 @@ namespace Barotrauma.Networking
class RemotePeer class RemotePeer
{ {
public UInt64 SteamID; public UInt64 SteamID;
public UInt64 OwnerSteamID;
public double? DisconnectTime; public double? DisconnectTime;
public bool Authenticating; public bool Authenticating;
public bool Authenticated; public bool Authenticated;
@@ -31,6 +32,7 @@ namespace Barotrauma.Networking
public RemotePeer(UInt64 steamId) public RemotePeer(UInt64 steamId)
{ {
SteamID = steamId; SteamID = steamId;
OwnerSteamID = 0;
DisconnectTime = null; DisconnectTime = null;
Authenticating = false; Authenticating = false;
Authenticated = false; Authenticated = false;
@@ -90,10 +92,19 @@ namespace Barotrauma.Networking
if (status == Steamworks.AuthResponse.OK) if (status == Steamworks.AuthResponse.OK)
{ {
remotePeer.OwnerSteamID = ownerID;
remotePeer.Authenticated = true; remotePeer.Authenticated = true;
remotePeer.Authenticating = false; remotePeer.Authenticating = false;
foreach (var msg in remotePeer.UnauthedMessages) foreach (var msg in remotePeer.UnauthedMessages)
{ {
//rewrite the owner id before
//forwarding the messages to
//the server, since it's only
//known now
int prevBitPosition = msg.Message.BitPosition;
msg.Message.BitPosition = sizeof(ulong) * 8;
msg.Message.Write(ownerID);
msg.Message.BitPosition = prevBitPosition;
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone(); byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
Array.Resize(ref msgToSend, msg.Message.LengthBytes); Array.Resize(ref msgToSend, msg.Message.LengthBytes);
ChildServerRelay.Write(msgToSend); ChildServerRelay.Write(msgToSend);
@@ -131,6 +142,7 @@ namespace Barotrauma.Networking
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(steamId); outMsg.Write(steamId);
outMsg.Write(remotePeer.OwnerSteamID);
outMsg.Write(data, 1, dataLength - 1); outMsg.Write(data, 1, dataLength - 1);
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0]; DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
@@ -142,34 +154,27 @@ namespace Barotrauma.Networking
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0; bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0; bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!remotePeer.Authenticated) if (!remotePeer.Authenticated & !remotePeer.Authenticating && isConnectionInitializationStep)
{ {
if (!remotePeer.Authenticating) remotePeer.DisconnectTime = null;
IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{ {
if (isConnectionInitializationStep) remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadInt32(); //skip owner key
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{ {
remotePeer.DisconnectTime = null; DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
//Console.WriteLine("received init step from "+steamId.ToString()+" ("+initializationStep.ToString()+")");
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{
remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadInt32(); //skip owner key
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
}
} }
} }
} }
@@ -336,6 +341,7 @@ namespace Barotrauma.Networking
{ {
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(selfSteamID); outMsg.Write(selfSteamID);
outMsg.Write(selfSteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep)); outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep));
outMsg.Write(Name); outMsg.Write(Name);
@@ -428,6 +434,7 @@ namespace Barotrauma.Networking
byte[] msgData = new byte[msg.LengthBytes]; byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length); msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
msgToSend.Write(selfSteamID); msgToSend.Write(selfSteamID);
msgToSend.Write(selfSteamID);
msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)); msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
msgToSend.Write((UInt16)length); msgToSend.Write((UInt16)length);
msgToSend.Write(msgData, 0, length); msgToSend.Write(msgData, 0, length);
@@ -147,7 +147,7 @@ namespace Barotrauma.Networking
} }
} }
public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? useRespawnShuttle = null) public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? radiationEnabled = null, bool? useRespawnShuttle = null)
{ {
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return; if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
@@ -212,6 +212,7 @@ namespace Barotrauma.Networking
outMsg.Write(autoRestart != null); outMsg.Write(autoRestart != null);
outMsg.Write(autoRestart ?? false); outMsg.Write(autoRestart ?? false);
outMsg.Write(radiationEnabled ?? RadiationEnabled);
outMsg.WritePadBits(); outMsg.WritePadBits();
} }
@@ -23,7 +23,7 @@ namespace Barotrauma
private GUIButton loadGameButton, deleteMpSaveButton; private GUIButton loadGameButton, deleteMpSaveButton;
public Action<SubmarineInfo, string, string> StartNewGame; public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
public Action<string> LoadGame; public Action<string> LoadGame;
private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }; private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 };
@@ -41,6 +41,8 @@ namespace Barotrauma
private set; private set;
} }
public GUITickBox EnableRadiationToggle { get; set; }
private readonly bool isMultiplayer; private readonly bool isMultiplayer;
public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null) public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null)
@@ -171,6 +173,8 @@ namespace Barotrauma
string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text); string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings { RadiationEnabled = EnableRadiationToggle?.Selected ?? GameMain.NetLobbyScreen.IsRadiationEnabled() };
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{ {
if (!hasRequiredContentPackages) if (!hasRequiredContentPackages)
@@ -184,7 +188,7 @@ namespace Barotrauma
{ {
if (GUIMessageBox.MessageBoxes.Count == 0) if (GUIMessageBox.MessageBoxes.Count == 0)
{ {
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer) if (isMultiplayer)
{ {
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
@@ -204,7 +208,7 @@ namespace Barotrauma
msgBox.Buttons[0].OnClicked = (button, obj) => msgBox.Buttons[0].OnClicked = (button, obj) =>
{ {
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer) if (isMultiplayer)
{ {
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
@@ -219,7 +223,7 @@ namespace Barotrauma
} }
else else
{ {
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer) if (isMultiplayer)
{ {
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
@@ -230,8 +234,7 @@ namespace Barotrauma
} }
}; };
InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(isMultiplayer ? 0.6f : 0.3f, 1f), buttonContainer.RectTransform), "", font: isMultiplayer ? GUI.Style.SmallFont : GUI.Style.Font, textColor: GUI.Style.Green)
font: isMultiplayer ? GUI.Style.SmallFont : GUI.Style.Font, textColor: GUI.Style.Green)
{ {
TextGetter = () => TextGetter = () =>
{ {
@@ -254,6 +257,12 @@ namespace Barotrauma
if (!isMultiplayer) if (!isMultiplayer)
{ {
EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 1f), buttonContainer.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font)
{
Selected = true,
ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip")
};
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton") var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton")
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -477,8 +477,8 @@ namespace Barotrauma
} }
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward)); string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.GetWithVariable("missionreward", "[reward]", rewardText), wrap: true); TextManager.GetWithVariable("missionreward", "[reward]", rewardText), wrap: true, parseRichText: true);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.Description, wrap: true); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.Description, wrap: true, parseRichText: true);
} }
missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(20)); missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(20));
foreach (GUIComponent child in missionTextContent.Children) foreach (GUIComponent child in missionTextContent.Children)
@@ -562,6 +562,11 @@ namespace Barotrauma
public void SelectTab(CampaignMode.InteractionType tab) public void SelectTab(CampaignMode.InteractionType tab)
{ {
if (Campaign.ShowCampaignUI || (Campaign.ForceMapUI && tab == CampaignMode.InteractionType.Map))
{
HintManager.OnShowCampaignInterface(tab);
}
selectedTab = tab; selectedTab = tab;
for (int i = 0; i < tabs.Length; i++) for (int i = 0; i < tabs.Length; i++)
{ {
@@ -1761,9 +1761,9 @@ namespace Barotrauma.CharacterEditor
#endif #endif
// Add to the selected content package // Add to the selected content package
contentPackage.AddFile(configFilePath, ContentType.Character); contentPackage.AddFile(configFilePath, ContentType.Character);
Barotrauma.IO.Validation.DevException = true; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
contentPackage.Save(contentPackage.Path); contentPackage.Save(contentPackage.Path);
Barotrauma.IO.Validation.DevException = false; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path)); DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
// Ragdoll // Ragdoll
@@ -882,7 +882,7 @@ namespace Barotrauma
return false; return false;
} }
GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, null); GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, CampaignSettings.Empty, null);
TestGameMode gameMode = (TestGameMode) gameSession.GameMode; TestGameMode gameMode = (TestGameMode) gameSession.GameMode;
gameMode.SpawnOutpost = true; gameMode.SpawnOutpost = true;
@@ -296,7 +296,7 @@ namespace Barotrauma
subInfo ??= SubmarineInfo.SavedSubmarines.GetRandom(s => subInfo ??= SubmarineInfo.SavedSubmarines.GetRandom(s =>
s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) && s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) &&
!nonPlayerFiles.Any(f => f.Path.CleanUpPath().Equals(s.FilePath.CleanUpPath(), StringComparison.InvariantCultureIgnoreCase))); !nonPlayerFiles.Any(f => f.Path.CleanUpPath().Equals(s.FilePath.CleanUpPath(), StringComparison.InvariantCultureIgnoreCase)));
GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, null); GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, CampaignSettings.Empty, null);
gameSession.StartRound(Level.Loaded.LevelData); gameSession.StartRound(Level.Loaded.LevelData);
(gameSession.GameMode as TestGameMode).OnRoundEnd = () => (gameSession.GameMode as TestGameMode).OnRoundEnd = () =>
{ {
@@ -1043,7 +1043,7 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
} }
private void StartGame(SubmarineInfo selectedSub, string saveName, string mapSeed) private void StartGame(SubmarineInfo selectedSub, string saveName, string mapSeed, CampaignSettings settings)
{ {
if (string.IsNullOrEmpty(saveName)) return; if (string.IsNullOrEmpty(saveName)) return;
@@ -1082,7 +1082,7 @@ namespace Barotrauma
selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub")); selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, mapSeed); GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, settings, mapSeed);
((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel(); ((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel();
} }
@@ -1134,6 +1134,7 @@ namespace Barotrauma
(int)(campaignSetupUI.StartButton.TextBlock.TextSize.X * 1.5f), (int)(campaignSetupUI.StartButton.TextBlock.TextSize.X * 1.5f),
campaignSetupUI.StartButton.RectTransform.MinSize.Y); campaignSetupUI.StartButton.RectTransform.MinSize.Y);
startButtonContainer.RectTransform.MinSize = new Point(0, campaignSetupUI.StartButton.RectTransform.MinSize.Y); startButtonContainer.RectTransform.MinSize = new Point(0, campaignSetupUI.StartButton.RectTransform.MinSize.Y);
campaignSetupUI.EnableRadiationToggle.RectTransform.Parent = startButtonContainer.RectTransform;
campaignSetupUI.InitialMoneyText.RectTransform.Parent = startButtonContainer.RectTransform; campaignSetupUI.InitialMoneyText.RectTransform.Parent = startButtonContainer.RectTransform;
} }
@@ -27,6 +27,8 @@ namespace Barotrauma
private GUIComponent jobVariantTooltip; private GUIComponent jobVariantTooltip;
private SubmarinePreview submarinePreview;
private readonly GUITextBox chatInput; private readonly GUITextBox chatInput;
private readonly GUITextBox serverLogFilter; private readonly GUITextBox serverLogFilter;
public GUITextBox ChatInput public GUITextBox ChatInput
@@ -41,6 +43,8 @@ namespace Barotrauma
private readonly GUIScrollBar levelDifficultyScrollBar; private readonly GUIScrollBar levelDifficultyScrollBar;
private readonly GUITickBox radiationEnabledTickBox;
private readonly GUIButton[] traitorProbabilityButtons; private readonly GUIButton[] traitorProbabilityButtons;
private readonly GUITextBlock traitorProbabilityText; private readonly GUITextBlock traitorProbabilityText;
@@ -1095,6 +1099,16 @@ namespace Barotrauma
} }
}; };
radiationEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font)
{
Selected = true,
OnSelected = box =>
{
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, radiationEnabled: box.Selected);
return true;
}
};
List<GUIComponent> settingsElements = settingsContent.Children.ToList(); List<GUIComponent> settingsElements = settingsContent.Children.ToList();
for (int i = 0; i < settingsElements.Count; i++) for (int i = 0; i < settingsElements.Count; i++)
{ {
@@ -1238,6 +1252,7 @@ namespace Barotrauma
} }
SeedBox.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); SeedBox.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
levelDifficultyScrollBar.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); levelDifficultyScrollBar.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
radiationEnabledTickBox.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
traitorProbabilityButtons[0].Enabled = traitorProbabilityButtons[1].Enabled = traitorProbabilityText.Enabled = traitorProbabilityButtons[0].Enabled = traitorProbabilityButtons[1].Enabled = traitorProbabilityText.Enabled =
!CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
botCountButtons[0].Enabled = botCountButtons[1].Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings); botCountButtons[0].Enabled = botCountButtons[1].Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
@@ -2533,8 +2548,14 @@ namespace Barotrauma
chatBox.RemoveChild(chatBox.Content.Children.First()); chatBox.RemoveChild(chatBox.Content.Children.First());
} }
string textWithSender = message.TextWithSender;
if (message.Type == ChatMessageType.Server)
{
RichTextData.GetRichTextData(textWithSender, out textWithSender);
}
GUITextBlock msg = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), chatBox.Content.RectTransform), GUITextBlock msg = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), chatBox.Content.RectTransform),
text: ChatMessage.GetTimeStamp() + (message.Type == ChatMessageType.Private ? TextManager.Get("PrivateMessageTag") + " " : "") + message.TextWithSender, text: ChatMessage.GetTimeStamp() + (message.Type == ChatMessageType.Private ? TextManager.Get("PrivateMessageTag") + " " : "") + textWithSender,
textColor: message.Color, textColor: message.Color,
color: ((chatBox.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f, color: ((chatBox.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f,
wrap: true, font: GUI.SmallFont) wrap: true, font: GUI.SmallFont)
@@ -43,6 +43,13 @@ namespace Barotrauma
private Dictionary<ulong, Steamworks.Ugc.Item?> pendingWorkshopDownloads = null; private Dictionary<ulong, Steamworks.Ugc.Item?> pendingWorkshopDownloads = null;
private string autoConnectName; private string autoConnectEndpoint; private string autoConnectName; private string autoConnectEndpoint;
private enum TernaryOption
{
Any,
Enabled,
Disabled
}
private class FriendInfo private class FriendInfo
{ {
public UInt64 SteamID; public UInt64 SteamID;
@@ -146,16 +153,19 @@ namespace Barotrauma
private GUITickBox filterFull; private GUITickBox filterFull;
private GUITickBox filterEmpty; private GUITickBox filterEmpty;
private GUITickBox filterWhitelisted; private GUITickBox filterWhitelisted;
private GUITickBox filterFriendlyFire; private Dictionary<string, GUIDropDown> ternaryFilters;
private GUITickBox filterKarma;
private GUITickBox filterTraitor;
private GUITickBox filterModded;
private GUITickBox filterVoip;
private Dictionary<string, GUITickBox> filterTickBoxes; private Dictionary<string, GUITickBox> filterTickBoxes;
private Dictionary<string, GUITickBox> playStyleTickBoxes; private Dictionary<string, GUITickBox> playStyleTickBoxes;
private Dictionary<string, GUITickBox> gameModeTickBoxes; private Dictionary<string, GUITickBox> gameModeTickBoxes;
private GUITickBox filterOffensive; private GUITickBox filterOffensive;
//GUIDropDown sends the OnSelected event before SelectedData is set, so we have to cache it manually.
private TernaryOption filterFriendlyFireValue = TernaryOption.Any;
private TernaryOption filterKarmaValue = TernaryOption.Any;
private TernaryOption filterTraitorValue = TernaryOption.Any;
private TernaryOption filterVoipValue = TernaryOption.Any;
private TernaryOption filterModdedValue = TernaryOption.Any;
private string sortedBy; private string sortedBy;
private GUIButton serverPreviewToggleButton; private GUIButton serverPreviewToggleButton;
@@ -173,6 +183,49 @@ namespace Barotrauma
CreateUI(); CreateUI();
} }
private void AddTernaryFilter(RectTransform parent, float elementHeight, string tag, Action<TernaryOption> valueSetter)
{
var filterLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementHeight), parent), isHorizontal: true)
{
Stretch = true
};
var box = new GUIFrame(new RectTransform(Vector2.One, filterLayoutGroup.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight)
{
IsFixedSize = true,
}, null)
{
HoverColor = Color.Gray,
SelectedColor = Color.DarkGray,
CanBeFocused = false
};
if (box.RectTransform.MinSize.Y > 0)
{
box.RectTransform.MinSize = new Point(box.RectTransform.MinSize.Y);
box.RectTransform.Resize(box.RectTransform.MinSize);
}
Vector2 textBlockScale = new Vector2((float)(filterLayoutGroup.Rect.Width - filterLayoutGroup.Rect.Height) / (float)Math.Max(filterLayoutGroup.Rect.Width, 1.0), 1.0f);
var filterLabel = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f) * textBlockScale, filterLayoutGroup.RectTransform, Anchor.CenterLeft), TextManager.Get("servertag." + tag + ".label"), textAlignment: Alignment.CenterLeft)
{
UserData = TextManager.Get("servertag." + tag + ".label")
};
GUI.Style.Apply(filterLabel, "GUITextBlock", null);
var dropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f) * textBlockScale, filterLayoutGroup.RectTransform, Anchor.CenterLeft), elementCount: 3);
dropDown.AddItem(TextManager.Get("any"), TernaryOption.Any);
dropDown.AddItem(TextManager.Get("servertag." + tag + ".true"), TernaryOption.Enabled, TextManager.Get("servertagdescription." + tag + ".true"));
dropDown.AddItem(TextManager.Get("servertag." + tag + ".false"), TernaryOption.Disabled, TextManager.Get("servertagdescription." + tag + ".false"));
dropDown.SelectItem(TernaryOption.Any);
dropDown.OnSelected = (_, data) => {
valueSetter((TernaryOption)data);
FilterServers();
return true;
};
ternaryFilters.Add(tag, dropDown);
}
private void CreateUI() private void CreateUI()
{ {
menu = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.85f), GUI.Canvas, Anchor.Center) { MinSize = new Point(GameMain.GraphicsHeight, 0) }); menu = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.85f), GUI.Canvas, Anchor.Center) { MinSize = new Point(GameMain.GraphicsHeight, 0) });
@@ -323,6 +376,7 @@ namespace Barotrauma
}; };
filterToggle.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally); filterToggle.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally);
ternaryFilters = new Dictionary<string, GUIDropDown>();
filterTickBoxes = new Dictionary<string, GUITickBox>(); filterTickBoxes = new Dictionary<string, GUITickBox>();
filterSameVersion = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("FilterSameVersion")) filterSameVersion = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("FilterSameVersion"))
@@ -382,40 +436,11 @@ namespace Barotrauma
CanBeFocused = false CanBeFocused = false
}; };
filterKarma = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.karma.true")) AddTernaryFilter(filters.Content.RectTransform, elementHeight, "karma", (value) => { filterKarmaValue = value; });
{ AddTernaryFilter(filters.Content.RectTransform, elementHeight, "traitors", (value) => { filterTraitorValue = value; });
UserData = TextManager.Get("servertag.karma.true"), AddTernaryFilter(filters.Content.RectTransform, elementHeight, "friendlyfire", (value) => { filterFriendlyFireValue = value; });
OnSelected = (tickBox) => { FilterServers(); return true; } AddTernaryFilter(filters.Content.RectTransform, elementHeight, "voip", (value) => { filterVoipValue = value; });
}; AddTernaryFilter(filters.Content.RectTransform, elementHeight, "modded", (value) => { filterModdedValue = value; });
filterTickBoxes.Add("servertag.karma", filterKarma);
filterTraitor = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.traitors.true"))
{
UserData = TextManager.Get("servertag.traitors.true"),
OnSelected = (tickBox) => { FilterServers(); return true; }
};
filterTickBoxes.Add("servertag.traitors", filterTraitor);
filterFriendlyFire = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.friendlyfire.false"))
{
UserData = TextManager.Get("servertag.friendlyfire.false"),
OnSelected = (tickBox) => { FilterServers(); return true; }
};
filterTickBoxes.Add("servertag.friendlyfire", filterFriendlyFire);
filterVoip = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.voip.false"))
{
UserData = TextManager.Get("servertag.voip.false"),
OnSelected = (tickBox) => { FilterServers(); return true; }
};
filterTickBoxes.Add("servertag.voip", filterVoip);
filterModded = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.modded.true"))
{
UserData = TextManager.Get("servertag.modded.true"),
OnSelected = (tickBox) => { FilterServers(); return true; }
};
filterTickBoxes.Add("servertag.modded", filterModded);
// Play Style Selection // Play Style Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUI.SubHeadingFont) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUI.SubHeadingFont)
@@ -1076,9 +1101,15 @@ namespace Barotrauma
else else
{ {
bool incompatible = bool incompatible =
(!serverInfo.ContentPackageHashes.Any() && serverInfo.ContentPackagesMatch()) || (serverInfo.ContentPackageHashes.Any() && !serverInfo.ContentPackagesMatch()) ||
(remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion)); (remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion));
var karmaFilterPassed = filterKarmaValue == TernaryOption.Any|| (filterKarmaValue == TernaryOption.Enabled) == serverInfo.KarmaEnabled;
var friendlyFireFilterPassed = filterFriendlyFireValue == TernaryOption.Any || (filterFriendlyFireValue == TernaryOption.Enabled) == serverInfo.FriendlyFireEnabled;
var traitorsFilterPassed = filterTraitorValue == TernaryOption.Any || (filterTraitorValue == TernaryOption.Enabled) == (serverInfo.TraitorsEnabled == YesNoMaybe.Yes || serverInfo.TraitorsEnabled == YesNoMaybe.Maybe);
var voipFilterPassed = filterVoipValue == TernaryOption.Any || (filterVoipValue == TernaryOption.Enabled) == serverInfo.VoipEnabled;
var moddedFilterPassed = filterModdedValue == TernaryOption.Any || (filterModdedValue == TernaryOption.Enabled) == serverInfo.GetPlayStyleTags().Any(t => t.Contains("modded.true"));
child.Visible = child.Visible =
serverInfo.OwnerVerified && serverInfo.OwnerVerified &&
serverInfo.ServerName.Contains(searchBox.Text, StringComparison.OrdinalIgnoreCase) && serverInfo.ServerName.Contains(searchBox.Text, StringComparison.OrdinalIgnoreCase) &&
@@ -1089,11 +1120,11 @@ namespace Barotrauma
(!filterEmpty.Selected || serverInfo.PlayerCount > 0) && (!filterEmpty.Selected || serverInfo.PlayerCount > 0) &&
(!filterWhitelisted.Selected || serverInfo.UsingWhiteList == true) && (!filterWhitelisted.Selected || serverInfo.UsingWhiteList == true) &&
(!filterOffensive.Selected || !ForbiddenWordFilter.IsForbidden(serverInfo.ServerName)) && (!filterOffensive.Selected || !ForbiddenWordFilter.IsForbidden(serverInfo.ServerName)) &&
(!filterKarma.Selected || serverInfo.KarmaEnabled == true) && karmaFilterPassed &&
(!filterFriendlyFire.Selected || serverInfo.FriendlyFireEnabled == false) && friendlyFireFilterPassed &&
(!filterTraitor.Selected || serverInfo.TraitorsEnabled == YesNoMaybe.Yes || serverInfo.TraitorsEnabled == YesNoMaybe.Maybe) && traitorsFilterPassed &&
(!filterVoip.Selected || serverInfo.VoipEnabled == false) && voipFilterPassed &&
(!filterModded.Selected || serverInfo.GetPlayStyleTags().Any(t => t.Contains("modded.true"))) && moddedFilterPassed &&
((selectedTab == ServerListTab.All && (serverInfo.LobbyID != 0 || !string.IsNullOrWhiteSpace(serverInfo.Port))) || ((selectedTab == ServerListTab.All && (serverInfo.LobbyID != 0 || !string.IsNullOrWhiteSpace(serverInfo.Port))) ||
(selectedTab == ServerListTab.Recent && serverInfo.Recent) || (selectedTab == ServerListTab.Recent && serverInfo.Recent) ||
(selectedTab == ServerListTab.Favorites && serverInfo.Favorite)); (selectedTab == ServerListTab.Favorites && serverInfo.Favorite));
@@ -2334,6 +2365,10 @@ namespace Barotrauma
{ {
element.Add(new XAttribute(filterBox.Key, filterBox.Value.Selected.ToString())); element.Add(new XAttribute(filterBox.Key, filterBox.Value.Selected.ToString()));
} }
foreach (KeyValuePair<string, GUIDropDown> ternaryFilter in ternaryFilters)
{
element.Add(new XAttribute(ternaryFilter.Key, ternaryFilter.Value.SelectedData.ToString()));
}
} }
public void LoadServerFilters(XElement element) public void LoadServerFilters(XElement element)
@@ -2344,6 +2379,15 @@ namespace Barotrauma
{ {
filterBox.Value.Selected = element.GetAttributeBool(filterBox.Key, filterBox.Value.Selected); filterBox.Value.Selected = element.GetAttributeBool(filterBox.Key, filterBox.Value.Selected);
} }
foreach (KeyValuePair<string, GUIDropDown> ternaryFilter in ternaryFilters)
{
string valueStr = element.GetAttributeString(ternaryFilter.Key, "");
TernaryOption ternaryOption = (TernaryOption)ternaryFilter.Value.SelectedData;
Enum.TryParse<TernaryOption>(valueStr, true, out ternaryOption);
var child = ternaryFilter.Value.ListBox.Content.GetChildByUserData(ternaryOption);
ternaryFilter.Value.Select(ternaryFilter.Value.ListBox.Content.GetChildIndex(child));
}
} }
} }
@@ -410,9 +410,9 @@ namespace Barotrauma
if (sub.HasTag(SubmarineTag.HideInMenus)) { continue; } if (sub.HasTag(SubmarineTag.HideInMenus)) { continue; }
string subPath = Path.GetFullPath(sub.FilePath); string subPath = Path.GetFullPath(sub.FilePath);
//ignore subs that are part of the vanilla content package //ignore files that are part of the vanilla content package
if (GameMain.VanillaContent != null && if (GameMain.VanillaContent != null &&
GameMain.VanillaContent.GetFilesOfType(ContentType.Submarine).Any(s => Path.GetFullPath(s) == subPath)) GameMain.VanillaContent.Files.Any(s => Path.GetFullPath(s.Path) == subPath))
{ {
continue; continue;
} }
@@ -870,7 +870,7 @@ namespace Barotrauma
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
GameSession gameSession = new GameSession(backedUpSubInfo, "", GameModePreset.TestMode, null); GameSession gameSession = new GameSession(backedUpSubInfo, "", GameModePreset.TestMode, CampaignSettings.Empty, null);
gameSession.StartRound(null, false); gameSession.StartRound(null, false);
(gameSession.GameMode as TestGameMode).OnRoundEnd = () => (gameSession.GameMode as TestGameMode).OnRoundEnd = () =>
{ {
@@ -897,6 +897,8 @@ namespace Barotrauma
foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory))) foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory)))
{ {
string categoryName = TextManager.Get("MapEntityCategory." + category);
maxTextWidth = (int)Math.Max(maxTextWidth, GUI.SubHeadingFont.MeasureString(categoryName.Replace(' ', '\n')).X + GUI.IntScale(50));
foreach (MapEntityPrefab ep in MapEntityPrefab.List) foreach (MapEntityPrefab ep in MapEntityPrefab.List)
{ {
if (!ep.Category.HasFlag(category)) { continue; } if (!ep.Category.HasFlag(category)) { continue; }
@@ -907,13 +909,14 @@ namespace Barotrauma
} }
entityLists[category + ep.Subcategory].Add(ep); entityLists[category + ep.Subcategory].Add(ep);
categoryKeys[category + ep.Subcategory] = category; categoryKeys[category + ep.Subcategory] = category;
string categoryName = TextManager.Get("subcategory." + ep.Subcategory, returnNull: true) ?? ep.Subcategory; string subcategoryName = TextManager.Get("subcategory." + ep.Subcategory, returnNull: true) ?? ep.Subcategory;
if (categoryName != null) if (subcategoryName != null)
{ {
maxTextWidth = (int)Math.Max(maxTextWidth, GUI.SubHeadingFont.MeasureString(categoryName.Replace(' ', '\n')).X + GUI.IntScale(50)); maxTextWidth = (int)Math.Max(maxTextWidth, GUI.SubHeadingFont.MeasureString(subcategoryName.Replace(' ', '\n')).X + GUI.IntScale(50));
} }
} }
} }
categorizedEntityList.Content.ClampMouseRectToParent = true; categorizedEntityList.Content.ClampMouseRectToParent = true;
int entitiesPerRow = (int)Math.Ceiling(categorizedEntityList.Content.Rect.Width / Math.Max(125 * GUI.Scale, 60)); int entitiesPerRow = (int)Math.Ceiling(categorizedEntityList.Content.Rect.Width / Math.Max(125 * GUI.Scale, 60));
foreach (string categoryKey in entityLists.Keys) foreach (string categoryKey in entityLists.Keys)
@@ -926,19 +929,33 @@ namespace Barotrauma
new GUIFrame(new RectTransform(Vector2.One, categoryFrame.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(Vector2.One, categoryFrame.RectTransform), style: "HorizontalLine");
string categoryName = entityLists[categoryKey].First().Subcategory; string categoryName = TextManager.Get("MapEntityCategory." + entityLists[categoryKey].First().Category);
categoryName = string.IsNullOrEmpty(categoryName) ? string subCategoryName = entityLists[categoryKey].First().Subcategory;
TextManager.Get("mapentitycategory.misc") : if (string.IsNullOrEmpty(subCategoryName))
(TextManager.Get("subcategory." + categoryName, returnNull: true) ?? categoryName);
new GUITextBlock(
new RectTransform(new Point(maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform, Anchor.TopLeft),
categoryName,
textAlignment: Alignment.TopLeft,
font: GUI.SubHeadingFont,
wrap: true)
{ {
Padding = new Vector4(GUI.IntScale(10)) new GUITextBlock(new RectTransform(new Point(maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform, Anchor.TopLeft),
}; categoryName, textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont, wrap: true)
{
Padding = new Vector4(GUI.IntScale(10))
};
}
else
{
subCategoryName = string.IsNullOrEmpty(subCategoryName) ?
TextManager.Get("mapentitycategory.misc") :
(TextManager.Get("subcategory." + subCategoryName, returnNull: true) ?? subCategoryName);
var categoryTitle = new GUITextBlock(new RectTransform(new Point(maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform, Anchor.TopLeft),
categoryName, textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
{
Padding = new Vector4(GUI.IntScale(10))
};
new GUITextBlock(new RectTransform(new Point(maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(0, (int)(categoryTitle.TextSize.Y + GUI.IntScale(10))) },
subCategoryName, textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont, wrap: true)
{
Padding = new Vector4(GUI.IntScale(10))
};
}
var entityListInner = new GUIListBox(new RectTransform(new Point(categoryFrame.Rect.Width - maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform, Anchor.CenterRight), var entityListInner = new GUIListBox(new RectTransform(new Point(categoryFrame.Rect.Width - maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform, Anchor.CenterRight),
style: null, style: null,
@@ -959,7 +976,6 @@ namespace Barotrauma
#if !DEBUG #if !DEBUG
if (ep.HideInMenus) { continue; } if (ep.HideInMenus) { continue; }
#endif #endif
CreateEntityElement(ep, entitiesPerRow, entityListInner.Content); CreateEntityElement(ep, entitiesPerRow, entityListInner.Content);
} }
@@ -976,6 +992,9 @@ namespace Barotrauma
foreach (MapEntityPrefab ep in MapEntityPrefab.List) foreach (MapEntityPrefab ep in MapEntityPrefab.List)
{ {
#if !DEBUG
if (ep.HideInMenus) { continue; }
#endif
CreateEntityElement(ep, entitiesPerRow, allEntityList.Content); CreateEntityElement(ep, entitiesPerRow, allEntityList.Content);
} }
} }
@@ -1354,7 +1373,7 @@ namespace Barotrauma
{ {
try try
{ {
Barotrauma.IO.Validation.DevException = true; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
TimeSpan time = DateTime.UtcNow - DateTime.MinValue; TimeSpan time = DateTime.UtcNow - DateTime.MinValue;
string filePath = Path.Combine(autoSavePath, $"AutoSave_{(ulong)time.TotalMilliseconds}.sub"); string filePath = Path.Combine(autoSavePath, $"AutoSave_{(ulong)time.TotalMilliseconds}.sub");
SaveUtil.CompressStringToFile(filePath, doc.ToString()); SaveUtil.CompressStringToFile(filePath, doc.ToString());
@@ -1390,7 +1409,7 @@ namespace Barotrauma
} }
}); });
Barotrauma.IO.Validation.DevException = false; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
CrossThread.RequestExecutionOnMainThread(DisplayAutoSavePrompt); CrossThread.RequestExecutionOnMainThread(DisplayAutoSavePrompt);
} }
catch (Exception e) catch (Exception e)
@@ -1558,9 +1577,9 @@ namespace Barotrauma
msgBox.Buttons[0].OnClicked = (bt, userdata) => msgBox.Buttons[0].OnClicked = (bt, userdata) =>
{ {
contentPackage.AddFile(savePath, ContentType.OutpostModule); contentPackage.AddFile(savePath, ContentType.OutpostModule);
Barotrauma.IO.Validation.DevException = true; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
contentPackage.Save(contentPackage.Path, reload: false); contentPackage.Save(contentPackage.Path, reload: false);
Barotrauma.IO.Validation.DevException = false; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
msgBox.Close(); msgBox.Close();
return true; return true;
}; };
@@ -1635,7 +1654,7 @@ namespace Barotrauma
if (Submarine.MainSub != null) if (Submarine.MainSub != null)
{ {
Barotrauma.IO.Validation.DevException = true; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
if (previewImage?.Sprite?.Texture != null && !previewImage.Sprite.Texture.IsDisposed && Submarine.MainSub.Info.Type != SubmarineType.OutpostModule) if (previewImage?.Sprite?.Texture != null && !previewImage.Sprite.Texture.IsDisposed && Submarine.MainSub.Info.Type != SubmarineType.OutpostModule)
{ {
bool savePreviewImage = true; bool savePreviewImage = true;
@@ -1655,7 +1674,7 @@ namespace Barotrauma
{ {
Submarine.MainSub.SaveAs(savePath); Submarine.MainSub.SaveAs(savePath);
} }
Barotrauma.IO.Validation.DevException = false; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
Submarine.MainSub.CheckForErrors(); Submarine.MainSub.CheckForErrors();
@@ -4695,6 +4714,7 @@ namespace Barotrauma
} }
graphics.Clear(backgroundColor); graphics.Clear(backgroundColor);
ImageManager.Draw(spriteBatch, cam); ImageManager.Draw(spriteBatch, cam);
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: cam.Transform); spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: cam.Transform);
@@ -63,8 +63,18 @@ namespace Barotrauma.Sounds
ALFormat = reader.Channels == 1 ? Al.FormatMono16 : Al.FormatStereo16; ALFormat = reader.Channels == 1 ? Al.FormatMono16 : Al.FormatStereo16;
SampleRate = reader.SampleRate; SampleRate = reader.SampleRate;
if (Buffers != null && SoundBuffers.BuffersGenerated < SoundBuffers.MaxBuffers)
{
Buffers.RequestAlBuffers(); FillBuffers();
}
}
public override void FillBuffers()
{
if (!Stream) if (!Stream)
{ {
reader.DecodedPosition = 0;
int bufferSize = (int)reader.TotalSamples * reader.Channels; int bufferSize = (int)reader.TotalSamples * reader.Channels;
float[] floatBuffer = new float[bufferSize]; float[] floatBuffer = new float[bufferSize];
@@ -86,7 +96,7 @@ namespace Barotrauma.Sounds
CastBuffer(floatBuffer, shortBuffer, readSamples); CastBuffer(floatBuffer, shortBuffer, readSamples);
Al.BufferData(ALBuffer, ALFormat, shortBuffer, Al.BufferData(Buffers.AlBuffer, ALFormat, shortBuffer,
readSamples * sizeof(short), SampleRate); readSamples * sizeof(short), SampleRate);
int alError = Al.GetError(); int alError = Al.GetError();
@@ -99,7 +109,7 @@ namespace Barotrauma.Sounds
CastBuffer(floatBuffer, shortBuffer, readSamples); CastBuffer(floatBuffer, shortBuffer, readSamples);
Al.BufferData(ALMuffledBuffer, ALFormat, shortBuffer, Al.BufferData(Buffers.AlMuffledBuffer, ALFormat, shortBuffer,
readSamples * sizeof(short), SampleRate); readSamples * sizeof(short), SampleRate);
alError = Al.GetError(); alError = Al.GetError();
@@ -52,16 +52,10 @@ namespace Barotrauma.Sounds
} }
} }
private uint alBuffer; private SoundBuffers buffers;
public uint ALBuffer public SoundBuffers Buffers
{ {
get { return !Stream ? alBuffer : 0; } get { return !Stream ? buffers : null; }
}
private uint alMuffledBuffer;
public uint ALMuffledBuffer
{
get { return !Stream ? alMuffledBuffer : 0; }
} }
public int ALFormat public int ALFormat
@@ -169,69 +163,20 @@ namespace Barotrauma.Sounds
{ {
if (!Stream) if (!Stream)
{ {
Al.GenBuffer(out alBuffer); buffers = new SoundBuffers(this);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError));
}
if (!Al.IsBuffer(alBuffer))
{
throw new Exception("Generated OpenAL buffer is invalid!");
}
Al.GenBuffer(out alMuffledBuffer);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError));
}
if (!Al.IsBuffer(alMuffledBuffer))
{
throw new Exception("Generated OpenAL buffer is invalid!");
}
} }
else else
{ {
alBuffer = 0; buffers = null;
} }
} }
public virtual void FillBuffers() { }
public virtual void DeleteALBuffers() public virtual void DeleteALBuffers()
{ {
Owner.KillChannels(this); Owner.KillChannels(this);
if (alBuffer != 0) buffers?.Dispose();
{
if (!Al.IsBuffer(alBuffer))
{
throw new Exception("Buffer to delete is invalid!");
}
Al.DeleteBuffer(alBuffer); alBuffer = 0;
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError));
}
}
if (alMuffledBuffer != 0)
{
if (!Al.IsBuffer(alMuffledBuffer))
{
throw new Exception("Buffer to delete is invalid!");
}
Al.DeleteBuffer(alMuffledBuffer); alMuffledBuffer = 0;
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError));
}
}
} }
public virtual void Dispose() public virtual void Dispose()
@@ -334,7 +334,11 @@ namespace Barotrauma.Sounds
return; return;
} }
Al.Sourcei(alSource, Al.Buffer, muffled ? (int)Sound.ALMuffledBuffer : (int)Sound.ALBuffer); if (Sound.Buffers.RequestAlBuffers())
{
Sound.FillBuffers();
}
Al.Sourcei(alSource, Al.Buffer, muffled ? (int)Sound.Buffers.AlMuffledBuffer : (int)Sound.Buffers.AlBuffer);
alError = Al.GetError(); alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
@@ -506,17 +510,26 @@ namespace Barotrauma.Sounds
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError)); throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
} }
if (!Al.IsBuffer(sound.ALBuffer)) if (Sound.Buffers.RequestAlBuffers())
{
Sound.FillBuffers();
}
if (!Al.IsBuffer(sound.Buffers.AlBuffer))
{ {
throw new Exception(sound.Filename + " has an invalid buffer!"); throw new Exception(sound.Filename + " has an invalid buffer!");
} }
if (!Al.IsBuffer(sound.Buffers.AlMuffledBuffer))
{
throw new Exception(sound.Filename + " has an invalid muffled buffer!");
}
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffle ? sound.ALMuffledBuffer : sound.ALBuffer; uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffled ? Sound.Buffers.AlMuffledBuffer : Sound.Buffers.AlBuffer;
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer); Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
alError = Al.GetError(); alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
{ {
throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + sound.ALBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError)); throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + alBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
} }
Al.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex)); Al.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
@@ -528,7 +541,7 @@ namespace Barotrauma.Sounds
} }
else else
{ {
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffle ? sound.ALMuffledBuffer : sound.ALBuffer; uint alBuffer = 0;
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer); Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
int alError = Al.GetError(); int alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
@@ -30,6 +30,8 @@ namespace Barotrauma.Sounds
private readonly SoundSourcePool[] sourcePools; private readonly SoundSourcePool[] sourcePools;
private readonly List<Sound> loadedSounds; private readonly List<Sound> loadedSounds;
public IReadOnlyList<Sound> LoadedSounds => loadedSounds;
private readonly SoundChannel[][] playingChannels = new SoundChannel[2][]; private readonly SoundChannel[][] playingChannels = new SoundChannel[2][];
private readonly object threadDeathMutex = new object(); private readonly object threadDeathMutex = new object();
@@ -172,22 +172,22 @@ namespace Barotrauma
return null; return null;
} }
public void Draw(SpriteBatch spriteBatch, Vector2 pos, float rotate = 0.0f, float scale = 1.0f, SpriteEffects spriteEffect = SpriteEffects.None) public void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate = 0.0f, float scale = 1.0f, SpriteEffects spriteEffect = SpriteEffects.None)
{ {
this.Draw(spriteBatch, pos, Color.White, rotate, scale, spriteEffect); this.Draw(spriteBatch, pos, Color.White, rotate, scale, spriteEffect);
} }
public void Draw(SpriteBatch spriteBatch, Vector2 pos, Color color, float rotate = 0.0f, float scale = 1.0f, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = null) public void Draw(ISpriteBatch spriteBatch, Vector2 pos, Color color, float rotate = 0.0f, float scale = 1.0f, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = null)
{ {
this.Draw(spriteBatch, pos, color, this.origin, rotate, new Vector2(scale, scale), spriteEffect, depth); this.Draw(spriteBatch, pos, color, this.origin, rotate, new Vector2(scale, scale), spriteEffect, depth);
} }
public void Draw(SpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate = 0.0f, float scale = 1.0f, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = null) public void Draw(ISpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate = 0.0f, float scale = 1.0f, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = null)
{ {
this.Draw(spriteBatch, pos, color, origin, rotate, new Vector2(scale, scale), spriteEffect, depth); this.Draw(spriteBatch, pos, color, origin, rotate, new Vector2(scale, scale), spriteEffect, depth);
} }
public virtual void Draw(SpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = null) public virtual void Draw(ISpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = null)
{ {
if (Texture == null) { return; } if (Texture == null) { return; }
//DrawSilhouette(spriteBatch, pos, origin, rotate, scale, spriteEffect, depth); //DrawSilhouette(spriteBatch, pos, origin, rotate, scale, spriteEffect, depth);
@@ -209,7 +209,7 @@ namespace Barotrauma
} }
} }
public void DrawTiled(SpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, public void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize,
Color? color = null, Vector2? startOffset = null, Vector2? textureScale = null, float? depth = null) Color? color = null, Vector2? startOffset = null, Vector2? textureScale = null, float? depth = null)
{ {
if (Texture == null) { return; } if (Texture == null) { return; }
@@ -5,14 +5,14 @@ namespace Barotrauma
{ {
partial class SpriteSheet : Sprite partial class SpriteSheet : Sprite
{ {
public override void Draw(SpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = default(float?)) public override void Draw(ISpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = default(float?))
{ {
if (texture == null) return; if (texture == null) return;
spriteBatch.Draw(texture, pos + offset, sourceRects[0], color, rotation + rotate, origin, scale, spriteEffect, depth == null ? this.depth : (float)depth); spriteBatch.Draw(texture, pos + offset, sourceRects[0], color, rotation + rotate, origin, scale, spriteEffect, depth == null ? this.depth : (float)depth);
} }
public void Draw(SpriteBatch spriteBatch, int spriteIndex, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = default(float?)) public void Draw(ISpriteBatch spriteBatch, int spriteIndex, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffect = SpriteEffects.None, float? depth = default(float?))
{ {
if (texture == null) return; if (texture == null) return;
@@ -100,38 +100,6 @@ namespace Barotrauma
return q1; return q1;
} }
/// <summary>
/// Convert a HSV value into a RGB value.
/// </summary>
/// <param name="hue">Value between 0 and 360</param>
/// <param name="saturation">Value between 0 and 1</param>
/// <param name="value">Value between 0 and 1</param>
/// <see href="https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB">Reference</see>
/// <returns></returns>
public static Color HSVToRGB(float hue, float saturation, float value)
{
float c = value * saturation;
float h = Math.Clamp(hue, 0, 360) / 60f;
float x = c * (1 - Math.Abs(h % 2 - 1));
float r = 0,
g = 0,
b = 0;
if (0 <= h && h <= 1) { r = c; g = x; b = 0; }
else if (1 < h && h <= 2) { r = x; g = c; b = 0; }
else if (2 < h && h <= 3) { r = 0; g = c; b = x; }
else if (3 < h && h <= 4) { r = 0; g = x; b = c; }
else if (4 < h && h <= 5) { r = x; g = 0; b = c; }
else if (5 < h && h <= 6) { r = c; g = 0; b = x; }
float m = value - c;
return new Color(r + m, g + m, b + m);
}
/// <summary> /// <summary>
/// Convert a RGB value into a HSV value. /// Convert a RGB value into a HSV value.
/// </summary> /// </summary>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1300.0.0</Version> <Version>0.1300.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1300.0.0</Version> <Version>0.1300.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1300.0.0</Version> <Version>0.1300.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
View File
View File
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1300.0.0</Version> <Version>0.1300.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1300.0.0</Version> <Version>0.1300.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -26,6 +26,7 @@ namespace Barotrauma
{ {
msg.Write(ID); msg.Write(ID);
msg.Write(Name); msg.Write(Name);
msg.Write(OriginalName);
msg.Write((byte)Gender); msg.Write((byte)Gender);
msg.Write((byte)Race); msg.Write((byte)Race);
msg.Write((byte)HeadSpriteId); msg.Write((byte)HeadSpriteId);
@@ -17,6 +17,8 @@ namespace Barotrauma
foreach (Character character in characters) foreach (Character character in characters)
{ {
character.WriteSpawnData(msg, character.ID, restrictMessageSize: false); character.WriteSpawnData(msg, character.ID, restrictMessageSize: false);
msg.Write(requireKill.Contains(character));
msg.Write(requireRescue.Contains(character));
msg.Write((ushort)characterItems[character].Count()); msg.Write((ushort)characterItems[character].Count());
foreach (Item item in characterItems[character]) foreach (Item item in characterItems[character])
{ {
@@ -32,11 +32,11 @@ namespace Barotrauma
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); } get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
} }
public static void StartNewCampaign(string savePath, string subPath, string seed) public static void StartNewCampaign(string savePath, string subPath, string seed, CampaignSettings settings)
{ {
if (string.IsNullOrWhiteSpace(savePath)) { return; } if (string.IsNullOrWhiteSpace(savePath)) { return; }
GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), savePath, GameModePreset.MultiPlayerCampaign, seed); GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), savePath, GameModePreset.MultiPlayerCampaign, settings, seed);
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
@@ -74,7 +74,7 @@ namespace Barotrauma
DebugConsole.ShowQuestionPrompt("Enter a save name for the campaign:", (string saveName) => DebugConsole.ShowQuestionPrompt("Enter a save name for the campaign:", (string saveName) =>
{ {
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName);
StartNewCampaign(savePath, GameMain.NetLobbyScreen.SelectedSub.FilePath, GameMain.NetLobbyScreen.LevelSeed); StartNewCampaign(savePath, GameMain.NetLobbyScreen.SelectedSub.FilePath, GameMain.NetLobbyScreen.LevelSeed, CampaignSettings.Empty);
}); });
} }
else else
@@ -667,13 +667,24 @@ namespace Barotrauma
} }
bool validateHires = msg.ReadBoolean(); bool validateHires = msg.ReadBoolean();
bool fireCharacter = msg.ReadBoolean();
bool renameCharacter = msg.ReadBoolean();
int renamedIdentifier = -1;
string newName = null;
bool existingCrewMember = false;
if (renameCharacter)
{
renamedIdentifier = msg.ReadInt32();
newName = msg.ReadString();
existingCrewMember = msg.ReadBoolean();
}
bool fireCharacter = msg.ReadBoolean();
int firedIdentifier = -1; int firedIdentifier = -1;
if (fireCharacter) { firedIdentifier = msg.ReadInt32(); } if (fireCharacter) { firedIdentifier = msg.ReadInt32(); }
Location location = map?.CurrentLocation; Location location = map?.CurrentLocation;
List<CharacterInfo> hiredCharacters = new List<CharacterInfo>();
CharacterInfo firedCharacter = null; CharacterInfo firedCharacter = null;
if (location != null && AllowedToManageCampaign(sender)) if (location != null && AllowedToManageCampaign(sender))
@@ -691,13 +702,45 @@ namespace Barotrauma
} }
} }
if (renameCharacter)
{
CharacterInfo characterInfo = null;
if (existingCrewMember && CrewManager != null)
{
characterInfo = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
}
else if(!existingCrewMember && location.HireManager != null)
{
characterInfo = location.HireManager.AvailableCharacters.FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
}
if (characterInfo != null && (characterInfo.Character?.IsBot ?? true))
{
if (existingCrewMember)
{
CrewManager.RenameCharacter(characterInfo, newName);
}
else
{
location.HireManager.RenameCharacter(characterInfo, newName);
}
}
else
{
DebugConsole.ThrowError($"Tried to rename an invalid character ({renamedIdentifier})");
}
}
if (location.HireManager != null) if (location.HireManager != null)
{ {
if (validateHires) if (validateHires)
{ {
foreach (CharacterInfo hireInfo in location.HireManager.PendingHires) foreach (CharacterInfo hireInfo in location.HireManager.PendingHires)
{ {
TryHireCharacter(location, hireInfo); if (TryHireCharacter(location, hireInfo))
{
hiredCharacters.Add(hireInfo);
};
} }
} }
@@ -706,10 +749,10 @@ namespace Barotrauma
List<CharacterInfo> pendingHireInfos = new List<CharacterInfo>(); List<CharacterInfo> pendingHireInfos = new List<CharacterInfo>();
foreach (int identifier in pendingHires) foreach (int identifier in pendingHires)
{ {
CharacterInfo match = location.GetHireableCharacters().FirstOrDefault(info => info.GetIdentifier() == identifier); CharacterInfo match = location.GetHireableCharacters().FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == identifier);
if (match == null) if (match == null)
{ {
DebugConsole.ThrowError($"Tried to hire a character that doesn't exist ({identifier})"); DebugConsole.ThrowError($"Tried to add a character that doesn't exist ({identifier}) to pending hires");
continue; continue;
} }
@@ -721,25 +764,39 @@ namespace Barotrauma
} }
location.HireManager.PendingHires = pendingHireInfos; location.HireManager.PendingHires = pendingHireInfos;
} }
location.HireManager.AvailableCharacters.ForEachMod(info =>
{
if(!location.HireManager.PendingHires.Contains(info))
{
location.HireManager.RenameCharacter(info, info.OriginalName);
}
});
} }
} }
// bounce back // bounce back
SendCrewState(validateHires, firedCharacter); if (renameCharacter && existingCrewMember)
{
SendCrewState(hiredCharacters, (renamedIdentifier, newName), firedCharacter);
}
else
{
SendCrewState(hiredCharacters, default, firedCharacter);
}
} }
/// <summary> /// <summary>
/// Notifies the clients of the current bot situation like syncing pending and available hires /// Notifies the clients of the current bot situation like syncing pending and available hires
/// available hires are also synced
/// </summary> /// </summary>
/// <param name="validateHires">When set to true notifies the clients that the hires have been validated.</param> /// <param name="hiredCharacters">Inform the clients that these characters have been hired.</param>
/// <param name="firedCharacter">When not null will inform the clients that his character has been fired.</param> /// <param name="firedCharacter">Inform the clients that this character has been fired.</param>
/// <remarks> /// <remarks>
/// It might be obsolete to sync available hires. I found that the available hires are always the same between /// It might be obsolete to sync available hires. I found that the available hires are always the same between
/// the client and the server when there's only one person on the server but when a second person joins both of /// the client and the server when there's only one person on the server but when a second person joins both of
/// their available hires are different from the server. /// their available hires are different from the server.
/// </remarks> /// </remarks>
public void SendCrewState(bool validateHires, CharacterInfo firedCharacter) public void SendCrewState(List<CharacterInfo> hiredCharacters, (int id, string newName) renamedCrewMember, CharacterInfo firedCharacter)
{ {
List<CharacterInfo> availableHires = new List<CharacterInfo>(); List<CharacterInfo> availableHires = new List<CharacterInfo>();
List<CharacterInfo> pendingHires = new List<CharacterInfo>(); List<CharacterInfo> pendingHires = new List<CharacterInfo>();
@@ -765,10 +822,26 @@ namespace Barotrauma
msg.Write((ushort)pendingHires.Count); msg.Write((ushort)pendingHires.Count);
foreach (CharacterInfo pendingHire in pendingHires) foreach (CharacterInfo pendingHire in pendingHires)
{ {
msg.Write(pendingHire.GetIdentifier()); msg.Write(pendingHire.GetIdentifierUsingOriginalName());
} }
msg.Write(validateHires); msg.Write((ushort)(hiredCharacters?.Count ?? 0));
if(hiredCharacters != null)
{
foreach (CharacterInfo info in hiredCharacters)
{
info.ServerWrite(msg);
msg.Write(info.Salary);
}
}
bool validRenaming = renamedCrewMember.id > -1 && !string.IsNullOrEmpty(renamedCrewMember.newName);
msg.Write(validRenaming);
if (validRenaming)
{
msg.Write(renamedCrewMember.id);
msg.Write(renamedCrewMember.newName);
}
msg.Write(firedCharacter != null); msg.Write(firedCharacter != null);
if (firedCharacter != null) { msg.Write(firedCharacter.GetIdentifier()); } if (firedCharacter != null) { msg.Write(firedCharacter.GetIdentifier()); }
@@ -786,6 +859,7 @@ namespace Barotrauma
new XAttribute("purchasedhullrepairs", PurchasedHullRepairs), new XAttribute("purchasedhullrepairs", PurchasedHullRepairs),
new XAttribute("purchaseditemrepairs", PurchasedItemRepairs), new XAttribute("purchaseditemrepairs", PurchasedItemRepairs),
new XAttribute("cheatsenabled", CheatsEnabled)); new XAttribute("cheatsenabled", CheatsEnabled));
modeElement.Add(Settings.Save());
CampaignMetadata?.Save(modeElement); CampaignMetadata?.Save(modeElement);
Map.Save(modeElement); Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement); CargoManager?.SavePurchasedItems(modeElement);
@@ -121,13 +121,14 @@ namespace Barotrauma.Networking
} }
} }
public bool IsBanned(IPAddress IP, ulong steamID, out string reason) public bool IsBanned(IPAddress IP, ulong steamID, ulong ownerSteamID, out string reason)
{ {
reason = string.Empty; reason = string.Empty;
if (IPAddress.IsLoopback(IP)) { return false; } if (IPAddress.IsLoopback(IP)) { return false; }
var bannedPlayer = bannedPlayers.Find(bp => var bannedPlayer = bannedPlayers.Find(bp =>
bp.CompareTo(IP) || bp.CompareTo(IP) ||
(steamID > 0 && (bp.SteamID == steamID || SteamManager.SteamIDStringToUInt64(bp.EndPoint) == steamID))); (steamID > 0 && (bp.SteamID == steamID || SteamManager.SteamIDStringToUInt64(bp.EndPoint) == steamID)) ||
(ownerSteamID > 0 && (bp.SteamID == ownerSteamID || SteamManager.SteamIDStringToUInt64(bp.EndPoint) == ownerSteamID)));
reason = bannedPlayer?.Reason; reason = bannedPlayer?.Reason;
return bannedPlayer != null; return bannedPlayer != null;
} }
@@ -166,6 +167,7 @@ namespace Barotrauma.Networking
public void BanPlayer(string name, ulong steamID, string reason, TimeSpan? duration) public void BanPlayer(string name, ulong steamID, string reason, TimeSpan? duration)
{ {
if (steamID == 0) { return; }
BanPlayer(name, "", steamID, reason, duration); BanPlayer(name, "", steamID, reason, duration);
} }
@@ -284,6 +284,7 @@ namespace Barotrauma.Networking
newClient.Connection = connection; newClient.Connection = connection;
newClient.Connection.Status = NetworkConnectionStatus.Connected; newClient.Connection.Status = NetworkConnectionStatus.Connected;
newClient.SteamID = connection.SteamID; newClient.SteamID = connection.SteamID;
newClient.OwnerSteamID = connection.OwnerSteamID;
newClient.Language = connection.Language; newClient.Language = connection.Language;
ConnectedClients.Add(newClient); ConnectedClients.Add(newClient);
@@ -308,12 +309,13 @@ namespace Barotrauma.Networking
SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient); SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient);
} }
SendChatMessage($"ServerMessage.JoinedServer~[client]={clName}", ChatMessageType.Server, null, changeType: PlayerConnectionChangeType.Joined); SendChatMessage($"ServerMessage.JoinedServer~[client]={ClientLogName(newClient)}", ChatMessageType.Server, null, changeType: PlayerConnectionChangeType.Joined);
serverSettings.ServerDetailsChanged = true; serverSettings.ServerDetailsChanged = true;
if (previousPlayer != null && previousPlayer.Name != newClient.Name) if (previousPlayer != null && previousPlayer.Name != newClient.Name)
{ {
SendChatMessage($"ServerMessage.PreviousClientName~[client]={clName}~[previousname]={previousPlayer.Name}", ChatMessageType.Server, null); string prevNameSanitized = previousPlayer.Name.Replace("‖", "");
SendChatMessage($"ServerMessage.PreviousClientName~[client]={ClientLogName(newClient)}~[previousname]={prevNameSanitized}", ChatMessageType.Server, null);
previousPlayer.Name = newClient.Name; previousPlayer.Name = newClient.Name;
} }
@@ -475,12 +477,14 @@ namespace Barotrauma.Networking
} }
else if (isCrewDead && respawnManager == null) else if (isCrewDead && respawnManager == null)
{ {
#if !DEBUG
if (endRoundTimer <= 0.0f) if (endRoundTimer <= 0.0f)
{ {
SendChatMessage(TextManager.GetWithVariable("CrewDeadNoRespawns", "[time]", "60"), ChatMessageType.Server); SendChatMessage(TextManager.GetWithVariable("CrewDeadNoRespawns", "[time]", "60"), ChatMessageType.Server);
} }
endRoundDelay = 60.0f; endRoundDelay = 60.0f;
endRoundTimer += deltaTime; endRoundTimer += deltaTime;
#endif
} }
else else
{ {
@@ -752,6 +756,7 @@ namespace Barotrauma.Networking
string seed = inc.ReadString(); string seed = inc.ReadString();
string subName = inc.ReadString(); string subName = inc.ReadString();
string subHash = inc.ReadString(); string subHash = inc.ReadString();
CampaignSettings settings = new CampaignSettings(inc);
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash); var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash);
@@ -772,10 +777,10 @@ namespace Barotrauma.Networking
string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName); string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName);
if (connectedClient.HasPermission(ClientPermissions.SelectMode) || connectedClient.HasPermission(ClientPermissions.ManageCampaign)) if (connectedClient.HasPermission(ClientPermissions.SelectMode) || connectedClient.HasPermission(ClientPermissions.ManageCampaign))
{ {
MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed); MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings);
} }
} }
} }
else else
{ {
string saveName = inc.ReadString(); string saveName = inc.ReadString();
@@ -1853,6 +1858,8 @@ namespace Barotrauma.Networking
{ {
outmsg.Write(autoRestartTimerRunning ? serverSettings.AutoRestartTimer : 0.0f); outmsg.Write(autoRestartTimerRunning ? serverSettings.AutoRestartTimer : 0.0f);
} }
outmsg.Write(serverSettings.RadiationEnabled);
} }
else else
{ {
@@ -2034,12 +2041,12 @@ namespace Barotrauma.Networking
} }
} }
startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(selectedSub, selectedShuttle, selectedMode), false); startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(selectedSub, selectedShuttle, selectedMode, CampaignSettings.Unsure), false);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private IEnumerable<object> StartGame(SubmarineInfo selectedSub, SubmarineInfo selectedShuttle, GameModePreset selectedMode) private IEnumerable<object> StartGame(SubmarineInfo selectedSub, SubmarineInfo selectedShuttle, GameModePreset selectedMode, CampaignSettings settings)
{ {
entityEventManager.Clear(); entityEventManager.Clear();
@@ -2067,7 +2074,7 @@ namespace Barotrauma.Networking
//don't instantiate a new gamesession if we're playing a campaign //don't instantiate a new gamesession if we're playing a campaign
if (campaign == null || GameMain.GameSession == null) if (campaign == null || GameMain.GameSession == null)
{ {
GameMain.GameSession = new GameSession(selectedSub, "", selectedMode, GameMain.NetLobbyScreen.LevelSeed, missionType: GameMain.NetLobbyScreen.MissionType); GameMain.GameSession = new GameSession(selectedSub, "", selectedMode, settings, GameMain.NetLobbyScreen.LevelSeed, missionType: GameMain.NetLobbyScreen.MissionType);
} }
List<Client> playingClients = new List<Client>(connectedClients); List<Client> playingClients = new List<Client>(connectedClients);
@@ -2716,6 +2723,10 @@ namespace Barotrauma.Networking
{ {
serverSettings.BanList.BanPlayer(client.Name, client.SteamID, reason, duration); serverSettings.BanList.BanPlayer(client.Name, client.SteamID, reason, duration);
} }
if (client.OwnerSteamID > 0)
{
serverSettings.BanList.BanPlayer(client.Name, client.OwnerSteamID, reason, duration);
}
} }
public void BanPreviousPlayer(PreviousPlayer previousPlayer, string reason, bool range = false, TimeSpan? duration = null) public void BanPreviousPlayer(PreviousPlayer previousPlayer, string reason, bool range = false, TimeSpan? duration = null)
@@ -2735,6 +2746,10 @@ namespace Barotrauma.Networking
{ {
serverSettings.BanList.BanPlayer(previousPlayer.Name, previousPlayer.SteamID, reason, duration); serverSettings.BanList.BanPlayer(previousPlayer.Name, previousPlayer.SteamID, reason, duration);
} }
if (previousPlayer.OwnerSteamID > 0)
{
serverSettings.BanList.BanPlayer(previousPlayer.Name, previousPlayer.OwnerSteamID, reason, duration);
}
string msg = $"ServerMessage.BannedFromServer~[client]={previousPlayer.Name}"; string msg = $"ServerMessage.BannedFromServer~[client]={previousPlayer.Name}";
if (!string.IsNullOrWhiteSpace(reason)) if (!string.IsNullOrWhiteSpace(reason))
@@ -2784,7 +2799,7 @@ namespace Barotrauma.Networking
client.HasSpawned = false; client.HasSpawned = false;
client.InGame = false; client.InGame = false;
if (string.IsNullOrWhiteSpace(msg)) { msg = $"ServerMessage.ClientLeftServer~[client]={client.Name}"; } if (string.IsNullOrWhiteSpace(msg)) { msg = $"ServerMessage.ClientLeftServer~[client]={ClientLogName(client)}"; }
if (string.IsNullOrWhiteSpace(targetmsg)) { targetmsg = "ServerMessage.YouLeftServer"; } if (string.IsNullOrWhiteSpace(targetmsg)) { targetmsg = "ServerMessage.YouLeftServer"; }
if (!string.IsNullOrWhiteSpace(reason)) if (!string.IsNullOrWhiteSpace(reason))
{ {
@@ -3747,7 +3762,7 @@ namespace Barotrauma.Networking
{ {
retVal += "color:#ff9900;"; retVal += "color:#ff9900;";
} }
retVal += "metadata:" + (client.SteamID != 0 ? client.SteamID.ToString() : client.ID.ToString()) + "‖" + (name ?? client.Name) + "‖end‖"; retVal += "metadata:" + (client.SteamID != 0 ? client.SteamID.ToString() : client.ID.ToString()) + "‖" + (name ?? client.Name).Replace("‖","") + "‖end‖";
return retVal; return retVal;
} }
@@ -3826,6 +3841,7 @@ namespace Barotrauma.Networking
public string Name; public string Name;
public string EndPoint; public string EndPoint;
public UInt64 SteamID; public UInt64 SteamID;
public UInt64 OwnerSteamID;
public float Karma; public float Karma;
public int KarmaKickCount; public int KarmaKickCount;
public readonly List<Client> KickVoters = new List<Client>(); public readonly List<Client> KickVoters = new List<Client>();
@@ -3835,6 +3851,7 @@ namespace Barotrauma.Networking
Name = c.Name; Name = c.Name;
EndPoint = c.Connection?.EndPointString ?? ""; EndPoint = c.Connection?.EndPointString ?? "";
SteamID = c.SteamID; SteamID = c.SteamID;
OwnerSteamID = c.OwnerSteamID;
} }
public bool MatchesClient(Client c) public bool MatchesClient(Client c)
@@ -184,7 +184,7 @@ namespace Barotrauma.Networking
return; return;
} }
if (serverSettings.BanList.IsBanned(inc.SenderConnection.RemoteEndPoint.Address, 0, out string banReason)) if (serverSettings.BanList.IsBanned(inc.SenderConnection.RemoteEndPoint.Address, 0, 0, out string banReason))
{ {
//IP banned: deny immediately //IP banned: deny immediately
inc.SenderConnection.Deny(DisconnectReason.Banned.ToString() + "/ " + banReason); inc.SenderConnection.Deny(DisconnectReason.Banned.ToString() + "/ " + banReason);
@@ -233,7 +233,7 @@ namespace Barotrauma.Networking
return; return;
} }
if (pendingClient != null) { pendingClients.Remove(pendingClient); } if (pendingClient != null) { pendingClients.Remove(pendingClient); }
if (serverSettings.BanList.IsBanned(conn.IPEndPoint.Address, conn.SteamID, out string banReason)) if (serverSettings.BanList.IsBanned(conn.IPEndPoint.Address, conn.SteamID, conn.OwnerSteamID, out string banReason))
{ {
Disconnect(conn, DisconnectReason.Banned.ToString() + "/ " + banReason); Disconnect(conn, DisconnectReason.Banned.ToString() + "/ " + banReason);
return; return;
@@ -308,7 +308,8 @@ namespace Barotrauma.Networking
} }
LidgrenConnection pendingConnection = pendingClient.Connection as LidgrenConnection; LidgrenConnection pendingConnection = pendingClient.Connection as LidgrenConnection;
if (serverSettings.BanList.IsBanned(pendingConnection.NetConnection.RemoteEndPoint.Address, steamID, out string banReason)) string banReason;
if (serverSettings.BanList.IsBanned(pendingConnection.NetConnection.RemoteEndPoint.Address, steamID, ownerID, out banReason))
{ {
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason); RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
return; return;
@@ -316,6 +317,7 @@ namespace Barotrauma.Networking
if (status == Steamworks.AuthResponse.OK) if (status == Steamworks.AuthResponse.OK)
{ {
pendingClient.OwnerSteamID = ownerID;
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder; pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
pendingClient.UpdateTime = Timing.TotalTime; pendingClient.UpdateTime = Timing.TotalTime;
} }
@@ -452,7 +454,7 @@ namespace Barotrauma.Networking
pendingClient.AuthSessionStarted = true; pendingClient.AuthSessionStarted = true;
} }
} }
else //TODO: could remove since this seems impossible else
{ {
if (pendingClient.SteamID != steamId) if (pendingClient.SteamID != steamId)
{ {
@@ -49,6 +49,16 @@ namespace Barotrauma.Networking
Connection.SetSteamIDIfUnknown(value ?? 0); Connection.SetSteamIDIfUnknown(value ?? 0);
} }
} }
private UInt64? ownerSteamId;
public UInt64? OwnerSteamID
{
get { return ownerSteamId; }
set
{
ownerSteamId = value;
Connection.SetOwnerSteamIDIfUnknown(value ?? 0);
}
}
public Int32? PasswordSalt; public Int32? PasswordSalt;
public bool AuthSessionStarted; public bool AuthSessionStarted;
@@ -59,6 +69,7 @@ namespace Barotrauma.Networking
InitializationStep = ConnectionInitialization.SteamTicketAndVersion; InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0; Retries = 0;
SteamID = null; SteamID = null;
OwnerSteamID = null;
PasswordSalt = null; PasswordSalt = null;
UpdateTime = Timing.TotalTime + Timing.Step * 3.0; UpdateTime = Timing.TotalTime + Timing.Step * 3.0;
TimeOut = NetworkConnection.TimeoutThreshold; TimeOut = NetworkConnection.TimeoutThreshold;
@@ -107,8 +118,8 @@ namespace Barotrauma.Networking
RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion, RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={version}"); $"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={version}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error); GameServer.Log($"{name} ({steamId}) couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red); DebugConsole.NewMessage($"{name} ({steamId}) couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return; return;
} }
@@ -119,7 +130,7 @@ namespace Barotrauma.Networking
if (nameTaken != null) if (nameTaken != null)
{ {
RemovePendingClient(pendingClient, DisconnectReason.NameTaken, ""); RemovePendingClient(pendingClient, DisconnectReason.NameTaken, "");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error); GameServer.Log($"{name} ({steamId}) couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error);
return; return;
} }
@@ -172,6 +183,7 @@ namespace Barotrauma.Networking
else if (pendingClient.Connection is SteamP2PConnection s) else if (pendingClient.Connection is SteamP2PConnection s)
{ {
serverSettings.BanList.BanPlayer(pendingClient.Name, s.SteamID, banReason, duration); serverSettings.BanList.BanPlayer(pendingClient.Name, s.SteamID, banReason, duration);
serverSettings.BanList.BanPlayer(pendingClient.Name, s.OwnerSteamID, banReason, duration);
} }
} }
@@ -183,7 +195,8 @@ namespace Barotrauma.Networking
} }
else if (pendingClient.Connection is SteamP2PConnection s) else if (pendingClient.Connection is SteamP2PConnection s)
{ {
return serverSettings.BanList.IsBanned(s.SteamID, out banReason); return serverSettings.BanList.IsBanned(s.SteamID, out banReason) ||
serverSettings.BanList.IsBanned(s.OwnerSteamID, out banReason);
} }
banReason = null; banReason = null;
return false; return false;
@@ -273,6 +286,7 @@ namespace Barotrauma.Networking
{ {
Steam.SteamManager.StopAuthSession(pendingClient.SteamID.Value); Steam.SteamManager.StopAuthSession(pendingClient.SteamID.Value);
pendingClient.SteamID = null; pendingClient.SteamID = null;
pendingClient.OwnerSteamID = null;
pendingClient.AuthSessionStarted = false; pendingClient.AuthSessionStarted = false;
} }
} }
@@ -123,6 +123,7 @@ namespace Barotrauma.Networking
if (!started) { return; } if (!started) { return; }
UInt64 senderSteamId = inc.ReadUInt64(); UInt64 senderSteamId = inc.ReadUInt64();
UInt64 ownerSteamId = inc.ReadUInt64();
byte incByte = inc.ReadByte(); byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0; bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
@@ -145,7 +146,9 @@ namespace Barotrauma.Networking
pendingClient?.Heartbeat(); pendingClient?.Heartbeat();
connectedClient?.Heartbeat(); connectedClient?.Heartbeat();
if (serverSettings.BanList.IsBanned(senderSteamId, out string banReason)) string banReason;
if (serverSettings.BanList.IsBanned(senderSteamId, out banReason) ||
serverSettings.BanList.IsBanned(ownerSteamId, out banReason))
{ {
if (pendingClient != null) if (pendingClient != null)
{ {
@@ -181,6 +184,10 @@ namespace Barotrauma.Networking
if (pendingClient != null) if (pendingClient != null)
{ {
if (ownerSteamId != 0)
{
pendingClient.Connection.SetOwnerSteamIDIfUnknown(ownerSteamId);
}
ReadConnectionInitializationStep(pendingClient, new ReadOnlyMessage(inc.Buffer, false, inc.BytePosition, inc.LengthBytes - inc.BytePosition, null)); ReadConnectionInitializationStep(pendingClient, new ReadOnlyMessage(inc.Buffer, false, inc.BytePosition, inc.LengthBytes - inc.BytePosition, null));
} }
else else
@@ -223,6 +230,7 @@ namespace Barotrauma.Networking
{ {
Language = GameMain.Config.Language Language = GameMain.Config.Language
}; };
OwnerConnection.SetOwnerSteamIDIfUnknown(OwnerSteamID);
OnInitializationComplete?.Invoke(OwnerConnection); OnInitializationComplete?.Invoke(OwnerConnection);
} }
@@ -159,6 +159,8 @@ namespace Barotrauma.Networking
AutoRestart = autoRestart; AutoRestart = autoRestart;
} }
RadiationEnabled = incMsg.ReadBoolean();
changed |= true; changed |= true;
} }
@@ -11,6 +11,8 @@ namespace Barotrauma
private SubmarineInfo selectedSub; private SubmarineInfo selectedSub;
private SubmarineInfo selectedShuttle; private SubmarineInfo selectedShuttle;
public bool RadiationEnabled = true;
public SubmarineInfo SelectedSub public SubmarineInfo SelectedSub
{ {
get { return selectedSub; } get { return selectedSub; }
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1300.0.0</Version> <Version>0.1300.0.1</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -62,6 +62,7 @@
<Character file="Content/Characters/Balloon/Balloon.xml" /> <Character file="Content/Characters/Balloon/Balloon.xml" />
<Character file="Content/Characters/Carrier/Carrier.xml" /> <Character file="Content/Characters/Carrier/Carrier.xml" />
<Character file="Content/Characters/Charybdis/Charybdis.xml" /> <Character file="Content/Characters/Charybdis/Charybdis.xml" />
<Character file="Content/Characters/Charybdisold/Charybdisold.xml" />
<Character file="Content/Characters/Coelanth/Coelanth.xml" /> <Character file="Content/Characters/Coelanth/Coelanth.xml" />
<Character file="Content/Characters/Crawler/Crawler.xml" /> <Character file="Content/Characters/Crawler/Crawler.xml" />
<Character file="Content/Characters/Crawlerhusk/Crawlerhusk.xml" /> <Character file="Content/Characters/Crawlerhusk/Crawlerhusk.xml" />
@@ -248,4 +249,6 @@
<OutpostModule file="Content/Map/Outposts/Hall4wayModule_01_Abandoned.sub" /> <OutpostModule file="Content/Map/Outposts/Hall4wayModule_01_Abandoned.sub" />
<BeaconStation file="Content/Map/BeaconStations/BeaconStation1.sub" /> <BeaconStation file="Content/Map/BeaconStations/BeaconStation1.sub" />
<OutpostModule file="Content/Map/Outposts/MineModule_04.sub" /> <OutpostModule file="Content/Map/Outposts/MineModule_04.sub" />
<OutpostModule file="Content/Map/Outposts/HallModuleHorizontal_Abandoned.sub" />
<OutpostModule file="Content/Map/Outposts/HallModuleVertical_Abandoned.sub" />
</contentpackage> </contentpackage>
@@ -232,8 +232,7 @@ namespace Barotrauma
public bool IsWithinSector(Vector2 worldPosition) public bool IsWithinSector(Vector2 worldPosition)
{ {
if (sectorRad >= MathHelper.TwoPi) return true; if (sectorRad >= MathHelper.TwoPi) { return true; }
Vector2 diff = worldPosition - WorldPosition; Vector2 diff = worldPosition - WorldPosition;
return MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir)) <= sectorRad * 0.5f; return MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir)) <= sectorRad * 0.5f;
} }
@@ -298,7 +298,7 @@ namespace Barotrauma
private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget)); private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget));
private string GetTargetingTag(AITarget aiTarget) private string GetTargetingTag(AITarget aiTarget)
{ {
if (aiTarget.Entity == null) { return null; } if (aiTarget?.Entity == null) { return null; }
string targetingTag = null; string targetingTag = null;
if (aiTarget.Entity is Character targetCharacter) if (aiTarget.Entity is Character targetCharacter)
{ {
@@ -377,6 +377,7 @@ namespace Barotrauma
{ {
if (DisableEnemyAI) { return; } if (DisableEnemyAI) { return; }
base.Update(deltaTime); base.Update(deltaTime);
UpdateTriggers(deltaTime);
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
if (steeringManager == insideSteering) if (steeringManager == insideSteering)
@@ -462,7 +463,7 @@ namespace Barotrauma
{ {
updateTargetsTimer -= deltaTime; updateTargetsTimer -= deltaTime;
} }
else if (avoidTimer <= 0) else if (avoidTimer <= 0 || activeTriggers.Any() && returnTimer <= 0)
{ {
CharacterParams.TargetParams targetingParams = null; CharacterParams.TargetParams targetingParams = null;
UpdateTargets(Character, out targetingParams); UpdateTargets(Character, out targetingParams);
@@ -1448,8 +1449,12 @@ namespace Barotrauma
break; break;
case AttackPattern.Circle: case AttackPattern.Circle:
if (IsCoolDownRunning) { break; } if (IsCoolDownRunning) { break; }
if (IsAttackRunning) { break; } if (IsAttackRunning && CirclePhase != CirclePhase.Strike) { break; }
if (selectedTargetingParams == null) { break; } if (selectedTargetingParams == null) { break; }
var targetSub = SelectedAiTarget.Entity?.Submarine;
if (targetSub == null) { break; }
float subSize = Math.Max(targetSub.Borders.Width, targetSub.Borders.Height) / 2;
float sqrDistToSub = Vector2.DistanceSquared(WorldPosition, targetSub.WorldPosition);
switch (CirclePhase) switch (CirclePhase)
{ {
case CirclePhase.Start: case CirclePhase.Start:
@@ -1471,22 +1476,31 @@ namespace Barotrauma
circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f))); circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f)));
canAttack = false; canAttack = false;
aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression); aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression);
CirclePhase = Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance) ? CirclePhase.CloseIn : CirclePhase.FallBack; if (targetSub.Borders.Width < 1000)
{
breakCircling = true;
CirclePhase = CirclePhase.CloseIn;
}
else if (sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance))
{
CirclePhase = CirclePhase.CloseIn;
}
else if (sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
{
CirclePhase = CirclePhase.FallBack;
}
else
{
CirclePhase = CirclePhase.Advance;
}
break; break;
case CirclePhase.CloseIn: case CirclePhase.CloseIn:
var sub = SelectedAiTarget.Entity?.Submarine; if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity))
if (sub == null)
{
CirclePhase = CirclePhase.Start;
break;
}
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(sub.Velocity))
{ {
strikeTimer = AttackingLimb.attack.CoolDown; strikeTimer = AttackingLimb.attack.CoolDown;
CirclePhase = CirclePhase.Strike; CirclePhase = CirclePhase.Strike;
} }
else if (!breakCircling && Vector2.DistanceSquared(WorldPosition, attackWorldPos) <= MathUtils.Pow2(circleFallbackDistance - 1000) && else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
sub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
{ {
CirclePhase = CirclePhase.Advance; CirclePhase = CirclePhase.Advance;
} }
@@ -1494,23 +1508,17 @@ namespace Barotrauma
break; break;
case CirclePhase.FallBack: case CirclePhase.FallBack:
bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true); bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true);
if (isBlocked || Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance)) if (isBlocked || sqrDistToSub > MathUtils.Pow2(subSize + circleFallbackDistance))
{ {
CirclePhase = CirclePhase.Advance; CirclePhase = CirclePhase.Advance;
break; break;
} }
return; return;
case CirclePhase.Advance: case CirclePhase.Advance:
var targetSub = SelectedAiTarget.Entity?.Submarine;
if (targetSub == null)
{
CirclePhase = CirclePhase.Start;
break;
}
Vector2 subSpeed = targetSub.Velocity; Vector2 subSpeed = targetSub.Velocity;
float requiredDistMultiplier = 1; float requiredDistMultiplier = 1;
// If the target sub is moving fast, just steer towards the target until close enough to strike // If the target sub is moving fast, just steer towards the target until close enough to strike
if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || distance > selectedTargetingParams.CircleStartDistance + 1000) if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance * 1.2f))
{ {
CirclePhase = CirclePhase.CloseIn; CirclePhase = CirclePhase.CloseIn;
} }
@@ -1532,7 +1540,7 @@ namespace Barotrauma
// When the offset position is outside of the sub it happens that the creature sometimes reaches the target point, // When the offset position is outside of the sub it happens that the creature sometimes reaches the target point,
// which makes it continue circling around the point (as supposed) // which makes it continue circling around the point (as supposed)
// But when there is some offset and the offset is too near, this is not what we want. // But when there is some offset and the offset is too near, this is not what we want.
if (targetSub.Borders.ContainsWorld(attackWorldPos + ConvertUnits.ToDisplayUnits(circleOffset))) if (AttackingLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
{ {
CirclePhase = CirclePhase.Strike; CirclePhase = CirclePhase.Strike;
strikeTimer = AttackingLimb.attack.CoolDown; strikeTimer = AttackingLimb.attack.CoolDown;
@@ -1646,15 +1654,15 @@ namespace Barotrauma
return; return;
} }
} }
if (UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb)) if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
{
CirclePhase = CirclePhase.Start;
}
else
{ {
IgnoreTarget(SelectedAiTarget); IgnoreTarget(SelectedAiTarget);
} }
} }
else if (IsAttackRunning)
{
AttackingLimb.attack.ResetAttackTimer();
}
} }
private readonly List<Limb> attackLimbs = new List<Limb>(); private readonly List<Limb> attackLimbs = new List<Limb>();
@@ -2060,6 +2068,7 @@ namespace Barotrauma
targetValue = 0; targetValue = 0;
selectedTargetMemory = null; selectedTargetMemory = null;
targetingParams = null; targetingParams = null;
bool isAnyTargetClose = false;
foreach (AITarget aiTarget in AITarget.List) foreach (AITarget aiTarget in AITarget.List)
{ {
@@ -2163,6 +2172,14 @@ namespace Barotrauma
continue; continue;
} }
} }
if (door == null)
{
// Ignore items inside ruins, unless we are in the same hull. We can't target the ruin walls.
if (item.Submarine == null && item.CurrentHull != Character.CurrentHull)
{
continue;
}
}
foreach (var prio in AIParams.Targets) foreach (var prio in AIParams.Targets)
{ {
if (item.HasTag(prio.Tag)) if (item.HasTag(prio.Tag))
@@ -2379,18 +2396,16 @@ namespace Barotrauma
} }
} }
} }
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
Vector2 toTarget = aiTarget.WorldPosition - character.WorldPosition; Vector2 toTarget = aiTarget.WorldPosition - character.WorldPosition;
float dist = toTarget.Length(); float dist = toTarget.Length();
float nonModifiedDist = dist;
//if the target has been within range earlier, the character will notice it more easily //if the target has been within range earlier, the character will notice it more easily
if (targetMemories.ContainsKey(aiTarget)) if (targetMemories.ContainsKey(aiTarget))
{ {
dist *= 0.9f; dist *= 0.9f;
} }
if (!CanPerceive(aiTarget, dist)) { continue; } if (!CanPerceive(aiTarget, dist)) { continue; }
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
//if the target is very close, the distance doesn't make much difference //if the target is very close, the distance doesn't make much difference
// -> just ignore the distance and attack whatever has the highest priority // -> just ignore the distance and attack whatever has the highest priority
@@ -2405,14 +2420,26 @@ namespace Barotrauma
if (targetParams.AttackPattern == AttackPattern.Circle) if (targetParams.AttackPattern == AttackPattern.Circle)
{ {
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null) if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose)
{ {
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine)) if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
{ {
// Prioritize targets that are near the horizontal center of the sub // Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X); float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter)); dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
} }
else
{
dist *= 5;
}
}
}
// Don't target characters that are outside of the allowed zone, unless attacking or escaping
if (targetParams.State != AIState.Attack && targetParams.State != AIState.Escape && targetParams.State != AIState.Avoid)
{
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
{
continue;
} }
} }
@@ -2486,11 +2513,26 @@ namespace Barotrauma
} }
} }
} }
else if (targetCharacter.Submarine == null && Character.Submarine == null)
{
// Ignore the target when it's far enough and blocked by the level geometry, because the steering avoidance probably can't get us to the target.
if (dist > Math.Clamp(ConvertUnits.ToDisplayUnits(colliderLength) * 10, 1000, 5000))
{
if (Submarine.PickBodies(SimPosition, targetCharacter.SimPosition, collisionCategory: Physics.CollisionLevel).Any())
{
continue;
}
}
}
} }
newTarget = aiTarget; newTarget = aiTarget;
selectedTargetMemory = targetMemory; selectedTargetMemory = targetMemory;
targetValue = valueModifier; targetValue = valueModifier;
targetingParams = targetParams; targetingParams = targetParams;
if (!isAnyTargetClose)
{
isAnyTargetClose = ConvertUnits.ToDisplayUnits(colliderLength) > nonModifiedDist;
}
} }
} }
@@ -2619,7 +2661,7 @@ namespace Barotrauma
} }
} }
} }
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null) if (!Character.AnimController.CanEnterSubmarine && wallTarget == null && selectedTargetingParams?.AttackPattern == AttackPattern.Straight)
{ {
if (closestBody.UserData is Structure w && w.Submarine != null && w.Submarine == SelectedAiTarget.Entity?.Submarine || if (closestBody.UserData is Structure w && w.Submarine != null && w.Submarine == SelectedAiTarget.Entity?.Submarine ||
closestBody.UserData is Item i && i.Submarine != null && i.Submarine == SelectedAiTarget.Entity?.Submarine) closestBody.UserData is Item i && i.Submarine != null && i.Submarine == SelectedAiTarget.Entity?.Submarine)
@@ -2753,6 +2795,44 @@ namespace Barotrauma
private readonly float stateResetCooldown = 10; private readonly float stateResetCooldown = 10;
private float stateResetTimer; private float stateResetTimer;
private bool isStateChanged; private bool isStateChanged;
private readonly Dictionary<AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<AITrigger, CharacterParams.TargetParams>();
private readonly HashSet<AITrigger> inactiveTriggers = new HashSet<AITrigger>();
public void LaunchTrigger(AITrigger trigger)
{
if (trigger.IsTriggered) { return; }
if (activeTriggers.ContainsKey(trigger)) { return; }
if (activeTriggers.ContainsValue(selectedTargetingParams))
{
if (!trigger.AllowToOverride) { return; }
var existingTrigger = activeTriggers.FirstOrDefault(kvp => kvp.Value == selectedTargetingParams && kvp.Key.AllowToBeOverridden);
if (existingTrigger.Key == null) { return; }
activeTriggers.Remove(existingTrigger.Key);
}
trigger.Launch();
activeTriggers.Add(trigger, selectedTargetingParams);
ChangeParams(selectedTargetingParams, trigger.State);
}
private void UpdateTriggers(float deltaTime)
{
foreach (var triggerObject in activeTriggers)
{
AITrigger trigger = triggerObject.Key;
trigger.UpdateTimer(deltaTime);
if (!trigger.IsActive)
{
trigger.Reset();
ResetParams(triggerObject.Value);
inactiveTriggers.Add(trigger);
}
}
foreach (AITrigger trigger in inactiveTriggers)
{
activeTriggers.Remove(trigger);
}
inactiveTriggers.Clear();
}
/// <summary> /// <summary>
/// Resets the target's state to the original value defined in the xml. /// Resets the target's state to the original value defined in the xml.
@@ -2768,11 +2848,7 @@ namespace Barotrauma
tempParams.Values.ForEach(t => AIParams.RemoveTarget(t)); tempParams.Values.ForEach(t => AIParams.RemoveTarget(t));
tempParams.Remove(tag); tempParams.Remove(tag);
} }
targetParams.Reset(); ResetParams(targetParams);
ResetAITarget();
// Enforce the idle state so that we don't keep following the target if there's one
State = AIState.Idle;
PreviousState = AIState.Idle;
return true; return true;
} }
else else
@@ -2784,6 +2860,27 @@ namespace Barotrauma
private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>(); private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>();
private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>(); private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>();
private void ChangeParams(CharacterParams.TargetParams targetParams, AIState state, float? priority = null)
{
if (targetParams == null) { return; }
if (priority.HasValue)
{
targetParams.Priority = priority.Value;
}
targetParams.State = state;
}
private void ResetParams(CharacterParams.TargetParams targetParams)
{
targetParams?.Reset();
if (selectedTargetingParams == targetParams || State == AIState.Idle)
{
ResetAITarget();
State = AIState.Idle;
PreviousState = AIState.Idle;
}
}
private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false) private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false)
{ {
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams)) if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
@@ -2938,45 +3035,56 @@ namespace Barotrauma
} }
} }
private bool IsPositionInsideAllowedZone(Vector2 pos, out Vector2 targetDir)
{
targetDir = Vector2.Zero;
if (AIParams.AvoidAbyss)
{
if (pos.Y < Level.Loaded.AbyssStart)
{
// Too far down
targetDir = Vector2.UnitY;
}
}
if (AIParams.StayInAbyss)
{
if (pos.Y > Level.Loaded.AbyssStart)
{
// Too far up
targetDir = -Vector2.UnitY;
}
else if (pos.Y < Level.Loaded.AbyssEnd)
{
// Too far down
targetDir = Vector2.UnitY;
}
}
float margin = 30000;
if (pos.X < -margin)
{
// Too far left
targetDir = Vector2.UnitX;
}
else if (pos.X > Level.Loaded.Size.X + margin)
{
// Too far right
targetDir = -Vector2.UnitX;
}
return targetDir == Vector2.Zero;
}
private Vector2 returnDir; private Vector2 returnDir;
private float returnTimer; private float returnTimer;
private void SteerInsideLevel(float deltaTime) private void SteerInsideLevel(float deltaTime)
{ {
if (State == AIState.Attack) { return; }
if (SteeringManager is IndoorsSteeringManager) { return; } if (SteeringManager is IndoorsSteeringManager) { return; }
if (Level.Loaded == null) { return; } if (Level.Loaded == null) { return; }
Point levelSize = Level.Loaded.Size; if (State == AIState.Attack && returnTimer <= 0) { return; }
float returnTime = 10; float returnTime = 5;
if (AIParams.AvoidAbyss) if (!IsPositionInsideAllowedZone(WorldPosition, out Vector2 targetDir))
{ {
if (WorldPosition.Y < Level.Loaded.AbyssStart) returnDir = targetDir;
{
// Too far down
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
returnDir = Vector2.UnitY;
}
}
else if (AIParams.StayInAbyss)
{
if (WorldPosition.Y > Level.Loaded.AbyssStart)
{
// Too far up
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
returnDir = -Vector2.UnitY;
}
}
float margin = AIParams.AvoidAbyss ? 0 : 30000;
if (WorldPosition.X < margin)
{
// Too far left
returnTimer = returnTime * Rand.Range(0.75f, 1.25f); returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
returnDir = Vector2.UnitX;
}
if (WorldPosition.X > levelSize.X + margin)
{
// Too far right
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
returnDir = -Vector2.UnitX;
} }
if (returnTimer > 0) if (returnTimer > 0)
{ {
@@ -15,7 +15,7 @@ namespace Barotrauma
private readonly AIObjectiveManager objectiveManager; private readonly AIObjectiveManager objectiveManager;
private float sortTimer; public float SortTimer { get; set; }
private float crouchRaycastTimer; private float crouchRaycastTimer;
private float reactTimer; private float reactTimer;
private float unreachableClearTimer; private float unreachableClearTimer;
@@ -131,7 +131,7 @@ namespace Barotrauma
outsideSteering = new SteeringManager(this); outsideSteering = new SteeringManager(this);
objectiveManager = new AIObjectiveManager(c); objectiveManager = new AIObjectiveManager(c);
reactTimer = GetReactionTime(); reactTimer = GetReactionTime();
sortTimer = Rand.Range(0f, sortObjectiveInterval); SortTimer = Rand.Range(0f, sortObjectiveInterval);
} }
public override void Update(float deltaTime) public override void Update(float deltaTime)
@@ -218,6 +218,7 @@ namespace Barotrauma
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (c.Submarine != Character.Submarine) { continue; } if (c.Submarine != Character.Submarine) { continue; }
if (c.Removed || c.IsDead || c.IsIncapacitated) { continue; }
if (IsFriendly(c)) { continue; } if (IsFriendly(c)) { continue; }
Vector2 toTarget = c.WorldPosition - WorldPosition; Vector2 toTarget = c.WorldPosition - WorldPosition;
float dist = toTarget.LengthSquared(); float dist = toTarget.LengthSquared();
@@ -264,14 +265,14 @@ namespace Barotrauma
CheckCrouching(deltaTime); CheckCrouching(deltaTime);
Character.ClearInputs(); Character.ClearInputs();
if (sortTimer > 0.0f) if (SortTimer > 0.0f)
{ {
sortTimer -= deltaTime; SortTimer -= deltaTime;
} }
else else
{ {
objectiveManager.SortObjectives(); objectiveManager.SortObjectives();
sortTimer = sortObjectiveInterval; SortTimer = sortObjectiveInterval;
} }
objectiveManager.UpdateObjectives(deltaTime); objectiveManager.UpdateObjectives(deltaTime);
@@ -288,14 +289,14 @@ namespace Barotrauma
{ {
if (Character.CurrentHull != null) if (Character.CurrentHull != null)
{ {
if (Character.TeamID == CharacterTeamType.FriendlyNPC) if (Character.IsOnPlayerTeam)
{ {
// Outpost npcs don't inform each other about threats, like crew members do. VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
VisibleHulls.ForEach(h => RefreshHullSafety(h));
} }
else else
{ {
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h)); // Outpost npcs don't inform each other about threats, like crew members do.
VisibleHulls.ForEach(h => RefreshHullSafety(h));
} }
} }
if (Character.SpeechImpediment < 100.0f) if (Character.SpeechImpediment < 100.0f)
@@ -1065,7 +1066,9 @@ namespace Barotrauma
{ {
if (!IsFriendly(attacker)) if (!IsFriendly(attacker))
{ {
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive; return c.AIController is HumanAIController humanAI &&
(humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders))
? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
} }
else else
{ {
@@ -1192,7 +1195,7 @@ namespace Barotrauma
{ {
base.Reset(); base.Reset();
objectiveManager.SortObjectives(); objectiveManager.SortObjectives();
sortTimer = sortObjectiveInterval; SortTimer = sortObjectiveInterval;
float waitDuration = characterWaitOnSwitch; float waitDuration = characterWaitOnSwitch;
if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>()) if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>())
{ {
@@ -1418,6 +1421,9 @@ namespace Barotrauma
item.StolenDuringRound = true; item.StolenDuringRound = true;
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f); otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
someoneSpoke = true; someoneSpoke = true;
#if CLIENT
HintManager.OnStoleItem(thief, item);
#endif
} }
// React if we are security // React if we are security
if (!TriggerSecurity(otherHumanAI)) if (!TriggerSecurity(otherHumanAI))
@@ -1554,7 +1560,7 @@ namespace Barotrauma
targetAdded = true; targetAdded = true;
} }
} }
}, (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity); }, range: (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity);
return targetAdded; return targetAdded;
} }
@@ -1726,11 +1732,9 @@ namespace Barotrauma
switch (myTeam) switch (myTeam)
{ {
case CharacterTeamType.None: case CharacterTeamType.None:
// Only enemies are in the Team "None"
return false;
case CharacterTeamType.Team1: case CharacterTeamType.Team1:
case CharacterTeamType.Team2: case CharacterTeamType.Team2:
// Team1 is only friendly to Team1 and friendly NPCs // Only friendly to the same team and friendly NPCs
return otherTeam == CharacterTeamType.FriendlyNPC; return otherTeam == CharacterTeamType.FriendlyNPC;
case CharacterTeamType.FriendlyNPC: case CharacterTeamType.FriendlyNPC:
// Friendly NPCs are friendly to both teams // Friendly NPCs are friendly to both teams
@@ -221,6 +221,19 @@ namespace Barotrauma
{ {
currentFlags.Add("CampaignNPC." + speaker.CampaignInteractionType); currentFlags.Add("CampaignNPC." + speaker.CampaignInteractionType);
} }
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode &&
(campaignMode.Map?.CurrentLocation?.Type?.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase) ?? false))
{
if (speaker.TeamID == CharacterTeamType.None)
{
currentFlags.Add("Bandit");
}
else if (speaker.TeamID == CharacterTeamType.FriendlyNPC)
{
currentFlags.Add("Hostage");
}
}
} }
return currentFlags; return currentFlags;
@@ -79,6 +79,7 @@ namespace Barotrauma
private float coolDownTimer; private float coolDownTimer;
private IEnumerable<Body> myBodies; private IEnumerable<Body> myBodies;
private float aimTimer; private float aimTimer;
private float reloadTimer;
private float spreadTimer; private float spreadTimer;
private bool canSeeTarget; private bool canSeeTarget;
@@ -147,6 +148,7 @@ namespace Barotrauma
Mode = CombatMode.Retreat; Mode = CombatMode.Retreat;
} }
spreadTimer = Rand.Range(-10, 10); spreadTimer = Rand.Range(-10, 10);
HumanAIController.SortTimer = 0;
} }
public override float GetPriority() public override float GetPriority()
@@ -170,6 +172,10 @@ namespace Barotrauma
base.Update(deltaTime); base.Update(deltaTime);
ignoreWeaponTimer -= deltaTime; ignoreWeaponTimer -= deltaTime;
checkWeaponsTimer -= deltaTime; checkWeaponsTimer -= deltaTime;
if (reloadTimer > 0)
{
reloadTimer -= deltaTime;
}
if (ignoreWeaponTimer < 0) if (ignoreWeaponTimer < 0)
{ {
ignoredWeapons.Clear(); ignoredWeapons.Clear();
@@ -219,7 +225,11 @@ namespace Barotrauma
{ {
OperateWeapon(deltaTime); OperateWeapon(deltaTime);
} }
if (!HoldPosition && seekAmmunitionObjective == null && seekWeaponObjective == null) if (HoldPosition)
{
SteeringManager.Reset();
}
else if (seekAmmunitionObjective == null && seekWeaponObjective == null)
{ {
Move(deltaTime); Move(deltaTime);
} }
@@ -641,7 +651,7 @@ namespace Barotrauma
var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand)); var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
if (character.Inventory.TryPutItem(Weapon, character, slots)) if (character.Inventory.TryPutItem(Weapon, character, slots))
{ {
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed; aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
} }
else else
{ {
@@ -912,7 +922,7 @@ namespace Barotrauma
} }
if (!canSeeTarget) if (!canSeeTarget)
{ {
aimTimer = Rand.Range(0.2f, 1f) / AimSpeed; aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
return; return;
} }
if (Weapon.RequireAimToUse) if (Weapon.RequireAimToUse)
@@ -930,6 +940,7 @@ namespace Barotrauma
aimTimer -= deltaTime; aimTimer -= deltaTime;
return; return;
} }
if (reloadTimer > 0) { return; }
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; } if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
if (holdFireCondition != null && holdFireCondition()) { return; } if (holdFireCondition != null && holdFireCondition()) { return; }
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position); float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
@@ -1010,18 +1021,25 @@ namespace Barotrauma
private void UseWeapon(float deltaTime) private void UseWeapon(float deltaTime)
{ {
character.SetInput(InputType.Shoot, false, true);
Weapon.Use(deltaTime, character);
float reloadTime = 0; float reloadTime = 0;
if (WeaponComponent is RangedWeapon rangedWeapon) if (WeaponComponent is RangedWeapon rangedWeapon)
{ {
reloadTime = rangedWeapon.Reload; // If the weapon is just equipped, we can't shoot just yet.
if (rangedWeapon.ReloadTimer <= 0)
{
reloadTime = rangedWeapon.Reload;
}
} }
if (WeaponComponent is MeleeWeapon mw) if (WeaponComponent is MeleeWeapon mw)
{ {
reloadTime = mw.Reload; if (!((HumanoidAnimController)character.AnimController).Crouching)
{
reloadTime = mw.Reload;
}
} }
aimTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.5f) / AimSpeed); character.SetInput(InputType.Shoot, false, true);
Weapon.Use(deltaTime, character);
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
} }
protected override void OnCompleted() protected override void OnCompleted()
@@ -1031,10 +1049,7 @@ namespace Barotrauma
{ {
Unequip(); Unequip();
} }
if (!HoldPosition) SteeringManager.Reset();
{
SteeringManager.Reset();
}
} }
protected override void OnAbandon() protected override void OnAbandon()
@@ -1044,10 +1059,7 @@ namespace Barotrauma
{ {
Unequip(); Unequip();
} }
if (!HoldPosition) SteeringManager.Reset();
{
SteeringManager.Reset();
}
} }
public override void Reset() public override void Reset()
@@ -10,6 +10,8 @@ namespace Barotrauma
protected override float IgnoreListClearInterval => 30; protected override float IgnoreListClearInterval => 30;
public override bool IgnoreUnsafeHulls => true; public override bool IgnoreUnsafeHulls => true;
protected override float TargetUpdateTimeMultiplier => 0.2f;
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { } : base(character, objectiveManager, priorityModifier) { }
@@ -48,7 +50,8 @@ namespace Barotrauma
public static bool IsValidTarget(Character target, Character character) public static bool IsValidTarget(Character target, Character character)
{ {
if (target == null || target.IsDead || target.Removed) { return false; } if (target == null || target.Removed) { return false; }
if (target.IsDead || target.IsUnconscious) { return false; }
if (target == character) { return false; } if (target == character) { return false; }
if (target.Submarine == null) { return false; } if (target.Submarine == null) { return false; }
if (character.Submarine == null) { return false; } if (character.Submarine == null) { return false; }
@@ -555,6 +555,13 @@ namespace Barotrauma
//otherwise characters can let go of the ladders too soon once they're close enough to the target //otherwise characters can let go of the ladders too soon once they're close enough to the target
if (PathSteering.CurrentPath.NextNode != null) { return false; } if (PathSteering.CurrentPath.NextNode != null) { return false; }
} }
if (!character.AnimController.InWater)
{
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
if (yDiff > CloseEnough) { return false; }
float xDiff = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
return xDiff <= CloseEnough;
}
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough; return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
} }
} }
@@ -11,6 +11,7 @@ namespace Barotrauma
protected HashSet<T> ignoreList = new HashSet<T>(); protected HashSet<T> ignoreList = new HashSet<T>();
private float ignoreListTimer; private float ignoreListTimer;
protected float targetUpdateTimer; protected float targetUpdateTimer;
protected virtual float TargetUpdateTimeMultiplier { get; } = 1;
private float syncTimer; private float syncTimer;
private readonly float syncTime = 1; private readonly float syncTime = 1;
@@ -61,7 +62,7 @@ namespace Barotrauma
ignoreListTimer += deltaTime; ignoreListTimer += deltaTime;
} }
} }
if (targetUpdateTimer < 0) if (targetUpdateTimer <= 0)
{ {
UpdateTargets(); UpdateTargets();
} }
@@ -69,9 +70,9 @@ namespace Barotrauma
{ {
targetUpdateTimer -= deltaTime; targetUpdateTimer -= deltaTime;
} }
if (syncTimer < 0) if (syncTimer <= 0)
{ {
syncTimer = syncTime * Rand.Range(0.9f, 1.1f); syncTimer = Math.Min(syncTime * Rand.Range(0.9f, 1.1f), targetUpdateTimer);
// Sync objectives, subobjectives and targets // Sync objectives, subobjectives and targets
foreach (var objective in Objectives) foreach (var objective in Objectives)
{ {
@@ -95,7 +96,7 @@ namespace Barotrauma
} }
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25% // the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1); private float CalculateTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1) * TargetUpdateTimeMultiplier;
public override void Reset() public override void Reset()
{ {
@@ -156,7 +157,7 @@ namespace Barotrauma
protected void UpdateTargets() protected void UpdateTargets()
{ {
SetTargetUpdateTimer(); CalculateTargetUpdateTimer();
Targets.Clear(); Targets.Clear();
FindTargets(); FindTargets();
CreateObjectives(); CreateObjectives();
@@ -386,7 +386,9 @@ namespace Barotrauma
Abandon = true; Abandon = true;
return false; return false;
} }
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter); bool isCompleted =
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold);
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam) if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
{ {
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name), character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
@@ -25,8 +25,8 @@ namespace Barotrauma
{ {
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored. // When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players. // If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
// On the other hand, if the bots too eagerly heal characters when it's not nevessary, it's inefficient and can feel frustrating, because it can't be controlled. // On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold; return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
} }
} }
@@ -83,7 +83,7 @@ namespace Barotrauma
if (character.AIController is HumanAIController humanAI) if (character.AIController is HumanAIController humanAI)
{ {
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; } if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>()) if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
{ {
if (!character.IsMedic && target != character) if (!character.IsMedic && target != character)
{ {
@@ -281,7 +281,7 @@ namespace Barotrauma
} }
} }
public const float MAX_SPEED = 30; public const float MAX_SPEED = 20;
public Vector2 TargetMovement public Vector2 TargetMovement
{ {
@@ -636,9 +636,12 @@ namespace Barotrauma
//always collides with bodies other than structures //always collides with bodies other than structures
if (!(f2.Body.UserData is Structure structure)) if (!(f2.Body.UserData is Structure structure))
{ {
lock (impactQueue) if (!f2.IsSensor)
{ {
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity)); lock (impactQueue)
{
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
}
} }
return true; return true;
} }
@@ -156,6 +156,8 @@ namespace Barotrauma
public Entity LastDamageSource; public Entity LastDamageSource;
public AttackResult LastDamage;
public float InvisibleTimer; public float InvisibleTimer;
private CharacterPrefab prefab; private CharacterPrefab prefab;
@@ -199,7 +201,12 @@ namespace Barotrauma
set => Params.Visibility = value; set => Params.Visibility = value;
} }
public bool IsTraitor; public bool IsTraitor
{
get;
set;
}
public string TraitorCurrentObjective = ""; public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase); public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male; public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male;
@@ -333,6 +340,7 @@ namespace Barotrauma
//text displayed when the character is highlighted if custom interact is set //text displayed when the character is highlighted if custom interact is set
public string customInteractHUDText; public string customInteractHUDText;
private Action<Character, Character> onCustomInteract; private Action<Character, Character> onCustomInteract;
public ConversationAction ActiveConversation;
public bool AllowCustomInteract public bool AllowCustomInteract
{ {
@@ -349,6 +357,9 @@ namespace Barotrauma
set set
{ {
lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f); lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
#if CLIENT
HintManager.OnHandcuffed(this);
#endif
} }
} }
@@ -605,6 +616,9 @@ namespace Barotrauma
get => _selectedConstruction; get => _selectedConstruction;
set set
{ {
#if CLIENT
HintManager.OnSetSelectedConstruction(this, _selectedConstruction, value);
#endif
_selectedConstruction = value; _selectedConstruction = value;
#if CLIENT #if CLIENT
if (Controlled == this) if (Controlled == this)
@@ -1661,6 +1675,12 @@ namespace Barotrauma
{ {
item.Use(deltaTime, this); item.Use(deltaTime, this);
} }
#if CLIENT
else if (item.RequireAimToUse && !IsKeyDown(InputType.Aim))
{
HintManager.OnShootWithoutAiming(this, item);
}
#endif
} }
} }
} }
@@ -1853,6 +1873,19 @@ namespace Barotrauma
return false; return false;
} }
public Item GetEquippedItem(string tagOrIdentifier)
{
if (Inventory == null) { return null; }
for (int i = 0; i < Inventory.Capacity; i++)
{
if (Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
var item = Inventory.GetItemAt(i);
if (item == null) { continue; }
if (item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; }
}
return null;
}
public bool CanAccessInventory(Inventory inventory) public bool CanAccessInventory(Inventory inventory)
{ {
if (!CanInteract || inventory.Locked) { return false; } if (!CanInteract || inventory.Locked) { return false; }
@@ -2857,6 +2890,7 @@ namespace Barotrauma
{ {
if (character == this) { continue; } if (character == this) { continue; }
if (character.TeamID != TeamID) { continue; } if (character.TeamID != TeamID) { continue; }
if (!HumanAIController.IsActive(character)) { continue; }
foreach (var currentOrder in character.CurrentOrders) foreach (var currentOrder in character.CurrentOrders)
{ {
if (currentOrder.Order == null) { continue; } if (currentOrder.Order == null) { continue; }
@@ -3268,12 +3302,13 @@ namespace Barotrauma
//#endif //#endif
// } // }
SetStun(stun);
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{ {
if (attacker.TeamID == TeamID) { return new AttackResult(); } if (attacker.TeamID == TeamID) { return new AttackResult(); }
} }
SetStun(stun);
Vector2 dir = hitLimb.WorldPosition - worldPosition; Vector2 dir = hitLimb.WorldPosition - worldPosition;
if (Math.Abs(attackImpulse) > 0.0f) if (Math.Abs(attackImpulse) > 0.0f)
{ {
@@ -3308,6 +3343,7 @@ namespace Barotrauma
}; };
if (attackResult.Damage > 0) if (attackResult.Damage > 0)
{ {
LastDamage = attackResult;
ApplyStatusEffects(ActionType.OnDamaged, 1.0f); ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f); hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
if (attacker != null) if (attacker != null)
@@ -153,6 +153,8 @@ namespace Barotrauma
private static ushort idCounter; private static ushort idCounter;
private const string disguiseName = "???"; private const string disguiseName = "???";
public bool HasNickname => Name != OriginalName;
public string OriginalName { get; private set; }
public string Name; public string Name;
public string DisplayName public string DisplayName
{ {
@@ -453,7 +455,7 @@ namespace Barotrauma
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1; public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
// Used for creating the data // Used for creating the data
public CharacterInfo(string speciesName, string name = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced) public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced)
{ {
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{ {
@@ -503,6 +505,7 @@ namespace Barotrauma
} }
} }
} }
OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name;
personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId); personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId);
Salary = CalculateSalary(); Salary = CalculateSalary();
if (ragdollFileName != null) if (ragdollFileName != null)
@@ -518,6 +521,7 @@ namespace Barotrauma
ID = idCounter; ID = idCounter;
idCounter++; idCounter++;
Name = infoElement.GetAttributeString("name", ""); Name = infoElement.GetAttributeString("name", "");
OriginalName = infoElement.GetAttributeString("originalname", null);
string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant(); string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant();
Salary = infoElement.GetAttributeInt("salary", 1000); Salary = infoElement.GetAttributeInt("salary", 1000);
Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race); Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race);
@@ -576,6 +580,11 @@ namespace Barotrauma
} }
} }
if (string.IsNullOrEmpty(OriginalName))
{
OriginalName = Name;
}
StartItemsGiven = infoElement.GetAttributeBool("startitemsgiven", false); StartItemsGiven = infoElement.GetAttributeBool("startitemsgiven", false);
string personalityName = infoElement.GetAttributeString("personality", ""); string personalityName = infoElement.GetAttributeString("personality", "");
ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty); ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty);
@@ -622,7 +631,17 @@ namespace Barotrauma
public int GetIdentifier() public int GetIdentifier()
{ {
int id = ToolBox.StringToInt(Name); return GetIdentifier(Name);
}
public int GetIdentifierUsingOriginalName()
{
return GetIdentifier(OriginalName);
}
private int GetIdentifier(string name)
{
int id = ToolBox.StringToInt(name);
id ^= HeadSpriteId; id ^= HeadSpriteId;
id ^= (int)Race << 6; id ^= (int)Race << 6;
id ^= HairIndex << 12; id ^= HairIndex << 12;
@@ -939,12 +958,24 @@ namespace Barotrauma
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos); partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos);
public void Rename(string newName)
{
if (string.IsNullOrEmpty(newName)) { return; }
Name = newName;
}
public void ResetName()
{
Name = OriginalName;
}
public XElement Save(XElement parentElement) public XElement Save(XElement parentElement)
{ {
XElement charElement = new XElement("Character"); XElement charElement = new XElement("Character");
charElement.Add( charElement.Add(
new XAttribute("name", Name), new XAttribute("name", Name),
new XAttribute("originalname", OriginalName),
new XAttribute("speciesname", SpeciesName), new XAttribute("speciesname", SpeciesName),
new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"), new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"),
new XAttribute("race", Head.race.ToString()), new XAttribute("race", Head.race.ToString()),
@@ -260,10 +260,13 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Use this method to skip clamping and additional logic of the setters. /// Use this method to skip clamping and additional logic of the setters.
/// Intended only to be used when the value is already clamped! (networking code)
/// Ideally we would keep this private, but doing so would require too much refactoring. /// Ideally we would keep this private, but doing so would require too much refactoring.
/// </summary> /// </summary>
public void SetStrength(float strength) => _strength = strength; public void SetStrength(float strength)
{
_nonClampedStrength = strength;
_strength = _nonClampedStrength;
}
public bool ShouldShowIcon(Character afflictedCharacter) public bool ShouldShowIcon(Character afflictedCharacter)
{ {
@@ -290,6 +290,9 @@ namespace Barotrauma
//how high the strength has to be for the affliction icon to be shown with a health scanner //how high the strength has to be for the affliction icon to be shown with a health scanner
public readonly float ShowInHealthScannerThreshold = 0.05f; public readonly float ShowInHealthScannerThreshold = 0.05f;
//how strong the affliction needs to be before bots attempt to treat it
public readonly float TreatmentThreshold = 5.0f;
//how much karma changes when a player applies this affliction to someone (per strength of the affliction) //how much karma changes when a player applies this affliction to someone (per strength of the affliction)
public float KarmaChangeOnApplied; public float KarmaChangeOnApplied;
@@ -376,6 +379,9 @@ namespace Barotrauma
{ {
DebugConsole.ThrowError("Cannot override all afflictions, because many of them are required by the main game! Please try overriding them one by one."); DebugConsole.ThrowError("Cannot override all afflictions, because many of them are required by the main game! Please try overriding them one by one.");
} }
List<(AfflictionPrefab prefab, XElement element)> loadedAfflictions = new List<(AfflictionPrefab prefab, XElement element)>();
foreach (XElement element in mainElement.Elements()) foreach (XElement element in mainElement.Elements())
{ {
bool isOverride = element.IsOverride(); bool isOverride = element.IsOverride();
@@ -510,10 +516,18 @@ namespace Barotrauma
if (prefab != null) if (prefab != null)
{ {
loadedAfflictions.Add((prefab, element));
Prefabs.Add(prefab, isOverride); Prefabs.Add(prefab, isOverride);
prefab.CalculatePrefabUIntIdentifier(Prefabs); prefab.CalculatePrefabUIntIdentifier(Prefabs);
} }
} }
//load the effects after all the afflictions in the file have been instantiated
//otherwise afflictions can't inflict other afflictions that are defined at a later point in the file
foreach ((AfflictionPrefab prefab, XElement element) in loadedAfflictions)
{
prefab.LoadEffects(element);
}
} }
public static void RemoveByFile(string filePath) public static void RemoveByFile(string filePath)
@@ -565,6 +579,7 @@ namespace Barotrauma
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f); MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f)); ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f); DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f); BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
@@ -584,9 +599,6 @@ namespace Barotrauma
case "icon": case "icon":
Icon = new Sprite(subElement); Icon = new Sprite(subElement);
break; break;
case "effect":
effects.Add(new Effect(subElement, Name));
break;
case "periodiceffect": case "periodiceffect":
periodicEffects.Add(new PeriodicEffect(subElement, Name)); periodicEffects.Add(new PeriodicEffect(subElement, Name));
break; break;
@@ -614,6 +626,19 @@ namespace Barotrauma
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) }); constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
} }
private void LoadEffects(XElement element)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "effect":
effects.Add(new Effect(subElement, Name));
break;
}
}
}
public override string ToString() public override string ToString()
{ {
return "AfflictionPrefab (" + Name + ")"; return "AfflictionPrefab (" + Name + ")";
@@ -36,7 +36,11 @@ namespace Barotrauma
public LimbHealth(XElement element, CharacterHealth characterHealth) public LimbHealth(XElement element, CharacterHealth characterHealth)
{ {
Name = TextManager.Get("HealthLimbName." + element.GetAttributeString("name", "")); string limbName = element.GetAttributeString("name", null) ?? "generic";
if (limbName != "generic")
{
Name = TextManager.Get("HealthLimbName." + limbName);
}
this.characterHealth = characterHealth; this.characterHealth = characterHealth;
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
@@ -664,7 +668,6 @@ namespace Barotrauma
} }
} }
partial void UpdateProjSpecific(float deltaTime); partial void UpdateProjSpecific(float deltaTime);
partial void UpdateLimbAfflictionOverlays(); partial void UpdateLimbAfflictionOverlays();
@@ -687,6 +690,10 @@ namespace Barotrauma
{ {
var affliction = limbHealths[i].Afflictions[j]; var affliction = limbHealths[i].Afflictions[j];
Limb targetLimb = Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == i); Limb targetLimb = Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == i);
if (targetLimb == null)
{
targetLimb = Character.AnimController.MainLimb;
}
affliction.Update(this, targetLimb, deltaTime); affliction.Update(this, targetLimb, deltaTime);
affliction.DamagePerSecondTimer += deltaTime; affliction.DamagePerSecondTimer += deltaTime;
if (affliction is AfflictionBleeding bleeding) if (affliction is AfflictionBleeding bleeding)
@@ -877,6 +884,7 @@ namespace Barotrauma
float minSuitability = -10, maxSuitability = 10; float minSuitability = -10, maxSuitability = 10;
foreach (Affliction affliction in GetAllAfflictions()) foreach (Affliction affliction in GetAllAfflictions())
{ {
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability) foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
{ {
if (!treatmentSuitability.ContainsKey(treatment.Key)) if (!treatmentSuitability.ContainsKey(treatment.Key))
@@ -20,6 +20,9 @@ namespace Barotrauma
[Serialize(1f, false)] [Serialize(1f, false)]
public float HealthMultiplier { get; protected set; } public float HealthMultiplier { get; protected set; }
[Serialize(1f, false)]
public float HealthMultiplierInMultiplayer { get; protected set; }
[Serialize(1f, false)] [Serialize(1f, false)]
public float AimSpeed { get; protected set; } public float AimSpeed { get; protected set; }
@@ -117,6 +120,10 @@ namespace Barotrauma
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null) public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
{ {
npc.CharacterHealth.MaxVitality *= HealthMultiplier; npc.CharacterHealth.MaxVitality *= HealthMultiplier;
if (GameMain.NetworkMember != null)
{
npc.CharacterHealth.MaxVitality *= HealthMultiplierInMultiplayer;
}
var humanAI = npc.AIController as HumanAIController; var humanAI = npc.AIController as HumanAIController;
if (humanAI != null) if (humanAI != null)
{ {

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