Unstable 0.16.1.0

This commit is contained in:
Markus Isberg
2022-01-27 00:30:32 +09:00
parent 7d6421a548
commit b259af5911
161 changed files with 1913 additions and 638 deletions
@@ -1,7 +1,11 @@
namespace Barotrauma using Microsoft.Xna.Framework;
namespace Barotrauma
{ {
abstract partial class AIObjective abstract partial class AIObjective
{ {
public static Color ObjectiveIconColor => Color.LightGray;
public static Sprite GetSprite(string identifier, string option, Entity targetEntity) public static Sprite GetSprite(string identifier, string option, Entity targetEntity)
{ {
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
@@ -154,7 +154,7 @@ namespace Barotrauma
public bool PlaySound; public bool PlaySound;
public GUIMessage(string rawText, Color color, float delay, string identifier = null, int? value = null) public GUIMessage(string rawText, Color color, float delay, string identifier = null, int? value = null, float lifeTime = 3.0f)
{ {
RawText = Text = rawText; RawText = Text = rawText;
if (value.HasValue) if (value.HasValue)
@@ -166,7 +166,7 @@ namespace Barotrauma
Size = GUI.Font.MeasureString(Text); Size = GUI.Font.MeasureString(Text);
Color = color; Color = color;
Identifier = identifier; Identifier = identifier;
Lifetime = 3.0f; Lifetime = lifeTime;
} }
} }
@@ -997,7 +997,7 @@ namespace Barotrauma
return nameColor; return nameColor;
} }
public void AddMessage(string rawText, Color color, bool playSound, string identifier = null, int? value = null) public void AddMessage(string rawText, Color color, bool playSound, string identifier = null, int? value = null, float lifetime = 3.0f)
{ {
GUIMessage existingMessage = null; GUIMessage existingMessage = null;
@@ -1026,7 +1026,7 @@ namespace Barotrauma
} }
if (existingMessage == null || !value.HasValue) if (existingMessage == null || !value.HasValue)
{ {
var newMessage = new GUIMessage(rawText, color, delay, identifier, value); var newMessage = new GUIMessage(rawText, color, delay, identifier, value, lifetime);
guiMessages.Insert(0, newMessage); guiMessages.Insert(0, newMessage);
if (playSound) if (playSound)
{ {
@@ -113,7 +113,7 @@ namespace Barotrauma
return return
character?.Inventory != null && character?.Inventory != null &&
character.AllowInput && !character.Removed && !character.IsKnockedDown &&
(controller?.User != character || !controller.HideHUD) && (controller?.User != character || !controller.HideHUD) &&
!IsCampaignInterfaceOpen && !IsCampaignInterfaceOpen &&
!ConversationAction.FadeScreenToBlack; !ConversationAction.FadeScreenToBlack;
@@ -16,6 +16,7 @@ namespace Barotrauma
private static Sprite infoAreaPortraitBG; private static Sprite infoAreaPortraitBG;
public bool LastControlled; public bool LastControlled;
public int CrewListIndex { get; set; } = -1;
#warning TODO: Refactor #warning TODO: Refactor
private Sprite disguisedPortrait; private Sprite disguisedPortrait;
@@ -831,7 +832,7 @@ namespace Barotrauma
}; };
new GUIFrame( new GUIFrame(
new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center), new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.ContentBackground.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black) style: "OuterGlow", color: Color.Black)
{ {
UserData = "outerglow", UserData = "outerglow",
@@ -966,10 +967,15 @@ namespace Barotrauma
foreach (Sprite sprite in characterSprites) { sprite.Remove(); } foreach (Sprite sprite in characterSprites) { sprite.Remove(); }
characterSprites.Clear(); characterSprites.Clear();
} }
public void Dispose() public void Dispose()
{ {
ClearSprites(); ClearSprites();
if (HeadSelectionList != null)
{
HeadSelectionList.RectTransform.Parent = null;
HeadSelectionList = null;
}
} }
~AppearanceCustomizationMenu() ~AppearanceCustomizationMenu()
@@ -143,7 +143,7 @@ namespace Barotrauma
Hull fireHull = Hull.hullList.GetRandom(h => h.Submarine == character.Submarine); Hull fireHull = Hull.hullList.GetRandom(h => h.Submarine == character.Submarine);
if (fireHull != null) if (fireHull != null)
{ {
var fakeFire = new DummyFireSource(Vector2.One * 500.0f, new Vector2(Rand.Range(fireHull.WorldRect.X, fireHull.WorldRect.Right), fireHull.WorldPosition.Y), fireHull, isNetworkMessage: true) var fakeFire = new DummyFireSource(Vector2.One * 500.0f, new Vector2(Rand.Range(fireHull.WorldRect.X, fireHull.WorldRect.Right), fireHull.WorldPosition.Y + 1), fireHull, isNetworkMessage: true)
{ {
CausedByPsychosis = true, CausedByPsychosis = true,
DamagesItems = false, DamagesItems = false,
@@ -2018,6 +2018,27 @@ namespace Barotrauma
limbIndicatorOverlay?.Remove(); limbIndicatorOverlay?.Remove();
limbIndicatorOverlay = null; limbIndicatorOverlay = null;
if (healthWindow != null)
{
healthWindow.RectTransform.Parent = null;
healthWindow = null;
}
if (healthBarHolder != null)
{
healthBarHolder.RectTransform.Parent = null;
healthBarHolder = null;
}
if (SuicideButton != null)
{
SuicideButton.RectTransform.Parent = null;
SuicideButton = null;
}
if (afflictionTooltip != null)
{
afflictionTooltip.RectTransform.Parent = null;
afflictionTooltip = null;
}
} }
} }
} }
@@ -389,6 +389,12 @@ namespace Barotrauma
DrawString(spriteBatch, new Vector2(10, 10), DrawString(spriteBatch, new Vector2(10, 10),
"FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond), "FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
Color.White, Color.Black * 0.5f, 0, SmallFont); Color.White, Color.Black * 0.5f, 0, SmallFont);
if (GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 1.0)
{
DrawString(spriteBatch, new Vector2(10, 25),
$"Physics: {GameMain.CurrentUpdateRate}",
(GameMain.CurrentUpdateRate < Timing.FixedUpdateRate) ? Color.Red : Color.White, Color.Black * 0.5f, 0, SmallFont);
}
} }
if (GameMain.ShowPerf) if (GameMain.ShowPerf)
@@ -397,7 +403,7 @@ namespace Barotrauma
DrawString(spriteBatch, new Vector2(300, y), DrawString(spriteBatch, new Vector2(300, y),
"Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" + "Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" +
" Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms", " Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms",
GUI.Style.Green, Color.Black * 0.8f, font: SmallFont); Style.Green, Color.Black * 0.8f, font: SmallFont);
y += 15; y += 15;
GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Style.Green); GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Style.Green);
y += 50; y += 50;
@@ -408,7 +414,6 @@ namespace Barotrauma
Color.LightBlue, Color.Black * 0.8f, font: SmallFont); Color.LightBlue, Color.Black * 0.8f, font: SmallFont);
y += 15; y += 15;
GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Color.LightBlue); GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Color.LightBlue);
GameMain.PerformanceCounter.UpdateIterationsGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), maxValue: 20, color: Style.Red);
y += 50; y += 50;
foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers) foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers)
{ {
@@ -431,7 +436,7 @@ namespace Barotrauma
} }
} }
if (GameMain.DebugDraw) if (GameMain.DebugDraw && !Submarine.Unloading && !(Screen.Selected is RoundSummaryScreen))
{ {
DrawString(spriteBatch, new Vector2(10, 25), DrawString(spriteBatch, new Vector2(10, 25),
"Physics: " + GameMain.World.UpdateTime, "Physics: " + GameMain.World.UpdateTime,
@@ -2435,6 +2440,11 @@ namespace Barotrauma
private static bool TogglePauseMenu(GUIButton button, object obj) private static bool TogglePauseMenu(GUIButton button, object obj)
{ {
pauseMenuOpen = !pauseMenuOpen; pauseMenuOpen = !pauseMenuOpen;
if (!pauseMenuOpen && PauseMenu != null)
{
PauseMenu.RectTransform.Parent = null;
PauseMenu = null;
}
return true; return true;
} }
@@ -22,13 +22,14 @@ namespace Barotrauma
{ {
GameMain.Instance.ResolutionChanged += RecalculateSize; GameMain.Instance.ResolutionChanged += RecalculateSize;
} }
_instance.ItemComponentHolder = new GUIFrame(new RectTransform(Vector2.One, _instance, Anchor.Center)).RectTransform; _instance.ChildrenChanged += OnChildrenChanged;
} }
return _instance; return _instance;
} }
} }
public RectTransform ItemComponentHolder; //GUICanvas stores the children as weak references, to allow elements that we no longer need to get garbage collected
private readonly List<WeakReference<RectTransform>> childrenWeakRef = new List<WeakReference<RectTransform>>();
private static Vector2 size => new Vector2(GameMain.GraphicsWidth / (float)GUI.UIWidth, 1f); private static Vector2 size => new Vector2(GameMain.GraphicsWidth / (float)GUI.UIWidth, 1f);
@@ -36,16 +37,41 @@ namespace Barotrauma
private enum ResizeAxis { Both = 0, X = 1, Y = 2 } private enum ResizeAxis { Both = 0, X = 1, Y = 2 }
private static void OnChildrenChanged(RectTransform _)
{
//add weak reference if we don't have one yet
foreach (var child in _instance.Children)
{
if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child))
{
_instance.childrenWeakRef.Add(new WeakReference<RectTransform>(child));
}
}
//get rid of strong references
_instance.children.Clear();
//remove dead children
for (int i = _instance.childrenWeakRef.Count - 2; i >= 0; i--)
{
if (!_instance.childrenWeakRef[i].TryGetTarget(out var child) || child.Parent != _instance)
{
_instance.childrenWeakRef.RemoveAt(i);
}
}
}
// Turn public, if there is a need to call this manually. // Turn public, if there is a need to call this manually.
private static void RecalculateSize() private static void RecalculateSize()
{ {
Vector2 recalculatedSize = size; Vector2 recalculatedSize = size;
// Scale children that are supposed to encompass the whole screen so that they are properly scaled on ultrawide as well // Scale children that are supposed to encompass the whole screen so that they are properly scaled on ultrawide as well
for (int i = 0; i < Instance.Children.Count(); i++) for (int i = 0; i < Instance.childrenWeakRef.Count; i++)
{ {
RectTransform target = Instance.GetChild(i); if (!_instance.childrenWeakRef[i].TryGetTarget(out RectTransform target) || target == null) { continue; };
if (target == null || target.RelativeSize.X < 1 && target.RelativeSize.Y < 1) continue;
_instance.children.Add(target);
if (target.RelativeSize.X < 1 && target.RelativeSize.Y < 1) { continue; }
ResizeAxis axis; ResizeAxis axis;
@@ -80,6 +106,7 @@ namespace Barotrauma
Instance.Resize(size, resizeChildren: true); Instance.Resize(size, resizeChildren: true);
Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos()); Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos());
_instance.children.Clear();
} }
} }
} }
@@ -362,6 +362,14 @@ namespace Barotrauma
RectTransform.ScaleChanged += () => dimensionsNeedsRecalculation = true; RectTransform.ScaleChanged += () => dimensionsNeedsRecalculation = true;
RectTransform.SizeChanged += () => dimensionsNeedsRecalculation = true; RectTransform.SizeChanged += () => dimensionsNeedsRecalculation = true;
UpdateDimensions(); UpdateDimensions();
rectT.ChildrenChanged += CheckForChildren;
}
private void CheckForChildren(RectTransform rectT)
{
if (rectT == ScrollBar.RectTransform || rectT == Content.RectTransform || rectT == ContentBackground.RectTransform) { return; }
throw new InvalidOperationException($"Children were added to {nameof(GUIListBox)}, Add them to {nameof(GUIListBox)}.{nameof(Content)} instead.");
} }
public void UpdateDimensions() public void UpdateDimensions()
@@ -827,13 +835,6 @@ namespace Barotrauma
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
foreach (GUIComponent child in Children)
{
if (child == ScrollBar || child == Content || child == ContentBackground) { continue; }
throw new InvalidOperationException($"Children were found in {nameof(GUIListBox)}, Add them to {nameof(GUIListBox)}.{nameof(Content)} instead.");
}
if (!Visible) { return; } if (!Visible) { return; }
UpdateChildrenRect(); UpdateChildrenRect();
@@ -618,6 +618,7 @@ namespace Barotrauma
public bool Close(GUIButton button, object obj) public bool Close(GUIButton button, object obj)
{ {
RectTransform.Parent = null;
Close(); Close();
return true; return true;
} }
@@ -347,7 +347,7 @@ namespace Barotrauma
// sum up all the afflictions and their strengths // sum up all the afflictions and their strengths
Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>(); Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in health.GetAllAfflictions().Where(a => !a.Prefab.IsBuff && a.Strength > 0)) foreach (Affliction affliction in health.GetAllAfflictions().Where(a => MedicalClinic.IsHealable(a)))
{ {
if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength)) if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength))
{ {
@@ -581,7 +581,6 @@ namespace Barotrauma
OnClicked = (button, _) => OnClicked = (button, _) =>
{ {
button.Enabled = false; button.Enabled = false;
ClosePopup();
medicalClinic.HealAllButtonAction(request => medicalClinic.HealAllButtonAction(request =>
{ {
switch (request.HealResult) switch (request.HealResult)
@@ -595,7 +594,9 @@ namespace Barotrauma
} }
button.Enabled = true; button.Enabled = true;
ClosePopup();
}); });
ClosePopup();
return true; return true;
} }
}; };
@@ -993,7 +994,7 @@ namespace Barotrauma
} }
} }
private void ClosePopup() public void ClosePopup()
{ {
if (selectedCrewElement is { } popup) if (selectedCrewElement is { } popup)
{ {
@@ -58,7 +58,7 @@ namespace Barotrauma
} }
} }
private readonly List<RectTransform> children = new List<RectTransform>(); protected readonly List<RectTransform> children = new List<RectTransform>();
public IEnumerable<RectTransform> Children => children; public IEnumerable<RectTransform> Children => children;
public int CountChildren => children.Count; public int CountChildren => children.Count;
@@ -20,7 +20,7 @@ namespace Barotrauma
private static Sprite ownerIcon, moderatorIcon; private static Sprite ownerIcon, moderatorIcon;
public enum InfoFrameTab { Crew, Mission, Reputation, Traitor, Submarine, Talents }; public enum InfoFrameTab { Crew, Mission, Reputation, Traitor, Submarine, Talents };
public static InfoFrameTab selectedTab; public static InfoFrameTab SelectedTab { get; private set; }
private GUIFrame infoFrame, contentFrame; private GUIFrame infoFrame, contentFrame;
private readonly List<GUIButton> tabButtons = new List<GUIButton>(); private readonly List<GUIButton> tabButtons = new List<GUIButton>();
@@ -130,8 +130,8 @@ namespace Barotrauma
{ {
if (!initialized) { Initialize(); } if (!initialized) { Initialize(); }
CreateInfoFrame(selectedTab); CreateInfoFrame(SelectedTab);
SelectInfoFrameTab(null, selectedTab); SelectInfoFrameTab(SelectedTab);
} }
public void Update() public void Update()
@@ -147,8 +147,8 @@ namespace Barotrauma
} }
} }
if (selectedTab != InfoFrameTab.Crew) return; if (SelectedTab != InfoFrameTab.Crew) { return; }
if (linkedGUIList == null) return; if (linkedGUIList == null) { return; }
if (GameMain.IsMultiplayer) if (GameMain.IsMultiplayer)
{ {
@@ -226,7 +226,7 @@ namespace Barotrauma
{ {
UserData = tab, UserData = tab,
ToolTip = TextManager.Get(textTag), ToolTip = TextManager.Get(textTag),
OnClicked = SelectInfoFrameTab OnClicked = (btn, userData) => { SelectInfoFrameTab((InfoFrameTab)userData); return true; }
}; };
tabButtons.Add(newButton); tabButtons.Add(newButton);
return newButton; return newButton;
@@ -277,16 +277,16 @@ namespace Barotrauma
talentsButton.Enabled = Character.Controlled?.Info != null; talentsButton.Enabled = Character.Controlled?.Info != null;
if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents)
{ {
SelectInfoFrameTab(null, InfoFrameTab.Crew); SelectInfoFrameTab(InfoFrameTab.Crew);
} }
}; };
talentPointNotification = GameSession.CreateTalentIconNotification(talentsButton); talentPointNotification = GameSession.CreateTalentIconNotification(talentsButton);
} }
private bool SelectInfoFrameTab(GUIButton button, object userData) public void SelectInfoFrameTab(InfoFrameTab selectedTab)
{ {
selectedTab = (InfoFrameTab)userData; SelectedTab = selectedTab;
CreateInfoFrame(selectedTab); CreateInfoFrame(selectedTab);
tabButtons.ForEach(tb => tb.Selected = (InfoFrameTab)tb.UserData == selectedTab); tabButtons.ForEach(tb => tb.Selected = (InfoFrameTab)tb.UserData == selectedTab);
@@ -310,7 +310,7 @@ namespace Barotrauma
case InfoFrameTab.Traitor: case InfoFrameTab.Traitor:
TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission; TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission;
Character traitor = GameMain.Client.Character; Character traitor = GameMain.Client.Character;
if (traitor == null || traitorMission == null) return false; if (traitor == null || traitorMission == null) { return; }
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor); CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
break; break;
case InfoFrameTab.Submarine: case InfoFrameTab.Submarine:
@@ -320,8 +320,6 @@ namespace Barotrauma
CreateTalentInfo(infoFrameHolder); CreateTalentInfo(infoFrameHolder);
break; break;
} }
return true;
} }
private const float jobColumnWidthPercentage = 0.138f; private const float jobColumnWidthPercentage = 0.138f;
@@ -859,7 +857,7 @@ namespace Barotrauma
string msg = ChatMessage.GetTimeStamp() + message.TextWithSender; string msg = ChatMessage.GetTimeStamp() + message.TextWithSender;
storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType)); storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType));
if (GameSession.IsTabMenuOpen && selectedTab == InfoFrameTab.Crew) if (GameSession.IsTabMenuOpen && SelectedTab == InfoFrameTab.Crew)
{ {
TabMenu instance = GameSession.TabMenuInstance; TabMenu instance = GameSession.TabMenuInstance;
instance.AddLineToLog(msg, message.ChangeType); instance.AddLineToLog(msg, message.ChangeType);
@@ -433,6 +433,7 @@ namespace Barotrauma
if (AvailableMoney >= hullRepairCost) if (AvailableMoney >= hullRepairCost)
{ {
Campaign.Money -= hullRepairCost; Campaign.Money -= hullRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
Campaign.PurchasedHullRepairs = true; Campaign.PurchasedHullRepairs = true;
button.Enabled = false; button.Enabled = false;
SelectTab(UpgradeTab.Repairs); SelectTab(UpgradeTab.Repairs);
@@ -467,6 +468,7 @@ namespace Barotrauma
if (AvailableMoney >= itemRepairCost && !Campaign.PurchasedItemRepairs) if (AvailableMoney >= itemRepairCost && !Campaign.PurchasedItemRepairs)
{ {
Campaign.Money -= itemRepairCost; Campaign.Money -= itemRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
Campaign.PurchasedItemRepairs = true; Campaign.PurchasedItemRepairs = true;
button.Enabled = false; button.Enabled = false;
SelectTab(UpgradeTab.Repairs); SelectTab(UpgradeTab.Repairs);
@@ -512,6 +514,7 @@ namespace Barotrauma
if (AvailableMoney >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles) if (AvailableMoney >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
{ {
Campaign.Money -= shuttleRetrieveCost; Campaign.Money -= shuttleRetrieveCost;
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
Campaign.PurchasedLostShuttles = true; Campaign.PurchasedLostShuttles = true;
button.Enabled = false; button.Enabled = false;
SelectTab(UpgradeTab.Repairs); SelectTab(UpgradeTab.Repairs);
@@ -5,7 +5,7 @@ using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
public static partial class GameAnalyticsManager static partial class GameAnalyticsManager
{ {
static partial void CreateConsentPrompt() static partial void CreateConsentPrompt()
{ {
@@ -30,6 +30,13 @@ namespace Barotrauma
public static PerformanceCounter PerformanceCounter; public static PerformanceCounter PerformanceCounter;
private static Stopwatch performanceCounterTimer;
private static int updateCount = 0;
public static int CurrentUpdateRate
{
get; private set;
}
public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version; public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
public static string[] ConsoleArguments; public static string[] ConsoleArguments;
@@ -122,6 +129,12 @@ namespace Barotrauma
private bool exiting; private bool exiting;
public static bool IsFirstLaunch
{
get;
private set;
}
public static GameMain Instance public static GameMain Instance
{ {
get; get;
@@ -349,6 +362,8 @@ namespace Barotrauma
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Item)); Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Item));
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent)); Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent));
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull)); Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
performanceCounterTimer = Stopwatch.StartNew();
} }
/// <summary> /// <summary>
@@ -579,6 +594,18 @@ namespace Barotrauma
{ {
Steamworks.SteamFriends.OnGameRichPresenceJoinRequested += OnInvitedToGame; Steamworks.SteamFriends.OnGameRichPresenceJoinRequested += OnInvitedToGame;
Steamworks.SteamFriends.OnGameLobbyJoinRequested += OnLobbyJoinRequested; Steamworks.SteamFriends.OnGameLobbyJoinRequested += OnLobbyJoinRequested;
if (SteamManager.TryGetUnlockedAchievements(out List<Steamworks.Data.Achievement> achievements))
{
//check the achievements too, so we don't consider people who've played the game before this "gamelaunchcount" stat was added as being 1st-time-players
//(people who have played previous versions, but not unlocked any achievements, will be incorrectly considered 1st-time-players, but that should be a small enough group to not skew the statistics)
if (!achievements.Any() && SteamManager.GetStatInt("gamelaunchcount") <= 0)
{
IsFirstLaunch = true;
GameAnalyticsManager.AddDesignEvent("FirstLaunch");
}
}
SteamManager.IncrementStat("gamelaunchcount", 1);
} }
#endif #endif
@@ -696,11 +723,10 @@ namespace Barotrauma
protected override void Update(GameTime gameTime) protected override void Update(GameTime gameTime)
{ {
Timing.Accumulator += gameTime.ElapsedGameTime.TotalSeconds; Timing.Accumulator += gameTime.ElapsedGameTime.TotalSeconds;
int updateIterations = (int)Math.Floor(Timing.Accumulator / Timing.Step); if (Timing.Accumulator > Timing.AccumulatorMax)
if (Timing.Accumulator > Timing.Step * 6.0)
{ {
//if the game's running too slowly then we have no choice //prevent spiral of death:
//but to skip a bunch of steps //if the game's running too slowly then we have no choice but to skip a bunch of steps
//otherwise it snowballs and becomes unplayable //otherwise it snowballs and becomes unplayable
Timing.Accumulator = Timing.Step; Timing.Accumulator = Timing.Step;
} }
@@ -736,7 +762,6 @@ namespace Barotrauma
PlayerInput.Update(Timing.Step); PlayerInput.Update(Timing.Step);
if (loadingScreenOpen) if (loadingScreenOpen)
{ {
//reset accumulator if loading //reset accumulator if loading
@@ -975,13 +1000,21 @@ namespace Barotrauma
Timing.Accumulator -= Timing.Step; Timing.Accumulator -= Timing.Step;
updateCount++;
sw.Stop(); sw.Stop();
PerformanceCounter.AddElapsedTicks("Update total", sw.ElapsedTicks); PerformanceCounter.AddElapsedTicks("Update total", sw.ElapsedTicks);
PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency); PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
PerformanceCounter.UpdateIterationsGraph.Update(updateIterations);
} }
if (!Paused) Timing.Alpha = Timing.Accumulator / Timing.Step; if (!Paused) { Timing.Alpha = Timing.Accumulator / Timing.Step; }
if (performanceCounterTimer.ElapsedMilliseconds > 1000)
{
CurrentUpdateRate = (int)Math.Round(updateCount / (double)(performanceCounterTimer.ElapsedMilliseconds / 1000.0));
performanceCounterTimer.Restart();
updateCount = 0;
}
} }
public static void ResetFrameTime() public static void ResetFrameTime()
@@ -1086,8 +1119,15 @@ namespace Barotrauma
{ {
double roundDuration = Timing.TotalTime - GameSession.RoundStartTime; double roundDuration = Timing.TotalTime - GameSession.RoundStartTime;
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail, GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
GameSession.GameMode?.Name ?? "none", GameSession.GameMode?.Preset.Identifier ?? "none",
roundDuration); roundDuration);
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier ?? "none") + ":";
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
{
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:ActiveEvents:" + activeEvent.ToString());
}
GameSession.LogEndRoundStats(eventId);
if (Tutorial.Initialized) if (Tutorial.Initialized)
{ {
((TutorialMode)GameSession.GameMode).Tutorial?.Stop(); ((TutorialMode)GameSession.GameMode).Tutorial?.Stop();
@@ -232,6 +232,8 @@ namespace Barotrauma
Location.StoreCurrentBalance -= itemValue; Location.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue; campaign.Money += itemValue;
GameAnalyticsManager.AddMoneyGainedEvent(itemValue, GameAnalyticsManager.MoneySource.Store, item.ItemPrefab.Identifier);
// Remove from the sell crate // Remove from the sell crate
// TODO: Simplify duplicate logic? // TODO: Simplify duplicate logic?
if (sellingMode == Store.StoreTab.Sell && ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } inventoryItem) if (sellingMode == Store.StoreTab.Sell && ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } inventoryItem)
@@ -83,7 +83,7 @@ namespace Barotrauma
partial void InitProjectSpecific() partial void InitProjectSpecific()
{ {
guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent) guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), null, Color.Transparent)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -302,7 +302,7 @@ namespace Barotrauma
/// </summary> /// </summary>
/// <param name="character">The character to remove</param> /// <param name="character">The character to remove</param>
/// <param name="removeInfo">If the character info is also removed, the character will not be visible in the round summary.</param> /// <param name="removeInfo">If the character info is also removed, the character will not be visible in the round summary.</param>
public void RemoveCharacter(Character character, bool removeInfo = false) public void RemoveCharacter(Character character, bool removeInfo = false, bool resetCrewListIndex = true)
{ {
if (character == null) if (character == null)
{ {
@@ -311,14 +311,15 @@ namespace Barotrauma
} }
characters.Remove(character); characters.Remove(character);
if (removeInfo) { characterInfos.Remove(character.Info); } if (removeInfo) { characterInfos.Remove(character.Info); }
if (resetCrewListIndex) { ResetCrewListIndex(character); }
} }
/// <summary> /// <summary>
/// Add character to the list without actually adding it to the crew /// Add character to the list without actually adding it to the crew
/// </summary> /// </summary>
public void AddCharacterToCrewList(Character character) public GUIComponent AddCharacterToCrewList(Character character)
{ {
if (character == null) { return; } if (character == null) { return null; }
var background = new GUIFrame( var background = new GUIFrame(
new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight), new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight),
@@ -510,6 +511,8 @@ namespace Barotrauma
return true; return true;
} }
}; };
return background;
} }
private void SetCharacterComponentTooltip(GUIComponent characterComponent) private void SetCharacterComponentTooltip(GUIComponent characterComponent)
@@ -549,13 +552,13 @@ namespace Barotrauma
if (characterInfos.Contains(revivedCharacter.Info)) { AddCharacter(revivedCharacter); } if (characterInfos.Contains(revivedCharacter.Info)) { AddCharacter(revivedCharacter); }
} }
public void KillCharacter(Character killedCharacter) public void KillCharacter(Character killedCharacter, bool resetCrewListIndex = true)
{ {
if (crewList.Content.GetChildByUserData(killedCharacter) is GUIComponent characterComponent) if (crewList.Content.GetChildByUserData(killedCharacter) is GUIComponent characterComponent)
{ {
CoroutineManager.StartCoroutine(KillCharacterAnim(characterComponent)); CoroutineManager.StartCoroutine(KillCharacterAnim(characterComponent));
} }
RemoveCharacter(killedCharacter); RemoveCharacter(killedCharacter, resetCrewListIndex: resetCrewListIndex);
} }
private IEnumerable<CoroutineStatus> KillCharacterAnim(GUIComponent component) private IEnumerable<CoroutineStatus> KillCharacterAnim(GUIComponent component)
@@ -601,9 +604,53 @@ namespace Barotrauma
{ {
if (crewList != this.crewList) { return; } if (crewList != this.crewList) { return; }
if (!(draggedElementData is Character)) { return; } if (!(draggedElementData is Character)) { return; }
if (crewList.HasDraggedElementIndexChanged) { return; }
if (!IsSinglePlayer) { return; } if (!IsSinglePlayer) { return; }
CharacterClicked(crewList.DraggedElement, draggedElementData); if (crewList.HasDraggedElementIndexChanged)
{
UpdateCrewListIndices();
}
else
{
CharacterClicked(crewList.DraggedElement, draggedElementData);
}
}
private void ResetCrewListIndex(Character c)
{
if (c?.Info == null) { return; }
c.Info.CrewListIndex = -1;
UpdateCrewListIndices();
}
private void UpdateCrewListIndices()
{
if (crewList == null) { return; }
for (int i = 0; i < crewList.Content.CountChildren; i++)
{
var characterComponent = crewList.Content.GetChild(i);
if (!(characterComponent?.UserData is Character c)) { continue; }
if (c.Info == null) { continue; }
c.Info.CrewListIndex = i;
}
}
private void SortCrewList()
{
if (crewList == null) { return; }
crewList.Content.RectTransform.SortChildren((x, y) =>
{
var infoX = (x.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
var infoY = (y.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
if (infoX.HasValue)
{
return infoY.HasValue ? infoX.Value.CompareTo(infoY.Value) : -1;
}
else
{
return infoY.HasValue ? 1 : 0;
}
});
UpdateCrewListIndices();
} }
#endregion #endregion
@@ -1077,7 +1124,13 @@ namespace Barotrauma
private string CreateOrderTooltip(Order orderPrefab, string option, Entity targetEntity) private string CreateOrderTooltip(Order orderPrefab, string option, Entity targetEntity)
{ {
if (orderPrefab == null) { return ""; } if (orderPrefab == null) { return ""; }
if (!string.IsNullOrEmpty(option)) if (orderPrefab.DisplayGiverInTooltip && orderPrefab.OrderGiver != null)
{
return TextManager.GetWithVariables("crewlistordericontooltip",
new string[2] { "[ordername]", "[orderoption]" },
new string[2] { orderPrefab.Name, orderPrefab.OrderGiver.DisplayName });
}
else if (!string.IsNullOrEmpty(option))
{ {
return TextManager.GetWithVariables("crewlistordericontooltip", return TextManager.GetWithVariables("crewlistordericontooltip",
new string[2] { "[ordername]", "[orderoption]" }, new string[2] { "[ordername]", "[orderoption]" },
@@ -1210,6 +1263,10 @@ namespace Barotrauma
DisableCommandUI(); DisableCommandUI();
Character.Controlled = character; Character.Controlled = character;
HintManager.OnChangeCharacter(); HintManager.OnChangeCharacter();
if (GameSession.TabMenuInstance != null && TabMenu.SelectedTab == TabMenu.InfoFrameTab.Talents)
{
GameSession.TabMenuInstance.SelectInfoFrameTab(TabMenu.SelectedTab);
}
} }
private int TryAdjustIndex(int amount) private int TryAdjustIndex(int amount)
@@ -1566,10 +1623,28 @@ namespace Barotrauma
{ {
crewList.Select(character, force: true); crewList.Select(character, force: true);
} }
// Icon colors might change based on the target so we check if they need to be updated
if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
{
foreach (var orderIcon in currentOrderIconList.Content.Children)
{
if (!(orderIcon.UserData is OrderInfo orderInfo)) { continue; }
if (!(orderInfo.Order is Order order)) { continue; }
if (order.ColoredWhenControllingGiver && order.OrderGiver != Character.Controlled)
{
orderIcon.Color = AIObjective.ObjectiveIconColor;
}
else
{
orderIcon.Color = order.Color;
}
}
}
// Only update the order highlights and objective icons here in singleplayer
// The server will let the clients know when they need to update in multiplayer
if (GameMain.IsSingleplayer && character.IsBot && character.AIController is HumanAIController controller && if (GameMain.IsSingleplayer && character.IsBot && character.AIController is HumanAIController controller &&
controller.ObjectiveManager is AIObjectiveManager objectiveManager) controller.ObjectiveManager is AIObjectiveManager objectiveManager)
{ {
// In multiplayer, these are set through character networking (the server lets the clients now when these are updated)
if (objectiveManager.CurrentObjective is AIObjective currentObjective) if (objectiveManager.CurrentObjective is AIObjective currentObjective)
{ {
if (objectiveManager.IsOrder(currentObjective)) if (objectiveManager.IsOrder(currentObjective))
@@ -1638,8 +1713,8 @@ namespace Barotrauma
bool foundMatch = false; bool foundMatch = false;
foreach (var orderIcon in currentOrderIconList.Content.Children) foreach (var orderIcon in currentOrderIconList.Content.Children)
{ {
var glowComponent = orderIcon.GetChildByUserData("glow"); if (!(orderIcon.GetChildByUserData("glow") is GUIComponent glowComponent)) { continue; }
if (glowComponent == null) { continue; } glowComponent.Color = orderIcon.Color;
if (foundMatch) if (foundMatch)
{ {
glowComponent.Visible = false; glowComponent.Visible = false;
@@ -1684,11 +1759,11 @@ namespace Barotrauma
objectiveIconFrame.ClearChildren(); objectiveIconFrame.ClearChildren();
if (sprite != null) if (sprite != null)
{ {
var objectiveIcon = CreateNodeIcon(Vector2.One, objectiveIconFrame.RectTransform, sprite, Color.LightGray, tooltip: tooltip); var objectiveIcon = CreateNodeIcon(Vector2.One, objectiveIconFrame.RectTransform, sprite, AIObjective.ObjectiveIconColor, tooltip: tooltip);
new GUIFrame(new RectTransform(new Vector2(1.5f), objectiveIcon.RectTransform, anchor: Anchor.Center), style: "OuterGlowCircular") new GUIFrame(new RectTransform(new Vector2(1.5f), objectiveIcon.RectTransform, anchor: Anchor.Center), style: "OuterGlowCircular")
{ {
CanBeFocused = false, CanBeFocused = false,
Color = Color.LightGray Color = AIObjective.ObjectiveIconColor
}; };
objectiveIconFrame.Visible = true; objectiveIconFrame.Visible = true;
} }
@@ -1909,7 +1984,7 @@ namespace Barotrauma
ScaleCommandUI(); ScaleCommandUI();
commandFrame = new GUIFrame( commandFrame = new GUIFrame(
new RectTransform(Vector2.One, GUICanvas.Instance, anchor: Anchor.Center), new RectTransform(Vector2.One, GUI.Canvas, anchor: Anchor.Center),
style: null, style: null,
color: Color.Transparent); color: Color.Transparent);
background = new GUIImage( background = new GUIImage(
@@ -3530,12 +3605,14 @@ namespace Barotrauma
public void Save(XElement parentElement) public void Save(XElement parentElement)
{ {
XElement element = new XElement("crew"); XElement element = new XElement("crew");
foreach (CharacterInfo ci in characterInfos) for (int i = 0; i < characterInfos.Count; i++)
{ {
var ci = characterInfos[i];
var infoElement = ci.Save(element); var infoElement = ci.Save(element);
if (ci.InventoryData != null) { infoElement.Add(ci.InventoryData); } if (ci.InventoryData != null) { infoElement.Add(ci.InventoryData); }
if (ci.HealthData != null) { infoElement.Add(ci.HealthData); } if (ci.HealthData != null) { infoElement.Add(ci.HealthData); }
if (ci.OrderData != null) { infoElement.Add(ci.OrderData); } if (ci.OrderData != null) { infoElement.Add(ci.OrderData); }
infoElement.Add(new XAttribute("crewlistindex", ci.CrewListIndex));
if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); } if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); }
} }
SaveActiveOrders(element); SaveActiveOrders(element);
@@ -97,7 +97,7 @@ namespace Barotrauma
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUICanvas.Instance), var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUI.Canvas),
isHorizontal: true, childAnchor: Anchor.CenterRight) isHorizontal: true, childAnchor: Anchor.CenterRight)
{ {
CanBeFocused = false CanBeFocused = false
@@ -108,7 +108,7 @@ namespace Barotrauma
buttonCenter = buttonHeight / 2, buttonCenter = buttonHeight / 2,
screenMiddle = GameMain.GraphicsWidth / 2; screenMiddle = GameMain.GraphicsWidth / 2;
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle - buttonWidth / 2, HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, buttonWidth, buttonHeight), GUICanvas.Instance), endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle - buttonWidth / 2, HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, buttonWidth, buttonHeight), GUI.Canvas),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{ {
Pulse = true, Pulse = true,
@@ -145,7 +145,7 @@ namespace Barotrauma
int readyButtonHeight = buttonHeight; int readyButtonHeight = buttonHeight;
int readyButtonWidth = (int) (GUI.Scale * 50); int readyButtonWidth = (int) (GUI.Scale * 50);
ReadyCheckButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle + (buttonWidth / 2) + GUI.IntScale(16), HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, readyButtonWidth, readyButtonHeight), GUICanvas.Instance), ReadyCheckButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle + (buttonWidth / 2) + GUI.IntScale(16), HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, readyButtonWidth, readyButtonHeight), GUI.Canvas),
style: "RepairBuyButton") style: "RepairBuyButton")
{ {
ToolTip = TextManager.Get("ReadyCheck.Tooltip"), ToolTip = TextManager.Get("ReadyCheck.Tooltip"),
@@ -108,6 +108,9 @@ namespace Barotrauma
case "pets": case "pets":
petsElement = subElement; petsElement = subElement;
break; break;
case "stats":
LoadStats(subElement);
break;
} }
} }
@@ -167,7 +170,7 @@ namespace Barotrauma
int buttonHeight = (int)(GUI.Scale * 40); int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(450); int buttonWidth = GUI.IntScale(450);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance), endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUI.Canvas),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{ {
Pulse = true, Pulse = true,
@@ -412,6 +415,10 @@ namespace Barotrauma
break; break;
case TransitionType.ProgressToNextLocation: case TransitionType.ProgressToNextLocation:
Map.MoveToNextLocation(); Map.MoveToNextLocation();
TotalPassedLevels++;
break;
case TransitionType.ProgressToNextEmptyLocation:
TotalPassedLevels++;
break; break;
} }
@@ -728,6 +735,7 @@ namespace Barotrauma
new XAttribute("purchaseditemrepairs", PurchasedItemRepairs), new XAttribute("purchaseditemrepairs", PurchasedItemRepairs),
new XAttribute("cheatsenabled", CheatsEnabled)); new XAttribute("cheatsenabled", CheatsEnabled));
modeElement.Add(Settings.Save()); modeElement.Add(Settings.Save());
modeElement.Add(SaveStats());
//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)
@@ -205,6 +205,11 @@ namespace Barotrauma
return; return;
} }
if (campaign?.CampaignUI?.MedicalClinic is { } ui)
{
ui.ClosePopup();
}
healAllRequests.Add(new RequestAction<HealRequest>(onReceived, GetTimeout())); healAllRequests.Add(new RequestAction<HealRequest>(onReceived, GetTimeout()));
ClientSend(null, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable); ClientSend(null, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable);
} }
@@ -600,6 +600,10 @@ namespace Barotrauma
OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; } 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)); tabButtons[(int)tab].Text = ToolBox.LimitString(buttonText, tabButtons[(int)tab].Font, (int)(0.75f * tabWidth * tabButtonHolder.Rect.Width));
if (tabButtons[(int)tab].Text != buttonText)
{
tabButtons[(int)tab].ToolTip = buttonText;
}
} }
/// Graphics tab -------------------------------------------------------------- /// Graphics tab --------------------------------------------------------------
@@ -796,7 +796,7 @@ namespace Barotrauma
if (quickUseAction != QuickUseAction.Drop) if (quickUseAction != QuickUseAction.Drop)
{ {
slot.QuickUseButtonToolTip = quickUseAction == QuickUseAction.None ? slot.QuickUseButtonToolTip = quickUseAction == QuickUseAction.None ?
"" : TextManager.GetWithVariable("QuickUseAction." + quickUseAction.ToString(), "[equippeditem]", item?.Name); "" : TextManager.GetWithVariable("QuickUseAction." + quickUseAction.ToString(), "[equippeditem]", character.HeldItems.FirstOrDefault()?.Name ?? item?.Name);
if (PlayerInput.PrimaryMouseButtonDown()) { slot.EquipButtonState = GUIComponent.ComponentState.Pressed; } if (PlayerInput.PrimaryMouseButtonDown()) { slot.EquipButtonState = GUIComponent.ComponentState.Pressed; }
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked())
{ {
@@ -970,7 +970,9 @@ namespace Barotrauma
{ {
return QuickUseAction.TakeFromCharacter; return QuickUseAction.TakeFromCharacter;
} }
else if (character.HeldItems.Any(i => i.OwnInventory != null && i.OwnInventory.CanBePut(item)) && allowInventorySwap) else if (character.HeldItems.Any(i =>
i.OwnInventory != null &&
(i.OwnInventory.CanBePut(item) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
{ {
return QuickUseAction.PutToEquippedItem; return QuickUseAction.PutToEquippedItem;
} }
@@ -1136,7 +1138,8 @@ namespace Barotrauma
foreach (Item heldItem in character.HeldItems) foreach (Item heldItem in character.HeldItems)
{ {
if (heldItem.OwnInventory != null && if (heldItem.OwnInventory != null &&
heldItem.OwnInventory.TryPutItem(item, Character.Controlled)) heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
(heldItem.OwnInventory.Capacity == 1 && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: true, allowCombine: false, user: Character.Controlled)))
{ {
success = true; success = true;
for (int j = 0; j < capacity; j++) for (int j = 0; j < capacity; j++)
@@ -256,6 +256,10 @@ namespace Barotrauma.Items.Components
{ {
targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false); targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false);
} }
if (GameMain.GameSession != null)
{
GameMain.GameSession.TimeSpentCleaning += deltaTime;
}
} }
else else
{ {
@@ -263,6 +267,10 @@ namespace Barotrauma.Items.Components
{ {
targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true); targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true);
} }
if (GameMain.GameSession != null)
{
GameMain.GameSession.TimeSpentPainting += deltaTime;
}
} }
Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos); Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
@@ -143,7 +143,7 @@ namespace Barotrauma.Items.Components
} }
} }
public GUIFrame GuiFrame { get; protected set; } public GUIFrame GuiFrame { get; set; }
[Serialize(false, false)] [Serialize(false, false)]
public bool AllowUIOverlap public bool AllowUIOverlap
@@ -554,7 +554,7 @@ namespace Barotrauma.Items.Components
color = GuiFrameSource.GetAttributeColor("color", Color.White); color = GuiFrameSource.GetAttributeColor("color", Color.White);
} }
string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", ""); string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", "");
GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas.ItemComponentHolder, Anchor.Center), style, color); GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color);
DefaultLayout = GUILayoutSettings.Load(GuiFrameSource); DefaultLayout = GUILayoutSettings.Load(GuiFrameSource);
if (GuiFrame != null) if (GuiFrame != null)
{ {
@@ -76,6 +76,9 @@ namespace Barotrauma.Items.Components
set; set;
} }
[Serialize(false, false, description: "If true, the contained state indicator calculates how full the item is based on the total amount of items that can be stacked inside it, as opposed to how many of the inventory slots are occupied.")]
public bool ShowTotalStackCapacityInContainedStateIndicator { get; set; }
[Serialize(false, false, description: "Should the inventory of this item be kept open when the item is equipped by a character.")] [Serialize(false, false, description: "Should the inventory of this item be kept open when the item is equipped by a character.")]
public bool KeepOpenWhenEquipped { get; set; } public bool KeepOpenWhenEquipped { get; set; }
@@ -63,7 +63,7 @@ namespace Barotrauma.Items.Components
Stretch = true, Stretch = true,
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelArea.RectTransform), TextManager.Get("uilabel.input"), font: GUI.SubHeadingFont) { Padding = Vector4.Zero }; var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelArea.RectTransform), TextManager.Get("deconstructor.input", fallBackTag: "uilabel.input"), font: GUI.SubHeadingFont) { Padding = Vector4.Zero };
inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height)); inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height));
new GUIFrame(new RectTransform(Vector2.One, inputLabelArea.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(Vector2.One, inputLabelArea.RectTransform), style: "HorizontalLine");
@@ -132,7 +132,7 @@ namespace Barotrauma.Items.Components
Stretch = true, Stretch = true,
RelativeSpacing = 0.03f RelativeSpacing = 0.03f
}; };
var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, separatorArea.RectTransform), TextManager.Get("uilabel.input"), font: GUI.SubHeadingFont) { Padding = Vector4.Zero }; var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, separatorArea.RectTransform), TextManager.Get("fabricator.input", fallBackTag: "uilabel.input"), font: GUI.SubHeadingFont) { Padding = Vector4.Zero };
inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height)); inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height));
new GUIFrame(new RectTransform(Vector2.One, separatorArea.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(Vector2.One, separatorArea.RectTransform), style: "HorizontalLine");
@@ -507,7 +507,7 @@ namespace Barotrauma.Items.Components
{ {
Vector2 origin = weaponSprite.Origin; Vector2 origin = weaponSprite.Origin;
float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y); float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y);
Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? GUI.Style.Red : GUI.Style.Green; Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUI.Style.Green;
weaponSprite.Draw(batch, center, color, origin, rotation, scale, it.SpriteEffects); weaponSprite.Draw(batch, center, color, origin, rotation, scale, it.SpriteEffects);
} }
}); });
@@ -1335,7 +1335,7 @@ namespace Barotrauma.Items.Components
RectangleF entityRect = ScaleRectToUI(structure, parent, border); RectangleF entityRect = ScaleRectToUI(structure, parent, border);
Vector2 spriteScale = new Vector2(entityRect.Size.X / sprite.size.X, entityRect.Size.Y / sprite.size.Y); Vector2 spriteScale = new Vector2(entityRect.Size.X / sprite.size.X, entityRect.Size.Y / sprite.size.Y);
sprite.Draw(spriteBatch, new Vector2(entityRect.Location.X + inflate, entityRect.Location.Y + inflate), structure.SpriteColor, Vector2.Zero, 0f, spriteScale, structure.SpriteEffects); sprite.Draw(spriteBatch, new Vector2(entityRect.Location.X + inflate, entityRect.Location.Y + inflate), structure.SpriteColor, Vector2.Zero, 0f, spriteScale, sprite.effects ^ structure.SpriteEffects);
} }
private static RectangleF ScaleRectToUI(MapEntity entity, RectangleF parentRect, RectangleF worldBorders) private static RectangleF ScaleRectToUI(MapEntity entity, RectangleF parentRect, RectangleF worldBorders)
@@ -1718,5 +1718,20 @@ namespace Barotrauma.Items.Components
return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray()); return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
} }
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
if (searchAutoComplete != null)
{
searchAutoComplete.RectTransform.Parent = null;
searchAutoComplete = null;
}
if (hullInfoFrame != null)
{
hullInfoFrame.RectTransform.Parent = null;
hullInfoFrame = null;
}
}
} }
} }
@@ -14,7 +14,7 @@ namespace Barotrauma.Items.Components
if (selectionUI == null) if (selectionUI == null)
{ {
selectionUI = new SubmarineSelection(true, null, GUICanvas.Instance.ItemComponentHolder); selectionUI = new SubmarineSelection(true, null, GUI.Canvas);
} }
GuiFrame = selectionUI.GuiFrame; GuiFrame = selectionUI.GuiFrame;
@@ -35,5 +35,15 @@ namespace Barotrauma.Items.Components
selectionUI?.Update(); selectionUI?.Update();
} }
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
if (selectionUI != null)
{
selectionUI.GuiFrame.RectTransform.Parent = null;
selectionUI = null;
}
}
} }
} }
@@ -348,6 +348,27 @@ namespace Barotrauma.Items.Components
} }
} }
private Vector2 GetTransducerPos()
{
if (!UseTransducers || connectedTransducers.Count == 0)
{
//use the position of the sub if the item is static (no body) and inside a sub
return item.Submarine != null && item.body == null ? item.Submarine.WorldPosition : item.WorldPosition;
}
Vector2 transducerPosSum = Vector2.Zero;
foreach (ConnectedTransducer transducer in connectedTransducers)
{
if (transducer.Transducer.Item.Submarine != null && !CenterOnTransducers)
{
return transducer.Transducer.Item.Submarine.WorldPosition;
}
transducerPosSum += transducer.Transducer.Item.WorldPosition;
}
return transducerPosSum / connectedTransducers.Count;
}
public override void OnItemLoaded() public override void OnItemLoaded()
{ {
base.OnItemLoaded(); base.OnItemLoaded();
@@ -10,6 +10,12 @@ namespace Barotrauma.Items.Components
private GUIProgressBar chargeIndicator; private GUIProgressBar chargeIndicator;
private GUIScrollBar rechargeSpeedSlider; private GUIScrollBar rechargeSpeedSlider;
[Serialize(0.0f, true)]
public float RechargeWarningIndicatorLow { get; set; }
[Serialize(0.0f, true)]
public float RechargeWarningIndicatorHigh { get; set; }
public Vector2 DrawSize public Vector2 DrawSize
{ {
//use the extents of the item as the draw size //use the extents of the item as the draw size
@@ -28,19 +34,38 @@ namespace Barotrauma.Items.Components
var upperArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedFrame.RectTransform, Anchor.TopCenter), style: null); var upperArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedFrame.RectTransform, Anchor.TopCenter), style: null);
var lowerArea = new GUIFrame(new RectTransform(new Vector2(1, 0.6f), paddedFrame.RectTransform, Anchor.BottomCenter), style: null); var lowerArea = new GUIFrame(new RectTransform(new Vector2(1, 0.6f), paddedFrame.RectTransform, Anchor.BottomCenter), style: null);
var rechargeRateContainer = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), upperArea.RectTransform), style: null);
string rechargeStr = TextManager.Get("PowerContainerRechargeRate"); var rechargeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.0f), rechargeRateContainer.RectTransform, Anchor.CenterLeft),
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), upperArea.RectTransform, Anchor.TopCenter), TextManager.Get("rechargerate"), textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft);
"RechargeRate", textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.Center) string kW = TextManager.Get("kilowatt");
var rechargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), rechargeRateContainer.RectTransform, Anchor.CenterRight),
"", textColor: GUI.Style.TextColor, font: GUI.Font, textAlignment: Alignment.CenterRight)
{ {
TextGetter = () => TextGetter = () => $"{(int)MathF.Round(currPowerConsumption)} {kW} ({(int)MathF.Round(RechargeRatio * 100)} %)"
{
return rechargeStr.Replace("[rate]", ((int)((rechargeSpeed / maxRechargeSpeed) * 100.0f)).ToString());
}
}; };
if (rechargeText.TextSize.X > rechargeText.Rect.Width) { rechargeText.Font = GUI.SmallFont; }
rechargeSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 0.4f), upperArea.RectTransform, Anchor.BottomCenter), var rechargeSliderContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.4f), upperArea.RectTransform, Anchor.BottomCenter));
barSize: 0.15f, style: "DeviceSlider")
if (RechargeWarningIndicatorLow > 0.0f || RechargeWarningIndicatorHigh > 0.0f)
{
var rechargeSliderFill = new GUICustomComponent(new RectTransform(new Vector2(0.95f, 0.9f), rechargeSliderContainer.RectTransform, Anchor.Center), (SpriteBatch sb, GUICustomComponent c) =>
{
if (RechargeWarningIndicatorLow > 0.0f)
{
float warningLow = c.Rect.Width * RechargeWarningIndicatorLow;
GUI.DrawRectangle(sb, new Vector2(c.Rect.X + warningLow, c.Rect.Y), new Vector2(c.Rect.Width - warningLow, c.Rect.Height), GUI.Style.Orange, isFilled: true);
}
if (RechargeWarningIndicatorHigh > 0.0f)
{
float warningHigh = c.Rect.Width * RechargeWarningIndicatorHigh;
GUI.DrawRectangle(sb, new Vector2(c.Rect.X + warningHigh, c.Rect.Y), new Vector2(c.Rect.Width - warningHigh, c.Rect.Height), GUI.Style.Red, isFilled: true);
}
});
}
rechargeSpeedSlider = new GUIScrollBar(new RectTransform(Vector2.One, rechargeSliderContainer.RectTransform, Anchor.Center),
barSize: 0.15f, style: "DeviceSliderSeeThrough")
{ {
Step = 0.1f, Step = 0.1f,
OnMoved = (GUIScrollBar scrollBar, float barScroll) => OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
@@ -61,17 +86,17 @@ namespace Barotrauma.Items.Components
// lower area -------------------------- // lower area --------------------------
var textArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), lowerArea.RectTransform), style: null); var chargeTextContainer = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), lowerArea.RectTransform), style: null);
var chargeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.0f), textArea.RectTransform, Anchor.CenterLeft), var chargeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.0f), chargeTextContainer.RectTransform, Anchor.CenterLeft),
TextManager.Get("charge"), textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft) TextManager.Get("charge"), textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{ {
ToolTip = TextManager.Get("PowerTransferTipPower") ToolTip = TextManager.Get("PowerTransferTipPower")
}; };
string kWmin = TextManager.Get("kilowattminute"); string kWmin = TextManager.Get("kilowattminute");
var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), textArea.RectTransform, Anchor.CenterRight), var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), chargeTextContainer.RectTransform, Anchor.CenterRight),
"", textColor: GUI.Style.TextColor, font: GUI.Font, textAlignment: Alignment.CenterRight) "", textColor: GUI.Style.TextColor, font: GUI.Font, textAlignment: Alignment.CenterRight)
{ {
TextGetter = () => $"{(int)Math.Round(charge)}/{(int)capacity} {kWmin} ({(int)Math.Round(MathUtils.Percentage(charge, capacity))} %)" TextGetter = () => $"{(int)MathF.Round(charge)}/{(int)capacity} {kWmin} ({(int)MathF.Round(MathUtils.Percentage(charge, capacity))} %)"
}; };
if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUI.SmallFont; } if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUI.SmallFont; }
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
@@ -19,5 +20,10 @@ namespace Barotrauma.Items.Components
ShapeExtensions.DrawLine(spriteBatch, pos + Vector2.UnitX * range, pos - Vector2.UnitX * range, Color.Cyan * 0.5f, 2); ShapeExtensions.DrawLine(spriteBatch, pos + Vector2.UnitX * range, pos - Vector2.UnitX * range, Color.Cyan * 0.5f, 2);
ShapeExtensions.DrawCircle(spriteBatch, pos, range, 32, Color.Cyan * 0.5f, 3); ShapeExtensions.DrawCircle(spriteBatch, pos, range, 32, Color.Cyan * 0.5f, 3);
} }
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
Channel = msg.ReadRangedInteger(MinChannel, MaxChannel);
}
} }
} }
@@ -1584,6 +1584,10 @@ namespace Barotrauma
{ {
containedState = item.Condition / item.MaxCondition; containedState = item.Condition / item.MaxCondition;
} }
else if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator)
{
containedState = itemContainer.Inventory.AllItems.Count() / (float)(itemContainer.GetMaxStackSize(0) * itemContainer.Capacity);
}
else else
{ {
var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault(); var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault();
@@ -315,7 +315,7 @@ namespace Barotrauma.MapCreatures.Behavior
} }
else else
{ {
RemoveClaim(itemId); RemoveClaim(item);
} }
} }
else else
@@ -128,13 +128,13 @@ namespace Barotrauma
{ {
//no flow particles between linked hulls (= rooms consisting of multiple hulls) //no flow particles between linked hulls (= rooms consisting of multiple hulls)
if (hull1.linkedTo.Contains(hull2)) { return; } if (hull1.linkedTo.Contains(hull2)) { return; }
foreach (Hull h in hull1.linkedTo) foreach (var linkedEntity in hull1.linkedTo)
{ {
if (h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; } if (linkedEntity is Hull h && h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; }
} }
foreach (Hull h in hull2.linkedTo) foreach (var linkedEntity in hull2.linkedTo)
{ {
if (h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; } if (linkedEntity is Hull h && h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; }
} }
} }
@@ -244,7 +244,7 @@ namespace Barotrauma
float emitInterval = 1.0f / particlesPerSec; float emitInterval = 1.0f / particlesPerSec;
while (particleTimer > emitInterval) while (particleTimer > emitInterval)
{ {
pos.X = Rand.Range(rect.X, rect.X + rect.Width); pos.X = Rand.Range(rect.X, rect.X + rect.Width + 1);
Vector2 velocity = new Vector2( Vector2 velocity = new Vector2(
lerpedFlowForce.X * Rand.Range(0.5f, 0.7f), lerpedFlowForce.X * Rand.Range(0.5f, 0.7f),
MathHelper.Clamp(lerpedFlowForce.Y, -500.0f, 1000.0f) * Rand.Range(0.5f, 0.7f)); MathHelper.Clamp(lerpedFlowForce.Y, -500.0f, 1000.0f) * Rand.Range(0.5f, 0.7f));
@@ -95,7 +95,7 @@ namespace Barotrauma
var prefab = ToolBox.SelectWeightedRandom(availablePrefabs, availablePrefabs.Select(p => p.GetCommonness(level.GenerationParams)).ToList(), Rand.RandSync.ClientOnly); var prefab = ToolBox.SelectWeightedRandom(availablePrefabs, availablePrefabs.Select(p => p.GetCommonness(level.GenerationParams)).ToList(), Rand.RandSync.ClientOnly);
if (prefab == null) { break; } if (prefab == null) { break; }
int amount = Rand.Range(prefab.SwarmMin, prefab.SwarmMax, Rand.RandSync.ClientOnly); int amount = Rand.Range(prefab.SwarmMin, prefab.SwarmMax + 1, Rand.RandSync.ClientOnly);
List<BackgroundCreature> swarmMembers = new List<BackgroundCreature>(); List<BackgroundCreature> swarmMembers = new List<BackgroundCreature>();
for (int n = 0; n < amount; n++) for (int n = 0; n < amount; n++)
{ {
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Barotrauma.Lights; using Barotrauma.Lights;
@@ -36,9 +37,6 @@ namespace Barotrauma
private static List<MapEntity> highlightedList = new List<MapEntity>(); private static List<MapEntity> highlightedList = new List<MapEntity>();
// Test feature. Not yet saved.
public static Dictionary<MapEntity, HashSet<MapEntity>> SelectionGroups { get; private set; } = new Dictionary<MapEntity, HashSet<MapEntity>>();
private static float highlightTimer; private static float highlightTimer;
private static GUIListBox highlightedListBox; private static GUIListBox highlightedListBox;
@@ -197,7 +195,7 @@ namespace Barotrauma
{ {
Paste(cam.ScreenToWorld(PlayerInput.MousePosition)); Paste(cam.ScreenToWorld(PlayerInput.MousePosition));
} }
else if (PlayerInput.KeyHit(Keys.G)) /*else if (PlayerInput.KeyHit(Keys.G))
{ {
if (SelectedList.Any()) if (SelectedList.Any())
{ {
@@ -217,7 +215,7 @@ namespace Barotrauma
} }
} }
} }
} }*/
} }
} }
@@ -360,14 +358,15 @@ namespace Barotrauma
{ {
if (highLightedEntity != null) if (highLightedEntity != null)
{ {
if (SelectionGroups.TryGetValue(highLightedEntity, out HashSet<MapEntity> group)) if (SubEditorScreen.IsLayerLinked(highLightedEntity)/*SelectionGroups.TryGetValue(highLightedEntity, out HashSet<MapEntity> group)*/)
{ {
foreach (MapEntity entity in group.Where(e => !newSelection.Contains(e))) ImmutableHashSet<MapEntity> entitiesInSameLayer = SubEditorScreen.GetEntitiesInSameLayer(highLightedEntity);
foreach (MapEntity entity in entitiesInSameLayer.Where(e => !newSelection.Contains(e)))
{ {
newSelection.Add(entity); newSelection.Add(entity);
} }
foreach (MapEntity entity in group) foreach (MapEntity entity in entitiesInSameLayer)
{ {
entity.IsIncludedInSelection = true; entity.IsIncludedInSelection = true;
} }
@@ -1197,14 +1196,24 @@ namespace Barotrauma
Rectangle selectionRect = Submarine.AbsRect(pos, size); Rectangle selectionRect = Submarine.AbsRect(pos, size);
foreach (MapEntity e in mapEntityList) foreach (MapEntity entity in mapEntityList)
{ {
if (!e.SelectableInEditor) continue; if (!entity.SelectableInEditor) { continue; }
if (Submarine.RectsOverlap(selectionRect, e.rect)) if (Submarine.RectsOverlap(selectionRect, entity.rect))
{ {
foundEntities.Add(e); foundEntities.Add(entity);
e.IsIncludedInSelection = true; entity.IsIncludedInSelection = true;
if (SubEditorScreen.IsLayerLinked(entity))
{
ImmutableHashSet<MapEntity> entitiesInSameLayer = SubEditorScreen.GetEntitiesInSameLayer(entity);
foreach (MapEntity layerEntity in entitiesInSameLayer.Where(e => !foundEntities.Contains(e)))
{
foundEntities.Add(layerEntity);
layerEntity.IsIncludedInSelection = true;
}
}
} }
} }
@@ -651,7 +651,11 @@ namespace Barotrauma
public void Dispose() public void Dispose()
{ {
previewFrame = null; if (previewFrame != null)
{
previewFrame.RectTransform.Parent = null;
previewFrame = null;
}
spriteRecorder?.Dispose(); spriteRecorder?.Dispose();
isDisposed = true; isDisposed = true;
} }
@@ -46,6 +46,13 @@ namespace Barotrauma.Networking
senderName = senderCharacter.Name; senderName = senderCharacter.Name;
} }
} }
Color? textColor = null;
if (msg.ReadBoolean())
{
textColor = msg.ReadColorR8G8B8A8();
}
msg.ReadPadBits(); msg.ReadPadBits();
switch (type) switch (type)
@@ -135,14 +142,18 @@ namespace Barotrauma.Networking
//only show the message box if the text differs from the text in the currently visible box //only show the message box if the text differs from the text in the currently visible box
if ((GUIMessageBox.VisibleBox as GUIMessageBox)?.Text?.Text != txt) if ((GUIMessageBox.VisibleBox as GUIMessageBox)?.Text?.Text != txt)
{ {
new GUIMessageBox("", txt); GUIMessageBox messageBox = new GUIMessageBox("", txt);
if (textColor != null) { messageBox.Text.TextColor = textColor.Value; }
} }
break; break;
case ChatMessageType.ServerMessageBoxInGame: case ChatMessageType.ServerMessageBoxInGame:
new GUIMessageBox("", txt, new string[0], type: GUIMessageBox.Type.InGame, iconStyle: styleSetting); {
GUIMessageBox messageBox = new GUIMessageBox("", txt, new string[0], type: GUIMessageBox.Type.InGame, iconStyle: styleSetting);
if (textColor != null) { messageBox.Text.TextColor = textColor.Value; }
}
break; break;
case ChatMessageType.Console: case ChatMessageType.Console:
DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]); DebugConsole.NewMessage(txt, textColor == null ? MessageColor[(int)ChatMessageType.Console] : textColor.Value);
break; break;
case ChatMessageType.ServerLog: case ChatMessageType.ServerLog:
if (!Enum.TryParse(senderName, out ServerLog.MessageType messageType)) if (!Enum.TryParse(senderName, out ServerLog.MessageType messageType))
@@ -152,7 +163,7 @@ namespace Barotrauma.Networking
GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType); GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType);
break; break;
default: default:
GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType); GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType, textColor: textColor);
break; break;
} }
LastID = id; LastID = id;
@@ -857,7 +857,7 @@ namespace Barotrauma.Networking
} }
break; break;
case ServerPacketHeader.STARTGAMEFINALIZE: case ServerPacketHeader.STARTGAMEFINALIZE:
DebugConsole.Log("Received STARTGAMEFINALIZE packet."); DebugConsole.NewMessage("Received STARTGAMEFINALIZE packet. Round init status: " + roundInitStatus);
if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize) if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize)
{ {
//waiting for a save file //waiting for a save file
@@ -1,13 +1,10 @@
using Barotrauma.Tutorials; using Barotrauma.Extensions;
using Barotrauma.IO;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using System.Globalization; using System.Globalization;
using Barotrauma.Extensions; using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
@@ -64,6 +61,7 @@ namespace Barotrauma
maxMissionCount = MathHelper.Clamp(maxMissionCount, maxMissionCount = MathHelper.Clamp(maxMissionCount,
CampaignSettings.MinMissionCountLimit, CampaignSettings.MinMissionCountLimit,
CampaignSettings.MaxMissionCountLimit); CampaignSettings.MaxMissionCountLimit);
maxMissionCountText.Text = maxMissionCount.ToString(CultureInfo.InvariantCulture); maxMissionCountText.Text = maxMissionCount.ToString(CultureInfo.InvariantCulture);
} }
maxMissionCountButtons[1] maxMissionCountButtons[1]
@@ -151,6 +151,7 @@ namespace Barotrauma
if (Campaign.Money >= CampaignMode.HullRepairCost) if (Campaign.Money >= CampaignMode.HullRepairCost)
{ {
Campaign.Money -= CampaignMode.HullRepairCost; Campaign.Money -= CampaignMode.HullRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.HullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
Campaign.PurchasedHullRepairs = true; Campaign.PurchasedHullRepairs = true;
} }
} }
@@ -196,6 +197,7 @@ namespace Barotrauma
if (Campaign.Money >= CampaignMode.ItemRepairCost) if (Campaign.Money >= CampaignMode.ItemRepairCost)
{ {
Campaign.Money -= CampaignMode.ItemRepairCost; Campaign.Money -= CampaignMode.ItemRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ItemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
Campaign.PurchasedItemRepairs = true; Campaign.PurchasedItemRepairs = true;
} }
} }
@@ -248,6 +250,7 @@ namespace Barotrauma
if (Campaign.Money >= CampaignMode.ShuttleReplaceCost) if (Campaign.Money >= CampaignMode.ShuttleReplaceCost)
{ {
Campaign.Money -= CampaignMode.ShuttleReplaceCost; Campaign.Money -= CampaignMode.ShuttleReplaceCost;
GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ShuttleReplaceCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
Campaign.PurchasedLostShuttles = true; Campaign.PurchasedLostShuttles = true;
} }
} }
@@ -54,7 +54,7 @@ namespace Barotrauma
private void CreateGUI() private void CreateGUI()
{ {
GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUICanvas.Instance) { MinSize = new Point(300, 400) }); GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUI.Canvas) { MinSize = new Point(300, 400) });
GUILayoutGroup layoutGroup = new GUILayoutGroup(RectTransform(0.9f, 0.9f, GuiFrame, Anchor.Center)) { Stretch = true }; GUILayoutGroup layoutGroup = new GUILayoutGroup(RectTransform(0.9f, 0.9f, GuiFrame, Anchor.Center)) { Stretch = true };
// === BUTTONS === // // === BUTTONS === //
@@ -501,8 +501,6 @@ namespace Barotrauma
ResetButtonStates(null); ResetButtonStates(null);
GameAnalyticsManager.SetCustomDimension01("");
if (GameMain.SteamWorkshopScreen != null) if (GameMain.SteamWorkshopScreen != null)
{ {
CoroutineManager.StartCoroutine(GameMain.SteamWorkshopScreen.RefreshDownloadState()); CoroutineManager.StartCoroutine(GameMain.SteamWorkshopScreen.RefreshDownloadState());
@@ -1019,7 +1017,10 @@ namespace Barotrauma
if (backgroundSprite == null) if (backgroundSprite == null)
{ {
backgroundSprite = (LocationType.List.Where(l => l.UseInMainMenu).GetRandom())?.GetPortrait(0); #if UNSTABLE
backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null);
#endif
backgroundSprite ??= LocationType.List.Where(l => l.UseInMainMenu).GetRandom()?.GetPortrait(0);
} }
if (backgroundSprite != null) if (backgroundSprite != null)
@@ -1159,7 +1160,7 @@ namespace Barotrauma
//GameMain.LobbyScreen.Select(); //GameMain.LobbyScreen.Select();
} }
#region UI Methods #region UI Methods
private void CreateCampaignSetupUI() private void CreateCampaignSetupUI()
{ {
menuTabs[(int)Tab.NewGame].ClearChildren(); menuTabs[(int)Tab.NewGame].ClearChildren();
@@ -1432,7 +1433,7 @@ namespace Barotrauma
playstyleDescription.TextAlignment = playstyleDescription.WrappedText.Contains('\n') ? playstyleDescription.TextAlignment = playstyleDescription.WrappedText.Contains('\n') ?
Alignment.CenterLeft : Alignment.Center; Alignment.CenterLeft : Alignment.Center;
} }
#endregion #endregion
private void FetchRemoteContent() private void FetchRemoteContent()
{ {
@@ -14,7 +14,7 @@ namespace Barotrauma
{ {
if (frame == null) if (frame == null)
{ {
frame = new GUIFrame(new RectTransform(GUICanvas.Instance.RelativeSize, GUICanvas.Instance), style: null) frame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: null)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Xml.Linq; using System.Xml.Linq;
@@ -21,6 +22,38 @@ namespace Barotrauma
{ {
class SubEditorScreen : EditorScreen class SubEditorScreen : EditorScreen
{ {
private enum LayerVisibility
{
Visible,
Invisible
}
private enum LayerLinkage
{
Unlinked,
Linked
}
private readonly struct LayerData
{
public readonly LayerVisibility Visible;
public readonly LayerLinkage Linkage;
public static readonly LayerData Default = new LayerData(LayerVisibility.Visible, LayerLinkage.Unlinked);
public LayerData(LayerVisibility visible, LayerLinkage linkage)
{
Visible = visible;
Linkage = linkage;
}
public void Deconstruct(out LayerVisibility isvisible, out LayerLinkage islinked)
{
isvisible = Visible;
islinked = Linkage;
}
}
private static readonly string[] crewExperienceLevels = private static readonly string[] crewExperienceLevels =
{ {
"CrewExperienceLow", "CrewExperienceLow",
@@ -94,6 +127,7 @@ namespace Barotrauma
private GUIFrame hullVolumeFrame; private GUIFrame hullVolumeFrame;
private GUIFrame saveAssemblyFrame; private GUIFrame saveAssemblyFrame;
private GUIFrame snapToGridFrame;
const int PreviouslyUsedCount = 10; const int PreviouslyUsedCount = 10;
private GUIFrame previouslyUsedPanel; private GUIFrame previouslyUsedPanel;
@@ -238,7 +272,7 @@ namespace Barotrauma
public bool WiringMode => mode == Mode.Wiring; public bool WiringMode => mode == Mode.Wiring;
public static readonly Dictionary<string, bool> Layers = new Dictionary<string, bool>(); private static readonly Dictionary<string, LayerData> Layers = new Dictionary<string, LayerData>();
public SubEditorScreen() public SubEditorScreen()
{ {
@@ -507,7 +541,7 @@ namespace Barotrauma
//----------------------------------------------- //-----------------------------------------------
layerPanel = new GUIFrame(new RectTransform(new Vector2(0.175f, 0.4f), GUI.Canvas)) layerPanel = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUI.Canvas))
{ {
Visible = false Visible = false
}; };
@@ -520,6 +554,7 @@ namespace Barotrauma
AutoHideScrollBar = false, AutoHideScrollBar = false,
OnSelected = (component, o) => OnSelected = (component, o) =>
{ {
if (GUI.MouseOn is GUITickBox) { return false; } // lol
if (!(o is string layer)) { return false; } if (!(o is string layer)) { return false; }
MapEntity.SelectedList.Clear(); MapEntity.SelectedList.Clear();
@@ -847,6 +882,19 @@ namespace Barotrauma
}; };
saveAssemblyFrame.RectTransform.MinSize = new Point(saveAssemblyFrame.Rect.Width, (int)(saveAssemblyButton.Rect.Height / saveAssemblyButton.RectTransform.RelativeSize.Y)); saveAssemblyFrame.RectTransform.MinSize = new Point(saveAssemblyFrame.Rect.Width, (int)(saveAssemblyButton.Rect.Height / saveAssemblyButton.RectTransform.RelativeSize.Y));
snapToGridFrame = new GUIFrame(new RectTransform(new Vector2(0.08f, 0.5f), TopPanel.RectTransform, Anchor.BottomLeft, Pivot.TopLeft)
{ MinSize = new Point((int)(250 * GUI.Scale), (int)(80 * GUI.Scale)), AbsoluteOffset = new Point((int)(10 * GUI.Scale), -saveAssemblyFrame.Rect.Height - entityCountPanel.Rect.Height - (int)(10 * GUI.Scale)) }, "InnerFrame")
{
Visible = false
};
var saveStampButton = new GUIButton(new RectTransform(new Vector2(0.9f, 0.8f), snapToGridFrame.RectTransform, Anchor.Center), TextManager.Get("subeditor.snaptogrid", fallBackTag: "spriteeditor.snaptogrid"));
saveStampButton.TextBlock.AutoScaleHorizontal = true;
saveStampButton.OnClicked += (btn, userdata) =>
{
SnapToGrid();
return true;
};
snapToGridFrame.RectTransform.MinSize = new Point(snapToGridFrame.Rect.Width, (int)(saveStampButton.Rect.Height / saveStampButton.RectTransform.RelativeSize.Y));
//Entity menu //Entity menu
//------------------------------------------------ //------------------------------------------------
@@ -941,7 +989,7 @@ namespace Barotrauma
}; };
paddedTab.Recalculate(); paddedTab.Recalculate();
UpdateLayerPanel();
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
} }
@@ -1161,6 +1209,7 @@ namespace Barotrauma
{ {
CanBeFocused = false, CanBeFocused = false,
LoadAsynchronously = true, LoadAsynchronously = true,
SpriteEffects = icon.effects,
Color = legacy ? iconColor * 0.6f : iconColor Color = legacy ? iconColor * 0.6f : iconColor
}; };
} }
@@ -1344,8 +1393,8 @@ namespace Barotrauma
} }
ImageManager.OnEditorSelected(); ImageManager.OnEditorSelected();
ReconstructLayers();
GameAnalyticsManager.SetCustomDimension01("editor");
if (!GameMain.Config.EditorDisclaimerShown) if (!GameMain.Config.EditorDisclaimerShown)
{ {
GameMain.Instance.ShowEditorDisclaimer(); GameMain.Instance.ShowEditorDisclaimer();
@@ -1455,7 +1504,6 @@ namespace Barotrauma
loadFrame = null; loadFrame = null;
MapEntity.DeselectAll(); MapEntity.DeselectAll();
MapEntity.SelectionGroups.Clear();
ClearUndoBuffer(); ClearUndoBuffer();
SetMode(Mode.Default); SetMode(Mode.Default);
@@ -2205,7 +2253,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), priceGroup.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), priceGroup.RectTransform),
TextManager.Get("subeditor.price"), textAlignment: Alignment.CenterLeft, wrap: true); TextManager.Get("subeditor.price"), textAlignment: Alignment.CenterLeft, wrap: true);
int basePrice = GameMain.DebugDraw ? 0 : Submarine.MainSub?.CalculateBasePrice() ?? 1000; int basePrice = (GameMain.DebugDraw ? 0 : Submarine.MainSub?.CalculateBasePrice()) ?? 1000;
new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), priceGroup.RectTransform), GUINumberInput.NumberType.Int, hidePlusMinusButtons: true) new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), priceGroup.RectTransform), GUINumberInput.NumberType.Int, hidePlusMinusButtons: true)
{ {
IntValue = Math.Max(Submarine.MainSub?.Info?.Price ?? basePrice, basePrice), IntValue = Math.Max(Submarine.MainSub?.Info?.Price ?? basePrice, basePrice),
@@ -2682,6 +2730,38 @@ namespace Barotrauma
return false; return false;
} }
private void SnapToGrid()
{
// First move components
foreach (Item item in MapEntity.SelectedList.Where(entity => entity is Item).Cast<Item>())
{
var wire = item.GetComponent<Wire>();
if (wire == null)
{
// Items snap to centre of nearest grid square
Vector2 offset = item.Position;
offset = new Vector2((MathF.Floor(offset.X / Submarine.GridSize.X) + .5f) * Submarine.GridSize.X - offset.X, (MathF.Floor(offset.Y / Submarine.GridSize.Y) + .5f) * Submarine.GridSize.Y - offset.Y);
item.Move(offset);
}
}
// Then move wires, separated as moving components also moves the start and end node of wires
foreach (Item item in MapEntity.SelectedList.Where(entity => entity is Item).Cast<Item>())
{
var wire = item.GetComponent<Wire>();
if (wire != null)
{
for (int i = 0; i < wire.GetNodes().Count; i++)
{
// Items wire nodes to centre of nearest grid square
Vector2 offset = wire.GetNodes()[i] + Submarine.MainSub.HiddenSubPosition;
offset = new Vector2((MathF.Floor(offset.X / Submarine.GridSize.X) + .5f) * Submarine.GridSize.X - offset.X, (MathF.Floor(offset.Y / Submarine.GridSize.Y) + .5f) * Submarine.GridSize.Y - offset.Y);
wire.MoveNode(i, offset);
}
}
}
}
private void CreateLoadScreen() private void CreateLoadScreen()
{ {
CloseItem(); CloseItem();
@@ -3232,7 +3312,6 @@ namespace Barotrauma
}), }),
new ContextMenuOption("editor.layer.openlayermenu", isEnabled: true, onSelected: () => new ContextMenuOption("editor.layer.openlayermenu", isEnabled: true, onSelected: () =>
{ {
if (visibilityButton is null) { return; }
previouslyUsedPanel.Visible = false; previouslyUsedPanel.Visible = false;
undoBufferPanel.Visible = false; undoBufferPanel.Visible = false;
showEntitiesPanel.Visible = false; showEntitiesPanel.Visible = false;
@@ -3289,7 +3368,7 @@ namespace Barotrauma
MoveToLayer(name, content); MoveToLayer(name, content);
} }
Layers.Add(name, true); Layers.Add(name, LayerData.Default);
UpdateLayerPanel(); UpdateLayerPanel();
} }
@@ -3304,7 +3383,7 @@ namespace Barotrauma
if (!string.IsNullOrWhiteSpace(newName)) if (!string.IsNullOrWhiteSpace(newName))
{ {
Layers.TryAdd(newName, true); Layers.TryAdd(newName, LayerData.Default);
} }
UpdateLayerPanel(); UpdateLayerPanel();
} }
@@ -3316,7 +3395,7 @@ namespace Barotrauma
{ {
if (!string.IsNullOrWhiteSpace(entity.Layer)) if (!string.IsNullOrWhiteSpace(entity.Layer))
{ {
Layers.TryAdd(entity.Layer, true); Layers.TryAdd(entity.Layer, LayerData.Default);
} }
} }
UpdateLayerPanel(); UpdateLayerPanel();
@@ -4352,8 +4431,13 @@ namespace Barotrauma
layerList.Content.ClearChildren(); layerList.Content.ClearChildren();
layerList.Deselect(); layerList.Deselect();
GUILayoutGroup buttonHeaders = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), layerList.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomLeft);
foreach (var (layer, isVisible) in Layers) new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headervisible"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true };
new GUIButton(new RectTransform(new Vector2(0.15f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headerlink"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true };
new GUIButton(new RectTransform(new Vector2(0.65f, 1f), buttonHeaders.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true };
foreach (var (layer, (visibility, linkage)) in Layers)
{ {
GUIFrame parent = new GUIFrame(new RectTransform(new Vector2(1f, 0.1f), layerList.Content.RectTransform), style: "ListBoxElement") GUIFrame parent = new GUIFrame(new RectTransform(new Vector2(1f, 0.1f), layerList.Content.RectTransform), style: "ListBoxElement")
{ {
@@ -4362,33 +4446,54 @@ namespace Barotrauma
GUILayoutGroup layerGroup = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUILayoutGroup layerGroup = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUITickBox layerVisibleButton = new GUITickBox(new RectTransform(Vector2.One, layerGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty) GUILayoutGroup layerVisibilityLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1f), layerGroup.RectTransform), childAnchor: Anchor.Center);
GUITickBox layerVisibleButton = new GUITickBox(new RectTransform(Vector2.One, layerVisibilityLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty)
{ {
Selected = isVisible, Selected = visibility == LayerVisibility.Visible,
OnSelected = box => OnSelected = box =>
{ {
if (!Layers.TryGetValue(layer, out bool _)) if (!Layers.TryGetValue(layer, out LayerData data))
{ {
UpdateLayerPanel(); UpdateLayerPanel();
return false; return false;
} }
Layers[layer] = box.Selected; Layers[layer] = new LayerData(box.Selected ? LayerVisibility.Visible : LayerVisibility.Invisible, data.Linkage);
return true;
}
};
GUILayoutGroup layerChainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 1f), layerGroup.RectTransform), childAnchor: Anchor.Center);
GUITickBox layerChainButton = new GUITickBox(new RectTransform(Vector2.One, layerChainLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty)
{
Selected = linkage == LayerLinkage.Linked,
OnSelected = box =>
{
if (!Layers.TryGetValue(layer, out LayerData data))
{
UpdateLayerPanel();
return false;
}
Layers[layer] = new LayerData(data.Visible, box.Selected ? LayerLinkage.Linked : LayerLinkage.Unlinked);
return true; return true;
} }
}; };
layerGroup.Recalculate(); layerGroup.Recalculate();
new GUITextBlock(new RectTransform(new Vector2(1.0f - layerVisibleButton.RectTransform.RelativeSize.X, 1f), layerGroup.RectTransform), layer, textAlignment: Alignment.CenterLeft) new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), layerGroup.RectTransform), layer, textAlignment: Alignment.CenterLeft)
{ {
CanBeFocused = false CanBeFocused = false
}; };
layerGroup.Recalculate(); layerGroup.Recalculate();
layerChainLayout.Recalculate();
layerVisibilityLayout.Recalculate();
} }
layerList.RecalculateChildren(); layerList.RecalculateChildren();
buttonHeaders.Recalculate();
} }
public void UpdateUndoHistoryPanel() public void UpdateUndoHistoryPanel()
@@ -4454,6 +4559,7 @@ namespace Barotrauma
saveFrame = null; saveFrame = null;
loadFrame = null; loadFrame = null;
saveAssemblyFrame = null; saveAssemblyFrame = null;
snapToGridFrame = null;
CreateUI(); CreateUI();
UpdateEntityList(); UpdateEntityList();
} }
@@ -4501,6 +4607,7 @@ namespace Barotrauma
hullVolumeFrame.Visible = MapEntity.SelectedList.Any(s => s is Hull); hullVolumeFrame.Visible = MapEntity.SelectedList.Any(s => s is Hull);
hullVolumeFrame.RectTransform.AbsoluteOffset = new Point(Math.Max(showEntitiesPanel.Rect.Right, previouslyUsedPanel.Rect.Right), 0); hullVolumeFrame.RectTransform.AbsoluteOffset = new Point(Math.Max(showEntitiesPanel.Rect.Right, previouslyUsedPanel.Rect.Right), 0);
saveAssemblyFrame.Visible = MapEntity.SelectedList.Count > 0; saveAssemblyFrame.Visible = MapEntity.SelectedList.Count > 0;
snapToGridFrame.Visible = MapEntity.SelectedList.Count > 0;
var offset = cam.WorldView.Top - cam.ScreenToWorld(new Vector2(0, GameMain.GraphicsHeight - EntityMenu.Rect.Top)).Y; var offset = cam.WorldView.Top - cam.ScreenToWorld(new Vector2(0, GameMain.GraphicsHeight - EntityMenu.Rect.Top)).Y;
@@ -4962,7 +5069,8 @@ namespace Barotrauma
MouseDragStart = Vector2.Zero; MouseDragStart = Vector2.Zero;
} }
if (!saveAssemblyFrame.Rect.Contains(PlayerInput.MousePosition) && dummyCharacter?.SelectedConstruction == null && !WiringMode && GUI.MouseOn == null) if (!saveAssemblyFrame.Rect.Contains(PlayerInput.MousePosition) && !snapToGridFrame.Rect.Contains(PlayerInput.MousePosition) &&
dummyCharacter?.SelectedConstruction == null && !WiringMode && GUI.MouseOn == null)
{ {
if (layerList is { Visible: true } && GUI.KeyboardDispatcher.Subscriber == layerList) if (layerList is { Visible: true } && GUI.KeyboardDispatcher.Subscriber == layerList)
{ {
@@ -5338,17 +5446,34 @@ namespace Barotrauma
public static bool IsLayerVisible(MapEntity entity) public static bool IsLayerVisible(MapEntity entity)
{ {
if (!IsSubEditor()) { return true; } if (!IsSubEditor() || string.IsNullOrWhiteSpace(entity.Layer)) { return true; }
if (string.IsNullOrWhiteSpace(entity.Layer)) { return true; } if (!Layers.TryGetValue(entity.Layer, out LayerData data))
if (!Layers.TryGetValue(entity.Layer, out bool isVisible))
{ {
Layers.TryAdd(entity.Layer, true); Layers.TryAdd(entity.Layer, LayerData.Default);
return true; return true;
} }
return isVisible; return data.Visible == LayerVisibility.Visible;
}
public static bool IsLayerLinked(MapEntity entity)
{
if (!IsSubEditor() || string.IsNullOrWhiteSpace(entity.Layer)) { return false; }
if (!Layers.TryGetValue(entity.Layer, out LayerData data))
{
Layers.TryAdd(entity.Layer, LayerData.Default);
return true;
}
return data.Linkage == LayerLinkage.Linked;
}
public static ImmutableHashSet<MapEntity> GetEntitiesInSameLayer(MapEntity entity)
{
if (string.IsNullOrWhiteSpace(entity.Layer)) { return ImmutableHashSet<MapEntity>.Empty; }
return MapEntity.mapEntityList.Where(me => me.Layer == entity.Layer).ToImmutableHashSet();
} }
} }
} }
@@ -65,7 +65,6 @@ namespace Barotrauma
Character.Controlled = dummyCharacter; Character.Controlled = dummyCharacter;
GameMain.World.ProcessChanges(); GameMain.World.ProcessChanges();
TabMenu.selectedTab = TabMenu.InfoFrameTab.Talents;
tabMenu = new TabMenu(); tabMenu = new TabMenu();
} }
@@ -1157,7 +1157,7 @@ namespace Barotrauma
public static void PlaySplashSound(Vector2 worldPosition, float strength) public static void PlaySplashSound(Vector2 worldPosition, float strength)
{ {
if (SplashSounds.Count == 0) { return; } if (SplashSounds.Count == 0) { return; }
int splashIndex = MathHelper.Clamp((int)(strength + Rand.Range(-2, 2)), 0, SplashSounds.Count - 1); int splashIndex = MathHelper.Clamp((int)(strength + Rand.Range(-2.0f, 2.0f)), 0, SplashSounds.Count - 1);
float range = 800.0f; float range = 800.0f;
var channel = SplashSounds[splashIndex].Play(1.0f, range, worldPosition, muffle: ShouldMuffleSound(Character.Controlled, worldPosition, range, null)); var channel = SplashSounds[splashIndex].Play(1.0f, range, worldPosition, muffle: ShouldMuffleSound(Character.Controlled, worldPosition, range, null));
} }
@@ -148,14 +148,8 @@ namespace Barotrauma
return t; return t;
} }
string fullPath = Path.GetFullPath(file); string fullPath = Path.GetFullPath(file);
foreach (Sprite s in LoadedSprites) reusedSprite = FindMatchingSprite(fullPath, requireTexture: true);
{ if (reusedSprite != null) { return reusedSprite.texture; }
if (s.FullPath == fullPath && s.texture != null && !s.texture.IsDisposed)
{
reusedSprite = s;
return s.texture;
}
}
if (File.Exists(file)) if (File.Exists(file))
{ {
@@ -176,6 +170,22 @@ namespace Barotrauma
return null; return null;
} }
private static Sprite FindMatchingSprite(string fullPath, bool requireTexture)
{
lock (list)
{
foreach (var wRef in list)
{
if (wRef.TryGetTarget(out Sprite sprite))
{
bool hasTexture = sprite.texture != null && !sprite.texture.IsDisposed;
if (sprite.FullPath == fullPath && (hasTexture || !requireTexture)) { return sprite; }
}
}
}
return null;
}
public void Draw(ISpriteBatch 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);
@@ -371,15 +381,9 @@ namespace Barotrauma
//check if another sprite is using the same texture //check if another sprite is using the same texture
if (!string.IsNullOrEmpty(FilePath)) //file can be empty if the sprite is created directly from a Texture2D instance if (!string.IsNullOrEmpty(FilePath)) //file can be empty if the sprite is created directly from a Texture2D instance
{ {
lock (list) if (FindMatchingSprite(FullPath, requireTexture: false) != null) { return; }
{
foreach (Sprite s in LoadedSprites)
{
if (s.FullPath == FullPath) { return; }
}
}
} }
//if not, free the texture //if not, free the texture
if (texture != null) if (texture != null)
{ {
@@ -89,7 +89,17 @@ namespace Barotrauma
for (int j = 0; j < infoTextFiles.Count; j++) for (int j = 0; j < infoTextFiles.Count; j++)
{ {
List<string> xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[j], Encoding.UTF8), language);
List<string> xmlContent = null;
try
{
xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[j], Encoding.UTF8), language);
}
catch (Exception e)
{
DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[j], e);
continue;
}
if (xmlContent == null) if (xmlContent == null)
{ {
DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[j]); DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[j]);
@@ -122,7 +122,7 @@ namespace Barotrauma
{ {
int width = 4096; int height = 4096; int width = 4096; int height = 4096;
Rectangle subDimensions = sub.Borders; Rectangle subDimensions = sub.CalculateDimensions(false);
Vector2 viewPos = subDimensions.Center.ToVector2(); Vector2 viewPos = subDimensions.Center.ToVector2();
float scale = Math.Min(width / (float)subDimensions.Width, height / (float)subDimensions.Height); float scale = Math.Min(width / (float)subDimensions.Width, height / (float)subDimensions.Height);
@@ -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.16.0.0</Version> <Version>0.16.1.0</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.16.0.0</Version> <Version>0.16.1.0</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.16.0.0</Version> <Version>0.16.1.0</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 Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.16.0.0</Version> <Version>0.16.1.0</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.16.0.0</Version> <Version>0.16.1.0</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>
@@ -28,11 +28,11 @@ namespace Barotrauma
if (!CheatsEnabled && IsCheat) if (!CheatsEnabled && IsCheat)
{ {
NewMessage("Client \"" + client.Name + "\" attempted to use the command \"" + names[0] + "\". Cheats must be enabled using \"enablecheats\" before the command can be used.", Color.Red); NewMessage("Client \"" + client.Name + "\" attempted to use the command \"" + names[0] + "\". Cheats must be enabled using \"enablecheats\" before the command can be used.", Color.Red);
GameMain.Server.SendConsoleMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", client); GameMain.Server.SendConsoleMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", client, Color.Red);
#if USE_STEAM #if USE_STEAM
NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red); NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red);
GameMain.Server.SendConsoleMessage("Enabling cheats will disable Steam achievements during this play session.", client); GameMain.Server.SendConsoleMessage("Enabling cheats will disable Steam achievements during this play session.", client, Color.Red);
#endif #endif
return; return;
@@ -367,7 +367,7 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Server.SendConsoleMessage("\"" + args[0] + "\" is not a valid bot spawn mode. (Valid modes are Fill and Normal)", client); GameMain.Server.SendConsoleMessage("\"" + args[0] + "\" is not a valid bot spawn mode. (Valid modes are Fill and Normal)", client, Color.Red);
} }
}); });
@@ -1046,12 +1046,12 @@ namespace Barotrauma
})); }));
AssignOnClientRequestExecute("clientlist", (Client client, Vector2 cursorWorldPos, string[] args) => AssignOnClientRequestExecute("clientlist", (Client client, Vector2 cursorWorldPos, string[] args) =>
{ {
GameMain.Server.SendConsoleMessage("***************", client); GameMain.Server.SendConsoleMessage("***************", client, Color.Cyan);
foreach (Client c in GameMain.Server.ConnectedClients) foreach (Client c in GameMain.Server.ConnectedClients)
{ {
GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.EndPointString + $", ping {c.Ping} ms", client); GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.EndPointString + $", ping {c.Ping} ms", client, Color.Cyan);
} }
GameMain.Server.SendConsoleMessage("***************", client); GameMain.Server.SendConsoleMessage("***************", client, Color.Cyan);
}); });
commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) => commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) =>
@@ -1164,13 +1164,13 @@ namespace Barotrauma
if (GameMain.Server == null || args.Length == 0) return; if (GameMain.Server == null || args.Length == 0) return;
if (!int.TryParse(args[0], out int maxPlayers)) if (!int.TryParse(args[0], out int maxPlayers))
{ {
GameMain.Server.SendConsoleMessage(args[0] + " is not a valid player count.", client); GameMain.Server.SendConsoleMessage(args[0] + " is not a valid player count.", client, Color.Red);
} }
else else
{ {
if (maxPlayers > NetConfig.MaxPlayers) if (maxPlayers > NetConfig.MaxPlayers)
{ {
GameMain.Server.SendConsoleMessage($"Setting the maximum amount of players to {maxPlayers} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", client); GameMain.Server.SendConsoleMessage($"Setting the maximum amount of players to {maxPlayers} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", client, Color.Red);
maxPlayers = NetConfig.MaxPlayers; maxPlayers = NetConfig.MaxPlayers;
} }
@@ -1455,7 +1455,7 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Server.SendConsoleMessage("\"" + args[1] + "\" is not a valid ban duration.", client); GameMain.Server.SendConsoleMessage("\"" + args[1] + "\" is not a valid ban duration.", client, Color.Red);
return; return;
} }
} }
@@ -1575,7 +1575,7 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Server.SendConsoleMessage("\"" + args[0] + "\" is not a valid bot spawn mode. (Valid modes are Fill and Normal)", client); GameMain.Server.SendConsoleMessage("\"" + args[0] + "\" is not a valid bot spawn mode. (Valid modes are Fill and Normal)", client, Color.Red);
} }
} }
); );
@@ -1599,7 +1599,7 @@ namespace Barotrauma
if (Submarine.MainSub == null || Level.Loaded == null) return; if (Submarine.MainSub == null || Level.Loaded == null) return;
if (Level.Loaded.Type == LevelData.LevelType.Outpost) if (Level.Loaded.Type == LevelData.LevelType.Outpost)
{ {
GameMain.Server.SendConsoleMessage("The teleportsub command is unavailable in outpost levels!", client); GameMain.Server.SendConsoleMessage("The teleportsub command is unavailable in outpost levels!", client, Color.Red);
return; return;
} }
@@ -1623,7 +1623,7 @@ namespace Barotrauma
{ {
if (!(GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)) if (!(GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign))
{ {
GameMain.Server.SendConsoleMessage("No campaign active.", client); GameMain.Server.SendConsoleMessage("No campaign active.", client, Color.Red);
return; return;
} }
mpCampaign.LastUpdateID++; mpCampaign.LastUpdateID++;
@@ -1671,13 +1671,13 @@ namespace Barotrauma
a.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase)); a.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (afflictionPrefab == null) if (afflictionPrefab == null)
{ {
GameMain.Server.SendConsoleMessage("Affliction \"" + args[0] + "\" not found.", client); GameMain.Server.SendConsoleMessage("Affliction \"" + args[0] + "\" not found.", client, Color.Red);
return; return;
} }
if (!float.TryParse(args[1], out float afflictionStrength)) if (!float.TryParse(args[1], out float afflictionStrength))
{ {
GameMain.Server.SendConsoleMessage("\"" + args[1] + "\" is not a valid affliction strength.", client); GameMain.Server.SendConsoleMessage("\"" + args[1] + "\" is not a valid affliction strength.", client, Color.Red);
return; return;
} }
@@ -1760,7 +1760,7 @@ namespace Barotrauma
c.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase)); c.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null) if (talentPrefab == null)
{ {
GameMain.Server.SendConsoleMessage("Couldn't find the talent \"" + args[0] + "\".", client); GameMain.Server.SendConsoleMessage("Couldn't find the talent \"" + args[0] + "\".", client, Color.Red);
return; return;
} }
targetCharacter.GiveTalent(talentPrefab); targetCharacter.GiveTalent(talentPrefab);
@@ -1786,12 +1786,12 @@ namespace Barotrauma
var job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase)); var job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (job == null) if (job == null)
{ {
GameMain.Server.SendConsoleMessage($"Failed to find the job \"{args[0]}\".", client); GameMain.Server.SendConsoleMessage($"Failed to find the job \"{args[0]}\".", client, Color.Red);
return; return;
} }
if (!TalentTree.JobTalentTrees.TryGetValue(job.Identifier, out TalentTree talentTree)) if (!TalentTree.JobTalentTrees.TryGetValue(job.Identifier, out TalentTree talentTree))
{ {
GameMain.Server.SendConsoleMessage($"No talents configured for the job \"{args[0]}\".", client); GameMain.Server.SendConsoleMessage($"No talents configured for the job \"{args[0]}\".", client, Color.Red);
return; return;
} }
talentTrees.Add(talentTree); talentTrees.Add(talentTree);
@@ -1858,6 +1858,10 @@ namespace Barotrauma
(Client client, Vector2 cursorWorldPos, string[] args) => (Client client, Vector2 cursorWorldPos, string[] args) =>
{ {
Character killedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args); Character killedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args);
if (killedCharacter == null)
{
GameMain.Server.SendConsoleMessage("Could not find the specified character.", client, Color.Red);
}
killedCharacter?.SetAllDamage(200.0f, 0.0f, 0.0f); killedCharacter?.SetAllDamage(200.0f, 0.0f, 0.0f);
} }
); );
@@ -1873,6 +1877,10 @@ namespace Barotrauma
GameMain.Server.SetClientCharacter(client, character); GameMain.Server.SetClientCharacter(client, character);
client.SpectateOnly = false; client.SpectateOnly = false;
} }
else
{
GameMain.Server.SendConsoleMessage("Could not find the specified character.", client, Color.Red);
}
} }
); );
@@ -1899,7 +1907,7 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Server.SendConsoleMessage(args[0] + " is not a valid difficulty setting (enter a value between 0-100)", client); GameMain.Server.SendConsoleMessage(args[0] + " is not a valid difficulty setting (enter a value between 0-100)", client, Color.Red);
NewMessage(args[0] + " is not a valid difficulty setting (enter a value between 0-100)", Color.Red); NewMessage(args[0] + " is not a valid difficulty setting (enter a value between 0-100)", Color.Red);
} }
} }
@@ -1923,7 +1931,7 @@ namespace Barotrauma
ClientPermissions permission = ClientPermissions.None; ClientPermissions permission = ClientPermissions.None;
if (!Enum.TryParse(perm, true, out permission)) if (!Enum.TryParse(perm, true, out permission))
{ {
GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient); GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient, Color.Red);
return; return;
} }
@@ -1955,7 +1963,7 @@ namespace Barotrauma
} }
if (client.Connection == GameMain.Server.OwnerConnection) if (client.Connection == GameMain.Server.OwnerConnection)
{ {
GameMain.Server.SendConsoleMessage("Cannot revoke permissions from the server owner!", senderClient); GameMain.Server.SendConsoleMessage("Cannot revoke permissions from the server owner!", senderClient, Color.Red);
return; return;
} }
@@ -1964,7 +1972,7 @@ namespace Barotrauma
ClientPermissions permission = ClientPermissions.None; ClientPermissions permission = ClientPermissions.None;
if (!Enum.TryParse(perm, true, out permission)) if (!Enum.TryParse(perm, true, out permission))
{ {
GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient); GameMain.Server.SendConsoleMessage(perm + " is not a valid permission!", senderClient, Color.Red);
return; return;
} }
client.RemovePermission(permission); client.RemovePermission(permission);
@@ -1988,7 +1996,7 @@ namespace Barotrauma
} }
if (client.Connection == GameMain.Server.OwnerConnection) if (client.Connection == GameMain.Server.OwnerConnection)
{ {
GameMain.Server.SendConsoleMessage("Cannot modify the rank of the server owner!", senderClient); GameMain.Server.SendConsoleMessage("Cannot modify the rank of the server owner!", senderClient, Color.Red);
return; return;
} }
@@ -1996,7 +2004,7 @@ namespace Barotrauma
PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.Equals(rank, StringComparison.OrdinalIgnoreCase)); PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.Equals(rank, StringComparison.OrdinalIgnoreCase));
if (preset == null) if (preset == null)
{ {
GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient); GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient, Color.Red);
return; return;
} }
@@ -2016,12 +2024,12 @@ namespace Barotrauma
var client = FindClient(args[0]); var client = FindClient(args[0]);
if (client == null) if (client == null)
{ {
GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient); GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient, Color.Red);
return; return;
} }
if (client.Connection == GameMain.Server.OwnerConnection) if (client.Connection == GameMain.Server.OwnerConnection)
{ {
GameMain.Server.SendConsoleMessage("Cannot modify the command permissions of the server owner!", senderClient); GameMain.Server.SendConsoleMessage("Cannot modify the command permissions of the server owner!", senderClient, Color.Red);
return; return;
} }
@@ -2040,7 +2048,7 @@ namespace Barotrauma
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i]));
if (matchingCommand == null) if (matchingCommand == null)
{ {
GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient, Color.Red);
} }
else else
{ {
@@ -2078,7 +2086,7 @@ namespace Barotrauma
} }
if (client.Connection == GameMain.Server.OwnerConnection) if (client.Connection == GameMain.Server.OwnerConnection)
{ {
GameMain.Server.SendConsoleMessage("Cannot revoke command permissions from the server owner!", senderClient); GameMain.Server.SendConsoleMessage("Cannot revoke command permissions from the server owner!", senderClient, Color.Red);
return; return;
} }
List<Command> revokedCommands = new List<Command>(); List<Command> revokedCommands = new List<Command>();
@@ -2096,7 +2104,7 @@ namespace Barotrauma
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i]));
if (matchingCommand == null) if (matchingCommand == null)
{ {
GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient); GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient, Color.Red);
} }
else else
{ {
@@ -2176,7 +2184,7 @@ namespace Barotrauma
{ {
if (args.Length < 2) if (args.Length < 2)
{ {
GameMain.Server.SendConsoleMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] [character]\". If the names consist of multiple words, you should surround them with quotation marks.", senderClient); GameMain.Server.SendConsoleMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] [character]\". If the names consist of multiple words, you should surround them with quotation marks.", senderClient, Color.Red);
return; return;
} }
@@ -2189,7 +2197,7 @@ namespace Barotrauma
var client = GameMain.Server.ConnectedClients.Find(c => c.Name == args[0]); var client = GameMain.Server.ConnectedClients.Find(c => c.Name == args[0]);
if (client == null) if (client == null)
{ {
GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient); GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient, Color.Red);
return; return;
} }
@@ -2206,17 +2214,18 @@ namespace Barotrauma
if (args.Length == 0) { return; } if (args.Length == 0) { return; }
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)) if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign))
{ {
GameMain.Server.SendConsoleMessage("No campaign active!", senderClient); GameMain.Server.SendConsoleMessage("No campaign active!", senderClient, Color.Red);
return; return;
} }
if (int.TryParse(args[0], out int money)) if (int.TryParse(args[0], out int money))
{ {
campaign.Money += money; campaign.Money += money;
GameAnalyticsManager.AddMoneyGainedEvent(money, GameAnalyticsManager.MoneySource.Cheat, "console");
campaign.LastUpdateID++; campaign.LastUpdateID++;
} }
else else
{ {
GameMain.Server.SendConsoleMessage($"\"{args[0]}\" is not a valid numeric value.", senderClient); GameMain.Server.SendConsoleMessage($"\"{args[0]}\" is not a valid numeric value.", senderClient, Color.Red);
} }
} }
); );
@@ -2226,7 +2235,7 @@ namespace Barotrauma
{ {
if (!(GameMain.GameSession?.GameMode is CampaignMode campaign)) if (!(GameMain.GameSession?.GameMode is CampaignMode campaign))
{ {
GameMain.Server.SendConsoleMessage("No campaign active!", senderClient); GameMain.Server.SendConsoleMessage("No campaign active!", senderClient, Color.Red);
return; return;
} }
@@ -2234,7 +2243,7 @@ namespace Barotrauma
if (args.Length < 1 || !int.TryParse(args[0], out destinationIndex)) return; if (args.Length < 1 || !int.TryParse(args[0], out destinationIndex)) return;
if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count) if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count)
{ {
GameMain.Server.SendConsoleMessage("Index out of bounds!", senderClient); GameMain.Server.SendConsoleMessage("Index out of bounds!", senderClient, Color.Red);
return; return;
} }
Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation); Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation);
@@ -2251,14 +2260,41 @@ namespace Barotrauma
NewMessage(tag, Color.Yellow); NewMessage(tag, Color.Yellow);
} }
})); }));
commands.Add(new Command("sendchatmessage", "Sends a chat message with specified type and color.", (string[] args) =>
{
if (args.Length < 2) { return; }
ChatMessageType chatMessageType = ChatMessageType.Default;
Color? chatMessageColor = null;
if (args.Length >= 3 && int.TryParse(args[2], out int result))
{
chatMessageType = (ChatMessageType)result;
}
if (args.Length >= 7 &&
int.TryParse(args[3], out int r) &&
int.TryParse(args[4], out int g) &&
int.TryParse(args[5], out int b) &&
int.TryParse(args[6], out int a))
{
chatMessageColor = new Color(r, g, b, a);
}
foreach (var client in GameMain.Server.ConnectedClients)
{
GameMain.Server.SendDirectChatMessage(ChatMessage.Create(args[0], args[1], chatMessageType, null, null, textColor: chatMessageColor), client);
}
}));
AssignOnClientRequestExecute( AssignOnClientRequestExecute(
"setskill", "setskill",
(senderClient, cursorWorldPos, args) => (senderClient, cursorWorldPos, args) =>
{ {
if (args.Length < 2) if (args.Length < 2)
{ {
GameMain.Server.SendConsoleMessage($"Missing arguments. Expected at least 2 but got {args.Length} (skill, level, name)", senderClient); GameMain.Server.SendConsoleMessage($"Missing arguments. Expected at least 2 but got {args.Length} (skill, level, name)", senderClient, Color.Red);
return; return;
} }
@@ -2268,7 +2304,7 @@ namespace Barotrauma
if (character?.Info?.Job == null) if (character?.Info?.Job == null)
{ {
GameMain.Server.SendConsoleMessage("Character is not valid.", senderClient); GameMain.Server.SendConsoleMessage("Character is not valid.", senderClient, Color.Red);
return; return;
} }
@@ -2295,7 +2331,7 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Server.SendConsoleMessage($"{levelString} is not a valid level. Expected number or \"max\".", senderClient); GameMain.Server.SendConsoleMessage($"{levelString} is not a valid level. Expected number or \"max\".", senderClient, Color.Red);
} }
} }
); );
@@ -2389,7 +2425,7 @@ namespace Barotrauma
if (string.IsNullOrWhiteSpace(command)) return; if (string.IsNullOrWhiteSpace(command)) return;
if (!client.HasPermission(ClientPermissions.ConsoleCommands) && client.Connection != GameMain.Server.OwnerConnection) if (!client.HasPermission(ClientPermissions.ConsoleCommands) && client.Connection != GameMain.Server.OwnerConnection)
{ {
GameMain.Server.SendConsoleMessage("You are not permitted to use console commands!", client); GameMain.Server.SendConsoleMessage("You are not permitted to use console commands!", client, Color.Red);
GameServer.Log(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" without a permission to use console commands.", ServerLog.MessageType.ConsoleUsage); GameServer.Log(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" without a permission to use console commands.", ServerLog.MessageType.ConsoleUsage);
return; return;
} }
@@ -2398,19 +2434,19 @@ namespace Barotrauma
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant()));
if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand) && client.Connection != GameMain.Server.OwnerConnection) if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand) && client.Connection != GameMain.Server.OwnerConnection)
{ {
GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client); GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client, Color.Red);
GameServer.Log(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" without a permission to use the command.", ServerLog.MessageType.ConsoleUsage); GameServer.Log(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" without a permission to use the command.", ServerLog.MessageType.ConsoleUsage);
return; return;
} }
else if (matchingCommand == null) else if (matchingCommand == null)
{ {
GameMain.Server.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client); GameMain.Server.SendConsoleMessage("Command \"" + splitCommand[0] + "\" not found.", client, Color.Red);
return; return;
} }
if (!MathUtils.IsValid(cursorWorldPos)) if (!MathUtils.IsValid(cursorWorldPos))
{ {
GameMain.Server.SendConsoleMessage("Could not execute command \"" + command + "\" - invalid cursor position.", client); GameMain.Server.SendConsoleMessage("Could not execute command \"" + command + "\" - invalid cursor position.", client, Color.Red);
NewMessage(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" with invalid cursor position.", Color.White); NewMessage(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" with invalid cursor position.", Color.White);
return; return;
} }
@@ -36,7 +36,7 @@ namespace Barotrauma
public static GameServer Server; public static GameServer Server;
public static NetworkMember NetworkMember public static NetworkMember NetworkMember
{ {
get { return Server as NetworkMember; } get { return Server; }
} }
public static GameSession GameSession; public static GameSession GameSession;
@@ -64,6 +64,9 @@ namespace Barotrauma
private static Stopwatch stopwatch; private static Stopwatch stopwatch;
private static Queue<int> prevUpdateRates = new Queue<int>();
private static int updateCount = 0;
private static ContentPackage vanillaContent; private static ContentPackage vanillaContent;
public static ContentPackage VanillaContent public static ContentPackage VanillaContent
{ {
@@ -364,6 +367,8 @@ namespace Barotrauma
DebugConsole.NewMessage("WARNING: Stopwatch frequency under 1500 ticks per second. Expect significant syncing accuracy issues.", Color.Yellow); DebugConsole.NewMessage("WARNING: Stopwatch frequency under 1500 ticks per second. Expect significant syncing accuracy issues.", Color.Yellow);
} }
Stopwatch performanceCounterTimer = Stopwatch.StartNew();
stopwatch = Stopwatch.StartNew(); stopwatch = Stopwatch.StartNew();
long prevTicks = stopwatch.ElapsedTicks; long prevTicks = stopwatch.ElapsedTicks;
while (ShouldRun) while (ShouldRun)
@@ -371,9 +376,11 @@ namespace Barotrauma
long currTicks = stopwatch.ElapsedTicks; long currTicks = stopwatch.ElapsedTicks;
double elapsedTime = Math.Max(currTicks - prevTicks, 0) / frequency; double elapsedTime = Math.Max(currTicks - prevTicks, 0) / frequency;
Timing.Accumulator += elapsedTime; Timing.Accumulator += elapsedTime;
if (Timing.Accumulator > 1.0) if (Timing.Accumulator > Timing.AccumulatorMax)
{ {
//prevent spiral of death //prevent spiral of death:
//if the game's running too slowly then we have no choice but to skip a bunch of steps
//otherwise it snowballs and becomes unplayable
Timing.Accumulator = Timing.Step; Timing.Accumulator = Timing.Step;
} }
prevTicks = currTicks; prevTicks = currTicks;
@@ -392,6 +399,7 @@ namespace Barotrauma
CoroutineManager.Update((float)Timing.Step, (float)Timing.Step); CoroutineManager.Update((float)Timing.Step, (float)Timing.Step);
Timing.Accumulator -= Timing.Step; Timing.Accumulator -= Timing.Step;
updateCount++;
} }
#if !DEBUG #if !DEBUG
@@ -407,10 +415,34 @@ namespace Barotrauma
DebugConsole.UpdateCommandLine((int)(Timing.Accumulator * 800)); DebugConsole.UpdateCommandLine((int)(Timing.Accumulator * 800));
#endif #endif
int frameTime = (int)(((double)(stopwatch.ElapsedTicks - prevTicks) / frequency) * 1000.0); int frameTime = (int)((stopwatch.ElapsedTicks - prevTicks) / frequency * 1000.0);
frameTime = Math.Max(0, frameTime); frameTime = Math.Max(0, frameTime);
Thread.Sleep(Math.Max(((int)(Timing.Step * 1000.0) - frameTime) / 2, 0)); Thread.Sleep(Math.Max(((int)(Timing.Step * 1000.0) - frameTime) / 2, 0));
if (performanceCounterTimer.ElapsedMilliseconds > 1000)
{
int updateRate = (int)Math.Round(updateCount / (double)(performanceCounterTimer.ElapsedMilliseconds / 1000.0));
prevUpdateRates.Enqueue(updateRate);
if (prevUpdateRates.Count >= 10)
{
int avgUpdateRate = (int)prevUpdateRates.Average();
if (avgUpdateRate < Timing.FixedUpdateRate * 0.98 && GameSession != null && Timing.TotalTime > GameSession.RoundStartTime + 1.0)
{
DebugConsole.AddWarning($"Running slowly ({avgUpdateRate} updates/s)!");
foreach (Client c in Server.ConnectedClients)
{
if (c.Connection == Server.OwnerConnection || c.Permissions != ClientPermissions.None)
{
Server.SendConsoleMessage($"Server running slowly ({avgUpdateRate} updates/s)!", c, Color.Orange);
}
}
}
prevUpdateRates.Clear();
}
performanceCounterTimer.Restart();
updateCount = 0;
}
} }
stopwatch.Stop(); stopwatch.Stop();
@@ -429,8 +461,9 @@ namespace Barotrauma
public static void ResetFrameTime() public static void ResetFrameTime()
{ {
Timing.Accumulator = 0.0f; Timing.Accumulator = 0.0f;
stopwatch?.Reset(); stopwatch?.Restart();
stopwatch?.Start(); prevUpdateRates.Clear();
updateCount = 0;
} }
public CoroutineHandle ShowLoading(IEnumerable<CoroutineStatus> loader, bool waitKeyHit = true) public CoroutineHandle ShowLoading(IEnumerable<CoroutineStatus> loader, bool waitKeyHit = true)
@@ -55,6 +55,7 @@ namespace Barotrauma
SoldItems.Add(item); SoldItems.Add(item);
Location.StoreCurrentBalance -= itemValue; Location.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue; campaign.Money += itemValue;
GameAnalyticsManager.AddMoneyGainedEvent(itemValue, GameAnalyticsManager.MoneySource.Store, item.ItemPrefab.Identifier);
} }
OnSoldItemsChanged?.Invoke(); OnSoldItemsChanged?.Invoke();
} }
@@ -285,11 +285,15 @@ namespace Barotrauma
break; break;
case TransitionType.ProgressToNextLocation: case TransitionType.ProgressToNextLocation:
Map.MoveToNextLocation(); Map.MoveToNextLocation();
TotalPassedLevels++;
break; break;
case TransitionType.End: case TransitionType.End:
EndCampaign(); EndCampaign();
IsFirstRound = true; IsFirstRound = true;
break; break;
case TransitionType.ProgressToNextEmptyLocation:
TotalPassedLevels++;
break;
} }
Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime)); Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
@@ -697,6 +701,7 @@ namespace Barotrauma
{ {
this.PurchasedHullRepairs = true; this.PurchasedHullRepairs = true;
Money -= hullRepairCost; Money -= hullRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
} }
else if (!purchasedHullRepairs) else if (!purchasedHullRepairs)
{ {
@@ -710,6 +715,7 @@ namespace Barotrauma
{ {
this.PurchasedItemRepairs = true; this.PurchasedItemRepairs = true;
Money -= itemRepairCost; Money -= itemRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(itemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
} }
else if (!purchasedItemRepairs) else if (!purchasedItemRepairs)
{ {
@@ -728,6 +734,7 @@ namespace Barotrauma
{ {
this.PurchasedLostShuttles = true; this.PurchasedLostShuttles = true;
Money -= shuttleRetrieveCost; Money -= shuttleRetrieveCost;
GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
} }
else if (!purchasedItemRepairs) else if (!purchasedItemRepairs)
{ {
@@ -998,7 +1005,9 @@ 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()); modeElement.Add(Settings.Save());
modeElement.Add(SaveStats());
CampaignMetadata?.Save(modeElement); CampaignMetadata?.Save(modeElement);
Map.Save(modeElement); Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement); CargoManager?.SavePurchasedItems(modeElement);
@@ -1,13 +1,14 @@
using System; using Barotrauma.Networking;
using System.ComponentModel; using System;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
internal partial class Growable internal partial class Growable
{ {
private const int serverHealthUpdateDelay = 10;
private int serverHealthUpdateTimer;
partial void LoadVines(XElement element) partial void LoadVines(XElement element)
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
@@ -0,0 +1,12 @@
using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
partial class WifiComponent
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.WriteRangedInteger(Channel, MinChannel, MaxChannel);
}
}
}
@@ -212,6 +212,11 @@ namespace Barotrauma.Networking
{ {
msg.Write(Sender.ID); msg.Write(Sender.ID);
} }
msg.Write(customTextColor != null);
if (customTextColor != null)
{
msg.WriteColorR8G8B8A8(customTextColor.Value);
}
msg.WritePadBits(); msg.WritePadBits();
if (Type == ChatMessageType.ServerMessageBoxInGame) if (Type == ChatMessageType.ServerMessageBoxInGame)
{ {
@@ -726,7 +726,15 @@ namespace Barotrauma.Networking
} }
break; break;
case ClientPacketHeader.REQUEST_STARTGAMEFINALIZE: case ClientPacketHeader.REQUEST_STARTGAMEFINALIZE:
if (gameStarted && connectedClient != null) if (connectedClient == null)
{
DebugConsole.AddWarning("Received a REQUEST_STARTGAMEFINALIZE message. Client not connected, ignoring the message.");
}
else if (!gameStarted)
{
DebugConsole.AddWarning("Received a REQUEST_STARTGAMEFINALIZE message. Game not started, ignoring the message.");
}
else
{ {
SendRoundStartFinalize(connectedClient); SendRoundStartFinalize(connectedClient);
} }
@@ -746,7 +754,7 @@ namespace Barotrauma.Networking
string seed = inc.ReadString(); string seed = inc.ReadString();
string subName = inc.ReadString(); string subName = inc.ReadString();
string subHash = inc.ReadString(); string subHash = inc.ReadString();
CampaignSettings settings = new CampaignSettings(inc); CampaignSettings settings = 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);
@@ -769,6 +777,7 @@ namespace Barotrauma.Networking
{ {
ServerSettings.RadiationEnabled = settings.RadiationEnabled; ServerSettings.RadiationEnabled = settings.RadiationEnabled;
ServerSettings.MaxMissionCount = settings.MaxMissionCount; ServerSettings.MaxMissionCount = settings.MaxMissionCount;
ServerSettings.SaveSettings();
MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings); MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings);
} }
} }
@@ -1904,7 +1913,7 @@ namespace Barotrauma.Networking
int chatMessageBytes = outmsg.LengthBytes; int chatMessageBytes = outmsg.LengthBytes;
WriteChatMessages(outmsg, c); WriteChatMessages(outmsg, c);
chatMessageBytes = outmsg.LengthBytes - outmsg.LengthBytes; chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE);
@@ -1927,7 +1936,11 @@ namespace Barotrauma.Networking
warningMsg += warningMsg +=
" Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n"; " Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n";
} }
if (GameSettings.VerboseLogging) { DebugConsole.AddWarning(warningMsg); } #if DEBUG || UNSTABLE
DebugConsole.ThrowError(warningMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.AddWarning(warningMsg); }
#endif
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg); GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
} }
@@ -1943,12 +1956,16 @@ namespace Barotrauma.Networking
//these large initial messages until the client acknowledges receiving them //these large initial messages until the client acknowledges receiving them
c.LastRecvLobbyUpdate++; c.LastRecvLobbyUpdate++;
SendVoteStatus(new List<Client>() { c });
} }
else else
{ {
serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable); serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
} }
if (isInitialUpdate)
{
SendVoteStatus(new List<Client>() { c });
}
} }
private void WriteChatMessages(IWriteMessage outmsg, Client c) private void WriteChatMessages(IWriteMessage outmsg, Client c)
@@ -2893,9 +2910,9 @@ namespace Barotrauma.Networking
SendDirectChatMessage(msg, recipient); SendDirectChatMessage(msg, recipient);
} }
public void SendConsoleMessage(string txt, Client recipient) public void SendConsoleMessage(string txt, Client recipient, Color? color = null)
{ {
ChatMessage msg = ChatMessage.Create("", txt, ChatMessageType.Console, null); ChatMessage msg = ChatMessage.Create("", txt, ChatMessageType.Console, sender: null, textColor: color);
SendDirectChatMessage(msg, recipient); SendDirectChatMessage(msg, recipient);
} }
@@ -115,10 +115,14 @@ namespace Barotrauma.Networking
return ShouldStartRespawnCountdown(characterToRespawnCount); return ShouldStartRespawnCountdown(characterToRespawnCount);
} }
private int GetMinCharactersToRespawn()
{
return Math.Max((int)(GameMain.Server.ConnectedClients.Count * GameMain.Server.ServerSettings.MinRespawnRatio), 1);
}
private bool ShouldStartRespawnCountdown(int characterToRespawnCount) private bool ShouldStartRespawnCountdown(int characterToRespawnCount)
{ {
int totalCharacterCount = GameMain.Server.ConnectedClients.Count; return characterToRespawnCount >= GetMinCharactersToRespawn();
return (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * GameMain.Server.ServerSettings.MinRespawnRatio, 1.0f);
} }
partial void UpdateWaiting(float deltaTime) partial void UpdateWaiting(float deltaTime)
@@ -129,7 +133,7 @@ namespace Barotrauma.Networking
} }
pendingRespawnCount = GetClientsToRespawn().Count(); pendingRespawnCount = GetClientsToRespawn().Count();
requiredRespawnCount = (int)Math.Max((float)GameMain.Server.ConnectedClients.Count * GameMain.Server.ServerSettings.MinRespawnRatio, 1.0f); requiredRespawnCount = GetMinCharactersToRespawn();
if (pendingRespawnCount != prevPendingRespawnCount || if (pendingRespawnCount != prevPendingRespawnCount ||
requiredRespawnCount != prevRequiredRespawnCount) requiredRespawnCount != prevRequiredRespawnCount)
{ {
@@ -223,11 +223,6 @@ namespace Barotrauma.Networking
AutoRestart = autoRestart; AutoRestart = autoRestart;
} }
RadiationEnabled = incMsg.ReadBoolean();
int maxMissionCount = MaxMissionCount + incMsg.ReadByte() - 1;
MaxMissionCount = MathHelper.Clamp(maxMissionCount, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit);
changed |= true; changed |= true;
UpdateFlag(NetFlags.Misc); UpdateFlag(NetFlags.Misc);
} }
@@ -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.16.0.0</Version> <Version>0.16.1.0</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>
@@ -340,7 +340,7 @@ namespace Barotrauma
{ {
targetingTag = "dead"; targetingTag = "dead";
} }
else if (AIParams.TryGetTarget(targetCharacter.CharacterHealth.GetActiveAfflictionTags(), out CharacterParams.TargetParams tp) && tp.Threshold > Character.GetDamageDoneByAttacker(targetCharacter)) else if (AIParams.TryGetTarget(targetCharacter.CharacterHealth.GetActiveAfflictionTags(), out CharacterParams.TargetParams tp) && tp.Threshold >= Character.GetDamageDoneByAttacker(targetCharacter))
{ {
targetingTag = tp.Tag; targetingTag = tp.Tag;
} }
@@ -678,7 +678,10 @@ namespace Barotrauma
return a.Damage >= selectedTargetingParams.Threshold; return a.Damage >= selectedTargetingParams.Threshold;
} }
Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character; Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character;
if (attacker != null) //if the attacker has the same targeting tag as the character we're protecting, we can't change the TargetState
//otherwise e.g. a pet that's set to follow humans would start attacking all humans (and other pets, since they're considered part of the same group) when a hostile human attacks it
//TODO: a way for pets to differentiate hostile and friendly humans?
if (attacker?.AiTarget != null && !targetCharacter.SpeciesName.Equals(GetTargetingTag(attacker.AiTarget), StringComparison.OrdinalIgnoreCase))
{ {
// Attack the character that attacked the target we are protecting // Attack the character that attacked the target we are protecting
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2); ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
@@ -1598,7 +1601,7 @@ namespace Barotrauma
} }
else else
{ {
sweepTimer = Rand.Range(-1000, 1000) * selectedTargetingParams.SweepSpeed; sweepTimer = Rand.Range(-1000f, 1000f) * selectedTargetingParams.SweepSpeed;
} }
} }
break; break;
@@ -2305,7 +2308,7 @@ namespace Barotrauma
if (item.Condition <= 0.0f) if (item.Condition <= 0.0f)
{ {
if (!wasBroken) { PetBehavior?.OnEat(item.GetTags(), 1.0f); } if (!wasBroken) { PetBehavior?.OnEat(item); }
Entity.Spawner.AddToRemoveQueue(item); Entity.Spawner.AddToRemoveQueue(item);
} }
} }
@@ -565,7 +565,7 @@ namespace Barotrauma
Character.AnimController.HeadInWater || Character.AnimController.HeadInWater ||
Character.Submarine == null || Character.Submarine == null ||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) || (Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOn) || !ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>() && ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOn) ||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) || ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10; Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10;
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character; bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
@@ -1090,7 +1090,6 @@ namespace Barotrauma
private void RespondToAttack(Character attacker, AttackResult attackResult) private void RespondToAttack(Character attacker, AttackResult attackResult)
{ {
float minorDamageThreshold = 10;
float healAmount = 0.0f; float healAmount = 0.0f;
if (attacker != null) if (attacker != null)
{ {
@@ -1099,7 +1098,7 @@ namespace Barotrauma
// excluding poisons etc // excluding poisons etc
float realDamage = attackResult.Damage - healAmount; float realDamage = attackResult.Damage - healAmount;
// including poisons etc // including poisons etc
float totalDamage = realDamage - healAmount; float totalDamage = realDamage;
if (attackResult.Afflictions != null) if (attackResult.Afflictions != null)
{ {
foreach (Affliction affliction in attackResult.Afflictions) foreach (Affliction affliction in attackResult.Afflictions)
@@ -1140,6 +1139,13 @@ namespace Barotrauma
} }
bool isAttackerInfected = false; bool isAttackerInfected = false;
bool isAttackerFightingEnemy = false; bool isAttackerFightingEnemy = false;
float minorDamageThreshold = 1;
float majorDamageThreshold = 20;
if (attacker.TeamID == Character.TeamID)
{
minorDamageThreshold = 10;
majorDamageThreshold = 40;
}
if (IsFriendly(attacker)) if (IsFriendly(attacker))
{ {
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character) if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
@@ -1148,11 +1154,11 @@ namespace Barotrauma
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate). // Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
return; return;
} }
float cumulativeDamage = Character.GetDamageDoneByAttacker(attacker); float cumulativeDamage = realDamage + Character.GetDamageDoneByAttacker(attacker);
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null; bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null;
if (isAccidental) if (isAccidental)
{ {
if (!Character.IsSecurity && cumulativeDamage > 1) if (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold)
{ {
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker); AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
} }
@@ -1161,7 +1167,7 @@ namespace Barotrauma
{ {
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0; isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0;
// Inform other NPCs // Inform other NPCs
if (isAttackerInfected || cumulativeDamage > 1 || totalDamage >= minorDamageThreshold) if (isAttackerInfected || cumulativeDamage > minorDamageThreshold || totalDamage > minorDamageThreshold)
{ {
if (GameMain.IsMultiplayer || !attacker.IsPlayer || Character.TeamID != attacker.TeamID) if (GameMain.IsMultiplayer || !attacker.IsPlayer || Character.TeamID != attacker.TeamID)
{ {
@@ -1170,27 +1176,36 @@ namespace Barotrauma
} }
if (Character.IsBot) if (Character.IsBot)
{ {
if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; } var combatMode = DetermineCombatMode(Character, cumulativeDamage);
if (attacker.IsPlayer) if (attacker.IsPlayer && !Character.IsInstigator && !ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>())
{ {
if (Character.IsSecurity) switch (combatMode)
{ {
if (attacker.TeamID != Character.TeamID && cumulativeDamage > 1 || cumulativeDamage > minorDamageThreshold) case AIObjectiveCombat.CombatMode.Defensive:
{ case AIObjectiveCombat.CombatMode.Retreat:
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityarrest"), null, 0.50f, "attackedbyfriendlysecurityarrest", minDurationBetweenSimilar: 30.0f); if (Character.IsSecurity)
} {
else Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityresponse"), null, 0.5f, "attackedbyfriendlysecurityresponse", minDurationBetweenSimilar: 10.0f);
{ }
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityresponse"), null, 0.50f, "attackedbyfriendlysecurityresponse", minDurationBetweenSimilar: 30.0f); else
} {
} Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.5f, "attackedbyfriendly", minDurationBetweenSimilar: 10.0f);
else if (!Character.IsInstigator && cumulativeDamage > 1) }
{ break;
Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.50f, "attackedbyfriendly", minDurationBetweenSimilar: 30.0f); case AIObjectiveCombat.CombatMode.Offensive:
case AIObjectiveCombat.CombatMode.Arrest:
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityarrest"), null, 0.5f, "attackedbyfriendlysecurityarrest", minDurationBetweenSimilar: 10.0f);
break;
case AIObjectiveCombat.CombatMode.None:
if (Character.IsSecurity && realDamage > 1)
{
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityresponse"), null, 0.5f, "attackedbyfriendlysecurityresponse", minDurationBetweenSimilar: 10.0f);
}
break;
} }
} }
// If the attacker is using a low damage and high frequency weapon like a repair tool, we shouldn't use any delay. // If the attacker is using a low damage and high frequency weapon like a repair tool, we shouldn't use any delay.
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage), attacker, delay: realDamage > 1 ? GetReactionTime() : 0); AddCombatObjective(combatMode, attacker, delay: realDamage > 1 ? GetReactionTime() : 0);
} }
if (!isAttackerFightingEnemy) if (!isAttackerFightingEnemy)
{ {
@@ -1203,15 +1218,15 @@ namespace Barotrauma
if (Character.Submarine != null && Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine)) if (Character.Submarine != null && Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{ {
// Non-friendly // Non-friendly
InformOtherNPCs(Character.GetDamageDoneByAttacker(attacker)); InformOtherNPCs();
} }
if (Character.IsBot) if (Character.IsBot)
{ {
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage: realDamage), attacker); AddCombatObjective(DetermineCombatMode(Character), attacker);
} }
} }
void InformOtherNPCs(float cumulativeDamage) void InformOtherNPCs(float cumulativeDamage = 0)
{ {
foreach (Character otherCharacter in Character.CharacterList) foreach (Character otherCharacter in Character.CharacterList)
{ {
@@ -1238,7 +1253,7 @@ namespace Barotrauma
} }
} }
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage, bool isWitnessing = false) AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage = 0, bool isWitnessing = false)
{ {
if (!IsFriendly(attacker)) if (!IsFriendly(attacker))
{ {
@@ -1258,7 +1273,6 @@ namespace Barotrauma
} }
else else
{ {
float dmgThreshold = attacker.TeamID == Character.TeamID ? 50 : minorDamageThreshold;
if (isAttackerInfected) if (isAttackerInfected)
{ {
cumulativeDamage = 100; cumulativeDamage = 100;
@@ -1266,8 +1280,7 @@ namespace Barotrauma
if (GameMain.IsSingleplayer && attacker.IsPlayer && Character.TeamID == attacker.TeamID) if (GameMain.IsSingleplayer && attacker.IsPlayer && Character.TeamID == attacker.TeamID)
{ {
// Bots in the player team never act aggressively in single player when attacked by the player // Bots in the player team never act aggressively in single player when attacked by the player
dmgThreshold = minorDamageThreshold; return cumulativeDamage > minorDamageThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
return cumulativeDamage > dmgThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
} }
if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine)) if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{ {
@@ -1308,21 +1321,25 @@ namespace Barotrauma
// Already targeting the attacker -> treat as a more serious threat. // Already targeting the attacker -> treat as a more serious threat.
cumulativeDamage *= 2; cumulativeDamage *= 2;
} }
if (cumulativeDamage > dmgThreshold) if (cumulativeDamage > majorDamageThreshold)
{ {
if (c.IsSecurity) if (c.IsSecurity)
{ {
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Arrest; return AIObjectiveCombat.CombatMode.Offensive;
} }
else else
{ {
return c == Character ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat; return c == Character ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat;
} }
} }
else else if (cumulativeDamage > minorDamageThreshold)
{ {
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat; return c.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat;
} }
else
{
return AIObjectiveCombat.CombatMode.None;
}
} }
Character FindInstigator() Character FindInstigator()
@@ -30,7 +30,7 @@ namespace Barotrauma
private float holdFireTimer; private float holdFireTimer;
private bool hasAimed; private bool hasAimed;
private bool isLethalWeapon; private bool isLethalWeapon;
private bool AllowCoolDown => !IsOffensiveOrArrest || Mode != initialMode; private bool AllowCoolDown => !IsOffensiveOrArrest || Mode != initialMode || character.TeamID == Enemy.TeamID;
public Character Enemy { get; private set; } public Character Enemy { get; private set; }
public bool HoldPosition { get; set; } public bool HoldPosition { get; set; }
@@ -143,7 +143,7 @@ namespace Barotrauma
{ {
Mode = CombatMode.Retreat; Mode = CombatMode.Retreat;
} }
spreadTimer = Rand.Range(-10, 10); spreadTimer = Rand.Range(-10f, 10f);
HumanAIController.SortTimer = 0; HumanAIController.SortTimer = 0;
} }
@@ -1177,7 +1177,7 @@ namespace Barotrauma
} }
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons", delay: 0, minDuration: 30); private void SpeakNoWeapons() => Speak("dialogcombatnoweapons", delay: 0, minDuration: 30);
private void AskHelp() => Speak("dialogcombatretreating", delay: Rand.Range(0, 1), minDuration: 20); private void AskHelp() => Speak("dialogcombatretreating", delay: Rand.Range(0f, 1f), minDuration: 20);
private void Speak(string textIdentifier, float delay, float minDuration) private void Speak(string textIdentifier, float delay, float minDuration)
{ {
@@ -43,7 +43,6 @@ namespace Barotrauma
private readonly float minDistance = 50; private readonly float minDistance = 50;
private readonly float seekGapsInterval = 1; private readonly float seekGapsInterval = 1;
private float seekGapsTimer; private float seekGapsTimer;
private bool cannotFollow;
/// <summary> /// <summary>
/// Display units /// Display units
@@ -52,6 +51,11 @@ namespace Barotrauma
{ {
get get
{ {
if (IsFollowOrderObjective && Target is Character targetCharacter && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null))
{
// Keep close when the target is going inside/outside
return minDistance;
}
float dist = _closeEnough * CloseEnoughMultiplier; float dist = _closeEnough * CloseEnoughMultiplier;
float extraMultiplier = Math.Clamp(CloseEnoughMultiplier * 0.6f, 1, 3); float extraMultiplier = Math.Clamp(CloseEnoughMultiplier * 0.6f, 1, 3);
if (character.AnimController.InWater) if (character.AnimController.InWater)
@@ -288,28 +292,16 @@ namespace Barotrauma
{ {
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager), TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () => Abandon = true, onAbandon: () => Abandon = true,
onCompleted: () => onCompleted: () => RemoveSubObjective(ref findDivingGear));
{
cannotFollow = false;
RemoveSubObjective(ref findDivingGear);
});
} }
else else
{ {
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager), TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () => Abandon = true, onAbandon: () => Abandon = true,
onCompleted: () => onCompleted: () => RemoveSubObjective(ref findDivingGear));
{
cannotFollow = false;
RemoveSubObjective(ref findDivingGear);
});
} }
return; return;
} }
else
{
cannotFollow = false;
}
} }
if (repeat) if (repeat)
{ {
@@ -735,7 +727,6 @@ namespace Barotrauma
findDivingGear = null; findDivingGear = null;
seekGapsTimer = 0; seekGapsTimer = 0;
TargetGap = null; TargetGap = null;
cannotFollow = false;
} }
} }
} }
@@ -408,7 +408,7 @@ namespace Barotrauma
if (orderGiver == null) { return null; } if (orderGiver == null) { return null; }
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier) newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
{ {
CloseEnough = Rand.Range(80, 100), CloseEnough = Rand.Range(80f, 100f),
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == orderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4), CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == orderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
ExtraDistanceOutsideSub = 100, ExtraDistanceOutsideSub = 100,
ExtraDistanceWhileSwimming = 100, ExtraDistanceWhileSwimming = 100,
@@ -221,6 +221,9 @@ namespace Barotrauma
/// </summary> /// </summary>
public int AssignmentPriority { get; } public int AssignmentPriority { get; }
public bool ColoredWhenControllingGiver { get; }
public bool DisplayGiverInTooltip { get; }
public static void Init() public static void Init()
{ {
Prefabs = new Dictionary<string, Order>(); Prefabs = new Dictionary<string, Order>();
@@ -406,6 +409,8 @@ namespace Barotrauma
DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false); DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false);
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Movement); AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Movement);
AssignmentPriority = Math.Clamp(orderElement.GetAttributeInt("assignmentpriority", 100), 0, 100); AssignmentPriority = Math.Clamp(orderElement.GetAttributeInt("assignmentpriority", 100), 0, 100);
ColoredWhenControllingGiver = orderElement.GetAttributeBool("coloredwhencontrollinggiver", false);
DisplayGiverInTooltip = orderElement.GetAttributeBool("displaygiverintooltip", false);
} }
/// <summary> /// <summary>
@@ -441,6 +446,8 @@ namespace Barotrauma
Hidden = prefab.Hidden; Hidden = prefab.Hidden;
IgnoreAtOutpost = prefab.IgnoreAtOutpost; IgnoreAtOutpost = prefab.IgnoreAtOutpost;
AssignmentPriority = prefab.AssignmentPriority; AssignmentPriority = prefab.AssignmentPriority;
ColoredWhenControllingGiver = prefab.ColoredWhenControllingGiver;
DisplayGiverInTooltip = prefab.DisplayGiverInTooltip;
OrderGiver = orderGiver; OrderGiver = orderGiver;
TargetEntity = targetEntity; TargetEntity = targetEntity;
@@ -134,6 +134,7 @@ namespace Barotrauma
aggregate += Items[i].Commonness; aggregate += Items[i].Commonness;
if (aggregate >= r && Items[i].Prefab != null) if (aggregate >= r && Items[i].Prefab != null)
{ {
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetProducedItem:" + pet.AiController.Character.SpeciesName + ":" + Items[i].Prefab.Identifier);
Entity.Spawner.AddToSpawnQueue(Items[i].Prefab, pet.AiController.Character.WorldPosition); Entity.Spawner.AddToSpawnQueue(Items[i].Prefab, pet.AiController.Character.WorldPosition);
break; break;
} }
@@ -200,6 +201,8 @@ namespace Barotrauma
break; break;
} }
} }
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetSpawned:" + aiController.Character.SpeciesName);
} }
public StatusIndicatorType GetCurrentStatusIndicatorType() public StatusIndicatorType GetCurrentStatusIndicatorType()
@@ -210,23 +213,44 @@ namespace Barotrauma
return StatusIndicatorType.None; return StatusIndicatorType.None;
} }
public bool OnEat(IEnumerable<string> tags, float amount) public bool OnEat(Item item)
{
bool success = OnEat(item.GetTags());
if (success)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetEat:" + AiController.Character.SpeciesName + ":" + item.prefab.Identifier);
}
return success;
}
public bool OnEat(Character character)
{
if (character == null || !character.IsDead) { return false; }
bool success = OnEat("dead");
if (success)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetEat:" + AiController.Character.SpeciesName + ":" + character.SpeciesName);
}
return success;
}
private bool OnEat(IEnumerable<string> tags)
{ {
foreach (string tag in tags) foreach (string tag in tags)
{ {
if (OnEat(tag, amount)) { return true; } if (OnEat(tag)) { return true; }
} }
return false; return false;
} }
public bool OnEat(string tag, float amount) private bool OnEat(string tag)
{ {
for (int i = 0; i < foods.Count; i++) for (int i = 0; i < foods.Count; i++)
{ {
if (tag.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase)) if (tag.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase))
{ {
Hunger += foods[i].Hunger * amount; Hunger += foods[i].Hunger;
Happiness += foods[i].Happiness * amount; Happiness += foods[i].Happiness;
#if CLIENT #if CLIENT
AiController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.5f); AiController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.5f);
#endif #endif
@@ -22,7 +22,13 @@ namespace Barotrauma
public override void CalculateImportanceSpecific() public override void CalculateImportanceSpecific()
{ {
if (TargetItemComponent is Turret turret && !turret.HasPowerToShoot()) { return; } if (TargetItemComponent is Turret turret && !turret.HasPowerToShoot())
{
//operate (= recharge the turrets) with low priority if they're out of power
//if something else (issues with reactor or the electrical grid) is preventing them from being charged, fixing those issues should take priority
Importance = ShipCommandManager.MinimumIssueThreshold * 1.05f;
return;
}
targetingImportances.Clear(); targetingImportances.Clear();
foreach (Character character in shipCommandManager.EnemyCharacters) foreach (Character character in shipCommandManager.EnemyCharacters)
@@ -51,7 +51,7 @@ namespace Barotrauma
private const float RamTimerMax = 17.5f; private const float RamTimerMax = 17.5f;
public readonly List<ShipIssueWorker> ShipIssueWorkers = new List<ShipIssueWorker>(); public readonly List<ShipIssueWorker> ShipIssueWorkers = new List<ShipIssueWorker>();
private const float MinimumIssueThreshold = 10f; public const float MinimumIssueThreshold = 10f;
private const float IssueDevotionBuffer = 5f; private const float IssueDevotionBuffer = 5f;
private float decisionTimer = 6f; private float decisionTimer = 6f;
@@ -254,7 +254,7 @@ namespace Barotrauma
private void SpawnInitialCells() private void SpawnInitialCells()
{ {
int brainRoomCells = Rand.Range(MinCellsPerBrainRoom, MaxCellsPerRoom); int brainRoomCells = Rand.Range(MinCellsPerBrainRoom, MaxCellsPerRoom + 1);
if (brain.CurrentHull?.WaterPercentage >= MinWaterLevel) if (brain.CurrentHull?.WaterPercentage >= MinWaterLevel)
{ {
for (int i = 0; i < brainRoomCells; i++) for (int i = 0; i < brainRoomCells; i++)
@@ -262,12 +262,12 @@ namespace Barotrauma
if (!TrySpawnCell(out _, brain.CurrentHull)) { break; } if (!TrySpawnCell(out _, brain.CurrentHull)) { break; }
} }
} }
int cellsInside = Rand.Range(MinCellsInside, MaxCellsInside); int cellsInside = Rand.Range(MinCellsInside, MaxCellsInside + 1);
for (int i = 0; i < cellsInside; i++) for (int i = 0; i < cellsInside; i++)
{ {
if (!TrySpawnCell(out _)) { break; } if (!TrySpawnCell(out _)) { break; }
} }
int cellsOutside = Rand.Range(MinCellsOutside, MaxCellsOutside); int cellsOutside = Rand.Range(MinCellsOutside, MaxCellsOutside + 1);
// If we failed to spawn some of the cells in the brainroom/inside, spawn some extra cells outside. // If we failed to spawn some of the cells in the brainroom/inside, spawn some extra cells outside.
cellsOutside = Math.Clamp(cellsOutside + brainRoomCells + cellsInside - protectiveCells.Count, cellsOutside, MaxCellsOutside); cellsOutside = Math.Clamp(cellsOutside + brainRoomCells + cellsInside - protectiveCells.Count, cellsOutside, MaxCellsOutside);
for (int i = 0; i < cellsOutside; i++) for (int i = 0; i < cellsOutside; i++)
@@ -420,7 +420,7 @@ namespace Barotrauma
if (Character.AIController is EnemyAIController enemyAi) if (Character.AIController is EnemyAIController enemyAi)
{ {
enemyAi.PetBehavior?.OnEat("dead", 1.0f); enemyAi.PetBehavior?.OnEat(target);
} }
character.SelectedCharacter = null; character.SelectedCharacter = null;
@@ -533,6 +533,8 @@ namespace Barotrauma
bool onSlope = Math.Abs(movement.X) > 0.01f && Math.Abs(floorNormal.X) > 0.1f && Math.Sign(floorNormal.X) != Math.Sign(movement.X); bool onSlope = Math.Abs(movement.X) > 0.01f && Math.Abs(floorNormal.X) > 0.1f && Math.Sign(floorNormal.X) != Math.Sign(movement.X);
bool movingHorizontally = !MathUtils.NearlyEqual(targetMovement.X, 0.0f);
if (Stairs != null || onSlope) if (Stairs != null || onSlope)
{ {
torso.PullJointWorldAnchorB = new Vector2( torso.PullJointWorldAnchorB = new Vector2(
@@ -562,10 +564,8 @@ namespace Barotrauma
if (!torso.Disabled) if (!torso.Disabled)
{ {
if (TorsoPosition.HasValue) if (TorsoPosition.HasValue) { y += TorsoPosition.Value; }
{ if (Crouching && !movingHorizontally) { y -= HumanCrouchParams.MoveDownAmountWhenStationary; }
y += TorsoPosition.Value;
}
torso.PullJointWorldAnchorB = torso.PullJointWorldAnchorB =
MathUtils.SmoothStep(torso.SimPosition, MathUtils.SmoothStep(torso.SimPosition,
new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce); new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce);
@@ -574,10 +574,8 @@ namespace Barotrauma
if (!head.Disabled) if (!head.Disabled)
{ {
y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier; y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier;
if (HeadPosition.HasValue) if (HeadPosition.HasValue) { y += HeadPosition.Value; }
{ if (Crouching && !movingHorizontally) { y -= HumanCrouchParams.MoveDownAmountWhenStationary; }
y += HeadPosition.Value;
}
head.PullJointWorldAnchorB = head.PullJointWorldAnchorB =
MathUtils.SmoothStep(head.SimPosition, MathUtils.SmoothStep(head.SimPosition,
new Vector2(footMid + movement.X * HeadLeanAmount, y), getUpForce * 1.2f); new Vector2(footMid + movement.X * HeadLeanAmount, y), getUpForce * 1.2f);
@@ -593,12 +591,15 @@ namespace Barotrauma
{ {
float torsoAngle = TorsoAngle.Value; float torsoAngle = TorsoAngle.Value;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
if (Crouching && !movingHorizontally) { torsoAngle -= HumanCrouchParams.ExtraTorsoAngleWhenStationary; }
torsoAngle -= herpesStrength / 150.0f; torsoAngle -= herpesStrength / 150.0f;
torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque); torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque);
} }
if (HeadAngle.HasValue) if (HeadAngle.HasValue)
{ {
head.body.SmoothRotate(HeadAngle.Value * Dir, CurrentGroundedParams.HeadTorque); float headAngle = HeadAngle.Value;
if (Crouching && !movingHorizontally) { headAngle -= HumanCrouchParams.ExtraHeadAngleWhenStationary; }
head.body.SmoothRotate(headAngle * Dir, CurrentGroundedParams.HeadTorque);
} }
if (!onGround) if (!onGround)
@@ -616,8 +617,7 @@ namespace Barotrauma
Vector2 waistPos = waist != null ? waist.SimPosition : torso.SimPosition; Vector2 waistPos = waist != null ? waist.SimPosition : torso.SimPosition;
//moving horizontally if (movingHorizontally)
if (TargetMovement.X != 0.0f)
{ {
//progress the walking animation //progress the walking animation
WalkPos -= MathHelper.ToRadians(CurrentAnimationParams.CycleSpeed) * walkCycleMultiplier * movement.X; WalkPos -= MathHelper.ToRadians(CurrentAnimationParams.CycleSpeed) * walkCycleMultiplier * movement.X;
@@ -261,9 +261,16 @@ namespace Barotrauma
public AttackResult LastDamage; public AttackResult LastDamage;
public Dictionary<ItemPrefab, double> ItemSelectedDurations
{
get { return itemSelectedDurations; }
}
private readonly Dictionary<ItemPrefab, double> itemSelectedDurations = new Dictionary<ItemPrefab, double>();
private double itemSelectedTime;
public float InvisibleTimer; public float InvisibleTimer;
private CharacterPrefab prefab; private readonly CharacterPrefab prefab;
public readonly CharacterParams Params; public readonly CharacterParams Params;
public string SpeciesName => Params?.SpeciesName ?? "null"; public string SpeciesName => Params?.SpeciesName ?? "null";
@@ -700,7 +707,7 @@ namespace Barotrauma
{ {
get get
{ {
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) { return 100.0f; } if (!CanSpeak || IsUnconscious || IsKnockedDown) { return 100.0f; }
return speechImpediment; return speechImpediment;
} }
set set
@@ -737,9 +744,7 @@ namespace Barotrauma
get => _selectedConstruction; get => _selectedConstruction;
set set
{ {
#if CLIENT
var prevSelectedConstruction = _selectedConstruction; var prevSelectedConstruction = _selectedConstruction;
#endif
_selectedConstruction = value; _selectedConstruction = value;
#if CLIENT #if CLIENT
HintManager.OnSetSelectedConstruction(this, prevSelectedConstruction, _selectedConstruction); HintManager.OnSetSelectedConstruction(this, prevSelectedConstruction, _selectedConstruction);
@@ -755,6 +760,19 @@ namespace Barotrauma
} }
} }
#endif #endif
if (prevSelectedConstruction == null && _selectedConstruction != null)
{
itemSelectedTime = Timing.TotalTime;
}
else if (prevSelectedConstruction != null && _selectedConstruction == null && itemSelectedTime > 0)
{
if (!itemSelectedDurations.ContainsKey(prevSelectedConstruction.Prefab))
{
itemSelectedDurations.Add(prevSelectedConstruction.Prefab, 0);
}
itemSelectedDurations[prevSelectedConstruction.Prefab] += Timing.TotalTime - itemSelectedTime;
itemSelectedTime = 0;
}
} }
} }
@@ -3950,27 +3968,44 @@ namespace Barotrauma
AnimController.Frozen = false; AnimController.Frozen = false;
if (GameAnalyticsManager.SendUserStatistics)
{
string characterType = "Unknown";
if (this == Controlled)
characterType = "Player";
else if (IsRemotePlayer)
characterType = "RemotePlayer";
else if (AIController is EnemyAIController)
characterType = "Enemy";
else if (AIController is HumanAIController)
characterType = "AICrew";
string causeOfDeathStr = causeOfDeathAffliction == null ?
causeOfDeath.ToString() : causeOfDeathAffliction.Prefab.Name.Replace(" ", "");
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":" + SpeciesName + ":" + causeOfDeathStr);
}
CauseOfDeath = new CauseOfDeath( CauseOfDeath = new CauseOfDeath(
causeOfDeath, causeOfDeathAffliction?.Prefab, causeOfDeath, causeOfDeathAffliction?.Prefab,
causeOfDeathAffliction?.Source ?? LastAttacker, LastDamageSource); causeOfDeathAffliction?.Source, LastDamageSource);
if (GameAnalyticsManager.SendUserStatistics)
{
string causeOfDeathStr = causeOfDeathAffliction == null ?
causeOfDeath.ToString() : causeOfDeathAffliction.Prefab.Identifier.Replace(" ", "");
string characterType = GetCharacterType(this);
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":" + causeOfDeathStr);
if (CauseOfDeath.Killer != null)
{
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":Killer:" + GetCharacterType(CauseOfDeath.Killer));
}
if (CauseOfDeath.DamageSource != null)
{
string damageSourceStr = CauseOfDeath.DamageSource.ToString();
if (CauseOfDeath.DamageSource is Item damageSourceItem) { damageSourceStr = damageSourceItem.ToString(); }
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":DamageSource:" + damageSourceStr);
}
static string GetCharacterType(Character character)
{
if (character.IsPlayer)
return "Player";
else if (character.AIController is EnemyAIController)
return "Enemy" + character.SpeciesName;
else if (character.AIController is HumanAIController && character.TeamID == CharacterTeamType.Team2)
return "EnemyHuman";
else if (character.Info != null && character.TeamID == CharacterTeamType.Team1)
return "AICrew";
else if (character.Info != null && character.TeamID == CharacterTeamType.FriendlyNPC)
return "FriendlyNPC";
return "Unknown";
}
}
OnDeath?.Invoke(this, CauseOfDeath); OnDeath?.Invoke(this, CauseOfDeath);
var abilityCharacterKiller = new AbilityCharacterKiller(CauseOfDeath.Killer); var abilityCharacterKiller = new AbilityCharacterKiller(CauseOfDeath.Killer);
@@ -4097,7 +4132,7 @@ namespace Barotrauma
info?.Remove(); info?.Remove();
#if CLIENT #if CLIENT
GameMain.GameSession?.CrewManager?.KillCharacter(this); GameMain.GameSession?.CrewManager?.KillCharacter(this, resetCrewListIndex: false);
#endif #endif
CharacterList.Remove(this); CharacterList.Remove(this);
@@ -4112,6 +4147,8 @@ namespace Barotrauma
} }
} }
itemSelectedDurations.Clear();
DisposeProjSpecific(); DisposeProjSpecific();
aiTarget?.Remove(); aiTarget?.Remove();
@@ -1527,7 +1527,7 @@ namespace Barotrauma
orderTargetElement.Add(new XAttribute("hullid", (uint)ot.Hull.ID)); orderTargetElement.Add(new XAttribute("hullid", (uint)ot.Hull.ID));
position -= ot.Hull.WorldPosition; position -= ot.Hull.WorldPosition;
} }
orderTargetElement.Add(new XAttribute("position", $"{position.X},{position.Y}")); orderTargetElement.Add(new XAttribute("position", XMLExtensions.Vector2ToString(position)));
orderElement.Add(orderTargetElement); orderElement.Add(orderTargetElement);
break; break;
case Order.OrderTargetType.WallSection when targetAvailableInNextLevel && order.TargetEntity is Structure s && order.WallSectionIndex.HasValue: case Order.OrderTargetType.WallSection when targetAvailableInNextLevel && order.TargetEntity is Structure s && order.WallSectionIndex.HasValue:
@@ -106,7 +106,7 @@ namespace Barotrauma
{ {
if (State != InfectionState.Active && stun) if (State != InfectionState.Active && stun)
{ {
character.SetStun(Rand.Range(2, 4)); character.SetStun(Rand.Range(2f, 3f));
} }
State = InfectionState.Active; State = InfectionState.Active;
ActivateHusk(); ActivateHusk();
@@ -363,7 +363,9 @@ namespace Barotrauma
public readonly string Name, Description; public readonly string Name, Description;
public readonly string TranslationOverride; public readonly string TranslationOverride;
public readonly bool IsBuff; public readonly bool IsBuff;
public readonly bool HealableInMedicalClinic;
public readonly float HealCostMultiplier; public readonly float HealCostMultiplier;
public readonly int BaseHealCost;
public readonly string CauseOfDeathDescription, SelfCauseOfDeathDescription; public readonly string CauseOfDeathDescription, SelfCauseOfDeathDescription;
@@ -656,7 +658,13 @@ namespace Barotrauma
Name = TextManager.Get("AfflictionName." + translationId, true) ?? element.GetAttributeString("name", ""); Name = TextManager.Get("AfflictionName." + translationId, true) ?? element.GetAttributeString("name", "");
Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", ""); Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", "");
IsBuff = element.GetAttributeBool("isbuff", false); IsBuff = element.GetAttributeBool("isbuff", false);
HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
!IsBuff &&
!AfflictionType.Equals("geneticmaterialbuff", StringComparison.OrdinalIgnoreCase) &&
!AfflictionType.Equals("geneticmaterialdebuff", StringComparison.OrdinalIgnoreCase));
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier).ToLowerInvariant(), 1f); HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier).ToLowerInvariant(), 1f);
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost).ToLowerInvariant(), 0);
if (element.Attribute("nameidentifier") != null) if (element.Attribute("nameidentifier") != null)
{ {
@@ -764,6 +764,7 @@ namespace Barotrauma
if (applyAffliction) if (applyAffliction)
{ {
afflictionsCopy.Add(newAffliction); afflictionsCopy.Add(newAffliction);
newAffliction.Source ??= attacker;
} }
appliedDamageModifiers.AddRange(tempModifiers); appliedDamageModifiers.AddRange(tempModifiers);
} }
@@ -26,6 +26,15 @@ namespace Barotrauma
class HumanCrouchParams : HumanGroundedParams class HumanCrouchParams : HumanGroundedParams
{ {
[Serialize(0.0f, true, description: "How much lower the character's head and torso move when stationary."), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2)]
public float MoveDownAmountWhenStationary { get; set; }
[Serialize(0.0f, true), Editable(-360f, 360f)]
public float ExtraHeadAngleWhenStationary { get; set; }
[Serialize(0.0f, true), Editable(-360f, 360f)]
public float ExtraTorsoAngleWhenStationary { get; set; }
public static HumanCrouchParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanCrouchParams>(character, AnimationType.Crouch); public static HumanCrouchParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanCrouchParams>(character, AnimationType.Crouch);
public static HumanCrouchParams GetAnimParams(Character character, string fileName = null) public static HumanCrouchParams GetAnimParams(Character character, string fileName = null)
{ {
@@ -23,7 +23,9 @@ namespace Barotrauma.Abilities
multiplier = 0 + Character.Info.GetSavedStatValue(StatTypes.None, scalingStatIdentifier); multiplier = 0 + Character.Info.GetSavedStatValue(StatTypes.None, scalingStatIdentifier);
} }
targetCharacter.GiveMoney((int)(multiplier * amount)); int totalAmount = (int)(multiplier * amount);
targetCharacter.GiveMoney(totalAmount);
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
} }
protected override void ApplyEffect(AbilityObject abilityObject) protected override void ApplyEffect(AbilityObject abilityObject)
@@ -16,7 +16,9 @@ namespace Barotrauma.Abilities
{ {
if ((abilityObject as IAbilityCharacter)?.Character is Character character) if ((abilityObject as IAbilityCharacter)?.Character is Character character)
{ {
Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality)); int totalAmount = (int)(vitalityPercentage * character.MaxVitality);
Character.GiveMoney(totalAmount);
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
} }
} }
} }
@@ -30,6 +30,7 @@ namespace Barotrauma.Abilities
if (!enemyCharacter.LockHands) { continue; } if (!enemyCharacter.LockHands) { continue; }
if (timesGiven > max) { continue; } if (timesGiven > max) { continue; }
Character.GiveMoney(moneyAmount); Character.GiveMoney(moneyAmount);
GameAnalyticsManager.AddMoneyGainedEvent(moneyAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
foreach (Character character in Character.GetFriendlyCrew(Character)) foreach (Character character in Character.GetFriendlyCrew(Character))
{ {
character.Info?.GiveExperience(experienceAmount); character.Info?.GiveExperience(experienceAmount);
@@ -12,8 +12,6 @@ namespace Barotrauma.Abilities
private readonly int moneyPerMission; private readonly int moneyPerMission;
private static List<Client> clientsAlreadyUsed = new List<Client>();
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0); moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
@@ -23,7 +21,9 @@ namespace Barotrauma.Abilities
{ {
if (Character?.Info is CharacterInfo info) if (Character?.Info is CharacterInfo info)
{ {
Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath); int totalAmount = moneyPerMission * info.MissionsCompletedSinceDeath;
Character.GiveMoney(totalAmount);
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
} }
} }
} }
@@ -1534,6 +1534,7 @@ namespace Barotrauma
if (int.TryParse(args[0], out int money)) if (int.TryParse(args[0], out int money))
{ {
campaign.Money += money; campaign.Money += money;
GameAnalyticsManager.AddMoneyGainedEvent(money, GameAnalyticsManager.MoneySource.Cheat, "console");
} }
else else
{ {
@@ -28,6 +28,7 @@ namespace Barotrauma
if (GameMain.GameSession?.GameMode is CampaignMode campaign) if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{ {
campaign.Money += Amount; campaign.Money += Amount;
GameAnalyticsManager.AddMoneyGainedEvent(Amount, GameAnalyticsManager.MoneySource.Event, ParentEvent.Prefab.Identifier);
#if SERVER #if SERVER
(campaign as MultiPlayerCampaign).LastUpdateID++; (campaign as MultiPlayerCampaign).LastUpdateID++;
#endif #endif
@@ -152,7 +152,7 @@ namespace Barotrauma
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100) if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
{ {
spawnPos = new Vector2( spawnPos = new Vector2(
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50), MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 201), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f); wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
} }
var item = new Item(itemPrefab, spawnPos, null); var item = new Item(itemPrefab, spawnPos, null);
@@ -319,6 +319,19 @@ namespace Barotrauma
} }
} }
foreach (Character character in characters)
{
if (character.Inventory == null) { continue; }
foreach (Item item in character.Inventory.AllItemsMod)
{
//item didn't spawn with the characters -> drop it
if (!characterItems.Any(c => c.Value.Contains(item)))
{
item.Drop(character);
}
}
}
// characters that survived will take their items with them, in case players tried to be crafty and steal them // characters that survived will take their items with them, in case players tried to be crafty and steal them
// this needs to run here in case players abort the mission by going back home // this needs to run here in case players abort the mission by going back home
// TODO: I think this might feel like a bug. // TODO: I think this might feel like a bug.
@@ -378,7 +378,10 @@ namespace Barotrauma
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier)); crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier));
crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier)); crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
campaign.Money += (int)(reward * missionMoneyGainMultiplier.Value); int totalReward = (int)(reward * missionMoneyGainMultiplier.Value);
campaign.Money += totalReward;
GameAnalyticsManager.AddMoneyGainedEvent(totalReward, GameAnalyticsManager.MoneySource.MissionReward, Prefab.Identifier);
foreach (Character character in crewCharacters) foreach (Character character in crewCharacters)
{ {
@@ -328,7 +328,7 @@ namespace Barotrauma
} }
else else
{ {
dir = new Vector2(1, Rand.Range(-1, 1)); dir = new Vector2(1, Rand.Range(-1f, 1f));
} }
Vector2 targetPos = spawnPos.Value + dir * offset; Vector2 targetPos = spawnPos.Value + dir * offset;
var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault(); var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault();
@@ -475,6 +475,7 @@ namespace Barotrauma
{ {
scatterAmount = 0; scatterAmount = 0;
} }
for (int i = 0; i < amount; i++) for (int i = 0; i < amount; i++)
{ {
string seed = Level.Loaded.Seed + i.ToString(); string seed = Level.Loaded.Seed + i.ToString();
@@ -540,6 +541,13 @@ namespace Barotrauma
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>()); SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
DebugConsole.NewMessage($"Spawned: {ToString()}. Strength: {StringFormatter.FormatZeroDecimal(monsters.Sum(m => m.Params.AI.CombatStrength))}.", Color.LightBlue, debugOnly: true); DebugConsole.NewMessage($"Spawned: {ToString()}. Strength: {StringFormatter.FormatZeroDecimal(monsters.Sum(m => m.Params.AI.CombatStrength))}.", Color.LightBlue, debugOnly: true);
} }
if (GameMain.GameSession != null)
{
GameAnalyticsManager.AddDesignEvent(
$"MonsterSpawn:{GameMain.GameSession.GameMode?.Preset?.Identifier ?? "none"}:{Level.Loaded?.LevelData?.Biome?.Identifier ?? "none"}:{SpawnPosType}:{speciesName}",
value: Timing.TotalTime - GameMain.GameSession.RoundStartTime);
}
}, delayBetweenSpawns * i); }, delayBetweenSpawns * i);
} }
} }
@@ -1,12 +1,11 @@
using System;
using Barotrauma.Steam; using Barotrauma.Steam;
using RestSharp; using RestSharp;
using System;
using System.Net; using System.Net;
using System.Threading.Tasks;
namespace Barotrauma namespace Barotrauma
{ {
public static partial class GameAnalyticsManager static partial class GameAnalyticsManager
{ {
public enum Consent public enum Consent
{ {
@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using Barotrauma.IO; using Barotrauma.IO;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -9,7 +10,7 @@ using System.Text;
namespace Barotrauma namespace Barotrauma
{ {
public static partial class GameAnalyticsManager static partial class GameAnalyticsManager
{ {
public enum ErrorSeverity public enum ErrorSeverity
{ {
@@ -29,6 +30,61 @@ namespace Barotrauma
Fail = 3 Fail = 3
} }
public enum CustomDimensions01
{
Vanilla,
Modded
}
public enum CustomDimensions02
{
None,
Difficulty0to10,
Difficulty10to20,
Difficulty20to30,
Difficulty30to40,
Difficulty40to50,
Difficulty50to60,
Difficulty60to70,
Difficulty70to80,
Difficulty80to90,
Difficulty90to100,
}
public enum ResourceCurrency
{
Money
}
public enum ResourceFlowType
{
Undefined = 0,
Source = 1,
Sink = 2
}
public enum MoneySource
{
Unknown,
MissionReward,
Store,
Event,
Ability,
Cheat
}
public enum MoneySink
{
Unknown,
Store,
Service,
Crew,
SubmarineUpgrade,
SubmarineWeapon,
SubmarinePurchase,
SubmarineSwitch
}
private readonly static HashSet<string> sentEventIdentifiers = new HashSet<string>(); private readonly static HashSet<string> sentEventIdentifiers = new HashSet<string>();
private class Implementation : IDisposable private class Implementation : IDisposable
@@ -69,13 +125,29 @@ namespace Barotrauma
internal void AddProgressionEvent(ProgressionStatus status, string progression01, string progression02, string progression03) internal void AddProgressionEvent(ProgressionStatus status, string progression01, string progression02, string progression03)
=> addProgressionEvent03(status, progression01, progression02, progression03); => addProgressionEvent03(status, progression01, progression02, progression03);
private readonly Action<ResourceFlowType, string, float, string, string> addResourceEvent;
internal void AddResourceEvent(ResourceFlowType flowType, string currency, float amount, string itemType, string itemId)
=> addResourceEvent(flowType, currency, amount, itemType, itemId);
private readonly Action<string> setCustomDimension01; private readonly Action<string> setCustomDimension01;
internal void SetCustomDimension01(string dimension01) internal void SetCustomDimension01(string dimension01)
=> setCustomDimension01(dimension01); => setCustomDimension01(dimension01);
private readonly Action<string[]> configureAvailableCustomDimensions01; private readonly Action<string[]> configureAvailableCustomDimensions01;
internal void ConfigureAvailableCustomDimensions01(params string[] customDimensions) internal void ConfigureAvailableCustomDimensions01(params CustomDimensions01[] customDimensions)
=> configureAvailableCustomDimensions01(customDimensions); => configureAvailableCustomDimensions01(customDimensions.Select(d => d.ToString()).ToArray());
private readonly Action<string> setCustomDimension02;
internal void SetCustomDimension02(string dimension02)
=> setCustomDimension02(dimension02);
private readonly Action<string[]> configureAvailableCustomDimensions02;
internal void ConfigureAvailableCustomDimensions02(params CustomDimensions02[] customDimensions)
=> configureAvailableCustomDimensions02(customDimensions.Select(d => d.ToString()).ToArray());
private readonly Action<string[]> configureAvailableResourceCurrencies;
internal void ConfigureAvailableResourceCurrencies(params ResourceCurrency[] customDimensions)
=> configureAvailableResourceCurrencies(customDimensions.Select(d => d.ToString()).ToArray());
private readonly Action<bool> setEnabledInfoLog; private readonly Action<bool> setEnabledInfoLog;
internal void SetEnabledInfoLog(bool enabled) internal void SetEnabledInfoLog(bool enabled)
@@ -94,6 +166,7 @@ namespace Barotrauma
private readonly object?[] args2 = new object?[2]; private readonly object?[] args2 = new object?[2];
private readonly object?[] args3 = new object?[3]; private readonly object?[] args3 = new object?[3];
private readonly object?[] args4 = new object?[4]; private readonly object?[] args4 = new object?[4];
private readonly object?[] args5 = new object?[5];
private Action Call(MethodInfo methodInfo) private Action Call(MethodInfo methodInfo)
=> () => methodInfo?.Invoke(null, null); => () => methodInfo?.Invoke(null, null);
@@ -131,6 +204,17 @@ namespace Barotrauma
args4[3] = arg4; args4[3] = arg4;
methodInfo.Invoke(null, args4); methodInfo.Invoke(null, args4);
}; };
private Action<T1, T2, T3, T4, T5> Call<T1, T2, T3, T4, T5>(MethodInfo methodInfo)
=> (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) =>
{
args5[0] = arg1;
args5[1] = arg2;
args5[2] = arg3;
args5[3] = arg4;
args5[4] = arg5;
methodInfo.Invoke(null, args5);
};
#endregion #endregion
private AssemblyLoadContext? loadContext; private AssemblyLoadContext? loadContext;
@@ -165,9 +249,15 @@ namespace Barotrauma
var mainClass = getType(MainClass); var mainClass = getType(MainClass);
var errorSeverityEnumType = getType($"{EnumPrefix}{nameof(ErrorSeverity)}"); var errorSeverityEnumType = getType($"{EnumPrefix}{nameof(ErrorSeverity)}");
var progressionStatusEnumType = getType($"{EnumPrefix}{nameof(ProgressionStatus)}"); var progressionStatusEnumType = getType($"{EnumPrefix}{nameof(ProgressionStatus)}");
var resourceFlowTypeEnumType = getType($"{EnumPrefix}{nameof(ResourceFlowType)}");
MethodInfo getMethod(string name, Type[] types) MethodInfo getMethod(string name, Type[] types)
{ {
foreach (var me in mainClass.GetMethods())
{
var aksjdnakjsdnf = me;
}
return mainClass?.GetMethod(name, BindingFlags.Public | BindingFlags.Static, binder: null, types: types, modifiers: null) return mainClass?.GetMethod(name, BindingFlags.Public | BindingFlags.Static, binder: null, types: types, modifiers: null)
?? throw new Exception($"Could not find method \"{name}\" with types {string.Join(',', types.Select(t => t.Name))}"); ?? throw new Exception($"Could not find method \"{name}\" with types {string.Join(',', types.Select(t => t.Name))}");
} }
@@ -190,10 +280,20 @@ namespace Barotrauma
new Type[] { progressionStatusEnumType, typeof(string), typeof(string) })); new Type[] { progressionStatusEnumType, typeof(string), typeof(string) }));
addProgressionEvent03 = Call<ProgressionStatus, string, string, string>(getMethod(nameof(AddProgressionEvent), addProgressionEvent03 = Call<ProgressionStatus, string, string, string>(getMethod(nameof(AddProgressionEvent),
new Type[] { progressionStatusEnumType, typeof(string), typeof(string), typeof(string) })); new Type[] { progressionStatusEnumType, typeof(string), typeof(string), typeof(string) }));
setCustomDimension01 = Call<string>(getMethod(nameof(SetCustomDimension01), setCustomDimension01 = Call<string>(getMethod(nameof(SetCustomDimension01),
new Type[] { typeof(string) })); new Type[] { typeof(string) }));
configureAvailableCustomDimensions01 = Call<string[]>(getMethod(nameof(ConfigureAvailableCustomDimensions01), configureAvailableCustomDimensions01 = Call<string[]>(getMethod(nameof(ConfigureAvailableCustomDimensions01),
new Type[] { typeof(string[]) })); new Type[] { typeof(string[]) }));
setCustomDimension02 = Call<string>(getMethod(nameof(SetCustomDimension02),
new Type[] { typeof(string) }));
configureAvailableCustomDimensions02 = Call<string[]>(getMethod(nameof(ConfigureAvailableCustomDimensions02),
new Type[] { typeof(string[]) }));
configureAvailableResourceCurrencies = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceCurrencies),
new Type[] { typeof(string[]) }));
addResourceEvent = Call<ResourceFlowType, string, float, string, string>(getMethod(nameof(AddResourceEvent),
new Type[] { resourceFlowTypeEnumType, typeof(string), typeof(float), typeof(string), typeof(string) }));
setEnabledInfoLog = Call<bool>(getMethod(nameof(SetEnabledInfoLog), setEnabledInfoLog = Call<bool>(getMethod(nameof(SetEnabledInfoLog),
new Type[] { typeof(bool) })); new Type[] { typeof(bool) }));
@@ -204,8 +304,7 @@ namespace Barotrauma
private void OnQuit() private void OnQuit()
{ {
try try
{ {
if (assembly != null) { onQuit?.Invoke(); } if (assembly != null) { onQuit?.Invoke(); }
} }
catch (Exception e) catch (Exception e)
@@ -298,10 +397,40 @@ namespace Barotrauma
loadedImplementation?.AddProgressionEvent(progressionStatus, progression01, progression02, progression03); loadedImplementation?.AddProgressionEvent(progressionStatus, progression01, progression02, progression03);
} }
public static void SetCustomDimension01(string dimension) public static void SetCustomDimension01(CustomDimensions01 dimension)
{ {
if (!SendUserStatistics) { return; } if (!SendUserStatistics) { return; }
loadedImplementation?.SetCustomDimension01(dimension); loadedImplementation?.SetCustomDimension01(dimension.ToString());
}
public static void SetCurrentLevel(LevelData levelData)
{
if (!SendUserStatistics) { return; }
CustomDimensions02 customDimension = CustomDimensions02.None;
if (levelData != null)
{
float levelDifficulty = levelData.Difficulty;
customDimension = (CustomDimensions02)MathHelper.Clamp((int)(levelDifficulty / 10) + 1, 0, Enum.GetValues(typeof(CustomDimensions02)).Length - 1);
}
loadedImplementation?.SetCustomDimension02(customDimension.ToString());
}
public static void AddMoneyGainedEvent(int amount, MoneySource moneySource, string eventId)
{
AddResourceEvent(ResourceFlowType.Source, ResourceCurrency.Money, amount, moneySource.ToString(), eventId);
}
public static void AddMoneySpentEvent(int amount, MoneySink moneySink, string eventId)
{
AddResourceEvent(ResourceFlowType.Sink, ResourceCurrency.Money, amount, moneySink.ToString(), eventId);
}
private static void AddResourceEvent(ResourceFlowType flowType, ResourceCurrency currency, float amount, string eventType, string eventId)
{
if (!SendUserStatistics) { return; }
loadedImplementation?.AddResourceEvent(flowType, currency.ToString(), amount, eventType, eventId);
} }
private static void Init() private static void Init()
@@ -359,7 +488,8 @@ namespace Barotrauma
+ exeName + ":" + exeName + ":"
+ AssemblyInfo.GitRevision + ":" + AssemblyInfo.GitRevision + ":"
+ buildConfiguration); + buildConfiguration);
loadedImplementation?.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor"); loadedImplementation?.ConfigureAvailableCustomDimensions01(Enum.GetValues(typeof(CustomDimensions01)).Cast<CustomDimensions01>().ToArray());
loadedImplementation?.ConfigureAvailableResourceCurrencies(Enum.GetValues(typeof(ResourceCurrency)).Cast<ResourceCurrency>().ToArray());
InitKeys(); InitKeys();
@@ -380,15 +510,16 @@ namespace Barotrauma
var allPackages = GameMain.Config?.AllEnabledPackages.ToList(); var allPackages = GameMain.Config?.AllEnabledPackages.ToList();
if (allPackages?.Count > 0) if (allPackages?.Count > 0)
{ {
StringBuilder sb = new StringBuilder("ContentPackage: "); List<string> packageNames = new List<string>();
int i = 0;
foreach (ContentPackage cp in allPackages) foreach (ContentPackage cp in allPackages)
{ {
string trimmedName = cp.Name.Replace(":", "").Replace(" ", ""); string sanitizedName = cp.Name.Replace(":", "").Replace(" ", "");
sb.Append(trimmedName.Substring(0, Math.Min(32, trimmedName.Length))); sanitizedName = sanitizedName.Substring(0, Math.Min(32, sanitizedName.Length));
if (i < allPackages.Count - 1) { sb.Append(" "); } packageNames.Add(sanitizedName);
loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName);
} }
loadedImplementation?.AddDesignEvent(sb.ToString()); packageNames.Sort();
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames));
} }
} }
@@ -132,6 +132,7 @@ namespace Barotrauma
// Exchange money // Exchange money
var itemValue = item.Quantity * buyValues[item.ItemPrefab]; var itemValue = item.Quantity * buyValues[item.ItemPrefab];
campaign.Money -= itemValue; campaign.Money -= itemValue;
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier);
Location.StoreCurrentBalance += itemValue; Location.StoreCurrentBalance += itemValue;
if (removeFromCrate) if (removeFromCrate)
@@ -291,7 +292,7 @@ namespace Barotrauma
float floorPos = hull.Rect.Y - hull.Rect.Height; float floorPos = hull.Rect.Y - hull.Rect.Height;
Vector2 position = new Vector2( Vector2 position = new Vector2(
hull.Rect.Width > 40 ? Rand.Range(hull.Rect.X + 20, hull.Rect.Right - 20) : hull.Rect.Center.X, hull.Rect.Width > 40 ? Rand.Range(hull.Rect.X + 20f, hull.Rect.Right - 20f) : hull.Rect.Center.X,
floorPos); floorPos);
//check where the actual floor structure is in case the bottom of the hull extends below it //check where the actual floor structure is in case the bottom of the hull extends below it
@@ -37,7 +37,6 @@ namespace Barotrauma
{ {
IsSinglePlayer = isSinglePlayer; IsSinglePlayer = isSinglePlayer;
conversationTimer = 5.0f; conversationTimer = 5.0f;
InitProjectSpecific(); InitProjectSpecific();
} }
@@ -100,10 +99,10 @@ namespace Barotrauma
foreach (XElement characterElement in element.Elements()) foreach (XElement characterElement in element.Elements())
{ {
if (!characterElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; } if (!characterElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; }
CharacterInfo characterInfo = new CharacterInfo(characterElement); CharacterInfo characterInfo = new CharacterInfo(characterElement);
#if CLIENT #if CLIENT
if (characterElement.GetAttributeBool("lastcontrolled", false)) { characterInfo.LastControlled = true; } if (characterElement.GetAttributeBool("lastcontrolled", false)) { characterInfo.LastControlled = true; }
characterInfo.CrewListIndex = characterElement.GetAttributeInt("crewlistindex", -1);
#endif #endif
characterInfos.Add(characterInfo); characterInfos.Add(characterInfo);
foreach (XElement subElement in characterElement.Elements()) foreach (XElement subElement in characterElement.Elements())
@@ -133,7 +132,7 @@ namespace Barotrauma
characterInfos.Remove(characterInfo); characterInfos.Remove(characterInfo);
} }
public void AddCharacter(Character character) public void AddCharacter(Character character, bool sortCrewList = true)
{ {
if (character.Removed) if (character.Removed)
{ {
@@ -155,7 +154,11 @@ namespace Barotrauma
characterInfos.Add(character.Info); characterInfos.Add(character.Info);
} }
#if CLIENT #if CLIENT
AddCharacterToCrewList(character); var characterComponent = AddCharacterToCrewList(character);
if (sortCrewList)
{
SortCrewList();
}
if (character.CurrentOrders != null) if (character.CurrentOrders != null)
{ {
foreach (var order in character.CurrentOrders) foreach (var order in character.CurrentOrders)
@@ -254,12 +257,16 @@ namespace Barotrauma
} }
} }
AddCharacter(character); AddCharacter(character, sortCrewList: false);
#if CLIENT #if CLIENT
if (IsSinglePlayer && (Character.Controlled == null || character.Info.LastControlled)) { Character.Controlled = character; } if (IsSinglePlayer && (Character.Controlled == null || character.Info.LastControlled)) { Character.Controlled = character; }
#endif #endif
} }
#if CLIENT
if (IsSinglePlayer) { SortCrewList(); }
#endif
//longer delay in multiplayer to prevent the server from triggering NPC conversations while the players are still loading the round //longer delay in multiplayer to prevent the server from triggering NPC conversations while the players are still loading the round
conversationTimer = IsSinglePlayer ? Rand.Range(5.0f, 10.0f) : Rand.Range(45.0f, 60.0f); conversationTimer = IsSinglePlayer ? Rand.Range(5.0f, 10.0f) : Rand.Range(45.0f, 60.0f);
} }

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