From 3f11cd7de362cc9aa3a6cb8083c482d11c65233e Mon Sep 17 00:00:00 2001 From: emyhrberg <121192176+emyhrberg@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:54:49 +0100 Subject: [PATCH 1/6] Fixed admin permissions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dladmin now works properly. 👍 --- Core/Systems/PermissionHandler.cs | 53 +++++++++++++++++++++++-------- build.txt | 2 +- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Core/Systems/PermissionHandler.cs b/Core/Systems/PermissionHandler.cs index 693e7676..7567021a 100644 --- a/Core/Systems/PermissionHandler.cs +++ b/Core/Systems/PermissionHandler.cs @@ -77,16 +77,30 @@ public static bool LooksLikeAdmin(Player player) /// The player to add. They must not already be an admin. public static void AddAdmin(Player player) { - ModPacket packet = ModLoader.GetMod("DragonLens").GetPacket(); - packet.Write("AdminUpdate"); - packet.Write(0); - packet.Write(player.whoAmI); - packet.Send(); + if (Main.netMode == NetmodeID.MultiplayerClient) + { + ModPacket packet = ModLoader.GetMod("DragonLens").GetPacket(); + packet.Write("AdminUpdate"); + packet.Write(0); + packet.Write(player.whoAmI); + packet.Send(); + return; + } if (Main.netMode == NetmodeID.Server) { - admins.Add(player.GetModPlayer().currentServerID); - visualAdmins.Add(player.whoAmI); + string id = player.GetModPlayer().currentServerID; + + if (!string.IsNullOrEmpty(id) && !admins.Contains(id)) + admins.Add(id); + + if (!visualAdmins.Contains(player.whoAmI)) + visualAdmins.Add(player.whoAmI); + + ModPacket packet = ModLoader.GetMod("DragonLens").GetPacket(); + packet.Write("AdminUpdate"); + packet.Write(0); + packet.Send(player.whoAmI); SendVisualAdmins(); } @@ -98,17 +112,30 @@ public static void AddAdmin(Player player) /// The player to remove. They must be an admin. public static void RemoveAdmin(Player player) { - ModPacket packet = ModLoader.GetMod("DragonLens").GetPacket(); - packet.Write("AdminUpdate"); - packet.Write(1); - packet.Write(player.whoAmI); - packet.Send(); + if (Main.netMode == NetmodeID.MultiplayerClient) + { + ModPacket packet = ModLoader.GetMod("DragonLens").GetPacket(); + packet.Write("AdminUpdate"); + packet.Write(1); + packet.Write(player.whoAmI); + packet.Send(); + return; + } if (Main.netMode == NetmodeID.Server) { - admins.Remove(player.GetModPlayer().currentServerID); + string id = player.GetModPlayer().currentServerID; + + if (!string.IsNullOrEmpty(id)) + admins.RemoveAll(n => n == id); + visualAdmins.Remove(player.whoAmI); + ModPacket packet = ModLoader.GetMod("DragonLens").GetPacket(); + packet.Write("AdminUpdate"); + packet.Write(1); + packet.Send(player.whoAmI); + SendVisualAdmins(); } } diff --git a/build.txt b/build.txt index 17fcf77d..29260727 100644 --- a/build.txt +++ b/build.txt @@ -1,4 +1,4 @@ displayName = DragonLens author = ScalarVector & Tomat & HeartPlusUp & QuestionMark -version = 1.11.1 +version = 2.0 dllReferences = StructureHelper \ No newline at end of file From 91186c552cd6d429e650d229b973abee28831e4f Mon Sep 17 00:00:00 2001 From: emyhrberg <121192176+emyhrberg@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:17:40 +0100 Subject: [PATCH 2/6] Added so all UIStates are no longer visible or updated for non-admins --- Core/Loaders/UILoading/UILoader.cs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Core/Loaders/UILoading/UILoader.cs b/Core/Loaders/UILoading/UILoader.cs index 6e7ecd4f..4863a74a 100644 --- a/Core/Loaders/UILoading/UILoader.cs +++ b/Core/Loaders/UILoading/UILoader.cs @@ -1,7 +1,10 @@ -using System; +using DragonLens.Core.Systems; +using System; using System.Collections.Generic; using System.Linq; +using Terraria.ID; using Terraria.UI; +using static ReLogic.Peripherals.RGB.Corsair.CorsairDeviceGroup; namespace DragonLens.Core.Loaders.UILoading { @@ -62,14 +65,14 @@ public override void Unload() /// Where this layer should be inserted /// The logic dictating the visibility of this layer /// The scale settings this layer should scale with - public static void AddLayer(List layers, UIState state, int index, bool visible, InterfaceScaleType scale) + public static void AddLayer(List layers, UserInterface ui, int index, Func visible, InterfaceScaleType scale) { - string name = state == null ? "Unknown" : state.ToString(); + string name = ui?.CurrentState?.ToString() ?? "Unknown"; layers.Insert(index, new LegacyGameInterfaceLayer("DragonLens: " + name, delegate { - if (visible) - state.Draw(Main.spriteBatch); + if (visible()) + ui.Draw(Main.spriteBatch, Main._drawInterfaceGameTime); return true; }, scale)); @@ -86,8 +89,11 @@ public override void UpdateUI(GameTime gameTime) foreach (UserInterface eachState in SortedUserInterfaces) { - if (eachState?.CurrentState != null && ((SmartUIState)eachState.CurrentState).Visible) + if (eachState?.CurrentState is SmartUIState s && s.Visible) { + if (Main.netMode != NetmodeID.SinglePlayer && !PermissionHandler.CanUseTools(Main.LocalPlayer)) + continue; + eachState.Update(gameTime); if (eachState.LeftMouse.WasDown && eachState.LeftMouse.LastDown is not null && eachState.LeftMouse.LastDown is not UIState) @@ -96,6 +102,7 @@ public override void UpdateUI(GameTime gameTime) if (eachState.RightMouse.WasDown && eachState.RightMouse.LastDown is not null && eachState.RightMouse.LastDown is not UIState) Main.mouseRight = false; } + } } @@ -139,7 +146,13 @@ public override void ModifyInterfaceLayers(List layers) SmartUIState state = inter.CurrentState as SmartUIState; int index = state.InsertionIndex(layers); - AddLayer(layers, state, index, state.Visible, state.Scale); + AddLayer(layers, inter, index, () => + { + if (Main.dedServ || Main.netMode == NetmodeID.SinglePlayer) + return state.Visible; + + return state.Visible && PermissionHandler.CanUseTools(Main.LocalPlayer); + }, state.Scale); orderedInterfaces.Add(new Tuple(inter, index)); } From bd8904d4298f5b4bcc91af104ac226ef8dbc5424 Mon Sep 17 00:00:00 2001 From: emyhrberg <121192176+emyhrberg@users.noreply.github.com> Date: Wed, 31 Dec 2025 20:57:20 +0100 Subject: [PATCH 3/6] Remove welcome text for non-admins, etc - Fix: Remove welcome text for non-admins - Fix: Remove toolbars from the map for non-admins - Fix: Remove first time setup for non-admins - Fix: Handle silent exception: index out of range in BuffSpawner receivepacket() better and safer - Fix: FastForward. Packet underflow is still happening but no visual bugs yet --- Content/GUI/FirstTimeLayoutPresetMenu.cs | 24 +++++++++++- Content/GUI/ToolbarState.cs | 49 ++++++++++++++++++++---- Content/Tools/Gameplay/FastForward.cs | 23 ++++++++++- Content/Tools/Spawners/BuffSpawner.cs | 28 ++++++++++++++ Core/Loaders/UILoading/UILoader.cs | 5 +-- Core/Systems/MOTDSystem.cs | 4 ++ build.txt | 2 +- 7 files changed, 121 insertions(+), 14 deletions(-) diff --git a/Content/GUI/FirstTimeLayoutPresetMenu.cs b/Content/GUI/FirstTimeLayoutPresetMenu.cs index 5bc455b8..d22632f5 100644 --- a/Content/GUI/FirstTimeLayoutPresetMenu.cs +++ b/Content/GUI/FirstTimeLayoutPresetMenu.cs @@ -6,13 +6,30 @@ using ReLogic.Content; using System.Collections.Generic; using System.IO; +using Terraria.ID; using Terraria.UI; namespace DragonLens.Content.GUI { internal class FirstTimeLayoutPresetMenu : SmartUIState { - public override bool Visible => FirstTimeSetupSystem.trueFirstTime; + public override bool Visible + { + get + { + if (!FirstTimeSetupSystem.trueFirstTime) + { + return false; + } + + if (Main.netMode == NetmodeID.SinglePlayer) + { + return true; + } + + return PermissionHandler.CanUseTools(Main.LocalPlayer); + } + } public override int InsertionIndex(List layers) { @@ -80,6 +97,11 @@ public FirstTimeLayoutButton(string localizationKey, string presetPath, Asset()?.UserInterface?.Update(Main._drawInterfaceGameTime ?? new GameTime()); - UILoader.GetUIState()?.UserInterface?.Update(Main._drawInterfaceGameTime ?? new GameTime()); //We update/draw the tool browser here too to ease customization + GameTime gameTime = Main._drawInterfaceGameTime ?? new GameTime(); + + ToolbarState toolbarState = UILoader.GetUIState(); + if (toolbarState != null && toolbarState.Visible) + { + toolbarState.UserInterface?.Update(gameTime); + } + + ToolBrowser toolBrowser = UILoader.GetUIState(); + if (toolBrowser != null && toolBrowser.Visible) + { + toolBrowser.UserInterface?.Update(gameTime); + } Main.spriteBatch.Begin(default, default, default, default, default, default, Main.UIScaleMatrix); - UILoader.GetUIState()?.Draw(Main.spriteBatch); - if (UILoader.GetUIState()?.Visible ?? false) - UILoader.GetUIState()?.Draw(Main.spriteBatch); + if (toolbarState != null && toolbarState.Visible) + { + toolbarState.Draw(Main.spriteBatch); + } + + if (toolBrowser != null && toolBrowser.Visible) + { + toolBrowser.Draw(Main.spriteBatch); + } + + Tooltip tooltip = UILoader.GetUIState(); + if (tooltip != null && tooltip.Visible) + { + tooltip.Draw(Main.spriteBatch); + } - UILoader.GetUIState()?.Draw(Main.spriteBatch); Main.spriteBatch.End(); Main.mouseX = savedX; Main.mouseY = savedY; } + public override void PostUpdateEverything() { if (Main.netMode == NetmodeID.Server) diff --git a/Content/Tools/Gameplay/FastForward.cs b/Content/Tools/Gameplay/FastForward.cs index 92446b72..8263f40e 100644 --- a/Content/Tools/Gameplay/FastForward.cs +++ b/Content/Tools/Gameplay/FastForward.cs @@ -85,14 +85,35 @@ internal class FastForwardSystem : ModSystem { public override void Load() { + if (Main.dedServ) + { + return; + } + Terraria.On_Main.DoUpdate += UpdateExtraTimes; } + public override void Unload() + { + Terraria.On_Main.DoUpdate -= UpdateExtraTimes; + } + private void UpdateExtraTimes(Terraria.On_Main.orig_DoUpdate orig, Main self, ref GameTime gameTime) { orig(self, ref gameTime); - for (int k = 0; k < FastForward.speedup; k++) + if (Main.netMode != NetmodeID.SinglePlayer) + { + return; + } + + int extra = FastForward.speedup; + if (extra <= 0) + { + return; + } + + for (int k = 0; k < extra; k++) { orig(self, ref gameTime); } diff --git a/Content/Tools/Spawners/BuffSpawner.cs b/Content/Tools/Spawners/BuffSpawner.cs index a7137490..3c38d25d 100644 --- a/Content/Tools/Spawners/BuffSpawner.cs +++ b/Content/Tools/Spawners/BuffSpawner.cs @@ -33,8 +33,31 @@ public override void RecievePacket(BinaryReader reader, int sender) int type = reader.ReadInt32(); int duration = reader.ReadInt32(); + if (type <= 0 || type >= BuffLoader.BuffCount) + { + ModLoader.GetMod("DragonLens").Logger.Warn( + $"BuffSpawner: invalid buff type {type} (BuffCount={BuffLoader.BuffCount}) from sender {sender}. Ignoring."); + return; + } + + if (duration < 1) + { + duration = 1; + } + + // Cap duration to 1 hour just to prevent absurdly long buffs because it could cause issues/weird behavior + if (duration > 60 * 60 * 60) + { + duration = 60 * 60 * 60; + } + foreach (NPC npc in Main.npc) { + if (!npc.active) + { + continue; + } + Rectangle clickbox = npc.Hitbox; clickbox.Inflate(32, 32); @@ -171,6 +194,11 @@ public override void SafeRightClick(UIMouseEvent evt) public override void Draw(SpriteBatch spriteBatch) { + if (selected <= 0 || selected >= BuffLoader.BuffCount) + { + selected = -1; + } + if (selected != -1) { Texture2D tex = Terraria.GameContent.TextureAssets.Buff[selected]?.Value; diff --git a/Core/Loaders/UILoading/UILoader.cs b/Core/Loaders/UILoading/UILoader.cs index 4863a74a..a1a75f8c 100644 --- a/Core/Loaders/UILoading/UILoader.cs +++ b/Core/Loaders/UILoading/UILoader.cs @@ -134,8 +134,7 @@ public static void ReloadState() where T : SmartUIState /// public override void ModifyInterfaceLayers(List layers) { - List> orderedInterfaces = new(); - + List> orderedInterfaces = []; for (int k = 0; k < UserInterfaces.Count; k++) { UserInterface inter = UserInterfaces[k]; @@ -143,7 +142,7 @@ public override void ModifyInterfaceLayers(List layers) if (inter.CurrentState is not SmartUIState) continue; - SmartUIState state = inter.CurrentState as SmartUIState; + var state = inter.CurrentState as SmartUIState; int index = state.InsertionIndex(layers); AddLayer(layers, inter, index, () => diff --git a/Core/Systems/MOTDSystem.cs b/Core/Systems/MOTDSystem.cs index 26634229..442711e6 100644 --- a/Core/Systems/MOTDSystem.cs +++ b/Core/Systems/MOTDSystem.cs @@ -13,6 +13,10 @@ internal class MOTDPlayer : ModPlayer public override void OnEnterWorld() { + // Only show to users who can use tools (i.e. not guests) + if (!PermissionHandler.CanUseTools(Player)) + return; + if (Mod.Version > seenMotd) { string MOTD = LocalizationHelper.GetText("MOTD", Mod.Version); diff --git a/build.txt b/build.txt index 29260727..8d9cda88 100644 --- a/build.txt +++ b/build.txt @@ -1,4 +1,4 @@ displayName = DragonLens author = ScalarVector & Tomat & HeartPlusUp & QuestionMark -version = 2.0 +version = 2.1 dllReferences = StructureHelper \ No newline at end of file From 06afb88c0efcd3521f8a8467fa518cd978520af8 Mon Sep 17 00:00:00 2001 From: emyhrberg <121192176+emyhrberg@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:06:25 +0100 Subject: [PATCH 4/6] Comment out some Logger calls --- Core/Systems/PermissionHandler.cs | 2 +- Core/Systems/ToolSystem/Tool.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/Systems/PermissionHandler.cs b/Core/Systems/PermissionHandler.cs index 7567021a..8cbb2441 100644 --- a/Core/Systems/PermissionHandler.cs +++ b/Core/Systems/PermissionHandler.cs @@ -183,7 +183,7 @@ private static void SendVisualAdmins() public static void HandlePacket(BinaryReader reader, int whoAmI) { int operation = reader.ReadInt32(); //First read the operation type - ModLoader.GetMod("DragonLens").Logger.Info("Recieved permission packet: " + operation); + //ModLoader.GetMod("DragonLens").Logger.Info("Recieved permission packet: " + operation); if (operation == 0) //Set admin { diff --git a/Core/Systems/ToolSystem/Tool.cs b/Core/Systems/ToolSystem/Tool.cs index 175cc36b..6444d924 100644 --- a/Core/Systems/ToolSystem/Tool.cs +++ b/Core/Systems/ToolSystem/Tool.cs @@ -114,11 +114,11 @@ public void NetSend(int toClient = -1, int ignoreClient = -1) return; #if DEBUG - if (Main.netMode == NetmodeID.Server) - Mod.Logger.Info($"Sending packet for tool {DisplayName} ({Name}) from server"); + //if (Main.netMode == NetmodeID.Server) + // Mod.Logger.Info($"Sending packet for tool {DisplayName} ({Name}) from server"); - if (Main.netMode == NetmodeID.MultiplayerClient) - Mod.Logger.Info($"Sending packet for tool {DisplayName} ({Name}) from {Main.LocalPlayer.whoAmI}"); + //if (Main.netMode == NetmodeID.MultiplayerClient) + // Mod.Logger.Info($"Sending packet for tool {DisplayName} ({Name}) from {Main.LocalPlayer.whoAmI}"); #endif ModPacket packet = Mod.GetPacket(); From 4e81c9d2e73f7ecf9c298668832ee7c8b976439b Mon Sep 17 00:00:00 2001 From: emyhrberg <121192176+emyhrberg@users.noreply.github.com> Date: Sun, 29 Mar 2026 01:13:48 +0100 Subject: [PATCH 5/6] Improve Player Manager and misc UI Player manager improvements: - Added new buttons, filters, stats, and custom UI! UI interaction improvements - Now draws a highlight of the tool when opening a tool's window (DraggableUIStates,Tool,etc) - Added so panels being dragged are always ordered on top by updating UILoader - Added so panels cant be clicked if they are below other panels by updating UILoader.UpdateUI() - Hide tooltips if hovering another panel with SmartUIElement.CanShowTooltip and UILoader.CanShowTooltip Misc: - Added custom noclip shift speeds - Fixed StyledScrollbar so you can drag it - Fixed ToggleButton to use Asset instead of a string path - Added better grid list toggle to Browser --- Assets/Filters/Dead.png | Bin 0 -> 841 bytes Assets/Filters/HealthFull.png | Bin 0 -> 858 bytes Assets/Filters/HealthLow.png | Bin 0 -> 1095 bytes Assets/Filters/Shimmer.png | Bin 0 -> 1010 bytes Assets/GUI/BringHere.png | Bin 0 -> 993 bytes Assets/GUI/Frozen.png | Bin 0 -> 1518 bytes Assets/GUI/GoTo.png | Bin 0 -> 1099 bytes Assets/GUI/Grid.png | Bin 0 -> 221 bytes Assets/GUI/List.png | Bin 0 -> 337 bytes Assets/GUI/ShimmerBackground.png | Bin 0 -> 18190 bytes Assets/GUI/Sort.png | Bin 561 -> 747 bytes Assets/Misc/Shimmer.png | Bin 0 -> 18190 bytes Assets/Stats/Ammo.png | Bin 0 -> 274 bytes Assets/Stats/BiomeBackground.png | Bin 0 -> 1538 bytes Assets/Stats/BossDamage.png | Bin 0 -> 1235 bytes Assets/Stats/Coin.png | Bin 0 -> 299 bytes Assets/Stats/Defense.png | Bin 0 -> 465 bytes Assets/Stats/Distance.png | Bin 0 -> 954 bytes Assets/Stats/Heart.png | Bin 0 -> 363 bytes Assets/Stats/HeldItem.png | Bin 0 -> 879 bytes Assets/Stats/InventoryCount.png | Bin 0 -> 1511 bytes Assets/Stats/Mana.png | Bin 0 -> 410 bytes Assets/Stats/MinionCount.png | Bin 0 -> 746 bytes Assets/Stats/NearbyEnemies.png | Bin 0 -> 587 bytes Assets/Stats/Ping.png | Bin 0 -> 874 bytes Assets/Stats/PlayerFull.png | Bin 0 -> 823 bytes Assets/Stats/PlayerHead.png | Bin 0 -> 486 bytes Assets/Stats/Position.png | Bin 0 -> 572 bytes Assets/Stats/PvE.png | Bin 0 -> 1746 bytes Assets/Stats/PvP.png | Bin 0 -> 2014 bytes Assets/Stats/Stopwatch.png | Bin 0 -> 650 bytes Assets/Stats/TeamWhite.png | Bin 0 -> 292 bytes Assets/Stats/Time.png | Bin 0 -> 816 bytes Assets/Stats/WhiteBackground.png | Bin 0 -> 169 bytes .../BackgroundOptionFilter.cs | 29 + .../PlayerManagerFilters/BiomeFilter.cs | 62 ++ .../ButtonOptionFilter.cs | 28 + .../PlayerOptionFilter.cs | 32 + .../PlayerManagerFilters/StatOptionFilter.cs | 29 + .../PlayerManagerFilters/TeamFilter.cs | 33 + Content/GUI/Browser.cs | 78 +- Content/GUI/DraggableUIState.cs | 17 + Content/GUI/FieldEditorMenu.cs | 3 +- Content/GUI/Filters.cs | 57 +- Content/GUI/FirstTimeLayoutPresetMenu.cs | 2 +- Content/GUI/LayoutPresetBrowser.cs | 2 +- Content/GUI/StyledScrollbar.cs | 34 +- Content/GUI/ThemeMenu.cs | 4 +- Content/GUI/ToggleButton.cs | 60 +- Content/GUI/ToolbarCustomizationElements.cs | 26 +- Content/GUI/ToolbarElements.cs | 2 +- Content/GUI/Tooltip.cs | 2 +- Content/Tools/CustomizeTool.cs | 2 +- Content/Tools/Developer/AssetManager.cs | 8 +- Content/Tools/Editors/AccessoryTray.cs | 2 + Content/Tools/Editors/ItemEditor.cs | 2 +- Content/Tools/Gameplay/Noclip.cs | 20 +- Content/Tools/Gameplay/Paint.cs | 5 +- Content/Tools/Gameplay/SpawnTool.cs | 1 + Content/Tools/Gameplay/Time.cs | 4 +- Content/Tools/Gameplay/Weather.cs | 7 +- .../Drawers/ModifyPlayerDrawInfo.cs | 44 + .../Drawers/PlayerBackgroundDrawer.cs | 234 +++++ .../Multiplayer/Drawers/PlayerStatDrawer.cs | 904 ++++++++++++++++++ Content/Tools/Multiplayer/PlayerManager.cs | 659 +++++++++---- .../Multiplayer/PlayerManagerNetHandler.cs | 128 ++- .../Multiplayer/PlayerManagerSettings.cs | 170 ++++ .../Tools/Multiplayer/PlayerManagerSystem.cs | 83 ++ .../Tools/Multiplayer/Trackers/BiomeHelper.cs | 186 ++++ .../Multiplayer/Trackers/NPCHitTracker.cs | 79 ++ .../Tools/Multiplayer/Trackers/PingTracker.cs | 94 ++ .../Trackers/PingTrackerNetHandler.cs | 123 +++ .../Multiplayer/Trackers/SessionTracker.cs | 87 ++ .../Trackers/SessionTrackerNetHandler.cs | 60 ++ Content/Tools/Spawners/BuffSpawner.cs | 6 +- Content/Tools/Spawners/DustSpawner.cs | 5 +- Content/Tools/Spawners/ItemSpawner.cs | 4 +- Content/Tools/Spawners/NPCSpawner.cs | 3 +- Content/Tools/Spawners/ProjectileSpawner.cs | 3 +- Content/Tools/Spawners/SoundSpawner.cs | 4 +- Content/Tools/Spawners/TileSpawner.cs | 4 +- Content/Tools/Visualization/Hitboxes.cs | 8 +- Content/Tools/Visualization/Inspect.cs | 6 - Core/Loaders/UILoading/SmartUIElement.cs | 3 + Core/Loaders/UILoading/SmartUIState.cs | 5 + Core/Loaders/UILoading/UILoader.cs | 170 +++- Core/Systems/ToolSystem/Tool.cs | 21 +- DragonLens.cs | 8 +- .../GUI/en-US_Mods.DragonLens.GUI.hjson | 9 +- .../GUI/ru-RU_Mods.DragonLens.GUI.hjson | 9 +- .../GUI/zh-Hans_Mods.DragonLens.GUI.hjson | 9 +- .../Tools/en-US_Mods.DragonLens.Tools.hjson | 252 ++++- .../Tools/ru-RU_Mods.DragonLens.Tools.hjson | 252 ++++- .../Tools/zh-Hans_Mods.DragonLens.Tools.hjson | 250 ++++- 94 files changed, 4107 insertions(+), 326 deletions(-) create mode 100644 Assets/Filters/Dead.png create mode 100644 Assets/Filters/HealthFull.png create mode 100644 Assets/Filters/HealthLow.png create mode 100644 Assets/Filters/Shimmer.png create mode 100644 Assets/GUI/BringHere.png create mode 100644 Assets/GUI/Frozen.png create mode 100644 Assets/GUI/GoTo.png create mode 100644 Assets/GUI/Grid.png create mode 100644 Assets/GUI/List.png create mode 100644 Assets/GUI/ShimmerBackground.png create mode 100644 Assets/Misc/Shimmer.png create mode 100644 Assets/Stats/Ammo.png create mode 100644 Assets/Stats/BiomeBackground.png create mode 100644 Assets/Stats/BossDamage.png create mode 100644 Assets/Stats/Coin.png create mode 100644 Assets/Stats/Defense.png create mode 100644 Assets/Stats/Distance.png create mode 100644 Assets/Stats/Heart.png create mode 100644 Assets/Stats/HeldItem.png create mode 100644 Assets/Stats/InventoryCount.png create mode 100644 Assets/Stats/Mana.png create mode 100644 Assets/Stats/MinionCount.png create mode 100644 Assets/Stats/NearbyEnemies.png create mode 100644 Assets/Stats/Ping.png create mode 100644 Assets/Stats/PlayerFull.png create mode 100644 Assets/Stats/PlayerHead.png create mode 100644 Assets/Stats/Position.png create mode 100644 Assets/Stats/PvE.png create mode 100644 Assets/Stats/PvP.png create mode 100644 Assets/Stats/Stopwatch.png create mode 100644 Assets/Stats/TeamWhite.png create mode 100644 Assets/Stats/Time.png create mode 100644 Assets/Stats/WhiteBackground.png create mode 100644 Content/Filters/PlayerManagerFilters/BackgroundOptionFilter.cs create mode 100644 Content/Filters/PlayerManagerFilters/BiomeFilter.cs create mode 100644 Content/Filters/PlayerManagerFilters/ButtonOptionFilter.cs create mode 100644 Content/Filters/PlayerManagerFilters/PlayerOptionFilter.cs create mode 100644 Content/Filters/PlayerManagerFilters/StatOptionFilter.cs create mode 100644 Content/Filters/PlayerManagerFilters/TeamFilter.cs create mode 100644 Content/Tools/Multiplayer/Drawers/ModifyPlayerDrawInfo.cs create mode 100644 Content/Tools/Multiplayer/Drawers/PlayerBackgroundDrawer.cs create mode 100644 Content/Tools/Multiplayer/Drawers/PlayerStatDrawer.cs create mode 100644 Content/Tools/Multiplayer/PlayerManagerSettings.cs create mode 100644 Content/Tools/Multiplayer/PlayerManagerSystem.cs create mode 100644 Content/Tools/Multiplayer/Trackers/BiomeHelper.cs create mode 100644 Content/Tools/Multiplayer/Trackers/NPCHitTracker.cs create mode 100644 Content/Tools/Multiplayer/Trackers/PingTracker.cs create mode 100644 Content/Tools/Multiplayer/Trackers/PingTrackerNetHandler.cs create mode 100644 Content/Tools/Multiplayer/Trackers/SessionTracker.cs create mode 100644 Content/Tools/Multiplayer/Trackers/SessionTrackerNetHandler.cs delete mode 100644 Content/Tools/Visualization/Inspect.cs diff --git a/Assets/Filters/Dead.png b/Assets/Filters/Dead.png new file mode 100644 index 0000000000000000000000000000000000000000..1a8095fa6187b35b260c2892a69bb01e9efa54c8 GIT binary patch literal 841 zcmV-P1GfB$P)x zVPOd73Q3hfsuVeb#zOoD?k-KxghMnU2rAmzsK;U;O+v`eJ@&cpn3vnD-6{6t&AwUQ z`@Z*PX1Ss$Y@S0>A8&7OU<89fh{kL-gPqIexQZ5w1?=VJWx!^$3Anz#4tR5O1IRhJ zvyF`nzV=sL z>tG_6%YlD18ije38^2SjRJbu1uxJ|h`+Y#S+f6dt-Q6V&y}!THk4O@k|M~p~VG8Fv zoemRgP91Y+fUCM-=7~f?5i^g+nq^t=_wPL#+p*c+!>J6htuf< zahlhtSQLEL!-rU?Y2m{065DcHsr>k+PR3e#& z!(m-!ip3)Fi$o%%3p^eVc@Jxu5g3rPJ|2(9L_@S6A0Npg%m_0_`|0V4_{s8b!suu; zBJY=$mJGtc-Q68I?J(l$bPBk$vqR>w&(F`#iPnhd*YEd;?Ql5$*-1^z2n^Wmc3q87 zoSd9+v1Sal6LK}0`}=z^LZJ}358z#{P$&RCKR@f>GKHr|#O?nn4)Y+S*dYoeVR~=ktJ8>&)KX4igQr>X8}jCOkcH9+^bO za0k7*x-!VESkf{c3pX~>(VVTn$|5CVxmwty5kH44pX$DdtdtS323<#QxWkr zA)d3RUXff7u{;c6O!NCY zrn~7WoOQ*aBTt!L;E2Wd+Y>{t-pG3rTF(!nsuF?fS-k{&S)~`WA^u<>nM}3~S`xJO zBG?k!b_V`8zN&lLVlLxf);56W&X4(ofm|*JCF53c#I!}#t!$HLcR0&~)CM%gdn-iG zxP3sIZ`1QSRl}ukUtt(KGSJ+^QhulfBGL~?PNeM+Ca>C5?Z(-3z^=BvbS`_EH&jCc zoI6YIKJb-GWp#Ud8+x%=^jSYpC={S15((9s6GKBoz}b@{)F-f~bKE1k9Q!O+J9!qC z9P)70p`Iw0r~OAqPXVLR7_hs$+b0Z&c)R1BogLuOR literal 0 HcmV?d00001 diff --git a/Assets/Filters/HealthLow.png b/Assets/Filters/HealthLow.png new file mode 100644 index 0000000000000000000000000000000000000000..57e17e5b7374f777af3f5ebf99016a93f50756ad GIT binary patch literal 1095 zcmV-N1i1T&P)0q1eY}G zsqUVx`o6BJ?jcphO$y(rbpW>Cz=MdCl;OxCGK8P%bsA>DrKP0~N)HbY=~@mT3<<_H zLxR^Qw!5P`K>@)>Q!_BcV9x>+o0dcWFS*blT6J^TAH!TmgAwon2b1MyJgzaj(G z2J)|YtmwG-Gc>U&e5CSRXy0bioEbmoF1WR_vZ5k_#l^)iUM`nGMCi`0eoXMF9(zU< z($c-LD2i}hvG=0_`TK7a2yy;LYtTSZ`QRalBIwpL7?2r?** zK`8f?7+;ho5lMtk>*vr&u$^iv|9!i@zTSayZEdZk4zOlarJ1espvc+TGpV&N{%Rc$}kv+=~GrWq=%Tix`F!js$U6;qwN3Ahzr6 z3=E0^>ZWLujZbrYa_2U315~auusBQ_MFta&=GY28zw`+Sy!+YXfP8CfD>&g}pnc8L z)6<^FTfosJq8zH7utDackb)kZ6ZUBl<|J)tK#-9C&6+W)*zS&PEh^iFB~1Z5ntl_8 za(o=6U?+HVc7a3*+j$WKtE;QQtNHo)j&+WYk2~6KgzY!5A0h$pr==l4re;wm#nYJ!v?DJa><7KlsC*|cPKQE1^nD<6fZr4! zXrrEV!-##^gkPm#;+;~gxnz*3GK@Iv?d=uyRn&SZC<=YD6hWxASfmIdzGxA_|3JhCAAD1=C`GHWQj;cW$TYV$CSeGIj+>J0Iu zzmD)Dr}ef2sEpvqLpNyK6-O9?Z3PJTeW<&;D_I%|P}{n|Gr<-bd*0LA0`SK16G9l0 z-l|EGs{z*LcJsoaKe432wgCJ#SFFyw5@+sGBMXIln6eL2(_YT8wg8QfzOT0wpyu#d zefoKr@9!e+Il%a?d~Q78XK^CP(W9%p9&DoNz&X8T07hOB$(t>#y!03YUYqF_FY5ux zh2=hiJ;&*MY6QdOVM_oahtKObLk);?hnV?QvfS*!oi8zpTr36!V}p~_JiCWuyBlab z_>sP=05$tQ&^>ktU!a%836rI;4S5In#-#{5(*C!8T8@I`ibXR7?$8+pF_2du~z&u*Dgi)Y1T_7WU(=P6J$Iq zfXHPlu4#Tpcbs~SPoEoS?CTkpNA?n3s;1Vbd9dIwl$BxlR)YR{KN}S$Q`M5$&(1S( z<&&&KFv&?t{{CT+kIQO#t2Y7iGL1tiqRT;kpDQ4t;gwGGRK-9{>OV07*qoM6N<$f*vo_^Z)<= literal 0 HcmV?d00001 diff --git a/Assets/GUI/BringHere.png b/Assets/GUI/BringHere.png new file mode 100644 index 0000000000000000000000000000000000000000..284d2b475cfd17d7458b6c33860048ce4a3ca02b GIT binary patch literal 993 zcmV<710MW|P)2}wTLrOnVYEgO0)ik^oAiC$GgD^Xy9w|0 z-FRP;k{>+IxpU@x%EHG5=uM}B@7I`D`CJa9~Gg?WEqk{!M~Y!Qljpp>eBrdW&yGKbfPfPRztpHHF3z8%O+`W>1)H}UtrVdo5Ry{;x_M?NdNhjR zkMcOMuf*~}6in2|0$3S0!jpZL7bqBbWVwK10vYpYZxzn4AgaxR_ zhNWiV@_^9pM6lLi@jBAVL*w-F(?=^=Udlwaqfh=ry0hbW5d$gs2s49pv z1TN2pR4}kn?0Wq9-c5>ENoPNJ-{ljZeCDm*KDTbbW^59c>%Bbl-{`M`s{S*2XX2vX z&6Y9#;j8}r-#2Szmx1kFielmFEwI*cetvijSltH{K(0XAUFRMak!FI%N`jTfT8WkC z9i+-27Jm&Amb_*dMBeTo$v)I*k*GHb-@i(254FynYC42PBISjGfY>B<;L+n9iA9z&Q_Ils}4jYcxY=CPt P00000NkvXXu0mjfs$<;) literal 0 HcmV?d00001 diff --git a/Assets/GUI/Frozen.png b/Assets/GUI/Frozen.png new file mode 100644 index 0000000000000000000000000000000000000000..7b078713e5134cbd488d293b547fca4875d38b1b GIT binary patch literal 1518 zcmVzC_8`fxa`7gttMxq1WbVJu6$+8EJ? z2eF=L35YZKfDpnWAf-LY6O0U)Bo;lhmZLJ45xSj4#Tm?+R>}=ezt6(k7ErbJL&n5I zi(m83@&$}`B4V?B5{CeYn{kg2)JY~0<> zO+}-bmOqlJx2Rj&!nZ>lgH1FqKzQzakijAceu0cn6LC zK0H1JdmPk`@=|lClfvmY$8Lem?g(!3=sw&^Zi0>bw{^2~Yb~F*_2B9AS~6SqyJFm)t%YWeCd8cj4Jy6}ttL)?~z;;JG>pYJgJbNIDu7wO$*+OEY<7yr25s5NYXY z>^pXXH*4w`TQZEqlw^7%zSs#Y8RrmnxvBc`8JAQ0(H2^oLG<)9t4!15Z{xo0@i;M% z9HneZ5iWNl70sF}Z%N~8zb$qGCK4U-C?yfvz;H?&!px9hQy~(NC>=sc2Tm0xV*G-% zN07EKA{1gsqM+!KDg5WPYJ@WQHsIy%TkTeOR?L~moUu06JbDM^#c9kL;pLjECCi>J zLv`(E!y_d_Ltw|=ADKIE9@Sf1xZDoROl%V9_62$P@)6``3MP)tU_y2ZY4KsS5-h)W z0bAUW*a=7}dGF>T@#T-rJWx3eB8oa6T#;$VP>O`aRAl`RT%T@vEGwo@;HR3k2pwTb zfm6gxK>b!=o|;|A=5-(8%QqOGU%=SXGS+7l4jZ+&*Q zeqeVPUAb~2i>6Lu)z?RO@R|Z_#sDs-mtDK|5e$X!3l~Xt#pKJ1S^8`vpRSxicHDpK zWQ;!W(_!fJ_0!nU!mNK4SaZ9++(G`iSrknyV*Evg1Z{DQzF-Wi8zRW#3Z=tCS&l&p zfngYITOURn%5uW@wjV^`#FmuIsFWl|4UfaU!HbZZt)8>0jXny9%iP4N7I z1SX@YsOjS3yhQqKDR}CSF(r`6%&WV2{eU2>6zw6cqxlW;tO62p3X84xq^^Brc7KB> za2}mKVdmt7c(k~e*B2FW*93`*Xug=2LggDD51j?>%tiDEbM(&L2YBq(K?Z^m`V7TI z@q--c>Y%Fwmfe0Ot3KF4PDUD?zu2sH$Sl`Gk_SxKwT)qI@l<>-88b4Dk@2Q;*vL62 zo!Qq;WN)*V>7|!i4kK*fYENWSOMoG3$xN7i-ka~Pv*uhnK+gC=^3F{sE8WH0AAHWj z`PZ>(?RNHbXl9qs<(>;|v4^4fA2V)jRWuFHBcpUKeIn5;LkP1S0y-j+z3pE5!wzPa z-x&4gCZdRflU7)oFNK2wU9oyS&&?mlwd1mI2tjpS3oq>s(Ud%#gH4K^3;$B5PxJu` z%sAgps@usxe}Mg6J{H&7d8n$1he}c?&+g`qJk93T2yKCr7nUI*AcQ#Oj32iQB9xuC z8XTb%707*qoM6N<$f)ria(EtDd literal 0 HcmV?d00001 diff --git a/Assets/GUI/GoTo.png b/Assets/GUI/GoTo.png new file mode 100644 index 0000000000000000000000000000000000000000..36b246860b58e308e36ecc2f4e7eed0abeb5bf79 GIT binary patch literal 1099 zcmV-R1ho5!P)(flZ7-AxlKbA!L7M=Dn_x z?)PS9=Wl0bN6mMsnX2mU`s?a?q^cMMukDKl+JJGLLA^%c>4&FOsRSmAc58NQJ{ag* zZ%*{I-{H5X`-^~31yBx$SKQzD-y-nLBh%{43U=PT3oH)FY8k2&4B_ZcNPJ_^tNzYy z%0t)y=kIHX$k`vxH0(S5^+LK1T~JPzA#ptseyOtCPDM)Qg8+ z478KymzF-tG~a1-6dmBbhe-?6ESF$~fwrCx2HVWoU-(=N#kiwzCmbA396ucRkB!f@ z^}HEX&p$n#db;JF?JzLF+*vq{Y7to{0^+ZGfPv5XU>Ne18T)85ocX;P6&Kq%>@2K{OzY_kEXg2c7v zUPRhHLftU7dn@v}QJgu|UT&Kikr3cEWA!_R`bLm}V8Ge6><1&G7Oo5o<0e$1#qI_O zd1hJ%vgQ^LZ*1=PE`+w#Uv9BT4J5k- z>GXr=dw=&8L&G?E>-ZTl+&8L;rzQApWgX6^;*?>YAHtK}#iE{P9BIU%5ITGDyC{Mn8YZ&#u~lHi->Z9krj)Tk^1bHBrzi8 z&@+SyViqAGEsaPjF>J0rMXwtH&3M%`ti`j185JtQavq8QSaAw9zGnQgm@h%%C11k9=~;#%_EQ=+ZFtU9^u^GS#~{IFU5{Ie=Lw5P ztCPi2&UN@O>)c>*&vve2QrmUQ>=lDf1MBPu8MzjVyqt~+#!DOVZeeDq@d)>Ic*|1& PbQ*)FtDnm{r-UW|p;buw literal 0 HcmV?d00001 diff --git a/Assets/GUI/List.png b/Assets/GUI/List.png new file mode 100644 index 0000000000000000000000000000000000000000..165b3f3239b22efdbe063fd42f918cd5e615c208 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DX&fq{|P z)5S3)qV;X4HQylvo?1U`#dD0@Lg5dC?lf8#2&=HMiHVtssf8^&rm^ypTIMAM!;nSa zE-cgkXS4U1p`r0I6VuF64n_{fD~22Hth==>!>R3-4M#(^)Mm45rh(imUR&2BM^x0GIw!X|NbkI~cSa5gR#IM`+E@(=+UH%|m zCvv{&Y1&+QzB=)q8B!OQ++jU&`sTlOqvnvdITb3G1&Y-JjyIfX@=9a;&6*g he7=(^i$OV)ylj4>;&Q#fa$p!Sc)I$ztaD0e0stU!d8GgV literal 0 HcmV?d00001 diff --git a/Assets/GUI/ShimmerBackground.png b/Assets/GUI/ShimmerBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..8528bcef2d03c74a96747d5a17a3347be96145fe GIT binary patch literal 18190 zcmV)1K+V62P)KvrVg2w`5C}uLALEe3B$R*&DPRaC1ml9`CYQ*zWXZB6S!GvRZ7(xBJH6cAPW^wL_srd$ zm4*2qeXeHb&b{Zn=Y600dw$P*G)>bYP1A^?2-7qPf`Fuxg2*73&r+>cDHe-_VW>VM zKZ}JvlF1~EW?lWB&E(LckfEVrf+*to=btB?Nm0lb*)=`QnRDlG9EZutY20>)FMjRw zWV2b!$Rq^h`f{FhHlwZ~zxR%Iv!&jXL)Ufs`}^74+8~{9=(qu6BO~$$E-x)(S{Ayd z(Q4G$dGx+7M=qDc4+H!lBAH0wbzR!+Ho06z-7C6Ax831&uXzoZ=PqCv21k!SPbyO+ z(gHNiROgZV-FfI*0>9Jbw|@OEICP|+&wchJr_TQ$me$tU+AQ^*NZ36qAx!{ zDwAWYRwpt{s*QDO^$OM67F;v(_)ud_3EAuP!tb`dIB8@PLSl?VDmo1@1u>j?AC7wEd65X)S(ZB%9bU+Kydr~VC z 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 literal 561 zcmV-10?z%3P)}&e#WNXr6V&PG)(JgA4SjW<$J?s z8{kv`WF;;{D>eZ2)Bu`_J2C}CI7oK`pozTz2jGDc7T!Kx2z&Fy`b1;f0oW<7!nH^{ zdz>>6PK3V2)i=HS0mK8>4uFC~$=FhGJk_jE-RNO3;Xb z-e@wFhqN7vI)T2Y{Ct?Jl+_HS3Pd!f2{+y1Qq@5 z{5XF)q5c>wL;>kt+i|u1GeD~j$e|nQ1=+EKvrVg2w`5C}uLALEe3B$R*&DPRaC1ml9`CYQ*zWXZB6S!GvRZ7(xBJH6cAPW^wL_srd$ zm4*2qeXeHb&b{Zn=Y600dw$P*G)>bYP1A^?2-7qPf`Fuxg2*73&r+>cDHe-_VW>VM zKZ}JvlF1~EW?lWB&E(LckfEVrf+*to=btB?Nm0lb*)=`QnRDlG9EZutY20>)FMjRw zWV2b!$Rq^h`f{FhHlwZ~zxR%Iv!&jXL)Ufs`}^74+8~{9=(qu6BO~$$E-x)(S{Ayd z(Q4G$dGx+7M=qDc4+H!lBAH0wbzR!+Ho06z-7C6Ax831&uXzoZ=PqCv21k!SPbyO+ z(gHNiROgZV-FfI*0>9Jbw|@OEICP|+&wchJr_TQ$me$tU+AQ^*NZ36qAx!{ zDwAWYRwpt{s*QDO^$OM67F;v(_)ud_3EAuP!tb`dIB8@PLSl?VDmo1@1u>j?AC7wEd65X)S(ZB%9bU+Kydr~VC z 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 TxizbR{$cQR^>bP0l+XkKwnb$b literal 0 HcmV?d00001 diff --git a/Assets/Stats/BiomeBackground.png b/Assets/Stats/BiomeBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..19db59a060bedaee9f2a8c54f584521ea5c61f78 GIT binary patch literal 1538 zcmV+d2L1VoP)b4#edoyoC=4Gi=6)TKXr48+VwOe# z1^}JR8}RT5AY*973;>PH8}O(A(!va_NMnlT-hfBU$wyTL`TijjDIF*lfqe(_JSK?> z67PEh9vE8DH9F5G^a3uOtHWCdOoaWVn(*UK?(>!KI28b3+Qte1$k}sIy-R`pL#E!~ zvlK|_$Taw%rF2%or2v_xS_)hkm8vW7tmxgHLDEocl>}rY4a&O;6ksJowfHNS-3T^W zk`dGuv*zB^ML#FeAPZAraC*DS4YL4gmMU9i-49Au*aJY*T zBTy2RK}Fz9*B@-LhS93kRy!v-9~zEqL4L_#6Xasvacu_Efu@>L3Q{Rxspd?gni5qL zkj9pvF2vXfq=Unf?6 zzY9;q%gN9WNAbgS=jz?l0>?TMg-34*Vx0(~2XaqGetNXN@cysm|zzyC4n zS_9ZizfLg?F=@0ax#)i)eGhUt$Y=DsF{FY&aMIPFz^+PQ^M{}CnEv`Jyt1hsdvI_Vn@awnqA^y2|EHa0S8Wl*Zn zNk;QZ@=E-({w$j-0f@=sNXaO8XCz#5BH5jz6P3htmh8V0akb#eqk zB^7h=Bs(M$382|>b}B)0GD@nDR7>QQeosn*&awjAcOK)97~!wauj0_`RaRhUPekVN)VZ7$QFgXZsCLC2=;rGPSmyiakeeQKN_KP9#N5UKcsRhyduzKHQe0x3=hCBKHq3p}Th zKuL%>#d{VgQDYn(Js}k4>2vyk6hZ~3eZ!hEEn&==un=CN^OFp8J&EW=z-p%9rM zb>g>n19F>&!rWD04)Cw*=AZ<*zlFzQJOfX}s-$i(%7lVg;QA)}PT6bn+90-v>w&>& z_5THOTac5XkwCJR4%y^RVgY)VBYs@`bDsT?%%Z^RT9xyfjqy4eW;}z!BNd7u5~S1$ zHv0g{F4^RwYWAO4dQP-D|}CFNqz|?=Vg4q5uE@07*qoM6N<$f-BkM`v3p{ literal 0 HcmV?d00001 diff --git a/Assets/Stats/BossDamage.png b/Assets/Stats/BossDamage.png new file mode 100644 index 0000000000000000000000000000000000000000..568a63deefae3c384dfa9406ca97499ec1a51b9b GIT binary patch literal 1235 zcmV;^1T6cBP)FdytF{Kh(#6CWsnocH z4uye48;wg%TGJo;M~$*}#9t)+i%FX%CYm%91f&|MG_lgU0WB?1q|zb;x@l6n*_pT8 zYwmpy65~V%O{G72CMTKq&0W5G&v(vwLsCjCq_G9v9XAPJ;RzVYz-23=Am#y*K?Xwz z0-h8&)!!<4K%0mlBtZxP&bh|I`O!t8_*mj97CX0DagAM*w}OeQIDYs*Y6OU+zd125 z!yhu<*DPJv#lG&Z5U+Ya7%9N3W4L(f3evYXXr!!f&t*j>Gp}sv6*vAofB=z+pL?6+*Uzpcg=FXEO52XZ3^C+1HTyh?0UQ{Lu17&@htJs)cr;Rq zU0q#xxxZsnVarxkgg|gozk&bJC|M<3x~2jXeXVle>Xr0hbBi8eS(g7JBP~8{LSlBl z5~4EoJXpRSXA&MLINxiB2as71rLiA&rxpSx8OY?w?$|3m&(oL^pfb)0YH6Rnzu}+e z8XS@g+!2dG20XpuVn`=x1dx=#$^K^9)%m49m1&w9d!~G+CZTUa?F^v(lNZ61V3|rq zpWumT336N7;W!Bl3>?$grv>@g_+zV@6j1ty5D+7Idipdh%fwTqJ2aMYUl2=-AecDQlDVjI1!8dts+eJ;r?>jc&kCWf8bSf`M!Qt*#E-Wb>&5KziCO;EXVH z`@8yCN&^NnkdyrcK)x{VFRy}_-V6Z=Y5N*j@mU*`a1Rv~VTOF+Nns#JlGF%D0^)W; zi_nv74kPtZ^Y0hhEx&(T|zl?BQ2^Es1Ft1~*|^p}D!Ujp5Uz&<>k73)5I%o=ErjRIiE!)i zFdoQ@{v`#KB@IC$Ve8^@DHcO@waR@Uw*4sF3maie;F>j}vCft)a1wDHW1}{vv9@)E zFhUGxYq!A+wq#6+Fu4lp@7{`8$drxCSLp9|)m6bT)3w59!zoGW--BtS>&mB$a}&aI zaIU5fn+FFq7GCo_Nyo*>{ynLMVAh1~(3YV=D4(hSP@jUS5;})d!aK5HS zyK3bZ>Oa66Bgc_d5GCcV<3I+2yx~Mhss1tHrlEQy8gm|jW@kdt$8BFV6dAqD4r{X% zFh)pK{QqFUY}wG6xT5s{XT}}--QPKnKL4IKpZT(U9Pf7l2XV49m91v`uMoUZ$6zV#<=hASPtM@Y#}wXTQJ#(S$`a pPcG(D5CS^SAVI2>;V=U;!>2lT#o2_A9XBPkf literal 0 HcmV?d00001 diff --git a/Assets/Stats/Defense.png b/Assets/Stats/Defense.png new file mode 100644 index 0000000000000000000000000000000000000000..6d77ac2d8a33a907be8425dbb8fcd3822ae3001c GIT binary patch literal 465 zcmV;?0WSWDP)}FP1{wnvoR)t)3=YPl2M=M@ zc>Cc)yar#qco9sCi3o#3@YS2QjHDz&Q3fgl9m_O!qSAm4G|pi@U$bcwm_B*(Bwh<1 zJ$i(~my?qN)8^*pV7juri~(CnFfj};V8JB;i({D2yIWiF8ibxEYHDgwLI4+FVi>F; za1ok{arsbHULGZlkPDa?hI&y<2oTCWObmlH1WrO-4$I%@X#!mxI(YKvAxe2LXhVPz zS8WI@5v^=&Q9>d#G!$OsSD@^81c0S6tw*XdH&yehc6^xAwa6n zi4B1d3^WFe#5BtlwEcN_{@?nHEhJ#6om8Kblm;GL-S~xJAVUBK6tw-BdH&z}gY07% zAl2t&<$?!SH~wW9$PhpVu(loykm~cHQ4Y`pFww=9!R!D4by%)i!6x^200000NkvXX Hu0mjfS(deF literal 0 HcmV?d00001 diff --git a/Assets/Stats/Distance.png b/Assets/Stats/Distance.png new file mode 100644 index 0000000000000000000000000000000000000000..1119974a682a749c60371101f981e3437d131c43 GIT binary patch literal 954 zcmV;r14aCaP)F!x1F{8s6N6Baq1Xsd{ z3qizR;95lgfG%etf^J-j;BtgPa3Mm#RX`zlgIt(EG)YfP+L@#~olBoS*D8sFNLJk$ zJ7kl0br#R5dW-KnZ+%BtYq?0t3*DVd16;fT^*Aq;blzrK#*QV=PMx%l!DuKFf=BM3Rx(Vd}runR=!`&Zxnapy1~1 z`iHkr%`RJi`KTo2lZ8L{`q+@VE-H<9tCHt~$tv9;2REPKj*s%}XLF=Uw`VC-FCdVD z_G+8R(A%!M2@*lf`fE?~A#w5oSvpk;3h?Pr*i$3A| zkn9BrtN4BA7%xk(#^q)~(~ZMZOq#?@j+9A~1lMudS#R|nvZ@!5WGO5651ANsi7~u1 zJ<3N&#w!!HNRd4vzZ@Bh?QSsuG>p#}7UHCthi zpLvxd==OidseYiNW?ft<=IwBr@WhP~3epAns41m%F(N}|hPl;sK5lFi+slTgw$NKZFdw2f*gCbwf!;e=#zK~#I7+}DNtsuhXbN28sF#T@cd@y~4 zgB?t3Gclp4d-DGun7;ZCLU(@o$bc&(m>33g8jwNrISdFTC4y=9m8(#E45MNCt};OQ z_y7~bP$&m}LVXGg2^b$2fQ1AxfQex+hrmu~es^bPLGd{$J{+1Ma16bShn94(kU-Dx zFo0fC;>-VoI|PUg33Nb;&j)v1fDJG*GBQ}Qu%gs{usQ&}%s>9;H__%YF$|>;fC2Q7 zK=wII4b?;7|NsAROoT z#jgPY!xu2ATt0_2N=z)iAQnta#0VCsTna7FQpzP3!<+P+v*yf}s>C)xNPy@({W_i9 zoz7)v%Q)xQPciN5={Num$a}e2yUWTYNg{6Yykpy0!*)^w?uJ`rY?dT6J?l>&(A8K$ z`Z`AiLgOLQ`9l$?5PLuNyl00$1zn95yvANi?DhH6Ydad0WX)X*>BAFYjD_OFbu|j+ zf-}UO4;0?~+{bp6g7!x?GN>*uOzXUFN5NCB*CpF3dFMxW-P2%WG1fQlO#PwBH zyzurDw|EBF;YtBh#jgW1BOY57C<6|qj97;T@4y(b!(NQ=^c?ZXR17Ysm3U?{0wj0g zKu3eZt5bRBT*K#}2HahHha&|# zL#mU3rT7{w=L?8WoXG)C`R-6kVB#Vgulj~jTX|h9;2L^AP`BWIm4eQPR@$n0OA*T1 z9AHcbOv3*7>0B@-+{rsv1ZF-$9G@IQQWf@2y9JsqlHJoc0+*wlcy=lbjsN{FGGU)Z z&h{nW;Mlw;7-Kkgp(w?2y7y0AD#hZI2(_+ic;g!-cHXgIHYAF6Xks3%8H!NO<^Yoz z7({$^UBVGbnCmKss)|tS;NY5`cJiLBgjmxw3{8lZ%+vvf9PAV@S;FGN8gbsq94w3v z5$9jA;HX*n9t{j*v%ZWJm}K$cb?-1*UuK?yS!cnMIx%)FmkV&MvXXSm!9cnmOpHP0 zA~nVdoAt09Uxz;^E}~4s=fI_aZH<82W#hG##l%USBN0mw7b;e)&WsZqYot`DH-4yC zSrRsb6A`p+BkWmgfI|b5L}1KD>*ePXkNNshR4y)-#C#C3=nC=2Km;c73fuAFh8(;U z81l4HA*It^22B(F&NX0lMNBuN<_5pc6SsB$dK>OWShg3DyRMu&Zi|aWVlozE--y3V zMYdh#hcgAgi@QNQxsA!ln5ecE&))oc+V}r|L;ULw`~)2{P}|~5?dt#l002ovPDHLk FV1kz=k5PN@^I{29dEC2Mni0Ul@g8z;3L?ZsTt4 zO1s|O_Vzx1&iR?=jzrT;T3_vj`@MQ{PR{Rq&iDMz_nb$G2$wVTWu2WX09*mU`>4*m zQd0Twy9_h}k%q`!Y|APb7nlW9R4qy-NW0Q83aIlS5-262{%3$DrR0f6)=~}=zP95I zZvE1CSTcJBmGUG;3xTHzd^kNc&f^cS;=#vysLQQDy9jF?qs53HeD?+xuUTHrH6oXW zP?z$72BqX@kKWAFd*4GF;TvCWd zE;g*}q|pPdEYJ7!^TS83NBfQ&Hy>vB5)f)CfHq2=+PR25ze`xy?Bgp--*C)*n_JlW zz*p$%+RbgN+vpn@LR5e#hjw-Byx-^QH5=K!?WZi8eJvV8*&6i z#}OG;A6bN20%}x3AQml}=9(PsO#$66k8xzrQ|y0nCx7cJ@rh;gS+{f!>sPjO)2db; z+EpSG$LxFVn+%K?mM@&mx{em^-_*&SckHHa<87cur>_A|BBF__kxDONJQwX{nAcd# zV6n)gOVAQASRCgMZobki&&u-TfeR=HRv=OaO@&u_3pmN3 z+$1LP_~no0BJ=OY#*VK0Ds0>G1d0GJn`4$1Ff};wrw70(9@x^rt`|f0yc%O|nR_coHXyQ-$+bl_icX#y1@O&6}+KR4qrIS;gzG50Yp_->DI_@=$?C z?}3B3WCo`I6uz(Vbj%G4n|bS0iPGdn`xWs}Wbnj*cNO!S0vv{p)&{=!{8_wx1+*Bn z(R}^W?NpSv{RCqnj)c|ouHx(kpA&^~#zRZ)y6uSiF@HKX z$l_~rXzdXvmgV^X&jd(yGEGnKIYjxyB8bw6Hfi?h0sK^Ego<+f^}S&hwd8QdkXm+D zdnCR45EAZO--@OAqG5stoFBI_quYdJnVR$XJz;um z4JzGRm2O#D6W|$5qC$x3QJzIE{o>g6VU6Js9w%g`txRxS&2o2QZ(T+CqE|198z1ekTJ&{@h*4^;<*J0 z;SGs>kN{&ieHeSL7?inyk(hD-JtR8%g;0D93z19bAoSLGwph!9p%(&NIMM(iuxs5r zFn#}$|KLvpEW{{+`TWTv?D-oOLa^Eqrheun!e%ou4Ca#12wL{T0L;fQ0E=7nx&sy> zFaS#nL#HI`wU7zJX%iGdrAGNvinM5~87YWbw1|1{#y$6qicI5Z z6GP+&kNdqd_n!Nmd(J&?NJ@!ZLgaLJ%nQK00OVYNG1rA=RrBQiB~4=wF3@cK&|LaJ z@%~G)+}}9q1U8QYk3xub%}#{Czmx*=;AK9p4gM3bg^n6hVxKQhb8!?U2lSk=hZiZU zDezP^%Y`dT>2n2-vnxhlIuN3ImZ4;u#bqr4wM*y5;8J`Y9Iey!UoLmM&`77P4TG zm2&;J_b+D@VIYerz26B5y&oa7+A>lZ&6*Nky(zd3!trAX;LzH=%=79kJPRFRp7`E` zojoz;b=w*yeAteF!$l`$XZ!|boC34PBa12vnHQJkqpx*{`Id%qyt>oR{M^-32#-dz zn3^&KwjN6xW&4W|ekVd83s6C*C)p7dHtzB8^KN_?wcG1aZ8ph_PQV>lA(!naU~ULt zxpxV3mrxt&AQJSG#|3p2@OgHlw7|>n_j+4-R43BbqkF@(vsduu$p_|b&+j4}yH06q zKw1(mr;yae`aJSgGn-(EWVP$x*AVIGX)I-B%}?OtyWoo=N62NkMdaO3xbGL z1u|C|e*Jii6RnCk6^!YISVxrHwYvEvBUPB1DvPwYRZau?6BwzCpW34te^RU6jnY?A z$$a?BFzkdrW+K7AWe>-1Qda(uWD_E*-sT0&0RlOO c`0oY$0s@-n5;LnLGXMYp07*qoM6N<$f>Q)lcK`qY literal 0 HcmV?d00001 diff --git a/Assets/Stats/NearbyEnemies.png b/Assets/Stats/NearbyEnemies.png new file mode 100644 index 0000000000000000000000000000000000000000..8ef1a21d48a41b5a9b2a9c81c1131d16ab07c8e0 GIT binary patch literal 587 zcmV-R0<`^!P){IzUj;~=6Q3@~6Mln6o500Qk9Zy2U-Gs6fCm^=u`$SJ|22juIdqT_J2=s{1E1JJV} zHpe`9bPsGX2xNPmf{Vk`4wiTyI#EDOp@bX)#QK_S2jFrHsg@4iD4+#kqKT=a4qzZ2 Z0089d#e_$&Ll6J}002ovPDHLkV1m7J_!0mB literal 0 HcmV?d00001 diff --git a/Assets/Stats/Ping.png b/Assets/Stats/Ping.png new file mode 100644 index 0000000000000000000000000000000000000000..ddf908db649d6f03dfd19e4ca31332a3f76cbb25 GIT binary patch literal 874 zcmV-w1C{)VP)#?Qts z1)(Zbi2&eIDy0Bg>(Cftdy;Li{NdPAQ4C_#lDlQ)C-w#z) zVNt;kKEH{5Z7Q~$Y$BGC^kW{yNaoNFAmc@ZDjSiL1HyO=VWh=mZ1Nny1SZ0x8?*J@ z6%Js0dZ;wNJMy~lWevRmz6cU-n^c29!8pn3YtutuNSL}jYadroW7sDWr}07r5>mE% zSi;TZfW3JC!aKm)vPm#=t{wp$kd2Y&$1r>Q157iIY&O%#s3)0@j)QjihM)H0;rTgO zj3_f_>Jo@{#tnjnZ_8hc@yivHusx=y);Crm36HP{eEEKV$dB(!KdSb1cO_CRZ(?!5 zXh=YFk`Jy^#~>jsY+afffJ|~hcFRH_NbrZZb>tMnle2HZ5HbaqmqO#{bcXBpJzDAF=THQ<>Yn z2%td{mJ#Uaj4|vEAk|97K7z#JiipP(Rf*SZ5_;W=IaibMntR@Y&3d4!KY)T9izSNX zSyk`5?EyD*RckXwUmk@iZy-bm@M3fxE5?#%+;{7ZsD%8l4e+{*O;dxV%2>%Pqbcy; zguo9*(7v(%c?fDHW!+b=9ruC%?tg)_M*uCxZ+&03cQgj26vc8`TK zn*oPOFIF3M%8d1=a~riQw=DEN?{ytgS5v)^s3p##O{Fr zbO&sSpxj()!k+>ZR!cuY={+Ew^Aymk{}rFnW9TsPStp1QqZO60aa?nrgsr47CC;GD zgZP{Vjp|D`{Uc=XKJ}{wmj|Dni zK3nSBGy&tm5W+uZ+22*5m;2_vPjSnOMKdEn8<~*X>CvY+@va4r>n#}W@xj+WjFHhG z%1evcQlI-=JQ!{xL>?P&GAEQH~ovKq}YJV7hHJXGr%?- z2ud!Zq*ThmwP)(#bk@OY-Hpb^tMGUpu*FQ`DOP$HsFbKonusPAU^M(Md`%ZoHk4wS zRIIJ7=G7BC9xvOGk@1yr8(#x{e*oTRZuTFx+)ir_Rl4L|U0x>NSGp5z4P_Wx(y*24 z#BQ(3>_@46c|s)v5_RD7Z^&DqMg}2l|2f4L<2SRuJzm9Hc+mg=002ovPDHLkV1i)h BeK-IB literal 0 HcmV?d00001 diff --git a/Assets/Stats/PlayerHead.png b/Assets/Stats/PlayerHead.png new file mode 100644 index 0000000000000000000000000000000000000000..028f879e27274e99f9897aa366a73ee672fd8642 GIT binary patch literal 486 zcmV@P)kpfuAwcJRQ7f#FZvV$Fxt0w~%ljLRttJ{444I?457)h-QkS%@m`6rl` z<>6p35aD6i{NN3o{qox{hRK(o5Of3!DHen103{KyB`{zn#>XJe#l~>q%@?qPK=|>O z9}FAry>Jz~bU?J9WP_UYd^D&%!`3WorvIqoZd44gRdi4p+&(@U&J4BL;4eUse zK0=OQBsL0=9f2J5Am73q^ZeUS21zb<25U)va5U7Myhkt!n255SfdL$x&%gcxvq2Vs zES-Gm377_148rq+9T?8N{tOler2%396R{3}g#av$K^Et<807*qoM6N<$fR literal 0 HcmV?d00001 diff --git a/Assets/Stats/Position.png b/Assets/Stats/Position.png new file mode 100644 index 0000000000000000000000000000000000000000..58ef202eea6fa9d4a0a9445962be129ff3628052 GIT binary patch literal 572 zcmV-C0>k}@P)4@{N&!N`M8Mpq9P`+_zUg{sYrzc28os@=*uOcJ)=m85&O? z-N6vMcJTz`piKiT*nA%nodIT>=t#lEpEEEpbasm4HSG3-mni%*SDu0CH4F^MA;w5B z3QV9DuituvVavu74CDYGe+z~{ZyT5c7=}(1NXw}(s475$8svK+DNit4Mpm7{(i9Rh z$4^6o76hI=zR93AZ8?~ID=QK%zGdTfhO+EnxS2#d03CoV27#h1V}`zIXW^Eb)fb%v&Oi)cVi;h+LQHA~0g&%OJ_iAnP(y~(jSwHE?77Fl;Npbhdwk{) zbih-zf&^wM%wiC@v-&*3fLq)s79*?2W#-_I0##@($jpmkm_BJ4T>R8T%!G9!K!AaP zVFHFYNH5GBk{y6tAb|`5fyWQ;F%Sb}<(1%ii2+OugE<$dDu^?jSQY?g&zrlJp*k5- zu*6m!XIQ=kk`N<(3>g-;Su((?Y?vBVpX21^0^~{r1YlJ#$Y2;7WH~H|L2PVlK$at$ zGialL5xp74$jFG&AfX08Zjhkc$HXw$LV(_YiS9OzIsgp}xB~!TPsg##{ao?@0000< KMNUMnLSTZKQs|uk literal 0 HcmV?d00001 diff --git a/Assets/Stats/PvE.png b/Assets/Stats/PvE.png new file mode 100644 index 0000000000000000000000000000000000000000..295d77d8f60c896b4684294bafd5a1f7ea623899 GIT binary patch literal 1746 zcmV;@1}*uCP)eP2r`rTD*1+20Fh#gFEvdeBsl%dHmjpF(7HZoVOX z5{%6@0Ew6q7EJpBLi)oUS@EMO+dMih0@t#3iRDijq_-DBvd&A*k;16;p3+kO6X*&O%*99Ue7V`16Q1b~nI* zMhuMw?l-_EM44p9{fsG)2oPIx`7U)zgeZj$_Kql@h{eXcFCYW#*|Zi7##+Q`Vu6Y} zpcc?StmB)r#$4pfI0SWVN>4|QLiLOn*~T;@I0{>W!eR3hk@D(nT>YV#k9P$*UR_CM z4T+!!w&o!;c|4#62YIi|1cEU9=}=&PC&=)S2voOvSHgWv>XgVlQx(qGU6*TXU^qlU zgi0hRJ9-wW3#NKqAZdu2Z``K6&HL=atJu8xEgU(17*2zAbk|a=Q_ypR6VaHn5&PO}aN61d zLMS9jfj1p%txHQ6q>2?QQpw(24OaNcqHbG9nN8% z8Xd$>K^xa(~3JLu?iw6ltcif1QHQ3A#fnJt(}d$W7m+mVkIUfXb^P$XKX6I zi%|wGUuVn$_Yc#Z4Jyej5xrQ}winybLIz*yW3Y3UIc6cmK z*lY+mTEgGybFxu->^urhZ8)OSAv#itxl5%;B$+*ZO)*&8w+U$ zA}it%qE4qo`~?L)&}!h%2LP-Nk3wnTS)6o=crG&$+0kj3y7weXYTB`5`9zFTYbX=E zvV)+~E&zkzb_Gjd#x^VOnLTWG6||(N@ZNt}%?po$PUV`ozfp%nEd+J~lticCrrrpa zORDtuNL)-ZO^b3sUk%JD*pJFL-{9Yo-N69ZsbzPtan{)9u4?pv@TUZWp~OK`D}Nrj zW*ut3I*B!rVVJr1CrCtsBvCG7Y(PC0r%b@UHY-YRmSRY}n#+I3_$0H+n=%#T&vNMc zRHB1Wl$l$wl}dP@gmHjPVhi*}BOFxpnRG4|eaHJ5Nzpt($*$;rH(2B_S22_Cghv5W z5<*vnCSXa33R9AYVbG~_m?i{cF@E zx*|LQ{*u%EQl!8IYQad;BdiOKg+C#*gCz|w3w8}V89*Ht#2ZtA!T3twJWnVDLgUqA z@tV~MQINO_A*as6K`F6AKnPJ_`^veXa&_}!@&-&1w&CKG1fHN|HF_;z3aY?#i-ixY zvIIL7iYig6=S-`xReDmd= zslifruLVp3#q!6%&V}iC-cjcUU}je+P#EOO?xxi%APAkrUd$@mpf&yF?)z#_3Yfow z3Ze^$iccVjl2DZhh#;U4Ao4uXGfGlNhS9aN#^a0P+hD!6riqUSI9zxChQZ|XUi8b~ oKy*TQ8xK;rDk`&o9pazZ-?T$L-_l_2GXMYp07*qoM6N<$g6Y&OK>z>% literal 0 HcmV?d00001 diff --git a/Assets/Stats/PvP.png b/Assets/Stats/PvP.png new file mode 100644 index 0000000000000000000000000000000000000000..2100d0cff163cf1018f90f41beb219212fc1f5a3 GIT binary patch literal 2014 zcmV<42O;>0P)&SIq%+k?t4SbjPGLOf0zqm1xyA21683Ggk_|mxV8?x z76TUDBA>f*=99s9VjrCXl?@m=pehR5>H{Qb_~SD{{|^Dp0KB9FMAEl2FB^sdEU9fo zuOhx@;Q9cFul07Si7Tt+^~KlApgjm^dh!%zuMW0YfMC$7m=I)TgkZtZW-w{^ z<%xRinlTY{k74WU!=)R+5~Lu1JodeRMSkO6T`f#-F~4>4xFn=J2mx=Ofp_-4jkRl6 zBk;DDx3;=ac=RH6&C5cMqQWo?EZg&$yf2;;<_j(?FgtIMhzTyPABcdTg-~C20hw9J zSW~ec+FfQ9Iu`t`34UI{=7#i(`%`jPc-NkKcL=x%EOPD({=ftp&Yz*G%8h7lx`LF{ zM3k?883Ar$B7*;2U0w^nxDP-zBVY~zZbo3ZQI;i;>y5AF#rT}b$+}@&Ra97XQXJPl zJ?-*+I3;m1m%dbv)YNEPxNsetH&$ZFi@%|t{Adyy8!ll>)n>FhC$hKk(hv|cBE!Sq zZz^$HUM^pIBA3NnszN0mK7?P4^n@SqU$*Vzk3TxYJGSrTwD?JUY{eqshb`I(i{A`h z_sMNG|BI1UdPa`uC#$62{FWFBdku;{^(h(}T)6ziV<1i8>HZO@4Nb+Nu}NtE?Ft-- zOqPDZ_ilCZ<~=`r)RC zKZ3*@*F_xZxg(t~+nvxA9X6d%zxyaA7fc;|A?U0P&&gvssM0$E03JMAk{JEWT z7ypY*B?#sC7N7tZsSq+0a=fny<8mg)EdOT{#OgP6xuG#ZWk3U=>Ae%Uxx83LVeY&F zWG5x#wHYb2sj@#3Pv_4BE9#&UgkpqpUY%Yjn~k}0Ga7=3Nka1pMgRtQz2b19!sEt^ z3nt+qf16}^j@|O!v~6$T;}@3Wug97Y>2M1YY}aT&3Grxdx(K0|%WXmT6`>#_BH$}g z&73gn1_o+VXU5XPiFoeKgAg?-2&inq^q>fI&3_E<)$B%?vs?1j6ayt?>m+z%!xvHs zok>4O;=DX5K@kO00iq8G62eR+khvEG#5`>Gw7WZ}JHk=n2*Is6(TEBQ0dxb?UVRrQ zOAE37PamSyX{U|PWnulUW~h!pE-hb&jLZ}XzO`x--gq_*^_Q+;O=Bbyf3|AG;`5cE zW=_6t{j!TJDo%CY!a=rU*5NDg@Ksoga$peP)%%Mo%)uzHX@NB`m`h7v8U(N2ya`vY z2TSm*gjitzjltE5m76x4jABm6qVn<3WOS@riP!I4!9O~CLBz7p3jrEnxNLUZ^LSwQ zBYtLXK7S+%(uwD=z`zc37J`fcN8y0-To7z7BIX0 zB6Kh`Qou|wNJpkU1h4#h7RvX0hWw(1$Vy0o~QB6h0c3#7D*^u?FJfk*R$9cT3np%HSYX;io1{;Df6d^O2AU{{8BW zoD%m4*X-Dh)VLU=#)M-}{!fuIZVb|5<7G!QJD}MWN9u-}B+;jnrYS7W7)zb8*x%I! zzuhM#!Io7^adyuKsBwLTcbc0KHAchi`BSA5HoR6%rK?_m-KrwUVMkz}2R#H_Z;xY< zg3BN7fbs8h_k~^5gy;hz5<^ZzRz#Qd>jA{a#-p;b zipo}(z~yEZYP3$;L( z`^Og@88Xzn{&*N2S5M0t@U40?6=rKDlYmCVi-HpHi$EJ5%^8b_4;{x0#UCrjC1Y00 zS-E41=R%Xl!j%aq1`u<$7cCh;bR*&BL@UDTy5YL<$cU$5Ujn{yOjXwg>uU^nKcY0UWs5*y^-XuK)l507*qoM6N<$g4+_fy#N3J literal 0 HcmV?d00001 diff --git a/Assets/Stats/Stopwatch.png b/Assets/Stats/Stopwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..4553f6c12694d77bfa19e8174954688fc5a95c93 GIT binary patch literal 650 zcmV;50(Jd~P)>R8JCYVD9 zj$vwq0&MAxg@FvvTf)y^;+h9$pV>W$Vcm+8VD`}`8bdD%7;$AoY*BzMDy(I0zn|H(2y?Jzt!TiBXF#E&jCb$ik&K_l~ zj1l^e7B!56E(%z1Nq~GG6k*PA?vpUs7mJt90{a#O$}8F#o;`+y1~O=Bs$)2NK2>wv?fl@fOPnB|g8<0)#uiT0Z4c-fTmOdzsj-d} zJUY;`DNGFvfPx=;U zx2$yXK`G9u#y&jFwk&@Z*v-EZ$^XpzBsOsP6=Qy}#c~)3X{}69UAS|v zdPHJPhF08H?(p+$m(}_6T4o~1>Ja(~; m@$d>}HVK1XtA@k|MuuWbw}aJ9;ypkgGI+ZBxvXk7R9J=WmQ83>Nf5_>)$hHTF)KtOD2nJr(HVoDJ$nckf~*o0 zT*yW6V_7h$%bqlP5CuU3eqs<8_Oj?r5!nR~f;YikQBX8$BD)d4E}qQfb$3a3jG(7C znjmQERv+E@4ElKHCMnE}8I0Ma5xMkIa}k&oqGK_)}+kW4BBa<4LF01`5K zyXsU#SX5ntI6vV>1e;FyNyg#f<7cE9{QlRSBG*pbp#}OtMMPZ&HiTw1zMD#l!D1A` z71luF!G|=d;Mt2Rj#W!Xy_MmS*L2huBjpxc!=h%IGGMJ0Bbk;D zAEWKoZR+(7uKstfB&Wc1oE0oB^7;p@FY9^9agNI$K$&r z{L`|L)BAUz)-_oHGzTEim{pOAXt{roH4m@w@9a*_?Az8<<$rlAb_tnSs$0+F@&+dP z&;yzF3}hnOcknoyfBKRC|LA4Yma~wwM0x%hSxB2P79-k!bda^(>&V9(^Lq!;G0%og z8)$8*M7!AwUR?N@ z<*RCp{<%H!K_2vZVO z;usRq`u3b9*MR^Y=8K*0>#vmD3~z5xd>HT0%f_M?vq#T(^7`FnOyUJs_Ws|-n7w(a z*>$yq95a*ezb~^tI>f*uAkLQ1u)~0X`A93Hgn}N=0fxdv2DSxn8C5G3Y=0chO9xuR N;OXk;vd$@?2>>__G=Bg9 literal 0 HcmV?d00001 diff --git a/Content/Filters/PlayerManagerFilters/BackgroundOptionFilter.cs b/Content/Filters/PlayerManagerFilters/BackgroundOptionFilter.cs new file mode 100644 index 00000000..5a4fb218 --- /dev/null +++ b/Content/Filters/PlayerManagerFilters/BackgroundOptionFilter.cs @@ -0,0 +1,29 @@ +using DragonLens.Content.Tools.Multiplayer; +using DragonLens.Helpers; +using ReLogic.Content; + +namespace DragonLens.Content.Filters.PlayerManagerFilters +{ + internal sealed class BackgroundOptionFilter : Filter + { + private readonly PlayerManagerBrowser browser; + private readonly string key; + + public BackgroundOptionFilter(PlayerManagerBrowser browser, string key, Asset texture) + : base(texture, LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}"), _ => false) + { + this.browser = browser; + this.key = key; + isModFilter = true; + } + + public override string Description => LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}.Toggle"); + public bool Enabled => browser.Settings.IsBackgroundMode(key); + + public void Toggle() + { + bool enabled = browser.Settings.IsBackgroundMode(key); + browser.Settings.SetBackgroundMode(key, !enabled); + } + } +} \ No newline at end of file diff --git a/Content/Filters/PlayerManagerFilters/BiomeFilter.cs b/Content/Filters/PlayerManagerFilters/BiomeFilter.cs new file mode 100644 index 00000000..bca2e209 --- /dev/null +++ b/Content/Filters/PlayerManagerFilters/BiomeFilter.cs @@ -0,0 +1,62 @@ +using DragonLens.Content.Tools.Multiplayer; +using DragonLens.Helpers; +using System; +using Terraria.GameContent.Bestiary; +using Terraria.Localization; + +namespace DragonLens.Content.Filters.PlayerManagerFilters +{ + internal sealed class BiomeFilter : Filter + { + private readonly SpawnConditionBestiaryInfoElement biome; + private readonly Func matchesPlayer; + private readonly string customName; + private readonly Asset customTexture; + + public BiomeFilter(SpawnConditionBestiaryInfoElement biome) + : base(null, "", n => n is not PlayerManagerItem item || !BiomeHelper.MatchesBiome(item.player, biome)) + { + this.biome = biome; + } + + public BiomeFilter(string name, Asset texture, Func matchesPlayer) + : base(null, "", n => n is not PlayerManagerItem item || !matchesPlayer(item.player)) + { + customName = name; + customTexture = texture; + this.matchesPlayer = matchesPlayer; + } + + public override string Name + { + get + { + if (customName != null) + return customName; + + if (biome == BiomeHelper.ShimmerBiome) + return "Aether"; + + return biome != null ? Language.GetTextValue(biome.GetDisplayNameKey()) : "Unknown"; + } + } + public override string Description => ""; + + public override void Draw(SpriteBatch spriteBatch, Rectangle target) + { + if (customTexture != null) + { + Texture2D tex = customTexture.Value; + int widest = Math.Max(tex.Width, tex.Height); + spriteBatch.Draw(tex, target.Center.ToVector2(), null, Color.White, 0f, tex.Size() * 0.5f, target.Width / (float)widest, SpriteEffects.None, 0f); + return; + } + + if (biome == null || !BiomeHelper.TryGetBestiaryIconDrawData(biome, out Asset texture, out Rectangle source)) + return; + + float scale = Math.Min((float)target.Width / source.Width, (float)target.Height / source.Height); + spriteBatch.Draw(texture.Value, target.Center.ToVector2(), source, Color.White, 0f, source.Size() * 0.5f, scale, SpriteEffects.None, 0f); + } + } +} \ No newline at end of file diff --git a/Content/Filters/PlayerManagerFilters/ButtonOptionFilter.cs b/Content/Filters/PlayerManagerFilters/ButtonOptionFilter.cs new file mode 100644 index 00000000..2b0614e1 --- /dev/null +++ b/Content/Filters/PlayerManagerFilters/ButtonOptionFilter.cs @@ -0,0 +1,28 @@ +using DragonLens.Content.Tools.Multiplayer; +using DragonLens.Helpers; +using ReLogic.Content; + +namespace DragonLens.Content.Filters.PlayerManagerFilters.Toggles +{ + internal sealed class ButtonOptionFilter : Filter + { + private readonly PlayerManagerBrowser browser; + private readonly string key; + + public ButtonOptionFilter(PlayerManagerBrowser browser, string key, Asset texture) + : base(texture, LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}"), _ => false) + { + this.browser = browser; + this.key = key; + } + public override string Name => base.Name + " Button"; + public override string Description => LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}.Toggle"); + + public bool Enabled => browser.Settings.IsButtonVisible(key); + + public void Toggle() + { + browser.Settings.SetButtonVisible(key, !browser.Settings.IsButtonVisible(key)); + } + } +} \ No newline at end of file diff --git a/Content/Filters/PlayerManagerFilters/PlayerOptionFilter.cs b/Content/Filters/PlayerManagerFilters/PlayerOptionFilter.cs new file mode 100644 index 00000000..52c8c7ee --- /dev/null +++ b/Content/Filters/PlayerManagerFilters/PlayerOptionFilter.cs @@ -0,0 +1,32 @@ +using DragonLens.Content.Tools.Multiplayer; +using DragonLens.Helpers; +using ReLogic.Content; + +namespace DragonLens.Content.Filters.PlayerManagerFilters +{ + internal sealed class PlayerOptionFilter : Filter + { + private readonly PlayerManagerBrowser browser; + private readonly string key; + + public PlayerOptionFilter(PlayerManagerBrowser browser, string key, Asset texture) + : base(texture, LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}"), _ => false) + { + this.browser = browser; + this.key = key; + isModFilter = true; + } + + public override string Description => LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}.Toggle"); + public bool Enabled => browser.Settings.IsPlayerMode(key); + + public void Toggle() + { + bool enabled = browser.Settings.IsPlayerMode(key); + browser.Settings.SetPlayerMode(key, !enabled); + + // Debug print + //Main.NewText(key + ": " + !enabled); + } + } +} \ No newline at end of file diff --git a/Content/Filters/PlayerManagerFilters/StatOptionFilter.cs b/Content/Filters/PlayerManagerFilters/StatOptionFilter.cs new file mode 100644 index 00000000..ab363e84 --- /dev/null +++ b/Content/Filters/PlayerManagerFilters/StatOptionFilter.cs @@ -0,0 +1,29 @@ +using DragonLens.Content.Tools.Multiplayer; +using DragonLens.Helpers; +using ReLogic.Content; +using System; + +namespace DragonLens.Content.Filters.PlayerManagerFilters.Toggles +{ + internal sealed class StatOptionFilter : Filter + { + private readonly PlayerManagerBrowser browser; + private readonly string key; + + public StatOptionFilter(PlayerManagerBrowser browser, string key, Asset texture) + : base(texture, LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}"), _ => false) + { + this.browser = browser; + this.key = key; + } + public override string Description => LocalizationHelper.GetToolText($"PlayerManager.Filters.{key}.Toggle"); + public bool Enabled => browser.Settings.IsStatVisible(key); + + public void Toggle() + { + bool enabled = browser.Settings.IsStatVisible(key); + int availableHeight = browser.listMode ? Math.Max(48, browser.buttonSize) : browser.buttonSize; + browser.Settings.TryToggleStat(key, !enabled, browser.listMode, availableHeight); + } + } +} \ No newline at end of file diff --git a/Content/Filters/PlayerManagerFilters/TeamFilter.cs b/Content/Filters/PlayerManagerFilters/TeamFilter.cs new file mode 100644 index 00000000..a6f0840f --- /dev/null +++ b/Content/Filters/PlayerManagerFilters/TeamFilter.cs @@ -0,0 +1,33 @@ +namespace DragonLens.Content.Filters.PlayerManagerFilters +{ + + /// + /// Draw filter with a custom spritesheet + /// Used for team icon filters + /// + internal class TeamFilter : Filter + { + public Rectangle sourceRect; + public Point drawSize; + public Color color = Color.White; + + public TeamFilter(Asset texture, string localizationKey, FilterDelegate shouldFilter, Rectangle sourceRect, Point drawSize) + : base(texture, localizationKey, shouldFilter) + { + this.sourceRect = sourceRect; + this.drawSize = drawSize; + } + + public override void Draw(SpriteBatch spriteBatch, Rectangle target) + { + Rectangle drawRect = new( + target.X + (target.Width - drawSize.X) / 2, + target.Y + (target.Height - drawSize.Y) / 2, + drawSize.X, + drawSize.Y + ); + + spriteBatch.Draw(texture.Value, drawRect, sourceRect, color); + } + } +} \ No newline at end of file diff --git a/Content/GUI/Browser.cs b/Content/GUI/Browser.cs index 0f2fcb9d..f5980d06 100644 --- a/Content/GUI/Browser.cs +++ b/Content/GUI/Browser.cs @@ -1,6 +1,7 @@ using DragonLens.Content.Filters; using DragonLens.Content.GUI.FieldEditors; using DragonLens.Content.Sorts; +using DragonLens.Content.Tools.Multiplayer; using DragonLens.Content.Tools.Spawners; using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; @@ -36,12 +37,27 @@ internal abstract class Browser : DraggableUIState public bool listMode; public bool filtersVisible; public int buttonSize = 36; + public virtual int MinButtonSize => 36; + public virtual int BrowserWidth => 500; + public override bool OwnsMouse(Point mouse) + { + if (BoundingBox.Contains(mouse)) + return true; + + if (!filtersVisible || filters is null) + return false; + + Rectangle filterBounds = filters.GetDimensions().ToRectangle(); + filterBounds.Height = (int)MathHelper.Min(420, filters.filters.GetTotalHeight() + 20); + + return filterBounds.Contains(mouse); + } public abstract string Name { get; } public virtual string IconTexture => "TestTool"; - public override Rectangle DragBox => new((int)basePos.X, (int)basePos.Y, 500, 64); + public override Rectangle DragBox => new((int)basePos.X, (int)basePos.Y, BrowserWidth, 64); public event FilterDelegate FilterEvent; public int sortIndex = 0; @@ -119,7 +135,7 @@ public virtual void PostInitialize() { } public sealed override void SafeOnInitialize() { - width = 500; + width = BrowserWidth; height = 600; scrollBar = new(UserInterface); @@ -128,8 +144,9 @@ public sealed override void SafeOnInitialize() Append(scrollBar); options = new(); - options.Width.Set(460, 0); + options.Width.Set(BrowserWidth - 40, 0); options.Height.Set(480, 0); + options.OverflowHidden = true; options.SetScrollbar(scrollBar); options.ListPadding = 0; Append(options); @@ -142,7 +159,13 @@ public sealed override void SafeOnInitialize() sizeSlider = new(this); Append(sizeSlider); - listButton = new("DragonLens/Assets/GUI/Play", () => listMode, LocalizationHelper.GetGUIText("Browser.ListView")); + listButton = new( + () => listMode ? Assets.GUI.List : Assets.GUI.Grid, + () => listMode, + () => LocalizationHelper.GetGUIText(listMode ? "Browser.ListView" : "Browser.GridView"), + () => LocalizationHelper.GetGUIText(listMode ? "Browser.ListViewDescription" : "Browser.GridViewDescription"), + drawHighlight: false + ); listButton.OnLeftClick += (n, k) => { listMode = !listMode; @@ -165,7 +188,7 @@ public sealed override void SafeOnInitialize() }; Append(filterButton); - sortButton = new("DragonLens/Assets/GUI/Sort", () => false, LocalizationHelper.GetGUIText("Browser.Sorts"), () => LocalizationHelper.GetGUIText($"Browser.Sort.{SortModes[sortIndex].Name}")); + sortButton = new(Assets.GUI.Sort, () => false, LocalizationHelper.GetGUIText("Browser.Sorts"), () => LocalizationHelper.GetGUIText($"Browser.Sort.{SortModes[sortIndex].Name}")); sortButton.OnLeftClick += (n, k) => { sortIndex++; @@ -176,14 +199,8 @@ public sealed override void SafeOnInitialize() Append(sortButton); filters = new(this); - filters.Width.Set(0, 0); filters.Height.Set(420, 0); - - if (filtersVisible) - filters.Width.Set(220, 0); - else - filters.Width.Set(0, 0); - + filters.Width.Set(filtersVisible ? 220 : 0, 0); Append(filters); SetupFilters(filters); @@ -193,7 +210,7 @@ public sealed override void SafeOnInitialize() public override void AdjustPositions(Vector2 newPos) { - scrollBar.Left.Set(newPos.X + 464, 0); + scrollBar.Left.Set(newPos.X + BrowserWidth - 32, 0); scrollBar.Top.Set(newPos.Y + 110, 0); options.Left.Set(newPos.X + 10, 0); @@ -202,7 +219,8 @@ public override void AdjustPositions(Vector2 newPos) searchBar.Left.Set(newPos.X + 10, 0); searchBar.Top.Set(newPos.Y + 66, 0); - sizeSlider.Left.Set(newPos.X + 354, 0); + //sizeSlider.Left.Set(newPos.X + 354, 0); + sizeSlider.Left.Set(newPos.X + BrowserWidth - 124, 0); sizeSlider.Top.Set(newPos.Y + 74, 0); listButton.Left.Set(newPos.X + 220, 0); @@ -233,12 +251,12 @@ public void Refresh() public override void Draw(SpriteBatch spriteBatch) { - var target = new Rectangle((int)basePos.X, (int)basePos.Y, 500, 600); + var target = new Rectangle((int)basePos.X, (int)basePos.Y, BrowserWidth, 600); GUIHelper.DrawBox(spriteBatch, target, ThemeHandler.BackgroundColor); Texture2D back = Assets.GUI.Gradient.Value; - var backTarget = new Rectangle((int)basePos.X + 8, (int)basePos.Y + 8, 400, 48); + var backTarget = new Rectangle((int)basePos.X + 12, (int)basePos.Y + 8, BrowserWidth - 100, 48); spriteBatch.Draw(back, backTarget, Color.Black * 0.5f); Texture2D gridBack = Terraria.GameContent.TextureAssets.MagicPixel.Value; @@ -261,6 +279,9 @@ internal abstract class BrowserButton : SmartUIElement public static int drawDelayTimer = 2; //Here so we dont draw on the first frame of the grid populating, causing a lag bonanza since every single button tries to draw. public bool filtered; + protected virtual int GridSize => (int)MathHelper.Clamp(parent.buttonSize, parent.MinButtonSize, 108); + protected virtual int ListHeight => GridSize; + protected virtual int ListWidth => (int)Parent.GetDimensions().Width - 24; public abstract string Identifier { get; } public abstract string Key { get; } // Key used for favorites @@ -269,7 +290,7 @@ internal abstract class BrowserButton : SmartUIElement public BrowserButton(Browser parent) { - int size = (int)MathHelper.Clamp(parent.buttonSize, 36, 108); + int size = (int)MathHelper.Clamp(parent.buttonSize, parent.MinButtonSize, 108); Width.Set(size, 0); Height.Set(size, 0); @@ -315,10 +336,8 @@ public override void SafeUpdate(GameTime gameTime) private void UpdateAsGrid() { - int size = (int)MathHelper.Clamp(parent.buttonSize, 36, 108); - - Width.Set(size, 0); - Height.Set(size, 0); + Width.Set(GridSize, 0); + Height.Set(GridSize, 0); MarginLeft = 2; MarginRight = 2; @@ -328,10 +347,8 @@ private void UpdateAsGrid() private void UpdateAsList() { - int size = (int)MathHelper.Clamp(parent.buttonSize, 36, 108); - - Width.Set(Parent.GetDimensions().Width - 24, 0); - Height.Set(size, 0); + Width.Set(ListWidth, 0); + Height.Set(ListHeight, 0); MarginLeft = 2; MarginRight = 2; @@ -354,7 +371,7 @@ public override void Recalculate() public virtual void SafeDraw(SpriteBatch spriteBatch, Rectangle iconArea) { } - public sealed override void Draw(SpriteBatch spriteBatch) + public override void Draw(SpriteBatch spriteBatch) { if (filtered) return; @@ -365,7 +382,7 @@ public sealed override void Draw(SpriteBatch spriteBatch) if (drawDelayTimer > 0) return; - int size = (int)MathHelper.Clamp(parent.buttonSize, 36, 108); + int size = GridSize; var drawBox = GetDimensions().ToRectangle(); @@ -470,7 +487,7 @@ public override void SafeUpdate(GameTime gameTime) { progress = MathHelper.Clamp((Main.MouseScreen.X - GetDimensions().Position().X) / GetDimensions().Width, 0, 1); - parent.buttonSize = (int)(36 + progress * (108 - 36)); + parent.buttonSize = (int)(parent.MinButtonSize + progress * (108 - parent.MinButtonSize)); if (!Main.mouseLeft) dragging = false; @@ -479,7 +496,8 @@ public override void SafeUpdate(GameTime gameTime) } else { - progress = (parent.buttonSize - 36) / (108 - 36f); + parent.buttonSize = (int)MathHelper.Clamp(parent.buttonSize, parent.MinButtonSize, 108); + progress = (parent.buttonSize - parent.MinButtonSize) / (108f - parent.MinButtonSize); } } @@ -500,7 +518,7 @@ public override void Draw(SpriteBatch spriteBatch) var draggerTarget = new Rectangle(dims.X + (int)(progress * dims.Width) - 5, dims.Y - 6, 10, 20); GUIHelper.DrawBox(spriteBatch, draggerTarget, ThemeHandler.ButtonColor); - if (IsMouseHovering && !Main.mouseLeft) + if (IsMouseHovering && !Main.mouseLeft && CanShowTooltip) { Tooltip.SetName(LocalizationHelper.GetGUIText("Browser.ButtonSizeSlider.Name")); Tooltip.SetTooltip(LocalizationHelper.GetGUIText("Browser.ButtonSizeSlider.Tooltip")); diff --git a/Content/GUI/DraggableUIState.cs b/Content/GUI/DraggableUIState.cs index f0a62151..b77d7409 100644 --- a/Content/GUI/DraggableUIState.cs +++ b/Content/GUI/DraggableUIState.cs @@ -1,6 +1,8 @@ using DragonLens.Core.Loaders.UILoading; +using DragonLens.Core.Systems.ToolSystem; using DragonLens.Helpers; using Terraria.GameContent.UI.Elements; +using Terraria.UI; namespace DragonLens.Content.GUI { @@ -9,6 +11,11 @@ namespace DragonLens.Content.GUI /// public abstract class DraggableUIState : SmartUIState { + public override bool OwnsMouse(Point mouse) + { + return BoundingBox.Contains(mouse); + } + public virtual Tool OwnerTool => null; public static bool draggingAny; private UIImageButton closeButton; @@ -59,6 +66,16 @@ public override bool Visible public virtual void AdjustPositions(Vector2 newPos) { } public virtual void SafeOnInitialize() { } + public override void SafeMouseDown(UIMouseEvent evt) + { + // Bring this UI state to front when clicking it. + if (BoundingBox.Contains(evt.MousePosition.ToPoint())) + UILoader.BringToFront(this); + + // If you only want to bring to front when clicking the header area, use: + //if (DragBox.Contains(evt.MousePosition.ToPoint())) + //UILoader.BringToFront(this); + } public virtual void DraggableUdpate(GameTime gameTime) { } diff --git a/Content/GUI/FieldEditorMenu.cs b/Content/GUI/FieldEditorMenu.cs index 99d05697..6d502a48 100644 --- a/Content/GUI/FieldEditorMenu.cs +++ b/Content/GUI/FieldEditorMenu.cs @@ -1,5 +1,4 @@ -using DragonLens.Content.GUI; -using DragonLens.Content.GUI.FieldEditors; +using DragonLens.Content.GUI.FieldEditors; using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Core.Systems.ToolSystem; diff --git a/Content/GUI/Filters.cs b/Content/GUI/Filters.cs index cecdba4c..916db82d 100644 --- a/Content/GUI/Filters.cs +++ b/Content/GUI/Filters.cs @@ -1,4 +1,6 @@ using DragonLens.Content.Filters; +using DragonLens.Content.Filters.PlayerManagerFilters; +using DragonLens.Content.Filters.PlayerManagerFilters.Toggles; using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Helpers; @@ -115,6 +117,45 @@ public FilterButton(Browser parent, Filter filter, FilterPanel panel) public override void SafeClick(UIMouseEvent evt) { + // Debug print filter type. + //Main.NewText(filter); + + // Special toggle filter. + if (filter is ButtonOptionFilter buttonToggle) + { + buttonToggle.Toggle(); + parent.Recalculate(); + parent.SortGrid(); + return; + } + + // Special toggle filter. + if (filter is StatOptionFilter statToggle) + { + statToggle.Toggle(); + parent.Recalculate(); + parent.SortGrid(); + return; + } + + // Special background filter. + if (filter is BackgroundOptionFilter backgroundToggle) + { + backgroundToggle.Toggle(); + parent.Recalculate(); + parent.SortGrid(); + return; + } + + // Special player filter. + if (filter is PlayerOptionFilter playerOptionsFilter) + { + playerOptionsFilter.Toggle(); + parent.Recalculate(); + parent.SortGrid(); + return; + } + active = !active; if (active && filter.isModFilter) @@ -134,17 +175,27 @@ public override void Draw(SpriteBatch spriteBatch) GUIHelper.DrawBox(spriteBatch, drawBox, ThemeHandler.ButtonColor); - if (active) + // Special case filter. + bool enabled = + active || + filter is ButtonOptionFilter buttonToggle && buttonToggle.Enabled || + filter is StatOptionFilter statToggle && statToggle.Enabled || + filter is BackgroundOptionFilter backgroundToggle && backgroundToggle.Enabled || + filter is PlayerOptionFilter playerFilter && playerFilter.Enabled; + + if (enabled) GUIHelper.DrawOutline(spriteBatch, drawBox, ThemeHandler.ButtonColor.InvertColor()); drawBox.Inflate(-4, -4); filter.Draw(spriteBatch, drawBox); - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Main.LocalPlayer.mouseInterface = true; Tooltip.SetName(filter.Name); - Tooltip.SetTooltip(filter.Description); + + if (!string.IsNullOrEmpty(filter.Description)) + Tooltip.SetTooltip(filter.Description); } } diff --git a/Content/GUI/FirstTimeLayoutPresetMenu.cs b/Content/GUI/FirstTimeLayoutPresetMenu.cs index d22632f5..d7a6bbbd 100644 --- a/Content/GUI/FirstTimeLayoutPresetMenu.cs +++ b/Content/GUI/FirstTimeLayoutPresetMenu.cs @@ -121,7 +121,7 @@ public override void Draw(SpriteBatch spriteBatch) Texture2D tex = texture.Value; spriteBatch.Draw(tex, GetDimensions().Position() + new Vector2(8, 36), null, Color.White, 0, Vector2.Zero, 0.5f, 0, 0); - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(name); Tooltip.SetTooltip(tooltip); diff --git a/Content/GUI/LayoutPresetBrowser.cs b/Content/GUI/LayoutPresetBrowser.cs index 879e4951..418a1874 100644 --- a/Content/GUI/LayoutPresetBrowser.cs +++ b/Content/GUI/LayoutPresetBrowser.cs @@ -74,7 +74,7 @@ public override void SafeClick(UIMouseEvent evt) public override void SafeDraw(SpriteBatch spriteBatch, Rectangle iconArea) { - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(Identifier); Tooltip.SetTooltip(tooltip); diff --git a/Content/GUI/StyledScrollbar.cs b/Content/GUI/StyledScrollbar.cs index 277d9480..9763f16c 100644 --- a/Content/GUI/StyledScrollbar.cs +++ b/Content/GUI/StyledScrollbar.cs @@ -1,13 +1,8 @@ using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Helpers; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; using Terraria.GameContent.UI.Elements; -using Terraria.ModLoader.UI.Elements; +using Terraria.Graphics.Renderers; using Terraria.UI; namespace DragonLens.Content.GUI @@ -16,16 +11,16 @@ internal class StyledScrollbar : Terraria.ModLoader.UI.Elements.FixedUIScrollbar { public float oldValue; public int scrolledRecently; - public static MethodInfo handleMethod = typeof(UIScrollbar).GetMethod("GetHandleRectangle", BindingFlags.NonPublic | BindingFlags.Instance); public StyledScrollbar(UserInterface userInterface) : base(userInterface) { } public override void Update(GameTime gameTime) { + base.Update(gameTime); + float value = GetValue(); - // UIGrids and lists update a frame later than scrolling, so we need to be recalculating for 2 frames if (value != oldValue) { oldValue = value; @@ -39,21 +34,22 @@ public override void Update(GameTime gameTime) } } - public override void Draw(SpriteBatch spriteBatch) + public override void DrawSelf(SpriteBatch spriteBatch) { - if (CanScroll) - { - var back = GetDimensions().ToRectangle(); - back.Inflate(2, 2); + if (userInterface == null || !CanScroll) + return; - GUIHelper.DrawBox(spriteBatch, back, ThemeHandler.BackgroundColor); + base.DrawSelf(spriteBatch); - var handle = (Rectangle)handleMethod.Invoke(this, null); - handle.Width = (int)(GetDimensions().Width - 4); - handle.Offset(2, 0); + Rectangle back = GetDimensions().ToRectangle(); + back.Inflate(2, 2); + GUIHelper.DrawBox(spriteBatch, back, ThemeHandler.BackgroundColor); - GUIHelper.DrawBox(spriteBatch, handle, ThemeHandler.ButtonColor); - } + Rectangle handle = (Rectangle)handleMethod.Invoke(this, null); + handle.Width = (int)GetDimensions().Width - 4; + handle.Offset(2, 0); + + GUIHelper.DrawBox(spriteBatch, handle, ThemeHandler.ButtonColor); } } } \ No newline at end of file diff --git a/Content/GUI/ThemeMenu.cs b/Content/GUI/ThemeMenu.cs index 28ddfc1e..b7ae6e51 100644 --- a/Content/GUI/ThemeMenu.cs +++ b/Content/GUI/ThemeMenu.cs @@ -168,7 +168,7 @@ public override void Draw(SpriteBatch spriteBatch) target.Inflate(-12, -12); theme.DrawBox(spriteBatch, target, ThemeHandler.ButtonColor); - if (IsMouseHovering && !Main.mouseLeft) + if (IsMouseHovering && !Main.mouseLeft && CanShowTooltip) { Tooltip.SetName(theme.Name); Tooltip.SetTooltip(theme.Description); @@ -207,7 +207,7 @@ public override void Draw(SpriteBatch spriteBatch) Texture2D tex = theme.GetIcon("ItemSpawner"); spriteBatch.Draw(tex, target.Center.ToVector2(), null, Color.White, 0, tex.Size() / 2f, 1, 0, 0); - if (IsMouseHovering && !Main.mouseLeft) + if (IsMouseHovering && !Main.mouseLeft && CanShowTooltip) { Tooltip.SetName(theme.Name); Tooltip.SetTooltip(theme.Description); diff --git a/Content/GUI/ToggleButton.cs b/Content/GUI/ToggleButton.cs index 4696a3e6..ac120e31 100644 --- a/Content/GUI/ToggleButton.cs +++ b/Content/GUI/ToggleButton.cs @@ -1,7 +1,11 @@ using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Helpers; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using ReLogic.Content; using System; +using Terraria.ModLoader; namespace DragonLens.Content.GUI { @@ -27,35 +31,79 @@ internal class ToggleButton : SmartUIElement /// public Func getInfo; + public Asset iconAsset; + public Func> getIconTexture; + public Func getTooltip; + public bool drawHighlight = true; + /// /// /// /// The texture of the icon to draw on the button /// How the button should determine if it is 'on' or not. While on, it will draw a colored outline around itself. /// What this button should say when hovered over - public ToggleButton(string iconTexture, Func isOn, string tooltip = "", Func getInfo = null) + public ToggleButton(string iconTexture, Func isOn, string tooltip = "", Func getInfo = null, bool drawHighlight = true) { this.iconTexture = iconTexture; + this.iconAsset = ModContent.Request(iconTexture); this.isOn = isOn; + this.tooltip = tooltip; + this.getInfo = getInfo; + this.drawHighlight = drawHighlight; + Width.Set(32, 0); Height.Set(32, 0); + } + + public ToggleButton(Asset iconTexture, Func isOn, string tooltip = "", Func getInfo = null, bool drawHighlight = true) + { + this.iconAsset = iconTexture; + this.isOn = isOn; this.tooltip = tooltip; this.getInfo = getInfo; + this.drawHighlight = drawHighlight; + + Width.Set(32, 0); + Height.Set(32, 0); + } + + public ToggleButton(Func> getIconTexture, Func isOn, Func getTooltip, Func getInfo = null, bool drawHighlight = true) + { + this.getIconTexture = getIconTexture; + this.isOn = isOn; + this.getTooltip = getTooltip; + this.getInfo = getInfo; + this.drawHighlight = drawHighlight; + + Width.Set(32, 0); + Height.Set(32, 0); } public override void Draw(SpriteBatch spriteBatch) { GUIHelper.DrawBox(spriteBatch, GetDimensions().ToRectangle(), ThemeHandler.ButtonColor); - Texture2D tex = ModContent.Request(iconTexture).Value; - spriteBatch.Draw(tex, GetDimensions().Center(), null, Color.White, 0, tex.Size() / 2, 1, 0, 0); + Texture2D tex = (getIconTexture != null ? getIconTexture() : iconAsset).Value; + spriteBatch.Draw(tex, GetDimensions().Center(), null, Color.White, 0f, tex.Size() / 2f, 1f, SpriteEffects.None, 0f); - if (isOn()) + if (drawHighlight && isOn()) GUIHelper.DrawOutline(spriteBatch, GetDimensions().ToRectangle(), ThemeHandler.ButtonColor.InvertColor()); - if (IsMouseHovering && tooltip != "") +#if DEBUG + + /* + * This debug block is: + * pretty good for debugging when having multiple states open and seeing when + * e.g filter toggle button goes out of focus + */ + //if (drawHighlight && isOn() && CanShowTooltip) + //GUIHelper.DrawOutline(spriteBatch, GetDimensions().ToRectangle(), ThemeHandler.ButtonColor.InvertColor()); +#endif + + string hoverName = getTooltip?.Invoke() ?? tooltip; + if (IsMouseHovering && hoverName != "" && CanShowTooltip) { - Tooltip.SetName(tooltip); + Tooltip.SetName(hoverName); Tooltip.SetTooltip(getInfo?.Invoke() ?? LocalizationHelper.GetGUIText($"ToggleButton.{(isOn() ? "On" : "Off")}")); } } diff --git a/Content/GUI/ToolbarCustomizationElements.cs b/Content/GUI/ToolbarCustomizationElements.cs index 2c1141c3..c4d7cb6d 100644 --- a/Content/GUI/ToolbarCustomizationElements.cs +++ b/Content/GUI/ToolbarCustomizationElements.cs @@ -12,7 +12,7 @@ internal abstract class LocalizedCustomizationElement : SmartUIElement { public override void Draw(SpriteBatch spriteBatch) { - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(LocalizationHelper.GetGUIText($"ToolbarCustomizationElements.{GetType().Name}.Name")); Tooltip.SetTooltip(LocalizationHelper.GetGUIText($"ToolbarCustomizationElements.{GetType().Name}.Tooltip")); @@ -88,22 +88,22 @@ public override void SafeMouseUp(UIMouseEvent evt) public override void SafeUpdate(GameTime gameTime) { - if (dragging) + if (dragging && CanShowTooltip) { int index = ParentToolbar.toolbar.toolList.IndexOf(parent.tool); - if (ParentToolbar.toolbar.orientation == Orientation.Horizontal) + if (ParentToolbar.toolbar.orientation == Orientation.Horizontal && CanShowTooltip) { - if (Main.MouseScreen.X > parent.GetDimensions().X + parent.GetDimensions().Width + 8 && index < parent.parent.toolbar.toolList.Count - 1) + if (Main.MouseScreen.X > parent.GetDimensions().X + parent.GetDimensions().Width + 8 && index < parent.parent.toolbar.toolList.Count - 1 && CanShowTooltip) SwapTools(index + 1); - else if (Main.MouseScreen.X < parent.GetDimensions().X - 8 && index > 0) + else if (Main.MouseScreen.X < parent.GetDimensions().X - 8 && index > 0 && CanShowTooltip) SwapTools(index - 1); } else { - if (Main.MouseScreen.Y > parent.GetDimensions().Y + parent.GetDimensions().Height + 8 && index < parent.parent.toolbar.toolList.Count - 1) + if (Main.MouseScreen.Y > parent.GetDimensions().Y + parent.GetDimensions().Height + 8 && index < parent.parent.toolbar.toolList.Count - 1 && CanShowTooltip) SwapTools(index + 1); - else if (Main.MouseScreen.Y < parent.GetDimensions().Y - 8 && index > 0) + else if (Main.MouseScreen.Y < parent.GetDimensions().Y - 8 && index > 0 && CanShowTooltip) SwapTools(index - 1); } } @@ -195,18 +195,18 @@ public override void SafeUpdate(GameTime gameTime) // This logic exists because this dragger is always present, not just when customizing. Has to be done due to // append order effecting click priority :/ - if (!CustomizeTool.customizing) + if (!CustomizeTool.customizing && CanShowTooltip) { Width.Set(0, 0); Height.Set(0, 0); return; } - if (dragging && draggedElement != null) + if (dragging && draggedElement != null && CanShowTooltip) { - if (Main.mouseRight && !debounce) + if (Main.mouseRight && !debounce && CanShowTooltip) { - if (DraggedToolbar.orientation == Orientation.Horizontal) + if (DraggedToolbar.orientation == Orientation.Horizontal && CanShowTooltip) { DraggedToolbar.orientation = Orientation.Vertical; draggedElement.Refresh(); @@ -247,7 +247,7 @@ public override void SafeUpdate(GameTime gameTime) DraggedToolbar.orientation = Orientation.Horizontal; } - if (DraggedToolbar.CollapseDirection != DraggedToolbar.lastKnownCollapse) + if (DraggedToolbar.CollapseDirection != DraggedToolbar.lastKnownCollapse && CanShowTooltip) { draggedElement.Refresh(); DraggedToolbar.lastKnownCollapse = DraggedToolbar.CollapseDirection; @@ -378,7 +378,7 @@ public override void Draw(SpriteBatch spriteBatch) spriteBatch.Draw(tex, GetDimensions().Center(), source, new Color(200, 200, 100), rotation, new Vector2(30, 0), 1, 0, 0); - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { string hideOption = LocalizationHelper.GetGUIText($"ToolbarCustomizationElements.HideOptionButton.{Toolbar.automaticHideOption}.Name"); diff --git a/Content/GUI/ToolbarElements.cs b/Content/GUI/ToolbarElements.cs index f444cfcc..076930f5 100644 --- a/Content/GUI/ToolbarElements.cs +++ b/Content/GUI/ToolbarElements.cs @@ -461,7 +461,7 @@ public override void Draw(SpriteBatch spriteBatch) tool.DrawIcon(spriteBatch, innerRect); - if (IsMouseHovering && !parent.toolbar.collapsed) + if (IsMouseHovering && !parent.toolbar.collapsed && CanShowTooltip) { Tooltip.SetName(tool.DisplayName); Tooltip.SetTooltip(tool.Description); diff --git a/Content/GUI/Tooltip.cs b/Content/GUI/Tooltip.cs index e52d47f4..2a8e3811 100644 --- a/Content/GUI/Tooltip.cs +++ b/Content/GUI/Tooltip.cs @@ -14,7 +14,7 @@ public class Tooltip : SmartUIState, ILoadable private static TextSnippet[] text; private static TextSnippet[] tooltip; private static Color color = Color.White; - + public override bool ParticipatesInHoverOwnership => false; public override bool Visible => true; public void Load(Mod mod) diff --git a/Content/Tools/CustomizeTool.cs b/Content/Tools/CustomizeTool.cs index 1dfe1bc8..8e8f23de 100644 --- a/Content/Tools/CustomizeTool.cs +++ b/Content/Tools/CustomizeTool.cs @@ -158,7 +158,7 @@ public override void SafeDraw(SpriteBatch spriteBatch, Rectangle iconBox) spriteBatch.Draw(icon, target.Center(), null, Color.White, 0, icon.Size() / 2f, scale, 0, 0); - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(tool.DisplayName); Tooltip.SetTooltip(tool.Description); diff --git a/Content/Tools/Developer/AssetManager.cs b/Content/Tools/Developer/AssetManager.cs index 8580e3ea..4044cade 100644 --- a/Content/Tools/Developer/AssetManager.cs +++ b/Content/Tools/Developer/AssetManager.cs @@ -3,6 +3,7 @@ using DragonLens.Content.Tools.Spawners; using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; +using DragonLens.Core.Systems.ToolSystem; using DragonLens.Helpers; using Microsoft.Xna.Framework.Content; using System; @@ -26,6 +27,7 @@ internal class AssetManager : BrowserTool internal class AssetBrowser : Browser { + public override Tool OwnerTool => ModContent.GetInstance(); public ReloadButton reloadButton; public override string Name => ModContent.GetInstance().DisplayName; @@ -205,7 +207,7 @@ public override void SafeDraw(SpriteBatch spriteBatch, Rectangle iconArea) spriteBatch.Draw(asset.Value, iconArea.Center.ToVector2(), null, Color.White, 0, asset.Size() / 2f, scale, 0, 0); - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(Identifier); @@ -325,7 +327,7 @@ public override void SafeDraw(SpriteBatch spriteBatch, Rectangle iconArea) Utils.DrawBorderString(spriteBatch, preview[..Math.Min(preview.Length, 4)], iconArea.Center(), color, 0.7f * (iconArea.Width / 32f), 0.5f, 0.1f); - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(Identifier); @@ -370,7 +372,7 @@ public override void Draw(SpriteBatch spriteBatch) GUIHelper.DrawBox(spriteBatch, GetDimensions().ToRectangle(), ThemeHandler.ButtonColor); spriteBatch.Draw(Assets.GUI.Refresh.Value, GetDimensions().Center(), null, Color.White, 0, Assets.GUI.Refresh.Size() / 2f, 1, 0, 0); - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(LocalizationHelper.GetToolText("AssetManager.Rescan.DisplayName")); Tooltip.SetTooltip(LocalizationHelper.GetToolText("AssetManager.Rescan.Description")); diff --git a/Content/Tools/Editors/AccessoryTray.cs b/Content/Tools/Editors/AccessoryTray.cs index c78447ce..01b00db4 100644 --- a/Content/Tools/Editors/AccessoryTray.cs +++ b/Content/Tools/Editors/AccessoryTray.cs @@ -1,4 +1,5 @@ using DragonLens.Content.GUI; +using DragonLens.Content.Tools.Developer; using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Core.Systems.ToolSystem; @@ -102,6 +103,7 @@ public override void UpdateEquips() internal class AccessoryTrayUI : DraggableUIState { + public override Tool OwnerTool => ModContent.GetInstance(); public UIGrid slots; public StyledScrollbar slotsScroll; diff --git a/Content/Tools/Editors/ItemEditor.cs b/Content/Tools/Editors/ItemEditor.cs index a70e216c..edd22e95 100644 --- a/Content/Tools/Editors/ItemEditor.cs +++ b/Content/Tools/Editors/ItemEditor.cs @@ -371,7 +371,7 @@ public override void Draw(SpriteBatch spriteBatch) spriteBatch.Draw(tex, GetDimensions().ToRectangle().TopLeft() + new Vector2(16, 16), null, Color.White, 0, tex.Size() / 2f, 0.5f, 0, 0); } - if (IsMouseHovering) + if (IsMouseHovering && CanShowTooltip) { Tooltip.SetName(name); diff --git a/Content/Tools/Gameplay/Noclip.cs b/Content/Tools/Gameplay/Noclip.cs index b61b2969..bf32e310 100644 --- a/Content/Tools/Gameplay/Noclip.cs +++ b/Content/Tools/Gameplay/Noclip.cs @@ -3,6 +3,7 @@ using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Core.Systems.ToolSystem; using DragonLens.Helpers; +using Microsoft.Xna.Framework.Input; using System; using System.IO; using Terraria.ID; @@ -102,14 +103,25 @@ public override void PostUpdate() // Check free cam to stop movement while freecam is active if (!FreeCamera.active) { + // Custom speeds with shift / ctrl keys: + // Ctrl+shift -> 120f + // Ctrl -> 7.5f + // Shift -> 60f + // (normal) -> 15f + + bool shift = Main.keyState.IsKeyDown(Keys.LeftShift) || Main.keyState.IsKeyDown(Keys.RightShift); + bool ctrl = Main.keyState.IsKeyDown(Keys.LeftControl) || Main.keyState.IsKeyDown(Keys.RightControl); + + float speed = ctrl && shift ? 120f : ctrl ? 7.5f : shift ? 60f : 15f; + if (Player.controlLeft) - desiredPos.X -= 15; + desiredPos.X -= speed; if (Player.controlRight) - desiredPos.X += 15; + desiredPos.X += speed; if (Player.controlUp) - desiredPos.Y -= 15; + desiredPos.Y -= speed; if (Player.controlDown) - desiredPos.Y += 15; + desiredPos.Y += speed; } if (Main.netMode == NetmodeID.MultiplayerClient && diff --git a/Content/Tools/Gameplay/Paint.cs b/Content/Tools/Gameplay/Paint.cs index 32e1a373..6f6ba088 100644 --- a/Content/Tools/Gameplay/Paint.cs +++ b/Content/Tools/Gameplay/Paint.cs @@ -1,4 +1,6 @@ using DragonLens.Content.GUI; +using DragonLens.Content.Tools.Multiplayer; +using DragonLens.Content.Tools.Visualization; using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Core.Systems.ToolSystem; @@ -49,6 +51,7 @@ public static string GetTextValue(string key, params object[] args) internal class PaintWindow : DraggableUIState { + public override Tool OwnerTool => ModContent.GetInstance(); public bool firstSet; public UIGrid structureButtons; @@ -87,7 +90,7 @@ public override void SafeOnInitialize() Append(adPanel); } - sampleButton = new("DragonLens/Assets/GUI/Picker", () => selecting, Paint.GetTextValue("CreateStructure")); + sampleButton = new(Assets.GUI.Picker, () => selecting, Paint.GetTextValue("CreateStructure")); sampleButton.OnLeftClick += (a, b) => selecting = !selecting; Append(sampleButton); diff --git a/Content/Tools/Gameplay/SpawnTool.cs b/Content/Tools/Gameplay/SpawnTool.cs index b6679843..7736b53f 100644 --- a/Content/Tools/Gameplay/SpawnTool.cs +++ b/Content/Tools/Gameplay/SpawnTool.cs @@ -70,6 +70,7 @@ public override void EditSpawnRate(Player player, ref int spawnRate, ref int max internal class SpawnWindow : DraggableUIState { + public override Tool OwnerTool => ModContent.GetInstance(); public SpawnSlider slider; public override Rectangle DragBox => new((int)basePos.X, (int)basePos.Y, 360, 54); diff --git a/Content/Tools/Gameplay/Time.cs b/Content/Tools/Gameplay/Time.cs index 48f89917..f7409358 100644 --- a/Content/Tools/Gameplay/Time.cs +++ b/Content/Tools/Gameplay/Time.cs @@ -1,4 +1,5 @@ using DragonLens.Content.GUI; +using DragonLens.Content.Tools.Editors; using DragonLens.Core.Loaders.UILoading; using DragonLens.Core.Systems.ThemeSystem; using DragonLens.Core.Systems.ToolSystem; @@ -79,6 +80,7 @@ public override void PreUpdateTime() internal class TimeWindow : DraggableUIState { + public override Tool OwnerTool => ModContent.GetInstance