diff --git a/.gitignore b/.gitignore
index bf5f81970..2afaafc0a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,9 +13,6 @@ bld/
[Rr]elease*/
*.o
-# Barotrauma content folder
-BarotraumaShared/Content/
-
# Misc vs crap
*.v12.suo
*.suo
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
index 9876cbc2c..3dc2aed42 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
@@ -176,6 +176,8 @@ namespace Barotrauma
healthBar = new GUIProgressBar(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAreaLeft, GUI.Canvas),
barSize: 1.0f, color: Color.Green, style: horizontal ? "GUIProgressBar" : "GUIProgressBarVertical")
{
+ Enabled = true,
+ HoverCursor = CursorState.Hand,
IsHorizontal = horizontal
};
healthBarShadow = new GUIProgressBar(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAreaLeft, GUI.Canvas),
diff --git a/Barotrauma/BarotraumaClient/ClientSource/ChatManager.cs b/Barotrauma/BarotraumaClient/ClientSource/ChatManager.cs
index 8034e9ee4..bd21ba601 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/ChatManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/ChatManager.cs
@@ -26,17 +26,14 @@ namespace Barotrauma
/// where I'm utilizing this
private readonly List registers = new List();
- private readonly bool skipDuplicate;
-
// Selector index
private int index;
// Local changes we've made into previously stored messages
private string[] localChanges;
- public ChatManager(bool skipDuplicate, bool loop, short maxCount)
+ public ChatManager(bool loop, short maxCount)
{
- this.skipDuplicate = skipDuplicate;
this.loop = loop;
this.maxCount = maxCount;
localChanges = new string[maxCount];
@@ -87,13 +84,10 @@ namespace Barotrauma
public void Store(string message)
{
Clear();
- string strip = StripMessage(message);
+ var strip = StripMessage(message);
if (string.IsNullOrWhiteSpace(strip)) { return; }
- if (skipDuplicate && messageList.Any(p => message == p))
- {
- return;
- }
+ if (messageList.Count > 1 && messageList[1] == message) { return; }
// insert to the second position as the first position is reserved for the original message if any
messageList.Insert(1, message);
@@ -104,7 +98,7 @@ namespace Barotrauma
}
// [It's also possible to lambdas too in short methods, if you like: string StripMessage(string text) => ChatMessage.GetChatMessageCommand(text, out string msg);]
- string StripMessage(string text)
+ static string StripMessage(string text)
{
ChatMessage.GetChatMessageCommand(text, out string msg);
return msg;
@@ -129,40 +123,61 @@ namespace Barotrauma
/// A message or null
private string SelectMessage(Direction direction, string original)
{
- if (direction == Direction.Other) { return null; }
-
- // temporarily save our changes in case we fat-finger and want to go back
- localChanges[index] = original;
-
- int dir = (int) direction;
-
- int nextIndex = (index + dir);
-
- if (loop && messageList.Count > 1)
+ var originalIndex = index;
+ while (true)
{
- nextIndex = LoopAround(nextIndex);
- }
- else
- {
- if (nextIndex > messageList.Count - 1)
+ if (direction == Direction.Other)
{
return null;
}
- }
-
- return nextIndex < 0 ? localChanges.FirstOrDefault() : EntryAt(index = nextIndex);
-
- string EntryAt(int i)
- {
- // if we've previously edited the entry then give us that, else give us the original message
- return localChanges[i] ?? messageList[i];
- }
- int LoopAround(int next)
- {
- if (next > (messageList.Count - 1)) { return 1; }
- if (next < 1) { return messageList.Count - 1; }
- return next;
+ // temporarily save our changes in case we fat-finger and want to go back
+ localChanges[index] = original;
+
+ var dir = (int) direction;
+
+ var nextIndex = (index + dir);
+
+ if (loop && messageList.Count > 1)
+ {
+ nextIndex = LoopAround(nextIndex);
+ }
+ else
+ {
+ if (nextIndex > messageList.Count - 1)
+ {
+ return null;
+ }
+ }
+
+ if (nextIndex >= 0 && EntryAt(nextIndex) == original && nextIndex != originalIndex && originalIndex != 0)
+ {
+ index = nextIndex;
+ continue;
+ }
+
+ return nextIndex < 0 ? localChanges.FirstOrDefault() : EntryAt(index = nextIndex);
+
+ string EntryAt(int i)
+ {
+ // if we've previously edited the entry then give us that, else give us the original message
+ return localChanges[i] ?? messageList[i];
+ }
+
+ int LoopAround(int next)
+ {
+ if (next > (messageList.Count - 1))
+ {
+ return 1;
+ }
+
+ if (next < 1)
+ {
+ return messageList.Count - 1;
+ }
+
+ return next;
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
index b45fedcb0..a62eb9fcc 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
@@ -61,7 +61,7 @@ namespace Barotrauma
public static GUITextBox TextBox => textBox;
- private static readonly ChatManager chatManager = new ChatManager(true, true, 64);
+ private static readonly ChatManager chatManager = new ChatManager(true, 64);
public static void Init()
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
index 67bd9cefe..42c2c4ab2 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
@@ -1,17 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
using Barotrauma.CharacterEditor;
using Barotrauma.Extensions;
+using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.Sounds;
-using Barotrauma.Tutorials;
using EventInput;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Xml.Linq;
namespace Barotrauma
{
@@ -26,11 +28,23 @@ namespace Barotrauma
PickItemFail,
DropItem
}
+
+ public enum CursorState
+ {
+ Default, // Cursor
+ Hand, // Hand with a finger
+ Move, // arrows pointing to all directions
+ IBeam, // Text
+ Dragging,// Closed hand
+ Waiting, // Hourglass
+ WaitingBackground // Cursor + Hourglass
+ }
public static class GUI
{
public static GUICanvas Canvas => GUICanvas.Instance;
-
+ public static CursorState MouseCursor = CursorState.Default;
+
public static readonly SamplerState SamplerState = new SamplerState()
{
Filter = TextureFilter.Linear,
@@ -82,8 +96,7 @@ namespace Barotrauma
public static GUIStyle Style;
private static Texture2D t;
-
- private static Sprite Cursor => Style.CursorSprite;
+ private static Sprite[] MouseCursorSprites => Style.CursorSprite;
private static bool debugDrawSounds, debugDrawEvents;
@@ -184,7 +197,7 @@ namespace Barotrauma
public static void Init(GameWindow window, IEnumerable selectedContentPackages, GraphicsDevice graphicsDevice)
{
- GUI.GraphicsDevice = graphicsDevice;
+ GraphicsDevice = graphicsDevice;
var files = ContentPackage.GetFilesOfType(selectedContentPackages, ContentType.UIStyle);
XElement selectedStyle = null;
@@ -306,7 +319,7 @@ namespace Barotrauma
if (GameMain.ShowFPS || GameMain.DebugDraw)
{
DrawString(spriteBatch, new Vector2(10, 10),
- "FPS: " + (int)GameMain.PerformanceCounter.AverageFramesPerSecond,
+ "FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
Color.White, Color.Black * 0.5f, 0, SmallFont);
}
@@ -339,7 +352,7 @@ namespace Barotrauma
y += 15;
}
- if (FarseerPhysics.Settings.EnableDiagnostics)
+ if (Settings.EnableDiagnostics)
{
DrawString(spriteBatch, new Vector2(320, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, Color.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont);
DrawString(spriteBatch, new Vector2(320, y + 15), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, Color.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont);
@@ -415,10 +428,10 @@ namespace Barotrauma
}
else
{
- soundStr += System.IO.Path.GetFileNameWithoutExtension(playingSoundChannel.Sound.Filename);
+ soundStr += Path.GetFileNameWithoutExtension(playingSoundChannel.Sound.Filename);
#if DEBUG
- if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.G))
+ if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.G))
{
if (PlayerInput.MousePosition.Y >= y && PlayerInput.MousePosition.Y <= y + 12)
{
@@ -507,10 +520,13 @@ namespace Barotrauma
if (GameMain.WindowActive && !HideCursor)
{
spriteBatch.End();
- spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable);
- Cursor.Draw(spriteBatch, PlayerInput.LatestMousePosition, 0, Scale / 2f);
+ spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable);
+
+ var sprite = MouseCursorSprites[(int) MouseCursor];
+ sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f);
+
spriteBatch.End();
- spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
+ spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
HideCursor = false;
}
@@ -723,13 +739,14 @@ namespace Barotrauma
GUIComponent prevMouseOn = MouseOn;
MouseOn = null;
int inventoryIndex = -1;
+
if (Inventory.IsMouseOnInventory())
{
inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame);
}
- for (int i = updateList.Count - 1; i > inventoryIndex; i--)
+ for (var i = updateList.Count - 1; i > inventoryIndex; i--)
{
- GUIComponent c = updateList[i];
+ var c = updateList[i];
if (!c.CanBeFocused) { continue; }
if (c.MouseRect.Contains(PlayerInput.MousePosition))
{
@@ -740,8 +757,198 @@ namespace Barotrauma
break;
}
}
+
+ MouseCursor = UpdateMouseCursorState(MouseOn);
return MouseOn;
}
+
+ private static CursorState UpdateMouseCursorState(GUIComponent c)
+ {
+ // Waiting and drag cursor override everything else
+ if (MouseCursor == CursorState.Waiting) { return CursorState.Waiting; }
+ if (GUIScrollBar.DraggingBar != null) { return GUIScrollBar.DraggingBar.Bar.HoverCursor; }
+
+ // Wire cursors
+ if (ConnectionPanel.HighlightedWire != null) { return CursorState.Hand; }
+ if (Wire.DraggingWire != null) { return CursorState.Dragging; }
+ if (Connection.DraggingConnected != null) { return CursorState.Dragging; }
+
+ if (c == null || c is GUICustomComponent)
+ {
+ switch (Screen.Selected)
+ {
+ // Character editor limbs
+ case CharacterEditorScreen editor:
+ return editor.GetMouseCursorState();
+ // Portrait area during gameplay
+ case GameScreen _ when HUDLayoutSettings.PortraitArea.Contains(PlayerInput.MousePosition):
+ return CursorState.Hand;
+ // Sub editor drag and highlight
+ case SubEditorScreen editor:
+ {
+ // Portrait area
+ if ((editor.CharacterMode || editor.WiringMode) &&
+ HUDLayoutSettings.PortraitArea.Contains(PlayerInput.MousePosition))
+ {
+ return CursorState.Hand;
+ }
+
+ foreach (var mapEntity in MapEntity.mapEntityList)
+ {
+ if (MapEntity.StartMovingPos != Vector2.Zero)
+ {
+ return CursorState.Dragging;
+ }
+ if (mapEntity.IsHighlighted)
+ {
+ return CursorState.Hand;
+ }
+ }
+ break;
+ }
+
+ // Campaign map highlighted location
+ case LobbyScreen lobby:
+ {
+ if (lobby.CampaignUI?.Campaign.Map.HighlightedLocation != null) { return CursorState.Hand; }
+ break;
+ }
+
+ case NetLobbyScreen lobby:
+ {
+ if (lobby.CampaignUI?.Campaign.Map.HighlightedLocation != null) { return CursorState.Hand; }
+ break;
+ }
+ }
+ }
+
+ if (c != null && c.Visible)
+ {
+ // When a button opens a submenu, it increases to the size of the entire screen.
+ // And this is of course picked up as clickable area.
+ // There has to be a better way of checking this but for now this works.
+ var monitorRect = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
+
+ var parent = FindInteractParent(c);
+
+ if (c.Enabled)
+ {
+ // Some parent elements take priority
+ // but not when the child is a GUIButton or GUITickBox
+ if (!(parent is GUIButton) && !(parent is GUIListBox) ||
+ (c is GUIButton) || (c is GUITickBox))
+ {
+ if (!c.Rect.Equals(monitorRect)) { return c.HoverCursor; }
+ }
+ }
+
+ // Children in list boxes can be interacted with despite not having
+ // a GUIButton inside of them so instead of hard coding we check if
+ // the children can be interacted with by checking their hover state
+ if (parent is GUIListBox)
+ {
+ var hoverParent = c;
+ while (true)
+ {
+ if (hoverParent == parent || hoverParent == null) { break; }
+ if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; }
+ hoverParent = hoverParent.Parent;
+ }
+ }
+
+ if (parent != null)
+ {
+ if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; }
+ }
+ }
+
+ if (Inventory.IsMouseOnInventory()) { return Inventory.GetInventoryMouseCursor(); }
+
+ var character = Character.Controlled;
+ // ReSharper disable once InvertIf
+ if (character != null)
+ {
+ // Health menus
+ if (character.CharacterHealth.MouseOnElement) { return CursorState.Hand; }
+
+ if (character.SelectedCharacter != null)
+ {
+ if (character.SelectedCharacter.CharacterHealth.MouseOnElement)
+ {
+ return CursorState.Hand;
+ }
+ }
+
+ // Character is hovering over an item placed in the world
+ if (character.FocusedItem != null) { return CursorState.Hand; }
+ }
+
+ return CursorState.Default;
+
+ static GUIComponent FindInteractParent(GUIComponent component)
+ {
+ while (true)
+ {
+ var parent = component.Parent;
+ if (parent == null) { return null; }
+
+ if (ContainsMouse(parent))
+ {
+ if (parent.Enabled)
+ {
+ switch (parent)
+ {
+ case GUIButton button:
+ return button;
+ case GUITextBox box:
+ return box;
+ case GUIListBox list:
+ return list;
+ case GUIScrollBar bar:
+ return bar;
+ }
+ }
+ component = parent;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ static bool ContainsMouse(GUIComponent component)
+ {
+ // If component has a mouse rectangle then use that, if not use it's physical rect
+ return !component.MouseRect.Equals(Rectangle.Empty) ?
+ component.MouseRect.Contains(PlayerInput.MousePosition) :
+ component.Rect.Contains(PlayerInput.MousePosition);
+ }
+ }
+
+ ///
+ /// Set the cursor to an hourglass.
+ /// Will automatically revert after 10 seconds or when is called.
+ ///
+ public static void SetCursorWaiting()
+ {
+ CoroutineManager.StartCoroutine(WaitCursorCoroutine(), "WaitCursorTimeout");
+
+ static IEnumerable