From c4b299c0ced0590644869483eb02e8c165525c68 Mon Sep 17 00:00:00 2001 From: "dust765@protonmail.com" Date: Fri, 26 Jan 2024 08:56:16 +0100 Subject: [PATCH 01/93] DUST765 - INITIALIZE --- .github/workflows/dust-deploy-activedev.yml | 97 +++++++++++++++++++++ .github/workflows/dust-deploy-dev.yml | 97 +++++++++++++++++++++ README.md | 47 ++++++++++ 3 files changed, 241 insertions(+) create mode 100644 .github/workflows/dust-deploy-activedev.yml create mode 100644 .github/workflows/dust-deploy-dev.yml diff --git a/.github/workflows/dust-deploy-activedev.yml b/.github/workflows/dust-deploy-activedev.yml new file mode 100644 index 0000000000..f64d1fc813 --- /dev/null +++ b/.github/workflows/dust-deploy-activedev.yml @@ -0,0 +1,97 @@ +name: Dust-ActiveDev-Deploy + +on: + workflow_dispatch: + workflow_run: + branches: [dev_dust765_activedev] + workflows: [Build-Test] + types: + - completed + +concurrency: + group: maindeploy + cancel-in-progress: true + +env: + CUO_ASSEMBLY_VERSION: '1.0.0.${{ github.run_number }}' + CUO_OUTPUT_PATH: '../../bin/dist' + CUO_PROJECT_PATH: "src/ClassicUO.Client/ClassicUO.Client.csproj" + CUO_ZIP_NAME: "Dust765_activedev.zip" + + DOTNET_NOLOGO: false + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + NUGET_XMLDOC_MODE: skip + +jobs: + removeoldrelease: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Remove old Release + uses: dev-drprasad/delete-tag-and-release@v0.2.1 + with: + delete_release: true + tag_name: Dust765_activedev + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build: + # if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ${{ matrix.os }} + strategy: + max-parallel: 1 + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + + steps: + - uses: actions/checkout@v4 + with: + ref: 'dev_dust765_activedev' + + - name: Get submodules + run: | + git config --global url."https://".insteadOf git:// + git submodule update --init --recursive + + - name: Setup .NET 7 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.x + include-prerelease: true + + - name: Build + run: dotnet publish ${{ env.CUO_PROJECT_PATH }} -c Release -o ${{ env.CUO_OUTPUT_PATH }} -p:IS_DEV_BUILD=true -p:AssemblyVersion=${{ env.CUO_ASSEMBLY_VERSION }} -p:FileVersion=${{ env.CUO_ASSEMBLY_VERSION }} + + - name: Create manifest + run: | + dotnet run --project tools/ManifestCreator/ManifestCreator.csproj "${{ env.CUO_OUTPUT_PATH }}" "unix-auto" "${{ runner.os }}${{ env.CUO_ZIP_NAME }}" + mkdir upload + mv manifest.xml upload + + - name: Create package + uses: thedoctor0/zip-release@master + with: + type: 'zip' + directory: ${{ env.CUO_OUTPUT_PATH }} + filename: ${{ runner.os }}${{ env.CUO_ZIP_NAME }} + exclusions: '*.zip manifest.xml' + + - name: Move output + run: mv "${{ env.CUO_OUTPUT_PATH }}/${{ runner.os }}${{ env.CUO_ZIP_NAME }}" upload + + - name: Upload Release + uses: ncipollo/release-action@v1 + with: + artifacts: "upload/${{ runner.os }}${{ env.CUO_ZIP_NAME }}" + name: 'Dust765 - ActiveDev - 1.0.0.${{ github.run_number }}' + body: 'These are built automatically on the dev_dust765_activedev branch. These may include features that are still being worked on/not complete.' + makeLatest: false + allowUpdates: true + prerelease: true + tag: Dust765_activedev + token: ${{ secrets.GITHUB_TOKEN }} + + diff --git a/.github/workflows/dust-deploy-dev.yml b/.github/workflows/dust-deploy-dev.yml new file mode 100644 index 0000000000..b22e5b0f24 --- /dev/null +++ b/.github/workflows/dust-deploy-dev.yml @@ -0,0 +1,97 @@ +name: Dust-Dev-Deploy + +on: + workflow_dispatch: + workflow_run: + branches: [dev_dust765] + workflows: [Build-Test] + types: + - completed + +concurrency: + group: maindeploy + cancel-in-progress: true + +env: + CUO_ASSEMBLY_VERSION: '1.0.0.${{ github.run_number }}' + CUO_OUTPUT_PATH: '../../bin/dist' + CUO_PROJECT_PATH: "src/ClassicUO.Client/ClassicUO.Client.csproj" + CUO_ZIP_NAME: "Dust765_dev.zip" + + DOTNET_NOLOGO: false + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + NUGET_XMLDOC_MODE: skip + +jobs: + removeoldrelease: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Remove old Release + uses: dev-drprasad/delete-tag-and-release@v0.2.1 + with: + delete_release: true + tag_name: Dust765_dev + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build: + # if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ${{ matrix.os }} + strategy: + max-parallel: 1 + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + + steps: + - uses: actions/checkout@v4 + with: + ref: 'dev_dust765' + + - name: Get submodules + run: | + git config --global url."https://".insteadOf git:// + git submodule update --init --recursive + + - name: Setup .NET 7 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.x + include-prerelease: true + + - name: Build + run: dotnet publish ${{ env.CUO_PROJECT_PATH }} -c Release -o ${{ env.CUO_OUTPUT_PATH }} -p:IS_DEV_BUILD=true -p:AssemblyVersion=${{ env.CUO_ASSEMBLY_VERSION }} -p:FileVersion=${{ env.CUO_ASSEMBLY_VERSION }} + + - name: Create manifest + run: | + dotnet run --project tools/ManifestCreator/ManifestCreator.csproj "${{ env.CUO_OUTPUT_PATH }}" "unix-auto" "${{ runner.os }}${{ env.CUO_ZIP_NAME }}" + mkdir upload + mv manifest.xml upload + + - name: Create package + uses: thedoctor0/zip-release@master + with: + type: 'zip' + directory: ${{ env.CUO_OUTPUT_PATH }} + filename: ${{ runner.os }}${{ env.CUO_ZIP_NAME }} + exclusions: '*.zip manifest.xml' + + - name: Move output + run: mv "${{ env.CUO_OUTPUT_PATH }}/${{ runner.os }}${{ env.CUO_ZIP_NAME }}" upload + + - name: Upload Release + uses: ncipollo/release-action@v1 + with: + artifacts: "upload/${{ runner.os }}${{ env.CUO_ZIP_NAME }}" + name: 'Dust765 - Latest Build - 1.0.0.${{ github.run_number }}' + body: 'These are built automatically on the dev_dust765 branch.' + makeLatest: false + allowUpdates: true + prerelease: true + tag: Dust765_dev + token: ${{ secrets.GITHUB_TOKEN }} + + diff --git a/README.md b/README.md index 441971eac4..ead7c64308 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,50 @@ +# Project dust765 +This project is to address a problem constructed within the toxicity of this community. This is to show the community, open source projects are not meant for cliques and high school drama but rather the expansion of something greater, innovation. -A penny for your thoughts, the adder that prays beneath the rose. + +![dust765_logo](https://user-images.githubusercontent.com/77043734/209156140-14558d04-eaf9-42f0-9939-ddec9cf6c1ac.png) + +# V2 VERSION INFO + +Project Dust765 moved away from Karasho ClassicUO to be built on TazUO in january 2024. The old repo is available [HERE](https://github.com/dust765/ClassicUO_old). + +In addition, [jsebold666](https://github.com/jsebold666) has been added as collaborator to help me out. + +# BRANCHES + +main is 1:1 from upstream (TazUO) + +dev is 1:1 from upstream (TazUO) + +dev_dust765 stable release + +dev_dust765_activedev stable work in progress releases + +Release: [![Release](https://github.com/dust765/ClassicUO/actions/workflows/build-test.yml/badge.svg?branch=dev_dust765)](https://github.com/dust765/ClassicUO/actions/workflows/build-test.yml) Dev: [![Dev](https://github.com/dust765/ClassicUO/actions/workflows/build-test.yml/badge.svg?branch=dev_dust765_activedev)](https://github.com/dust765/ClassicUO/actions/workflows/build-test.yml) + +# contact and team info + +Discord: dust765#2787 + +Dust765: 7 Link, 6 Gaechti, 5 Syrupz and jsebold666 + +Join me on TazUO discord: + + + +# feature showcase (old version) + +[Video Part 1 on YouTube](https://youtu.be/aqHiiOhx8Q8) + +[Video Part 2 on YouTube](https://youtu.be/P7YBrI3s6ZI) + +[Video Part 3 on YouTube](https://youtu.be/074Osj1Fcrg) + +# new features will be listed here + + +------------------------------------------------------------------ +

From 1a039fa4057f1842e8949e2ba04a745d813bc94c Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 26 Jan 2024 16:26:54 -0700 Subject: [PATCH 02/93] Improved last health bar position saving --- .../Game/UI/Gumps/HealthBarGump.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index ad477f3eed..546d80aeb3 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -30,21 +30,21 @@ #endregion -using System; -using System.Xml; +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.GameObjects; using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Controls; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Resources; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using SDL2; +using System; using System.Text.Json.Serialization; +using System.Xml; namespace ClassicUO.Game.UI.Gumps { @@ -148,14 +148,19 @@ public override void Dispose() GameActions.SendCloseStatus(LocalSerial); }*/ - if (IsLastTarget && ProfileManager.CurrentProfile != null) - ProfileManager.CurrentProfile.LastTargetHealthBarPos = Location; - _textBox?.Dispose(); _textBox = null; base.Dispose(); } + protected override void OnMove(int x, int y) + { + base.OnMove(x, y); + + if (IsLastTarget && ProfileManager.CurrentProfile != null) + ProfileManager.CurrentProfile.LastTargetHealthBarPos = Location; + } + public override void Save(XmlTextWriter writer) { base.Save(writer); @@ -1142,7 +1147,7 @@ protected override void BuildGump() ); Control m, s; Add - (m = + (m = new LineCHB ( HPB_BAR_SPACELEFT, From 4620a39d4ae2c20b60d5e4edbea0af6608dfbc47 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 26 Jan 2024 16:44:31 -0700 Subject: [PATCH 03/93] Mouse click-thru on roofs when Modern circle of transparency is on --- .../Game/Scenes/GameSceneDrawingSorting.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs b/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs index 29fd6b285d..380f921ba7 100644 --- a/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs +++ b/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs @@ -30,20 +30,18 @@ #endregion -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.GameObjects; using ClassicUO.Game.Managers; using ClassicUO.Game.Map; -using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Utility; -using ClassicUO.Utility.Logging; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System; +using System.Runtime.CompilerServices; namespace ClassicUO.Game.Scenes { @@ -404,7 +402,7 @@ out bool allowSelection if (itemData.IsDoor || itemData.IsRoof) { obj.AlphaHue = 65; - allowSelection = true; + allowSelection = itemData.IsDoor; return true; } if (itemData.IsRoof && _noDrawRoofs) From fc4455bc8a3507b90dd62ec64775de1ca7b17c3f Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 26 Jan 2024 16:47:50 -0700 Subject: [PATCH 04/93] Fix alpha for objects above max z level for COT --- src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs b/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs index 380f921ba7..527a343e60 100644 --- a/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs +++ b/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs @@ -389,7 +389,8 @@ out bool allowSelection { if (obj.Z >= _maxZ) { - CalculateAlpha(ref obj.AlphaHue, 0); + obj.AlphaHue = 0; + //CalculateAlpha(ref obj.AlphaHue, 0); } else { From a82396a9f15c26eee3a468f427b535523f35c0cb Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 26 Jan 2024 16:55:58 -0700 Subject: [PATCH 05/93] Add optional Assemblies.cfg file for scripts --- src/ClassicUO.Client/ScriptCompiler.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/ClassicUO.Client/ScriptCompiler.cs b/src/ClassicUO.Client/ScriptCompiler.cs index 74f5d1e7e4..0ea000c6db 100644 --- a/src/ClassicUO.Client/ScriptCompiler.cs +++ b/src/ClassicUO.Client/ScriptCompiler.cs @@ -36,6 +36,24 @@ public static string[] GetReferenceAssemblies() CUOEnviroment.ExecutablePath + "/ClassicUO.exe" }; + var path = Path.Combine(CUOEnviroment.ExecutablePath, "Data/Assemblies.cfg"); + + if (File.Exists(path)) + { + using (var ip = new StreamReader(path)) + { + string line; + + while ((line = ip.ReadLine()) != null) + { + if (line.Length > 0 && !line.StartsWith("#")) + { + list.Add(line); + } + } + } + } + list.AddRange(m_AdditionalReferences); return list.ToArray(); From 4e0f37e193b5770f0012d4c88a913541a6b0d845 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 26 Jan 2024 17:11:15 -0700 Subject: [PATCH 06/93] Add modern options gump to customizble UI system --- .../Game/UI/Gumps/ModernOptionsGump.cs | 68 +++++++++++++------ 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index f13a348291..867fb0624b 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -17,6 +17,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Text.Json.Serialization; using System.Threading.Tasks; namespace ClassicUO.Game.UI.Gumps @@ -31,6 +32,27 @@ internal class ModernOptionsGump : Gump private Profile profile; private ModernOptionsGumpLanguage lang; + private static ThemeSettings _settings; + private static ThemeSettings Theme + { + get + { + if (_settings == null) + { + _settings = (ThemeSettings)UISettings.Load(typeof(ModernOptionsGump).ToString()); + if (_settings == null) + { + _settings = new ThemeSettings(); + ThemeSettings.Save(typeof(ModernOptionsGump).ToString(), _settings); + } + return _settings; + } + else + { + return _settings; + } + } + } public ModernOptionsGump() : base(0, 0) { @@ -6241,33 +6263,39 @@ public SettingsOption(string optionLabel, Control control, int maxTotalWidth, PA public Area FullControl { get; } } - private static class Theme + private class ThemeSettings : UISettings { - public const int SLIDER_WIDTH = 150; - public const int COMBO_BOX_WIDTH = 225; - public const int SCROLL_BAR_WIDTH = 15; - public const int INPUT_WIDTH = 200; - public const int TOP_PADDING = 5; - public const int INDENT_SPACE = 40; - public const int BLANK_LINE = 20; - public const int HORIZONTAL_SPACING_CONTROLS = 20; + public int SLIDER_WIDTH { get; set; } = 150; + public int COMBO_BOX_WIDTH { get; set; } = 225; + public int SCROLL_BAR_WIDTH { get; set; } = 15; + public int INPUT_WIDTH { get; set; } = 200; + public int TOP_PADDING { get; set; } = 5; + public int INDENT_SPACE { get; set; } = 40; + public int BLANK_LINE { get; set; } = 20; + public int HORIZONTAL_SPACING_CONTROLS { get; set; } = 20; + + public int STANDARD_TEXT_SIZE { get; set; } = 20; - public const int STANDARD_TEXT_SIZE = 20; + public float NO_MATCH_SEARCH { get; set; } = 0.5f; - public const float NO_MATCH_SEARCH = 0.5f; + public ushort BACKGROUND { get; set; } = 897; + public ushort SEARCH_BACKGROUND { get; set; } = 899; + public ushort BLACK { get; set; } = 0; - public const ushort BACKGROUND = 897; - public const ushort SEARCH_BACKGROUND = 899; - public const ushort BLACK = 0; + [JsonConverter(typeof(ColorJsonConverter))] + public Color DROPDOWN_OPTION_NORMAL_HUE { get; set; } = Color.White; + [JsonConverter(typeof(ColorJsonConverter))] - public static Color DROPDOWN_OPTION_NORMAL_HUE = Color.White; - public static Color DROPDOWN_OPTION_HOVER_HUE = Color.AntiqueWhite; - public static Color DROPDOWN_OPTION_SELECTED_HUE = Color.CadetBlue; + public Color DROPDOWN_OPTION_HOVER_HUE { get; set; } = Color.AntiqueWhite; + [JsonConverter(typeof(ColorJsonConverter))] + public Color DROPDOWN_OPTION_SELECTED_HUE { get; set; } = Color.CadetBlue; - public static Color BUTTON_FONT_COLOR = Color.White; - public static Color TEXT_FONT_COLOR = Color.White; + [JsonConverter(typeof(ColorJsonConverter))] + public Color BUTTON_FONT_COLOR { get; set; } = Color.White; + [JsonConverter(typeof(ColorJsonConverter))] + public Color TEXT_FONT_COLOR { get; set; } = Color.White; - public static string FONT = TrueTypeLoader.EMBEDDED_FONT; + public string FONT { get; set; } = TrueTypeLoader.EMBEDDED_FONT; } private static class PositionHelper From d2bb90c06b0d49b008ece503b955a8ba59a25151 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 1 Feb 2024 01:09:17 -0700 Subject: [PATCH 07/93] Added saveposition to xmlgump options --- .../Game/UI/XmlGumpHandler.cs | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/XmlGumpHandler.cs b/src/ClassicUO.Client/Game/UI/XmlGumpHandler.cs index 4c7ac08ce6..9afd0ec26d 100644 --- a/src/ClassicUO.Client/Game/UI/XmlGumpHandler.cs +++ b/src/ClassicUO.Client/Game/UI/XmlGumpHandler.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using System.Xml; using static ClassicUO.Game.UI.XmlGumpHandler; @@ -27,6 +28,8 @@ public static XmlGump CreateGumpFromFile(string filePath) if (File.Exists(filePath)) { + gump.FilePath = filePath; + XmlDocument xmlDoc = new XmlDocument(); try { @@ -54,6 +57,12 @@ public static XmlGump CreateGumpFromFile(string filePath) gump.IsLocked = locked; } break; + case "saveposition": + if (bool.TryParse(attr.Value, out bool savePos)) + { + gump.SavePosition = savePos; + } + break; } } @@ -798,14 +807,22 @@ public XmlProgressBarInfo(Control control, int maxSize, string value, string max internal class XmlGump : Gump { - public List>> TextBoxUpdates { get; set; } = new List>>(); + ///

+ /// The frequency of UI updates for Xml Gumps for text/progress bar changes. This affects all xml gumps, it is not set individually. + /// + public static uint UpdateFrequency { get; set; } = 250; + public List>> TextBoxUpdates { get; set; } = new List>>(); public List ProgressBarUpdates { get; set; } = new List(); - public List VerticalProgressBarUpdates { get; set; } = new List(); + public bool SavePosition { get; set; } = false; + public string FilePath { get; set; } private uint nextUpdate = 0; + private bool savingFile = false; + private uint saveFileAfter = uint.MaxValue; + public XmlGump() : base(0, 0) { } @@ -873,7 +890,53 @@ public override void Update() } } - nextUpdate = Time.Ticks + 250; + nextUpdate = Time.Ticks + UpdateFrequency; + } + + if (Time.Ticks > saveFileAfter) + { + saveFileAfter = uint.MaxValue; + Task.Run(SaveFile); + } + } + + protected override void OnMove(int x, int y) + { + base.OnMove(x, y); + + if (SavePosition) + { + saveFileAfter = Time.Ticks + 10000; + } + } + + private void SaveFile() + { + if (!savingFile) + { + savingFile = true; + + if (!string.IsNullOrEmpty(FilePath) && File.Exists(FilePath)) + { + XmlDocument xmlDoc = new XmlDocument(); + try + { + xmlDoc.LoadXml(File.ReadAllText(FilePath)); + + if (xmlDoc.DocumentElement != null) + { + XmlElement root = xmlDoc.DocumentElement; + root.SetAttribute("x", X.ToString()); + root.SetAttribute("y", Y.ToString()); + + xmlDoc.Save(FilePath); + } + } + catch (Exception e) { GameActions.Print(e.Message); } + + } + + savingFile = false; } } } From ffb8d257da182cbca2397383556ee203f0caf42a Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 1 Feb 2024 01:09:38 -0700 Subject: [PATCH 08/93] Journal and modern paperdoll anchor can be disabled --- src/ClassicUO.Client/Configuration/Language.cs | 2 ++ src/ClassicUO.Client/Configuration/Profile.cs | 3 +++ .../Game/UI/Gumps/ModernOptionsGump.cs | 13 +++++++++++++ .../Game/UI/Gumps/ModernPaperdoll.cs | 10 +++------- .../Game/UI/Gumps/ResizableJournal.cs | 11 ++++++----- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 80afb81173..c9449ba1b6 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -435,6 +435,7 @@ public class TazUO public string JournalStyle { get; set; } = "Journal style"; public string JournalHideBorders { get; set; } = "Hide borders"; public string HideTimestamp { get; set; } = "Hide timestamp"; + public string JournalAnchor { get; set; } = "Make anchorable"; #endregion #region ModernPaperdoll @@ -443,6 +444,7 @@ public class TazUO public string PaperdollHue { get; set; } = "Paperdoll hue"; public string DurabilityBarHue { get; set; } = "Durability bar hue"; public string ShowDurabilityBarBelow { get; set; } = "Show durability bar below %"; + public string PaperdollAnchor { get; set; } = "Make anchorable"; #endregion #region Nameplates diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index 550fc0b242..8e48fe0c69 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -590,6 +590,9 @@ public int CoolDownConditionCount public uint SOSGumpID { get; set; } = 1915258020; + public bool ModernPaperdollAnchorEnabled { get; set; } = false; + public bool JournalAnchorEnabled { get; set; } = false; + public void Save(string path, bool saveGumps = true) { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index 867fb0624b..5958e0fe6b 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2084,6 +2084,12 @@ private void BuildTazUO() { profile.HideJournalTimestamp = b; }), true, page); + content.BlankLine(); + content.AddToRight(c = new CheckboxWithLabel(lang.GetTazUO.MakeAnchorable, 0, profile.JournalAnchorEnabled, (b) => + { + profile.JournalAnchorEnabled = b; + ResizableJournal.UpdateJournalOptions(); + }), true, page); #endregion #region Modern paperdoll @@ -2105,6 +2111,7 @@ private void BuildTazUO() content.AddToRight(new ModernColorPickerWithLabel(lang.GetTazUO.DurabilityBarHue, profile.ModernPaperDollDurabilityHue, (h) => { profile.ModernPaperDollDurabilityHue = h; + ModernPaperdoll.UpdateAllOptions(); }), true, page); content.RemoveIndent(); content.BlankLine(); @@ -2112,6 +2119,12 @@ private void BuildTazUO() { profile.ModernPaperDoll_DurabilityPercent = i; }), true, page); + content.BlankLine(); + content.AddToRight(c = new CheckboxWithLabel(lang.GetTazUO.PaperdollAnchor, 0, profile.ModernPaperdollAnchorEnabled, (b) => + { + profile.ModernPaperdollAnchorEnabled = b; + ModernPaperdoll.UpdateAllOptions(); + }), true, page); #endregion #region Nameplates diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernPaperdoll.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernPaperdoll.cs index 6904f398ec..198b6729aa 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernPaperdoll.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernPaperdoll.cs @@ -6,15 +6,11 @@ using ClassicUO.Game.Scenes; using ClassicUO.Game.UI.Controls; using ClassicUO.Input; -using ClassicUO.Network; using ClassicUO.Renderer; -using ClassicUO.Renderer.Animations; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using System.Xml; namespace ClassicUO.Game.UI.Gumps @@ -38,12 +34,11 @@ internal class ModernPaperdoll : AnchorableGump public ModernPaperdoll(uint localSerial) : base(localSerial, 0) { UIManager.GetGump()?.Dispose(); - #region ASSIGN FIELDS + #region SET VARS AcceptMouseInput = true; CanMove = true; CanCloseWithRightClick = true; - #endregion - #region SET VARS + AnchorType = ProfileManager.CurrentProfile.ModernPaperdollAnchorEnabled ? ANCHOR_TYPE.NONE : ANCHOR_TYPE.DISABLED; Width = WIDTH; Height = HEIGHT; GroupMatrixHeight = Height; @@ -225,6 +220,7 @@ protected override void UpdateContents() public void UpdateOptions() { backgroundImage.Hue = ProfileManager.CurrentProfile.ModernPaperDollHue; + AnchorType = ProfileManager.CurrentProfile.ModernPaperdollAnchorEnabled ? ANCHOR_TYPE.NONE : ANCHOR_TYPE.DISABLED; foreach (var layerSlot in itemLayerSlots) { layerSlot.Value.UpdateOptions(); diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs b/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs index 92f26c5e92..638072d0b9 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs @@ -1,14 +1,14 @@ using ClassicUO.Configuration; using ClassicUO.Game.Data; +using ClassicUO.Game.GameObjects; using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Controls; using ClassicUO.Input; using ClassicUO.Renderer; using ClassicUO.Utility.Collections; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using Microsoft.Xna.Framework; -using ClassicUO.Game.GameObjects; using System.Linq; namespace ClassicUO.Game.UI.Gumps @@ -50,7 +50,7 @@ internal class ResizableJournal : ResizableGump #endregion public ResizableJournal() : base(_lastWidth, _lastHeight, MIN_WIDTH, MIN_HEIGHT, 0, 0) { - AnchorType = ANCHOR_TYPE.NONE; + AnchorType = ProfileManager.CurrentProfile.JournalAnchorEnabled ? ANCHOR_TYPE.NONE : ANCHOR_TYPE.DISABLED; CanMove = true; _prevCanMove = true; AcceptMouseInput = true; @@ -303,13 +303,14 @@ public void UpdateOptions() _backgroundTexture.Alpha = (float)ProfileManager.CurrentProfile.JournalOpacity / 100; BorderControl.Alpha = (float)ProfileManager.CurrentProfile.JournalOpacity / 100; _background.Hue = ProfileManager.CurrentProfile.AltJournalBackgroundHue; + AnchorType = ProfileManager.CurrentProfile.JournalAnchorEnabled ? ANCHOR_TYPE.NONE : ANCHOR_TYPE.DISABLED; BuildBorder(); } public static void UpdateJournalOptions() { - foreach(ResizableJournal j in UIManager.Gumps.OfType()) + foreach (ResizableJournal j in UIManager.Gumps.OfType()) { j.UpdateOptions(); } @@ -423,7 +424,7 @@ public JournalEntriesContainer(int x, int y, int width, int height, ScrollBarBas _scrollBar.IsVisible = false; AcceptMouseInput = true; CanMove = true; - + X = x; Y = y; Width = lastWidth = width; From c877e3c013ab422c9af74756fafe6eca0f3e00f3 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 1 Feb 2024 01:36:01 -0700 Subject: [PATCH 09/93] Multiple journal support --- src/ClassicUO.Client/Configuration/Profile.cs | 5 +- src/ClassicUO.Client/Game/GameActions.cs | 29 ++---------- .../Game/UI/Gumps/ResizableJournal.cs | 47 ++++++++++++++++++- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index 8e48fe0c69..12bdb9c360 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -820,10 +820,9 @@ public List ReadGumps(string path) break; case GumpType.Journal: - //gump = new JournalGump(); gump = new ResizableJournal(); - x = ProfileManager.CurrentProfile.JournalPosition.X; - y = ProfileManager.CurrentProfile.JournalPosition.Y; + //x = ProfileManager.CurrentProfile.JournalPosition.X; + //y = ProfileManager.CurrentProfile.JournalPosition.Y; break; case GumpType.MacroButton: diff --git a/src/ClassicUO.Client/Game/GameActions.cs b/src/ClassicUO.Client/Game/GameActions.cs index 8d011395a5..bed07327f7 100644 --- a/src/ClassicUO.Client/Game/GameActions.cs +++ b/src/ClassicUO.Client/Game/GameActions.cs @@ -30,7 +30,6 @@ #endregion -using System; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.GameObjects; @@ -42,6 +41,7 @@ using ClassicUO.Resources; using ClassicUO.Utility; using Microsoft.Xna.Framework; +using System; using static ClassicUO.Network.NetClient; namespace ClassicUO.Game @@ -147,30 +147,7 @@ public static void OpenStatusBar() public static void OpenJournal() { - ResizableJournal resizableJournal = UIManager.GetGump(); - if (resizableJournal == null) - UIManager.Add(new ResizableJournal()); - else - { - resizableJournal.SetInScreen(); - resizableJournal.BringOnTop(); - } - //JournalGump journalGump = UIManager.GetGump(); - - //if (journalGump == null) - //{ - // UIManager.Add(new JournalGump { X = 64, Y = 64 }); - //} - //else - //{ - // journalGump.SetInScreen(); - // journalGump.BringOnTop(); - - // if (journalGump.IsMinimized) - // { - // journalGump.IsMinimized = false; - // } - //} + UIManager.Add(new ResizableJournal()); } public static void OpenSkills() @@ -207,7 +184,7 @@ public static void OpenMiniMap() public static void BandageSelf() { Item bandage = World.Player.FindBandage(); - if(bandage != null) + if (bandage != null) { NetClient.Socket.Send_TargetSelectedObject(bandage.Serial, World.Player.Serial); } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs b/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs index 638072d0b9..c9d8c4a9a4 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ResizableJournal.cs @@ -6,10 +6,11 @@ using ClassicUO.Input; using ClassicUO.Renderer; using ClassicUO.Utility.Collections; -using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; +using System.Xml; +using Point = Microsoft.Xna.Framework.Point; namespace ClassicUO.Game.UI.Gumps { @@ -19,7 +20,7 @@ internal class ResizableJournal : ResizableGump public static bool ReloadTabs { get; set; } = false; private static int BORDER_WIDTH = 4; - private static int MIN_WIDTH = (BORDER_WIDTH * 2) + (TAB_WIDTH * 4); + private static int MIN_WIDTH = (BORDER_WIDTH * 2) + (TAB_WIDTH * 4) + 20; private const int MIN_HEIGHT = 100; private const int SCROLL_BAR_WIDTH = 14; #region TABS @@ -316,6 +317,46 @@ public static void UpdateJournalOptions() } } + public override void Save(XmlTextWriter writer) + { + base.Save(writer); + writer.WriteAttributeString("rw", Width.ToString()); + writer.WriteAttributeString("rh", Height.ToString()); + + int c = 0; + foreach (var tab in _tab) + { + if (tab.IsSelected) + { + writer.WriteAttributeString("tab", c.ToString()); + break; + } + c++; + } + } + + public override void Restore(XmlElement xml) + { + base.Restore(xml); + + Point savedSize = new Microsoft.Xna.Framework.Point(Width, Height); + + if (int.TryParse(xml.GetAttribute("rw"), out int width) && width > 0) + { + savedSize.X = width; + } + if (int.TryParse(xml.GetAttribute("rh"), out int height) && height > 0) + { + savedSize.Y = height; + } + if (int.TryParse(xml.GetAttribute("tab"), out int tab)) + { + OnButtonClick(tab); //Simulate selecting a tab + } + + ResizeWindow(savedSize); + } + protected override void OnMouseWheel(MouseEventType delta) { base.OnMouseWheel(delta); @@ -385,6 +426,8 @@ public override void Update() { base.Update(); + if (IsDisposed) { return; } + if (X != _lastX || Y != _lastY) { _lastX = X; From 061f8a9dab6c52c2c206a62c34e79ec1c1c47748 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 1 Feb 2024 02:25:32 -0700 Subject: [PATCH 10/93] Move included gump art to embedded to avoid overriding peoples custom art --- src/ClassicUO.Assets/ClassicUO.Assets.csproj | 20 +++ src/ClassicUO.Assets/PNGLoader.cs | 116 +++++++++++++----- src/ClassicUO.Assets/gumpartassets/40303.png | Bin 0 -> 880 bytes src/ClassicUO.Assets/gumpartassets/40304.png | Bin 0 -> 3229 bytes src/ClassicUO.Assets/gumpartassets/40305.png | Bin 0 -> 825 bytes src/ClassicUO.Assets/gumpartassets/40306.png | Bin 0 -> 3204 bytes src/ClassicUO.Assets/gumpartassets/40307.png | Bin 0 -> 44229 bytes src/ClassicUO.Assets/gumpartassets/40308.png | Bin 0 -> 3244 bytes src/ClassicUO.Assets/gumpartassets/40309.png | Bin 0 -> 825 bytes src/ClassicUO.Assets/gumpartassets/40310.png | Bin 0 -> 3230 bytes src/ClassicUO.Assets/gumpartassets/40311.png | Bin 0 -> 825 bytes src/ClassicUO.Assets/gumpartassets/40312.png | Bin 0 -> 68251 bytes .../Game/UI/Gumps/ResizableJournal.cs | 1 + src/ClassicUO.Client/GameController.cs | 8 +- 14 files changed, 110 insertions(+), 35 deletions(-) create mode 100644 src/ClassicUO.Assets/gumpartassets/40303.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40304.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40305.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40306.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40307.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40308.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40309.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40310.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40311.png create mode 100644 src/ClassicUO.Assets/gumpartassets/40312.png diff --git a/src/ClassicUO.Assets/ClassicUO.Assets.csproj b/src/ClassicUO.Assets/ClassicUO.Assets.csproj index 52717bc63b..be6392dd25 100644 --- a/src/ClassicUO.Assets/ClassicUO.Assets.csproj +++ b/src/ClassicUO.Assets/ClassicUO.Assets.csproj @@ -5,10 +5,30 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/ClassicUO.Assets/PNGLoader.cs b/src/ClassicUO.Assets/PNGLoader.cs index 22eba727ee..58572ca9cd 100644 --- a/src/ClassicUO.Assets/PNGLoader.cs +++ b/src/ClassicUO.Assets/PNGLoader.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.Remoting.Messaging; using System.Threading.Tasks; namespace ClassicUO.Assets @@ -64,17 +63,13 @@ public GumpInfo LoadGumpTexture(uint graphic) FileStream titleStream = File.OpenRead(fullImagePath); texture = Texture2D.FromStream(GraphicsDevice, titleStream); titleStream.Close(); - Color[] buffer = new Color[texture.Width * texture.Height]; - texture.GetData(buffer); - for (int i = 0; i < buffer.Length; i++) - buffer[i] = Color.FromNonPremultiplied(buffer[i].R, buffer[i].G, buffer[i].B, buffer[i].A); - texture.SetData(buffer); + FixPNGAlpha(ref texture); gump_textureCache.Add(graphic, texture); } } - if(texture == null) + if (texture == null) { return new GumpInfo(); } @@ -128,7 +123,7 @@ public ArtInfo LoadArtTexture(uint graphic) private uint[] GetPixels(Texture2D texture) { - if(texture == null) + if (texture == null) { return new uint[0]; } @@ -147,41 +142,98 @@ private uint[] GetPixels(Texture2D texture) public Task Load() { - return Task.Run - (() => - { - string strExeFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location; - exePath = Path.GetDirectoryName(strExeFilePath); - - string gumpPath = Path.Combine(exePath, IMAGES_FOLDER, GUMP_EXTERNAL_FOLDER); - if (Directory.Exists(gumpPath)) + return Task.Run( + () => { - string[] files = Directory.GetFiles(gumpPath, "*.png", SearchOption.TopDirectoryOnly); - gump_availableIDs = new uint[files.Length]; + string strExeFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + exePath = Path.GetDirectoryName(strExeFilePath); - for (int i = 0; i < files.Length; i++) + string gumpPath = Path.Combine(exePath, IMAGES_FOLDER, GUMP_EXTERNAL_FOLDER); + if (Directory.Exists(gumpPath)) { - string fname = Path.GetFileName(files[i]); - uint.TryParse(fname.Substring(0, fname.Length - 4), out gump_availableIDs[i]); + string[] files = Directory.GetFiles(gumpPath, "*.png", SearchOption.TopDirectoryOnly); + gump_availableIDs = new uint[files.Length]; + + for (int i = 0; i < files.Length; i++) + { + string fname = Path.GetFileName(files[i]); + uint.TryParse(fname.Substring(0, fname.Length - 4), out gump_availableIDs[i]); + } } - } - string artPath = Path.Combine(exePath, IMAGES_FOLDER, ART_EXTERNAL_FOLDER); - if (Directory.Exists(artPath)) + string artPath = Path.Combine(exePath, IMAGES_FOLDER, ART_EXTERNAL_FOLDER); + if (Directory.Exists(artPath)) + { + string[] files = Directory.GetFiles(artPath, "*.png", SearchOption.TopDirectoryOnly); + art_availableIDs = new uint[files.Length]; + + for (int i = 0; i < files.Length; i++) + { + string fname = Path.GetFileName(files[i]); + if (uint.TryParse(fname.Substring(0, fname.Length - 4), out uint gfx)) + { + art_availableIDs[i] = gfx + 0x4000; + } + } + } + }); + } + + public Task LoadResourceAssets() + { + return Task.Run( + () => { - string[] files = Directory.GetFiles(artPath, "*.png", SearchOption.TopDirectoryOnly); - art_availableIDs = new uint[files.Length]; + var assembly = this.GetType().Assembly; - for (int i = 0; i < files.Length; i++) + //Load the custom gump art included with TUO + for (uint i = 40303; i <= 40312; i++) { - string fname = Path.GetFileName(files[i]); - if(uint.TryParse(fname.Substring(0, fname.Length - 4), out uint gfx)) + //Check if the art already exists + var gumpInfo = LoadGumpTexture(i); + + if (gumpInfo.Pixels == null || gumpInfo.Pixels.IsEmpty) { - art_availableIDs[i] = gfx + 0x4000; + gumpInfo = GumpsLoader.Instance.GetGump(i); + if (gumpInfo.Pixels != null && !gumpInfo.Pixels.IsEmpty) + { + continue; + } } + + var resourceName = assembly.GetName().Name + $".gumpartassets.{i}.png"; + Console.WriteLine(resourceName); + try + { + Stream stream = assembly.GetManifestResourceStream(resourceName); + if (stream != null) + { + Texture2D texture = Texture2D.FromStream(GraphicsDevice, stream); + FixPNGAlpha(ref texture); + gump_textureCache.Add(i, texture); + + + //Increase available gump id's + uint[] availableIDs = new uint[gump_availableIDs.Length + 1]; + gump_availableIDs.CopyTo(availableIDs, 0); + availableIDs[availableIDs.Length - 1] = i; + gump_availableIDs = availableIDs; + + stream.Dispose(); + } + } + catch (Exception e) { Console.WriteLine(e.Message); } } - } - }); + }); + } + + private static void FixPNGAlpha(ref Texture2D texture) + { + Color[] buffer = new Color[texture.Width * texture.Height]; + texture.GetData(buffer); + for (int i = 0; i < buffer.Length; i++) + buffer[i] = Color.FromNonPremultiplied(buffer[i].R, buffer[i].G, buffer[i].B, buffer[i].A); + texture.SetData(buffer); } } } diff --git a/src/ClassicUO.Assets/gumpartassets/40303.png b/src/ClassicUO.Assets/gumpartassets/40303.png new file mode 100644 index 0000000000000000000000000000000000000000..52212efa744e0532631ae5ebaf65e1d20f807eb9 GIT binary patch literal 880 zcmV-$1CRWPP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0|iM$K~y+THB()0 zRYeqC^Km|V&%L*_y|ks0kdjiNkiY}c7`29|(FYPAjL|pvANtEQK!Uz#3?Y0Jhz5|h zRHW_wIQPssXJ)p0uJK`#ea@b}_u6Z%5!WyGjt9NC+geBCnnc}tB(X#{GY zxTbWzaIMdaT%(92%9g~7LsN;buJqktK9W1THTI7!j01=OB?LY^m%(xT%TojAJ%j|f zFIMI?-aDJ)=VxOt$BhTy!Rdj<_W2BvBv>!7wxBWSD$M7=nUfJLiCONG3pv(L#;97t zQV~n(J<;7KHEavqemq6lNUZb)Hn^zqgvJSMor?S!-<)!K-mA^BVLg>Xqg+!HHngt)5cNII_Q2p zb@=rUgJ~I@==Qeml$TuT$pI-kCuST8;3%dcUC)hY_6t*&y6ll3{ zZ81VO4WtrY4;?=G;#<6Y>!IF1n}6>od%Dmq%V;q&z?4TVb~N%taq)jneejpzJkZSu zm)^;cHv3S-Dtxffaf=-#!vk92qC4!giZ8~@`ck3T)`X+dqhVFDgk@aI(4CVrWQXI{ zNz?L!3s70HED^YNzCfR+ScbiOCY5V?xw5-QP4+L`xR5=K{mNGW0000AHIP00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3^GYXK~z{rZJ4=_ zWLFi3&s%Egs-EfY=^4-91zWZef&>WykU(OQafWb21PBR)5CJ0L&p_k^kP#9PAvO^} z044w=Km-OE8;==#rl)&my4LqD-*;Y>O~9*u^$P+JA00{o`#@x8reDW^Gm1b#=Vll;yhX@0>sCqKcT*RXytqS5-Dq;&Qp}`#$2$ zuJ0#Ns+#w6dJiJ1$BbfzRa5{ zTE#H9$$BUP% z&wu2&06Z=hU92`e_|Wf-WE8yf?gc6Dyz?rn+{14>Fgn{{Dlj@W2m@?()0JawtC{V) zcLysfDgNM@`W?^jG1QFODozgDkf~AA#Pr%TvAOtHJX~yI1#U=;GlJq|-o)d_1SPdg6?KlyBEQ@%Dvn;u$tDtR#NT?hC! zJh^ym=Fi5Lzw*uaKKD>D`4;c+nBt<%|g5^@`SZ8L?3P%%k=^)4S60h8y0}KY` zRbT(qjbbz^zF_QTEqMY}34^!`6HIrr1dg~IEjjjeC?xVH&0rw?RL59h$O2iJzRXXh z*@seP@RU>=Z5K@b+}&9`k*|}Onq|R@$`jX)4r9B%q?^SD2fu!A6Q_qwy#LlT#*;d> zjQQ*BO}zKCjl=Ok&~QsS`V@#@OWY7ZsWH|D1!Qa>(+uwk90OxtCVLswV-c)r#FTA zV#FpvfRJ!9nz2UEfY)ftS?J+4`yWsSB#`ww$D|Fu)5ag~%-}_?1ZH$5i6|X1B%4|N zhYUEu0oTX^yIj>9zFc+1(U@5&Zy@s#P7`>RMqbTA>wQa2XHnOX0WV{;i={M)8`^AU zF25TxewrDDWBu^#aAHDc%nIRhXkgBBt-)|aENOqhn#)FJC_$#8bqsk?1A-vRSp_X) z44G&QO@gqjtZ28c*(mUi-yJg6gpG^rmXfhwz^(m8h=xeK;EW{pGw72PZ%BuzxdsuI z5~~viL%5M?ZT;W1#CJ>E{mU+HpKaohHQ<=raw0Gt$8LuVFzVH9#3KXYE_xCuM!hmZ zcW)ar*DSRT_~ELofRv(CBB_AagnUa<}c#Cn+hOagBz)e-)dDR(D(U=6=bW3PSP6+${D=j=Qu zP~|!aAnF#N}CTwW7*4xhQeEt&r5<8cK{$f!m%|nJX zV9sDwAv1h9y!z$~H<4M=Q-Z`NNwP|@C6(^-FO{&zCer(ONRihN+jWM~WqC>z4BsM2 zBji|z4vP+sL$kXl$W$n+$1%*pn6s~8lwS%SD}6Ru_3|GPQOoy7~oLOjL;nubfI@}yv4}5mJlP#a^krA9K!zg z#v-1)K8hz#COM8nFHebpB}c2aV2+JWSiPA3V5r)7rA z!2(h6jD^hw>4HPHozs;??5(nl0598!|xUg?>33YWfg(X1)i({jO=qgrOW>WrLvJiv<~H z)H2Prwfe?qZj^>e9?f^M6psL2(5&*@6Z72nB_#fg5{*)jgh;9){BjFsgc>n}QwPJc z#4$ut^JK~#BSn3~HZz+TKg=;JU93G>f8wR@#9Mdo#*csW{dn%ikPVls5^p`|;`$X1 zg#^ZcgG1u;pNuYcS9pE_6A-R91FBe@p zRW-Wx(fIA}ejle-#_{~MbsDy2;BD4`Qyg$SWv_(n*<=(i|9ck4$47BXwUDDWj&ovMqfp0WA|}?Ef!F@&9npaq*Qw{xYS~0LG=G zXdF(v0vSN=FURXFo!~r;v&}vFBmdFGoNV(eRS;1K%{FfL(?hgUUS?Ng6<1=sx{a(s z&O;7Wuijn7brxVtPwwOZ@L*N5^XTH%k?V{lmNv@Qf~~bUg_xx7!1}E`pAcD)L*3-a zV}h4{@GG@lF^wBhva`DmS<8_H+y^Z;-FA>&dLU;9#N0#zS+>fpQ7 ztV3`-%}}tG34NTAs+$n@@NilejDHXPQxjr7j!P($6!xz~XkX4&tL`sni|#il-Qtv| zN>x4p&|75h8IGYy`EO-$lqJr%hhBqmHk-DE?eE-v?7`yFtX6$ZDYzU}^=4E03z}!h zxPh((&b9nbAh4k@P?4kKxUYYvL>3`b*8HBLXrzG>ARju_9TAyXwlreLk}!Fb1$hcX z4f0?2chy(-h^gD)KX3Z7WX)X=3immP-a*R^D#801e@=E2qxG{>0{!Yi{fqgezBO%H z|LuMST1T(mTfV$pZd=OUGmjmOzD*Kc8xwHnm)m=H&bL1UXKu)vsyh2GdQ4tA)srZ9 P00000NkvXXu0mjf(2Fk4 literal 0 HcmV?d00001 diff --git a/src/ClassicUO.Assets/gumpartassets/40305.png b/src/ClassicUO.Assets/gumpartassets/40305.png new file mode 100644 index 0000000000000000000000000000000000000000..2277f6f311b1afa1fde5cb38bad2c62e4cbe0a4b GIT binary patch literal 825 zcmV-91IGM`P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0?tW9K~y+TB~v+X z6G0UIW@azj8{2Ub5l8@;AP1rV1x>_JKq|UOP}4w5K}p9?;0IAspcEj)At9tF5duNN zk;7~6H8Xr~ap4`!e(!z9d#i3;N;9JnWx3n!wasKwxK;1G2}13w+Uxl=P$z{`)6yzH zVJ;1jwlsboRv0eCwYolR`azCeFRozo!4q7(wyDxs$AN*S07R75PYjmkVn{QsBP65ySik!S^Sv1T zG$_nsKYX4|Ym5trx9{F#u`@+T##Urty+Y+Zl7P?B;jlFLvUh-kU;EILZ%`g%9h2p3 zzd*V762qAz1Y|bGh&KwQ^-o2>WZLg?l6-m`psCZ;lM(z zo5CmrJi(S{QFi%EfI-JV>c+XNwSW}0oJ>8O&``2>iVQ3;K!<;}QNm`l!rrjfqqfb_ zQ7{_TUs;8a076h!77}hnpfo9K*Nm^%3Nfubb`Of?zOv3~HV1crtpEM8F&V`~BZi>= zSr|&-YZRQuac*gWnC6<7IZ+)k*Z`P%ME`s%g(BB;8Vn4cR=H4(O0gg;T3Bj=B5sQ; zOwMtMUnveaA$hha*RrvMEn&`ZH?qQ0kt!u@j83OG%pAI%0G8f`v1EZXlCvDbP$B2L zNyX)w9W<8dB5f4h*m{bMdynzydyOPkm{7$yXCwNP3CXWbi`)pDUcG~@htIIKb_M4b z82kEi%a5+!#;e!cIK}nc7+QS$Q#BrCmBWBjqJnPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3>is8K~z{rMVL90 zB*hiSGqb9%xq1&+SOSTFH55STFu;?60;G^715=)4$d@BaA3~Ns48d2BCy9_aLa+j{ zyD+=6JKfV=r+&XzJy1P8Rh9Yj9shUa%g%4Sk>;<=*89_Cl~Nkh?UPyBHbdHXLt4}& zJ$G8CN6*@H^O*b7E>$I^Wu4O{H2=7N_1DrrS-m}sW12HD!XoiJj6*6p$2+fFIraU> z*qA!sGvh-_{W#t%RT+QZ^HRYzL)|fRx~fuzLIpyZ7g+TJD_sbqt7c3s@9@jgyc*nj z^1EwPktHr*VZ+>TV6dE(RKs{)r*yIDQ_guTv#RXSHq9y+_{9`;U(7Q0=3wAm)z4NX zn9R{uZSJh=UzPPyb^EFvk&NAH$;7VhQ^f-|#<i%|GW9qFel^ z&6v}<%+fwwBM=dMX#Y}BalAqmTmqvZwTA%e zV7gj`PH7NGHA5tS2d#{a;XmbQRkx0ga)4`%1l$vvEK!9U!~J&)do#v>fyqHV zcyE~NQ2p_CfL6zjZF#!uFFM20PlgmT4Cxk_4MxW+)_3aU9LuC%XlhE&3JW%jkqqL1 zk*MiMR}ONJ%rNpD2F`2FY=)=$?xA|bw{;XT&n2us1xRR@%bV zqREZ`D5yPF7-jMd5Gg=HwB{YuL0`eDZ1U^g&;c1l$!DZkyVC0-b9l22pzjNG3by<_c{7RZdssnI16v0%e|c;bhK)htLS(FqWuLk_9f~_y`cwnFX{p*AEMf zd4UoA0fu26UvH{!P0YEp`*yff_U*V{yC*PfLMdD|XQ&a#vs&hAr)g1e5da(o5N?!B zL%LwAfhG}kUO8!ol)|EJC6 z;9eQ<#Bi9P6_DRGl)Kn*w4Y&1Rfh|hG*|#@lIZR=5)-v<<}1^&jJJFag3TUc(~c<@ z78f#MVClyl3>;T~C~_fj*NSr0Uc?eKdH_Ra8EFRTa&3yMU?3$>Yg0mECC zQSv86_)ZCj!mKL!Y|1_)5t6E|bko@?wMQ!+RTt^%0z;UtP`SqUuD$JE z!^}z$D=zxO8dMFVS&ZGd%uH?eNmuo zhcY226a8RNzVKN-Zn30-)@y5v&l*{OWaklDkXoSH+T}Cf%3P>UR!F$_Zu)e4H`VhajAjPU*l4-I zTFOi;J@DzHF@17AC&?tU$aBrkewf~U@7L*PKmAEMzqp*hQ7S|V^eZ-xH)DGLw|_|A zK5J65xa(M7y|AABd0lO)zy0&$^zV=Uo}NGI(h~5F0O^A6e6Bm-Wbo!)Kci?;bwP9V3)xCIMBP{F zkTw(yHIjMHF73x&$U0~%pd>oiUKy>Mic5JpFyfBN23AyMAa}e)z*Kamc@;4(?y5nk ztB;w~W^E76V6K;}hgL0vNCtw)?HtFa)|8rojCKYyk=H9w$R#~^0nf+m3)@}C=E@-~gfZ`lUBgbPNH$b9+JdI8QCPyH zNst>4_pM5u2~k6MX``kaeW5PnHWL#b2iqBqj*UCd4P@gDJ5U#U>EnQcXPa&3d=UpT z&#Dxg3N$=No2R+qh3mvoPqi><7zwqF1EbD`{6#xN*m3e6GuA-NN*M@>3R)MGE@)mx zXR(h^2t(FLEmFatu!vfTWgz!B!SN|#AOrp%aZr%)b%A9qagfG`w(-8LC|~CN_Zl(G zf|%y+xyrNCb&enm4RXGUHl6_)t*Cj%gjlGqfW}=vGZtB?8VV`trogU!H>OP@+JNtn z4kA_wCPOjij}y_FaZxKd`VzNt<>Z1|f@apS@Ni-=3=M0S1fKAvI|~h={fHAz*-Kyx zYq}bM;-Fcc)D$haRxCg2WuIWGQ-LWB11ufioQER1VcvJC6anv1MK}A|_Z! zjjRj1P?UTn*l=&z&c(z|RYDt1J)kmzIpGr?NH5 zx&Qr@#%H(pJ_>~!>LkD`4kw^e*7QD;Rnw)3x^4o`+Tah7KJ(R*1GURH4I9h6Dt81& zmAqC+i~tN_WxKg-3odsURx z%;tI)eLIu%U3yljx>C=Wr3{1?H3vRYd{47IG~D7sbr9KZoY>^~=v%07K-j;DP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DtXfG#K~#8Ny}j9! zW!ZJ!x3j8ps?4l;=(!s`0B8&Zh^Yxcq(DfNg5o?$v>kRh9E!00_xRBfe(;+jEZMR| zNfu?=6e$uANss^)vzrw5GlG+QZrB+;bN` zbb8>FdhPP=0a6^;A%cE`Ql`(t-^VcfZU&om7xHXcA6 z0>TA_Vm0=`4<-#lHvX>8^TY*RwtjGFtgRdv_azE>J9qAngUkD+$L_d`KFYg$KOyB( zPyBYJ|Gu$!K!@r^JqGk*Vj6|_?+w?b0W~61N9&^y`n8os(TPTK4y`S!F8dU}KQ3Io z4co{Bgg-~>IzzuCJyw?wjFqMRV2z!-(c`Ydd-v{*-Le(<*nN3%zj^FRDR}P)hqgH& zUT&Ad&q-j#1NW(j0Q}foXpJ{%2XEQq?yhvXXFay>C{+Di=JMTfK=ezCvLC!;#QpoF zgW`nf(fsd<&$XL7ZWGH@9`tA_SFujCpK48s4`lF31~<^rMfnMkKI5otsyv7|q*OFY5+IIF4YOE*kz?mcW-0L?uEqSq#}4gM zAu4=Forgx+QDeh^hQz@8byzPoGM$AA;^Z^{4c)M(k`_3V8dH-$LyE}?Mfv_fLfm2K z;w(vb#Eni=M=GmW<R1bQ;Qi`gH*eo5Tf}BGAp^gM*?l#KY2 zCLZZbs3H$03%~otQ)RWifKtrT2xvnfh|mqiVd2(C@p4SkgeaF{IVSn!rGbSAlZk#; zncyT*_=IHXFh4h3;RvRf3j(v!y?_z%jn><`@sS)FiVpf-z^sivbeQj+x#9sJ`lnN` zFE5U@)dQZPy)qC!a?prX25>-@reo8%%bE=VW(Nk*Wz8fw2USO(sRA$nlLSptS9d0i>9UVyrMpE4N zEga*6qsp5NWeNhb7(n?ELumiobvEn(=y;W?4$e40Z3mW)xjUth!UP zddMmW$HWQRv2dyecLL?3Cp4gVWS0zVL08)Rk>l%;sbllj9k(l&Tnzs1Oe&Ha zwCdJ9+SAtbqYOgiwk}?rf~I*d)w^sS-ggFXz$OTWn7Ex% z3g7o^6)89g6{He0#(r844cal0;9fgZf^c3*MozPPAitsW_RuLbo$_FNSJ$e{94UVf zCIXxrIi86H#?^s~E-o&OLE+(V#4o2rqBm8P9(7 zg|V=>HV*E)IbMJH+vCpm&1nE+G><;vlQ^AOqdXC1;}~}|AmA+M0dH){K&lqE&gmHG z{AO(Ex)#2}q#3;fv|cC{l<8%)a;TP_UXiMzk(epb4VdzQ;%jJzQ+f0NpRBfRAV_ow zbYeVi?8_mj*2|ZndL4pijJ-ePrY1|CxMWYX~^%GBz z>)Yx?_pXkGohvSD`1;EiwTg}I?7`fTZyi}%GM}-qf1EtLWPUfd?izf7k^CV=208oXVnP*aBT*nmd z)rnKws}0?b0IZ&PcKp-}e}8=Ct6v=#U-?sw+Dq1r0qn0{y5#z=EUF<(n;D+XB} zQyzKQD}uMQ6kBw?dShpt{NUdj&prFWarTXu#;tSzQ8VWL47wuw`YY<-?B9s#L3MBj zzC-HZXD{9!ANuHL#<#xxt#RP~?eWCPRb6WB)4;#%s_NeK=#D(D&+)T`pMQQsHYwx7L|}y?-D#Jrf}}WDVAkvNVrP;iYpfyxlPU z@**`m5`Xdfo$-lJeQsR8dU<^N#jlOeK6k|RzN01gD{o&j&Z0UcT(B{EoxN~>eEee{ zmahB9Q%{^8Z+!QwuMR{IO{TiPGOab2Y0$B~Kh#(K3C-dnoU0ZH(Xg-G4m()j$6y<7{d6Lz0^>x@43hY`i(E#2AaPDc7KBfykW0qGZ+z}p zZHCTKt1xaU(9p44LUUNGBFZO2z?nFPo+*)tg$kkcce)!EeCSAFj5KI?q7P_7F@ACr z2fsv5yn;L92cc^rJkn4hgMo|D(7KyP>W1v!|I4dnXo)w*mp*!U96or!dlB23eqVj( zx}A=W2_JUXk7{PTcI)mqb8*|((jR|n&5GclT!Yi`&nZj`I`po2ycbV>gp?c!2P)s6IaqNkm z7JmC@PLv#!>=wr9j8AD-6LRQJEyEY-MxXO-a;OO2S+Lff$q1yf~$SfE{KZQb#pk|Pe;ca@#zV&snlHB@zjfw@{o}FYtL`tkgyrB8ZohZd;R)dL$Rp{_ zs1JOoCltZMmZSpltJL3w(3f5fol15*WiJLGKEM2_V{RYc$QQOhP+2QdWjJRkPm!=w zR4s#I*=#dr@*O0OLFWygS#2I~23?0pB^_B@OAwL|2^}I8jwY03@C_x7%h3$UC7LYa z7(ETnsNIb^ftYrEZ*ARm2?Zq zCq|j=qA%rcWDWi7(tDXs9()?i$2FU-(HLjRm$_>o3lOvYiHoLp?opp>LUgB%0Nchf zo%27Bg2N!qjx@4~b8Cz}pH+2m?ChCW2EaeKtw}Td#mg5Zj7zP3zD;)QklI}{ICX2W znDNrpZSky?g-$e;Co84#^<@npYiz*5-1=abwpVPF{t&KSl7m|?U*DeuFf*%_g9tv?5a8ww_t++ijQf9H{p`!)@N>DISaa1{t zW2YzI7$L$K$PY)LbasqSbVesq%ZuE%OhbEC)xokmxcJwjPzKoADvG10Bqd&>9n~H0#6(y%SCjrh9ZTWlE;#@ zX#!C=wnz$MLL3SmoTbCM6&^Z=carevGt@5QBf0uoX5+%5^HX`KL_twiG1p?Gj@-ulr=k;ii#LB^MI`x5(u$0}{U z9ay3RTrxfV&>`PFDK#8AMp)uwX#s^51&ZC@2%u-z9}#XB3LcPwk8NuJGtO^*?l@MF zK}E_mpl=)ILFLTOdFdGTnHp`#?#5^4nH)`FqB!@=0ed$Q@)l!uYKT(1=mJC=&xry@ zkwKvnT{Jmd<@lbjGqNIJVCZFF%YlYEfrcrlqn%Olcdy&oH*nR{B|hn8E9Rz;}RQ3sAn2K*V23h8;3z1U1W3V#3~Fu!89O8i3AJd zFJHcpQe`~#(8joVd&h$TmuDUb^l2YvmA1ah05EZTe?S01qgHw@VjzXRX>+4`WeeBKF5jt1l9%LS@^pjJ4CSyVvIu22O{vBIAt_s8k5nN1_=&j!2_CK3?JI$QHt<# zP-VEB>>6M=_8Losu%6GFhZ0-Cq_6Q!iXMu$qsnO90&zzpUwG7q2%+x-tAN{L=&_?2 z{NlB(aqZU5IKHtwj&3Y@B_MLct?fI;gT*aIPVuB_I5Kw-Sf+7(*|R!e90_AUBaH3K zt}46(u8wny%1L*un zrZQ)DBT5Ct7peK{>kTKTc@i?qcLqZ`$AeHL#+~trK0TWQ9W{!|m?*d}?SZsAnewp_ z5RX)xBxOWOKc7s4k+h%1c zzk9IlXiGj+7c}b-Ih@^3or>H^Fi>Tk1gIA%-lNfB-YTEr5HVG7^#O+*UmpI3mgsj? zfb)=x^Jhtyh9VQ*4XFSEQW@u2U<8|pG@nXmb3wuswgF6|58nsDjgYS(@;Tr)uM05c zO{}7KnZlL4d%t$Hm5<*-)C);uQFlg5>ac%*ZfRL};h^Wu+jqx1mv4>hbZ}jRpV(OO z{cQv>+w@Eyeww-Dd|c<^2evDkh!2{@&9MV|D}T=Kqfvl(W`)!R8TJl=Tq!FA<-xrN z4ps!nqozH$AjdWrqlrIk%(tK-v0V6ZPq%FXU}J36gF-uAfJosL4*-cXxxqTDEDp462P@%-xq>Z8Piw4Q&}BeVh5DPian>j@RD4?hZ}f zxA2RfI0ES;G)7PXh%ydgk4|AMP(|!mC;_#UvN0ck9suBrKN3PS z4It(>!(o)qhK+L)3I*22FWPw^l}!qxs=PXt@GKV{QfzvP=zz>F=*%OgJW|T9=2{+l z(_i63ow@ctdtuYpHEXL&iwiAAUKKLc+$i{#7Nk?5zI}zRDJJN+lB}2Gb zJ*M`g``PjM-@^FL8&|Y4%TsDksDq!Z4i3O?NB$FuCKvztbjJct;v1FS#S@kyRFk@) z9={%>*^p!2%5z^Ltq@H)kw1gbCW|xvrSlJcRJ8~m(D#QzgvJrGLrbVE^uxgIWJ=T2 zF@9H$-A*1Pp%TgGRvUof``QP6s4%>tF|A7iq>=}R0JpNx2|K|n9W)dyf9^74*{CeL zST^U!A&jOxcmC>I8qvkz^wERkq?T(eyXS$y;NsOS>*gn-%(@x_O~e~HuCFrVn62>) z(x~J^nYyxs2mGW|0X%F(cq@UGs2>H;XC|N7iMTsS$&9tkB)XEXa%MkYBL^+I%W+>h!)V~YGv0BTQA%I9F!!$kAao7cv*o7;LeE2#y zMJmh4O{b32MkY(N?7ENf(p%S@f9l9Vb@G*Rju}LnY*Kb;{_ugG5TFhepkIEz7z9=V z4XvX?mAno8zDIN|B)vQ5O2(SJ%oLxy@^m2Le zi(J$ZMhV9Uj6StraaTZ$-1!-wOCaOuJLzKmOGnakKv8_COHywy!xizR&|JP58?tx6 zW*PvWezK3a?Ol9(_HI1j%)2+n<;`t!#?udNjAMuXdL0~{=KBfYI_ZR;%3HvaZ7GPI zl{B<2!N-cTJ~j_Vhrd(ZHj%gR<&SPCwwK7G223rS41Zg02o+jMI;s-nMPVJSES-;x zDt9H7Bw>fCJOEd(uTQQBdRS6$>2PkCWU1kinc0Rc8oi$?0xixtb4G&9Xet-&BDCtL z;PR`T)s@WZq`B^<3)47n>-zgPzbaFjrP^^dc2*PNNv-e^W@v7KvDpQ14ci@rnz;Vp z>p+gJp|7vmE5CpKxCfvkB;O=(Ylyxa87RYt@y9k*`H~Xo!3u;j;*=m0EifRRmjTdA zdiewGYCSC?0|N=t?uf*l<|IULl@Z$$#&$i>0^nOPG!uVyY_cSfK{G43=*iyD9bF!= zWMviAtl$F;ZHGUgk8Oe~mZ225su!EU$Ye>)y>BizDPmUVV5Y?8Se0mS|HV%pw#>p+2s+F|$Ot)G`luup)HjW6IvQ9E8_W343W7-lWXU0B2&>gj^*t`KkqO<{*MCe64wEbcUJvlA8ccktR`usM{F`b&?8QLyy|etn zY#>?n=@)RHT;A%A`^a#2GZw5D->Aj#kPJG~v&aO8!4Aq;8**t6w*^H`P6eiHI5wK7~lD;uc}?5 zCwnaDbnfCU;oTo64;}QQs)&Zg_J}x}*0M|Z7yUc;*&|VR1TI*s+1RJ9a z^lcmJ0sdZ?Wl+{B*O`Ra*?xl9)=_rRYX%1a5oH)FErc-Y#+Z$&E-A)NDelgpaym@} z%qr-Ln2+QL*PXQh-n=~{S*a+r1<#}bWQ}oTZBa|LC6DG9@wyuMv5$U9 zPQEg}`jtN%PafK`49d(CU8O@D5Zh@mQG)u!o&j%a373K3t&2B3tK?QmHi=jfkO$Z@ zuG<(_Gyst^@lqbm!{E@(iHg$o9BGWaldKg|p zM?ct;Oq+o_*}F z#@9oM>k?G*$Cs5 zq%*7dchiFIBv4?F_0u05&piFyc>ehhk2l^pGydq`{uA>62;j@@F`i^vlYBbyH($Rx zp7`Kr$HzbVQ90Dz@%D?qXE{_3{Hl^0g8ZvD?r7<^Fb=P?gpUqj+7{4|6wUQbW_AX( zeuOr<0;Z@0fd>uMj}qj#?y|ScQVy5;Q4St9Dvq;?DUX4hC}j)kI8?*Dl|!5|Xt(PP zH*Q;SC9CCf0KE%eYWkramdrFW7}SYLfWvq}D^<`F{>dYgd}QRtpyS;EJQPL3z9d7!n?6M7 zopMF28(7v+I5LXssS~<;AXeJ`t+pftI768bn&?0|?N7nNuYZ?{OUHUl8l9onE0X&L>=?TGW7KC6RpIZdbQqEg47iC;FJ#TOG;ECi4*P|TB!oeA! zM;0^bF<{U*tLN{J&%f}QvATM2y!F||XJA>;5`9fNu4=hVXZgnK*T%ayG_$X-j*mZma@@OlVXSGEXVm5qPk{UE z)SKn1uS@0B`1wFs(evk4BtT&RMe{kZ1JuHM^_?JbOt7ox<1YE&@TN3W=dZ zKW#{za9G+HUE%}SQte5nQJ#0l#R(j)w+U^7a5WG7%{-`+O5VD9cf9b0FO4&=d~2Ls zx$PAJzRpVF>dh^$(4c1Z^s%RoyB>f1?Th2U^5JoB`}+98^T(@RbAWCN9?W;!hvX0{3Zf*0Ad`+0 zu))!B>jIV=-LZVhPll986enkNs+E;rZpW{+*rE0eqD)57lSfvZrmR;CELkxMjYlUY zp9-;DX8F#w(3WNiItRK6RTae{BwHswHn26KJTdA(xQT(yaw!?ja6dlariL2REH&22L)vOd1-(t%tC8n9V^F>tfGqsHP@XZNL0y-+eF3z3FHj$GCKT%dca?t~~Sb#Ho#GCq07z9Dh8p zZnbhmC-)^ZD&6pFbjL>aZ4FG=5DC#g`!|Hs#;D2BAFe6u)}X1~23Y=gKHt9wkJG#H zRZ54Qhk4`wXb3XKgpfzf5kl@gDG#rtP>pz$_8@`s&Lti^;}p%zFomN}MmCMuo)MV+ zse-VT=X>Rh?81#%3v}#*S%urg~$Qcce=Xl^s8{ z>^MxVv%buvx3}+la5}ucH2C=;%BQy32Y?pyUc1F>a3ha}A^EPCj*QYcEt_GQMdV}* zE;z0&CLHtRRQ#zAO2C4=sY6cELu2fkJ&*woHViJru~pj#r|jir4-Ua4OvLRAAL4xE zLo~n3Xa|LA3J`;1{Ay1?fn$wdI_%?>V%ubXd4;=K)Oz2#lil-nX zF$_4^_v(%9aYeHA!-`2%ZdM*dvO;i&(Z`LWvThV>G1&&9$b%jS*4OkD zSia(d%1I`7=F$bojc0w+E0m_ibU`c1iZ!Eufc~dXW=W;qQ%W%lKmBs+v z8*${Jf7=wjnf>VG$YWr(-5CHB$Ifi#G3X)=`|#r;fWe-Do&6Uszx+^?^4uQC@Dba4 z@RuEI3KVgTutDkzpc`p)0|njk08a4J_K8<#GcVdS_;8;vd_o5RSuP+hWWUjcM1)3} zA``?Y085B1%}gwfa?u|gDu_`xRC#tDo6-@H_Vc{djn3u=DNq*PIL5qF0H$l+4kjRN z8x(Zo^@IN)(81w>!6u-h6@`aqCdoyWsQ^Aj%pip00OzmVavgg4{MgAgcW`8LY2jT` z8e4p(K}DxDMjRkBkeDG$;k-lVJ1Jr4@zDyV`l{$TUlU4?9I{PHd|LGHCO8oOWm&)-RPrlF_9jND6I!9a;WIcmEy) zWno8|PQJ(PELov>J0mf;BN^qK5je8Z2&q4Pd^ z^kAT9Ke%#Z%a>g2-+)iMF_^(?eI>s^;4-uCX!MINf!I-fLCX2fFxW=6_KHiqc(%au z(198{6p$fYuad}7h0xLd!-op)0jM6#3xMmkNXmQ`{{BxL-sL_n3Sl(<5rpMQHf^|8 zF|PAM){FvXCPpZRv$yzM_ey_N0A(LKde9w#krJJe1Gtkb4V`sRp`TEKUit7K;?PZB zRnXTD;Z#SaGN^d&;!VrU^;?{PxE))|FCJe!fHC#$(VPQ2Lzme!BRYE$H?&00^%GKa zEyVRW&xOOE{emTR7I<*Y#vq8k66qO*Wi`ukfFF&dAqBz*53b%wk}esrA}P8V#Rny{ z$&MUs#di^+bM$8wkWDB4{Q|wor=7e!E=JhOy1>jQ?3&IOJI5w{P3%rFLk&2CKoRlG zZ6KD>=tp;PVf6Aq9YwC80OGZpxPE;{j4_j|%LQ075Z6p`Sl+ zUhIhv9a>+OoXBJIHiN;!ICp+io%Bxq<4tBx7&D+Vh{FLq@R^Op+A@IsTC(s{UVIy$ z!bXvsI`CV6>UwbXz*wf;X>S!|qtIdsqNgucmFfwA=P;NwXfykv%8uzWk2v;%-tsC0 zW;_F2X5{iRSPLI}_<#p68&E@cHm0hhiD`E;<`i&dgLeZyWM>r(cRvs~FEg?Lmg$_cLToO! zxlZtKyGdU2x82;3@<;K9FLNye+md{S0pz-7XK1_^0(Th^JpQ}_-?4D9U~pnUWjyZZ zD`1VU!4n@t2g@m&0(4m(SGND6itWd z^1$(GTj?H{$y<2-q5Z@S&eVzGM(B>lnFsgn6at>NzI=V#`ICml7>IMFGA5~3^yn=` z__(2si;*&l-j;)#o$_629lgtIy{IKLWE4lb14kK)>o9n6Pnw2f)2khlayW4|$0!*mEvKMEQ3g-y7vs|)a4=QA(239v>Tfck zJG{E&6$3L0b%2&9$QC7!fuUy_9>$<9kT$@MOAOC8rArC0zNk!FK@Xh=bTDM9yE<)c z%ci1Z?*ZNTOCSGeLt>cGfbLYB={)=&m)l*uST(^Dl&hON9t8aVT$1U{MawVY;y@qs zb+P!c8X{y+RA3(HLL7h$TB-~$%IWmjqxGlWwyW~kfW=Qp^GAi;;ea+$`d0i=pL}=A#%E11Ky`djBD_dDvy!~LWL&6shPc?ri99cE)1D9saR#%nEV2V zQF_V~CQIV{&>?i>hhqSj!nnw*LOTeJ1t(s5$1N&w(m6p0-F#;QpzGx|yvRl_x>G;& zML(RtJMKzT4kEbla*`QPDVEtZFuT}vHZU$;zip?=2429OyO}hg8;(wta(%IxbrE9A zHJ(1ykA}thA31r@@&W2{b8E+!05>$?(9n+@J2;MPEW3PC{3is+PytfZi#lQE!BQDG z3%8Y=EwIjl)HmQ*rJGknC~l_%pFZO-5icMoi> zH#5HCcf}u|PlYE>S@O@PdDW8#BITw1(`Q&I(C?@Nx98x_eu8~if*1r2qn9qt@WZx& z`>R5?6CbTYeCa5BE)$UJe;0)!<`A~x*ajwWZ#x0=jmpTy#v739?Fh3IY6J%`Af1$l z`Wfx1AcmiH4iQ*#8l7c#I?+!K)&RXL{R4m~pb*g2;ItaT_0bAKBRTin`(H`zT&g$q z0Pp1CgAh25&NL9vrkS@3Qh*-kuiW;dlGyEzn)x z08O~ z6M3*uZto{dl<@WkA7B8C3@=F$?50Iy{~Kv6Jg+YR=SLbWd$N=>nq^Gaq5|g;fenqKjP97^ETiZ~&;Y%Ut;?0m^~gv%M~96W02=h>AG z2V+ezjooHf`sftyoS_~0Z*?h--EDujLnu(iY6y`$RCJpIE;JY85}W9P;dE#n>0HxL zPs|5T*NJbV2)a%{>xXXfiO^uP*e~>Km9Yv4C1nYtGddz8`n+`Znt3u)KdwQX9~LzPhFlle?tTR0l!PGel9u>F(LpHhm}DPgC%Z|Qkb!!jcmiI zBrqY{P)!Riv>3!>PS1FB<#V3p1HnsP)3*_R6d*s1l1-N$nAilv@N~p(wA^+;uQUqs z(1kJ%-tOsZC}px_^33pK0;4R>WXEs?HpZhL{qk5^IXEu9_42s<+E=7UY{In_uZ?10 zVg@_1w(Mn__0(~D`2*wApZ&tPabt7bx%S3*`_;d+jC4*_&*MrQ*aMqwrZjTj1E>#1 zA%MndoieCbzD`CHIEQcPlNmp8z~hagsKWr&{y~_zCHQohUfME%qj%dqM{v^$bD(^7 zomp)ehm&}_y^uw^fY_|w@ZsomcfRBTl;9)OmsInoEqD%qI-!wE6@cc;k7D4R_c{5% zg{#}+?1fw7>4(?Hk@da5HGk>cHE%}JM$bNa*sD43vSE`0j_Cv`{?7%McTbNGfBa|0fyKq~ z+N-aOSHAg&*Z_MPztz{`LOUanUO;Sp$EJJi|zfe$bn$b3Dluai2_Z~D#ye%*Xie?icF zFrfEc5tijl2)$^_+oS*q?nL>kAMHIE90?8A02O=dvko^84c_wR*lDF&UA2g z%^AdLV|<&R5p$Wy#_F}j=f=S-B91I0;B2pV8A25w9Z-W^Lji74LjHJRKjqc-CzkAycQmelyoZ ze6UIPE<#Y=LEF~0n% z$HwBmJIZ6U4WJ6oW7`&F+t#Hk*^2hhe0>K(4j3N*qihoNYJWMe2GevR9h5sp8^jm7 zo!XZG?ACEFVa>a)XHAEL#&>!`fuZdWe%U$l2)R^BS&%_b#I0?A3Al4A>@+Z6#|HG` z*4cx2?0LX?s!N?L4n9(h`jOrASrPU3;tn;x9d6DMDv(u-4g z%mv`m&w~5uR8mhz{;#=%F~W4Z>xm60?PnBCya4ZLy27n?}I@);Sj%0c{9x zvp_#_usmg?ec+Th_Jw(PMjv>A%WX>PMZM8cV_0adOJtEIO3FE+vs@jN#or3g7@>?D zQzTJ#FA=-fceW2nl(Yv{?Xc{h#EiWBlP)ED2lISy@TcHXz+ zwnh$}6NFdazUIr8(??eQ41Au%i*7HTxjtS#f7fac!mznkRbN*}yysg@{luG4BS^G7*n*5%LnN%I zjes&#AES9Cpbv(E2j$4I6PZ80lz)W_w~kpZ`F!2zz8^zejJv3-2f*90odFDeXGj#J} zS8Pq&yNt|yo#8wu!iW3Q=n6i%p|6dmFKKWxk&qD@$jL*a9^!cXnah=aUx3?45$=>+ zyrvmm70n<{f86iel=u?u%m9JySj8cSen;(@FlP{c3%~Z6W4r9kactj>@>G}y>sfaA zd2}(;*US;rKR$rD)fK0xod$^yP?-l&jrh13^j@G}@E z`Pj`*nyK!faY-`6jLc4%4jfADc$=@^!LNomt_}<$59&}C zIx=mrwv3Ft=8OT7@&ld>BBuep7G8LI)vw_qL49x(&p4I#=9QQbuFJ8|knMXa36Yg2 zS0@`%`qDW`6Q2pC5p`C(G5TOU&-OH`@}XFU|=ILLd^6owRdIR|jRvrL!{n zHQhXzr;wRppmC#x*gq2B>7{UiXK*^6FuFEScc41};JkdJpF-%JZ8lXCP6cyO%V77M z-FEEt)}>A7ojQ6j*WdIiWl#std@4(~D1CiOUiczdfwFNc22ke<4@@@V9M)igqAo8; z>BPcqIn}Gu?6R{d=X_;0J&wt&+Wyfm4Z(3C#y2vM1-ud|<^*uECn(P57Af-K#p==Y zCZ_IH4`q0Eb3V9cWV(63orhv!Xgq+rz&ESWw6iBA`&d>00IMr>Z+z%`Bkn-}2ln_L zkM|;&iSMO1ue)C`LsMp75ex2xBZfR4tzBQquhuM!4|oFn*7j9=;S-N7c0MHrFB2t* znVgXdCAs#aleZv|JApzyR-9GI{j+qiC!O~W}_1Ww4;#v6vuF#bkIiFixq?O^znc^ zH#o~}C6;p4hXyrW?B4ALW2)83h@!mWoXFP)7Id;KVPj^}d*pJ0Cm$hnrvwC3RE~l^358 zrgRK7RH;V;^xPI>#f&cgUo`Q5NY~)h3BLq**20fhtIaa`^=4om6e16NE|IMxdVT7N zrQM9K2uh{&b;Hv^X*q}>CZ8oDoq#5w97f%UhB6cp@aN0s<0-3zAaWrtJoCYLNGA*p zkT8wNk1qHT40-OGHZnLf8p4Q@X%q2mv$P}^h6xlZuzW`+0w^4YYxp`wi1Sx|8ghmM zWu@{hmM;1UfaNFZVCPrfy5>uyXCFP{WxBPCdImqHC`9C=54w3E<#bke~< z4)i_&a+p2xpbRzyH??#JGq%Znp2@=Yil9#+d0AUJ!l7*mkcXV^c<7C;8Qg{M{OC^; zW#)_?{E2~ATLSc}sDOBNT;Y1cQ7)5II%M>1FmGYtL%dBDIdjE^Da{} z&_YMHS4VCS%5;4C$>m)bu#QIooW$4UaxDKOQ<-L{?_HgC_;pdnN>`E@-JM^DXMW|` zP^y$WoDwu*86(jCY6~rf}4L$)pkj)SL8I;h$ zVoKIu2aqRer9+%4fYXIBz%XLT}nKt-|3`zb;l0c0>@1JJ#!r5gjq(~lnZ^UAg< zND3f9s?mkyak2EEKQro+ZW{zw=SsM9iH7#@(^xWIcTgOV&JUvhHd#1)vZN;;CeY_y z0=_;~nkr^FPP@{kL&p!-0B0!+_4_lg^ge|EKOas_5gDh)8;`gd;x2pkZ#Y zjX4qq2YT|NZ!`xTTTy4~?~dXU0G;vc$m$syXuZ+RJoW7X&Z5Co=w>cxUnj6W`k15Q zgz$%+|0XAgO*Z-__r}5GQkfq*fdf!S9;^a~L4{wTQN)jBcqYVmpnm@Ob)!=;Ic4GR#X!NI{uymIILu801U0ClJc4TPQ8 z9Q40!r~v)YhX!IWo3}$kR#JGK23}ofzWmk=(>(LYhGy`^$xd94W!4Q1Riraf$UaBg z(S2MxbUOo-<#2L(v0ytRy3WoUJ<*5OQXs_>O7B1DAu9HxVTpAE7-g z6AGbuCQ|4DK)h+g@Gky32s|i?$a%`a$M|uO@A)c6tSvKsKs+>%#ou5ac@3=t03X^0 zKXrkC!HP)_+w$DMvs-s`P@F-RZcaOzv}ob$x}cBK(S?LCZO8Uk&Je)sYwFp*kK7fKXmf48Rn{ zX&kNtaVj7kFt3*7_q8a)Y}pP*#c6a5bWa1u9g)#6(@t^G`ZYMNWda5P|J<$ekio#g zh6;3Q2lI~s!H+ywMkiuQCQTNK2;fKl`^6(Kk%-n=UvZxyIhs&vz^cH~z^-WQ?W#0GnFe;Hbq zb<8!~{>-vo;7cErP-jSzga;tv-1fFp!V{&0hc(!g%Sx&2tWaNrPz{t&mCXK0r>LCH z2@9WWK)cfM!;5rio2TvTZ!+fXoX%7EQ0Yrk_Y-IMp>c~RaXLm1^rREfU~4%Xd{cm| zK!s%6ei^VxMsJGHi#`;3+7EJyk#Ah$%0N8#x6Ph|RhL30SN^(~_~|3O|8L2IZrcE3 zK%Bpn4Q{U2^Rf)SNM+DsLp=AxJt@Fa@r7UhRGvjAk=;MkNP6Fb97VV|i#K}^60R;! zgvLn&;Sf|DMO5yv4&_yzT%vmu4+QQER{up*$N!vn`RU&Y}KF4r@3WZznofuggu zFtl!F>S&jcg&|ZA&mN(5zJrdp4M+)FVH_`iB3)}tY|tAR&lu}R9zQ{SE?3E;)bC3V(8+qtZ8d=8Lje#LNNZ8?M_ z!j}^veCO>+eZixey&+5yo!#?@6tE@0v8}i)$OPBd%kV-rK1uzlD|ZLz9^^CIgM+@B zFf9Wi)>j8fmdQUUa1Z?LOSe2QKY40Bli$(-`$R5%#9V^9zMiF(Lz*&loju?s9e?JT zRq2gW&m6#IWh6VQYLMGjbgW{m14jYX&LAQxOUDDpOKIXDz6U_VK;xNQ2YkTodTtvknCi%2c*9`Dv^G_Gd;XefIoKHT*qaW6{ZJlgJCS_UJgTCOC8&?EQls zH@HNR4GRb8pgi*@Lm{-}(>`sgK)$GM%WeZC@F! z2YC%>+7%^;?%~%foGx=HrO}7#W|btFLWW~^c_)V>$pjjD+RM7;LH;?!BrJH{94YlFtp6gmcuPn;u3!0q~hyv0L8f-ANHU6oN z?e&`SdAS8ooG$#GkFN8Ai8Q=0;X#M-$fq=pp>NILF&N-R5waT^gIQ2;XX?hWus!t1 zU=;v(86h-`>(>v7hdZ@+_PgH9Q#=UUaO6zf6|oaY20@4@fY#Z2z}J2if3U2{fmzuD zNR#C74+MuZdb>k7r=e{!4^=1STTkUsE7!%}6nGZYaa&6=mTu>-ZVleq##?3{J+bcF zmheC#A*+*I`eY^G8ArU+Rxi$38J#-=CwbsG0+tRS`dVcjOgtdNi#pD1 zj<4^fWa7zcLmvtx&mCGv1{6sv8`Jsut%hx@G-X^0Ses=0a1bJGeD=bPaplI2X-*wh z2iFpv{TBB_(4-_Q4(f|PbD2Q9`)8zzXW)~qYtUNw@<-Npxq;p@CgoHNWQ>Ql7>ADB zh61M<(P@3*&owb=#nVtI6vMcI6ylhfZIx&s8ZkCrC2NV-&KF2D>CUcHzER2a^CEr{ zuY{6>+G7B&$#WPwZ%#$^+~;0xpNH-`pM>hmVW{_w(OaTw}Au zeMd_-8kB1-8lN_SKeDhp4Gnl^#WCGp<(vXKC|^S4(U!FHZSe<}-<$(<650+Yp0_{H zDI`%B$|cG$k4l`f&SSHdwx`2*;R>CnBqTY*XWK!1rB*@-YM;{A02%svb=i(gp2}NuNEW0 z$Y-$EHTbbzMgq@n%C+%?06}DzwicN(N-!Uy>{2R+mNY?T2+MH;m?x!VfDUbWbY*#` zl3y`lbnFBl9_BD10Q(9U#QZngAUOydBG(h?hkQJCENi?6jiw2KdvCZ1|v^R z&hhsr0_cj2gHgWi@F1|`ec{&1SUUXZSYBQpD>q*nOZ)R~E?$1{?p0lz$woNW(+_QE z<RThrS=-$6mPu)|@Okh;(MMqMCGBRgj&1~WYUK}l*%%Xc_GSK0{OKM&x zqxusBcS7f>OsBL*FtNGwby$vov;kVi*WbN4KKA)v8xKG9@HqG8o8!Xw{$Q*v%l7J^{LAWFmu~yj&`>j5Jooq^ z>BjE`xOUqbZ(hCUm-avZ=*l>8cuArnjLRdpuhPg(ZZvR*BUBRmw*K_n)Qf_Y`EY6M z>;lKzVHI-UnnE4eGJJxg7N|43@|u#WjU``mc#>@iTRkH zjxI_&?z)pYuCnu2Z+mC@^pS)9ANH5tx~`e}fPYx>%p-?9lkybV87-gJl+UY|QQ(<} z4q5g~Z(TDV{uD#qMY8XCHs&|`>sqyj;#Q8s79_G z5M7k+PHBD)jo~l@4+qYx{K9!qw|Qtj^N_q*LCbKw&z1FF^gj-~`1-Z6wQzVm_Qca; z^}wBR_}*FPP>GV@F)$&Ia%_py zGRs*r;Xn%>bxr%Z^_*|nI(2>C5AbDHFTQ4#a8yBtL9|WIBf!Xd;`GLN?VTI;d49}8 zXX6h$xGcJSV@tF7KHt4~jlYx?@T3`SlSye|oIJWB-E*6Yyd&!1Yb#5xtJk{Jg_z|2 z`OA{I?|=q?d*g)<9XCJV(zPvJhi|%`@aE^D&pdj#N?GN}7dDTM4}R$R@%EcH81&G=aw9l4h0WrqjB4{5liqAU-!!x<9ikk2XEKLFi0 z3LJKx2?Ti>Y?dD$is*~Wx-`YcalQz8YG?q zMAvR2=mUv(cfBBdt3;*v+3gNAgm5%xH)jVqUU^fqyPSqbds0rvQWCpzUB z&oDG8Vt7@zsVFO1{IPmWjr{GWTp z3PN|0Z>xhNl=h~hfAZPmwmtCObJx|$x5gt{x&71=%kJE`743jcsDJyfui1b13#ZUc zfUzPVFiTUxAf?nZV>A41x(O1fymN!wCQizF^vL_8;`=E1qaqf$<_{S;Fcx>C}1bx7Jmf)qa@(a>_T zSJGwJmBAnbaAYwE`+ebT;wp#9=-X$mdb8~E=8pdd?5JjGO5f$h3s<+izhs}(0UHl; zBHk&*NxKQ@6AIhDE9%ToJk1Q(ya4#{r*Yxf-wOvj12D#gqBDKsDkyJZBQ<^8z8F8WJvUiA+8;k9MIr_2A8 zA)WTrO)?`kx-r%=n`3+Q1U2~SKsX1NSU47sD7V47sv%ST93h>M0!#zXBr9K#&YQek zB9YEujOe7yA|Wy{8X$!1ndDh(Fwg}$x3kMq4;auU@JlB)163h)=U_I=WemDd9=(YZ zvKhwaE{}u4$unNb;!>19!pL6|^BjP5u}X?7LVesROSW$*3yepGVAAzR0lJ{K4(iRN z#Q7^*4!K3g%H}jN))G>Bg_?^$6;9h+}Mo5sT3=Krs zqY^LX$%|PX-ABIRp6Pmva(H$_{lW!Vuk+U0v$61Wx`1=E8 z7F76fhJm8RM^c>&q_MN4qrup~0LMG93R51N08QtHQ#1{jIu(lQN+*spyDZt_ldwAg zZ_Atp4YC|<>>2Q5itnGjYI}pn9gkzWMn8S*pq-)Rz87Ta%_yIG(2kv#bm=1|odeA10Ko0U7C5ceh{ zw4qlAR32l8TrK<^-*b&P0Se&kyhnj`Hc`2{F#3 zfO83 zJk?D{2D*6%O_@Q7yfJ46%r0+V=HV7Mig|Ip&h;WYv|hg zPB&A4JXjH;qmRlbLcp#rzs2UixP5O$@^vN>=8O%b?M>xf59vTOFA_>!2 zI5Zsu^?C`2lj5-6n4nvkj`bTb($Q{3?kd@DqAh7E`FM=>d2)J^&mZ6A-Me`z4`E`{M3nYj*g1X z{VFT}wm>rA!LtGUq?0GmOs_Mi_+aEwMv6NTrN&zpL~4DGdRwPp-`!3&~|i~w-eBqG#cikQ5YO>MvRBWAi*&?5IGr9 ziKrmwf`?+rsC@~BBYRL07Zs_4^AeHd!^ay- z!fhWyKYqI(pA>%~+vX)N^3gX+z>`5J@#w+Hp#WcHK=H!oRO; zTG>l^*Zohf*cVznNq{_<>#+TFLM{)(@Pd@AYPfdK!zulD!uD-3aNnT4pcfnJO5dW4 z!H-8_`#lRxVAs{ru@8F*^Ew-p;pbD}C1yUbr4U-zg^xdSz}|ros|prQoKb&B{$;0qZ4i3+E!cTNwaP*?8^j!Em(memNq4y*H1;CT-u?(A3cST1Aw3cn^`C8 zs+o$`=;!By&gDa9G?NL}{zB&>=@UBX@~Jk@;E7yjU)xV7&w%>yBpda_#xQW}XQpr+ z*zDvDH~1Pe@C~gr@2f*|9q&Ov06!S(Z2mxei?(Wcx$N*tL^!l;6!zCTcADRfpdYvL z81OTAr*DBrn*;VYF_@ct?%?2FE{BR*N~d-a_P+#42K9!2%Lf>sfUHy)Jnz{*kcZ6x&W$g% zytxOEhi(9EyztvUa{?KL5J;%3MMvz;FbmUeq>01QFYpuKlsIJHY5+0(eh7%cHI4do zU}m7a>pOA)jEyYb6HOyxU@AjreCFW|b@Dt!Kt9fZ0f?hZuJxi99}+kiqn>fq$c*;j zn}_604p6#>4nvVqCbm>A>WfH_Gg2ESRyS0~H(>G}ic)wV+?`C{ofC+x=rb1Z!!pQSTs zVt@X67k^2)5<+)E@nWR~xSa&BXRxGy zJgD-_cKAyM18hP2Q%5#M!6Bc)Ja>|_5(C;sNCT&u(*APO*WY=1ja3pnPaIzLipT@1 zl%Y-rIxQ1}aO{KfMJAH8cjROs$fU|kJ*Xe0Q!Tr0L@SOSd;WM)V88wK&mI@gIU=CA z*>L(=w<>RAHiTi8v6Qfb?ioltGM>%`3d$41{}!gFvzT32zCHc$AwQu7PSdc=euWpo zs|GkGym|8H#E})_pbw)oBDv4Z=2s*G>2T~uvSiGRn9-6Ifb|n1JQ-OD;S&}rM`1NY zW(FBi^4)+U!bwbDWdM!F^%D)_S4^vMid8KSV+)}bt-?+#>^NevX*wM5LK7e2G&lwS z2|%K)6Lny)rJf!LR8eo#2}f$UGcfzK>O<+0dq^Bz!CWR;+T&kSg*ik2qe`rkzzX}hIt z2JoQV@$OJsKJVQ3tr4!vcqpHxExURM&A(n^3LKiKgp(%WZ+3{;A>;vzZH>}AxPM63 zRmfOeS~1?;JKHwU%Nhu{4kho|M-F-U#*;lL1a!AKKNhx>5>UF;tZ029FX&Rq4({lw|K!YF9zG`w&Vd2|YFL%z$!j}=)D z?%<-U-zBKHFB?lgtQ{IBHT&k7bM&DTN1lLk#R=e-!A~JY@KPqS_{o&K3JTk0HgHKN z5s*SQ?bz>P=+#|eCYEoYvK*D1^r!fK>YV|zFM9)O7w&_D=-G-;i5^*<)VD5RVk%_*!zWKRd2oy)oW+<;C&*<9TqO+ZN3F=mr1$ zr&O+^QG$9H6zM2HzEO1QhN01@OY6-F03?dtP>!dJqSV`Bawroi?tCR6GIM5-gPg|4 zNpc%7Ws15%{b438ed%n(>1^PXcnd_1(>nQCctLMAt{=?3cKm=PB7CfqfHO8@HDz4{ zu;IFsjz!%7cMjy}6uHr>udPFwTT#Aj1Ic(+$-n^My69M1KQUI1ePC=}zc$`|<3$bL z%fbUOJL+_Behl*PiGyS1zyUio@ZERrkEfpf__%rD<#B9f%XwAg%FyNv@NF|PnS{`V zWik1*@#OCUE>GxeSt4ikgeuJzAdYq%GNH?{8Q0v{IQ>PL_=3W|?k3OUu$j!obpnre zM31&T9hZS4gN$_dB***HMJN>L5yJJompX>l!Rxef#Ek_W4hY&wl>PXmOl3)Lf@%?jGjl;5#8~!vl_cP)Bz00fPXFmTW|Ge~{{j+~M4js7ZO%9&fW@Pk6 zhm6ZxOT63>4;&UpL_x0Od|O0vGP=?r`#gy7PzU$)u^VMJe2s+Q1FT@|Korg4C9P8- za-P|6R`=#ZJ&EHWI2O3JroMi^hrR%6rxQg609^)wFvDpvd-IOY;_Wt?<;M|;Ne`95 z!@Q75XH_I-iXi-fIw7CcitDMouCKec1smPq8nb%P?9R%C7YH)T@ZK?2Fl>fhxwSBU zbdG#g0j@%~0P8`qyi16D+#FK#``zf(CGuNx{-W?}StV^#& zwd0lXsi#-RBPZ6xLxcO-Yu;nw7j*1UvyuSd@qc~o;&|%GC&v?qcgDtYUgHhZt^;~? z9bHSGy7TBVQGUVgv0m1a7jp=|2L%BE2SskHN*k}@#d?z0VA)ERDNOGbpE9r!;}6ok zijW>@r{Lu%SvtX|~)d%2Op3X4?;?!B^$~G@}mekC0)T{8wvs2mY{52l;+&2!drqiQa z6&aWeNTVMe$ay*#*zupVXRV0l==Q{QKm))Z$jltxPE%jStyd|5$pA>2N1i1qeC6~t zy!nJo10H4BAHsLoqv02D)Bzyy4`>u75A49Kl>?UYTQ3|J#@>R4uF!+f2KHHvPS|;# zeH;@gl36z+%lm>zn8sSEZ5;1=VRA9|I^%!WHaY$2vyQ_gyeT!b=DW%f&Ab=bx}JY&XYlvF{&aH zoAB{vgA4QpS5Z2YUlgcMkr@X*WZ1cgR@}tMBi{wo&lHwr-h34H>`lm#r5=3Z=*<2M z0IYZzK#`daE?uaQWjQ%P(^G%q?8DHZiDP3f&v>MiJr?xk=dQ@O#NY4;XfI~=^%dGt zhXw&|F|qpj-r1|3I*{$`cOfW&dv-89uAz}#`8c1ORpYavI?PUwzH1j zfSCu9^RqwT)EwT8TRdQqP`K%$`TvM%i{m1{>R3Jm60m0DcA_Y&XlpcFf|u{^)BjU$DLCS5ugrmgq{PvOz$H$aEC0u)S;aV=OYda@`+#@(lVo+If8t0 z$4n?3c!)-Ui?NxXODA1`JeYMBx_teWi>^0=25*vOKV%+SZ+B2px^pT@*qxSgLJxMr zA?cF^MW@J29)p*otUcO7+-RbG=1)jb2N#9{0h&JpjFIfmXluLY`z}6v~rWDg*_^(XNP53JoZ4XKUpd zZIFgB{YsWIbU_%K;Fl1v0uDvljmj=)>3jr9E@@Z6V z-dv{}BVcD1t{sjYMKX~A^&f$-a@fm;>3w5L}^YV$akw)DB z2E>=&yz2Oo^_6k@*sAGxVk@-RBXrn-DyEHbZL8@MHk`zOt2i>ik=mk-oh_E5W_bDo zd95RLoA~OCZQ?J%L8q*ARjiZm9I4aG#cXSIgB4B5ay#-l0}0`SA2CUwBNp>OVE(X$ zKYHNLVG^y-2wiCJ=Bd2j_*{2zofL~>krRM8e0Im17jF7GikHiAs|h@hb_z`br#adm z7pOQg>^uUN;?0aKjPu&tH~iDl=N>ua4$e}X>u=Da$AWKfkY=<8=oDOD;1paJg9lgg z83=4!@$A3iMfK+8PDUIf69HFXU3Gwu!+RH2Y2Ut4IS)zDXz(f$*8#rh!a;(EgfhFr zlS58PTvXt<3#PLv}HGlFbjMbnP6P%lZKl?HJW2)=QGOBj01~E_pBkF zbToi=`Ry?6yzn%vXrblLJQ~-#Au*h9?Gu7GSVEd4vat;Em9< zqjC6x=JbDFU@-jtxofWbV<*=A7&A*$>VQh^f2daT464(Q)aE*qa5~D<4p6!y)<$gm zp6hI^oCzQ&v@Nl}LQ1hQcYex98U(|abaKD}w^Ectq)0TcH1yypOlh5zX#ZzWWyJ#> z99a?2zwndr3PJk?kamW>^8O(&U6imOBQGJ~#;&lUVmFJ=17z@sb754aTnP-2tMFWN zfJI87ihC6QVSr=vG#lZQr`JsbEw7}7Gb7xM+dFxQotDBhF4v!QWI7@b$IE)KL4J1&4)yQnni?%f^>*%U*l|Ol4i9FH3hGm1F?%FR&EmhY6<80GEbGBy{1CS(BeI?$_Xl zTx%p(XG&C08m-o=c~;@UJ+lU48dM zVXtHyuM<_Z2#z21+T@IuR4y}HKzXL3tY)YJ>#Y-csA2;Mm&Tfvr>tkG9|hgPF)GV6 zuEqT5B=iDw;m7&Ru%4yW030lXyJIjTdn}RB7>)gE9~r3|3P)gNz><{Np3X&@(6g#Y zyOD^qFTh3|*Sq}CAIKw~iijsR@X}WJ*Z@ArLRa*r?l^=8F=dsiK74(t_9YL^+-}uo&J;de zr~@(LHO`qAP`N(a$xG)4!5qwIFU*#b0qVK4b7#Ev?xuff$Zz#oZDh5>;E4kGRtEI$ zc$u(f;8b-M05RC{K^5F)vJVr3FLl&@47$0S5TB+^+$VG}kiyHHITb&AuM@}+3$3Ok zuk`_$i)xq8$D>K{_M{JK#J937dYL zl>;8=NT=sMrzR{E@ef@vE{?+r03PRb-FHyS!5h3PNR8Y-=R+s4n&~ZiX4^wN-;Hw0 zWP6AR#VMUZMC_pyXxn zsvUjtfQHov`Mg9T*JD6J>=55cSA;va0|_mODwIr9=oIwO12o}=?8Knvfkyxdwl$MZ z7m3*wkk>N~xOQHhq_qoSM`>s?m~Coh_S!p}e#iLnLo2TPJixvOqT)OOb)f_un41^~ zup5Iog9LHwpriGKmG?swdcYt<{!2$BFB}UZ4Fsk-+dY;CL5YZ{h zB##)O4W`QWt|CV3B_RMm=&)teM_%MI`hEAzRm`=t$4(*~kFv$6AE6{k zhu+exL-|8TbzLjZr|y?-Z26^%y)z52QlRsDBTKTuqjShv z^Tt&MCk7?&C1^kst6J*wLz~nazU=rTmjSZD4KL=oypXC%x*aIR@!VddbyU+(CF@N4 zcy*&Vv>l>h;zeMM%kK88xE$UEe_=XLrZo=rpi}S|0UiA9%eU3B_l*;W@=qb)+j%7e zow(!US0yRoSyDXU&&hfvRWPFiCISh4e!JPACr#jPz~I;H@9mPkwVtglZPeua*GxVF2Mk z46_>oiMLo&>BNk{ee=upIwm&8ysQ^39Y4!8ZUybj2An*eQJqf13;voP^`gH6jM`im z9zC>V2W1wA-hT`yy{(gEv7Cn&|As*)C5~Qs6jj&kzFov1n9j^!)biLOmlNdiv>!OE zGT2vuIQGcCgWujFz6^joBhGUGSry>8*foO*B=DoX{Tp_%N_&;f60NxM6hR7Loy7|W zYr9jJ^2mTczb<4jHIp9(>I>YVYNgnF;HSw9uv~BZ-#ldl26G@m=>!QUfxDFAs6%0) zO?9ZG4{-6R=n73%4y`-ro;<`X4>1)kOsX8ahpPqz9I7l}dniu3d$N)VdSE2q*Vx6V z`8A_(ESoCIV3Tyv(8pNC56|+gSJ~OVh+>fb=I4&@S|GKdqH;>^411%3(Y6>-%*Zrv z+&Ds4E`|oc?f82^mt&|v27?1jD_SydjeSaV2|%aeS#&a4cH^)}jlrao^jbS~eB9l> zJ(d^v^Ykpi;JJM_KNB@W>y#xLwEQ~=2BAa3kD2eTA3#wDX7&>j9TR$+0W4^{o;v7g zOo0974|sb9QbV)*nd>?9$)yIfF89RgoZbl)6FMOs9o{_V>CGbX!f|+7kKJTC{Va#?LNO@vwG5zw35-?%CCgCSgO~sF$BXzVF9h)>soH!V($;As-W32V z-rQ@zKkd_z9es8XYLrNqKC}p-XI~lXww_3byEiKQl3q=Yhx9;Gmls*PB^F`+8QRW`oNs zZh{IKHCgtiqvQM<6si#601D&mK#Z0$Mq{pFi7zg%jw4TfethTquZ`Eg|Ci%4&mArN zG~l;hzidOb5_BAR5gvZ@Bjfn7ljDvChrj&u{~)JM2SX&4rJ@*{+k241kJ*dsPXsWt zQEz_X=1nT$P&b~OLk6n@cShxLjogi7n^>Q8diY+{Y{O1EGP@DMVW~|TKD_V`o507r z{o?PJ8Y-VWE)lrp$B$n;2uVMHL6<>-c1b-l=+a)j0}nlQj?FVGH$-h5GP1D=d`is; zz)QW534ZFrjM{aEKW&|QpbHQ3v@9qb1fWqMR4;u2XrbHS6~iU#LBKYfw|2&v^P9H8 zlc&~IUp6{bC|c+^s1MAr2kq#m1eG^+K4wZtl+JGwOkp5Qr%_DbloHih`8zwc&rzM! z?J8rq{v4vS9Yst18O+;8@L|=*Ukd>lxXBfN=xQkq^5jcOqb~WzBu_x+_VYK2JKv{( z_gX~cH@HeaY(e09{nAgTg9kFoDBBVoIyc}Yx$>z9PVad}-~QDWO6FHEmSKfCrZ`L# zX}%o*H+PSWhoAl2c;)5qjX(O6KO4XJnPZxr;_%EGFTZuoGvA^*;o~Q?RMV`)yWIGt z+0N>d1y7bC;^7KJF6HbY2crepqYz7)S39}kYSY~MWGXvk`#ehn4 zKbwYQM;^dX{AQfa*-q{H5KpB%V6anIPW%Y~vZy21x#&onp)-2HiU_0Dpr)mdn^pb*D}u!ZzT61APx1NfVkhd@D`}ZuSU5hZYpT zm-g1S(9@Z`!mvKn0+VJ2W1#5{)Ayn&$5$9kvMQ)M5bXFX4j)cElOQOyixXi}F9jtV zzR6>@su{~i91+>fFbp&v+)BqDRF+mZ#_I8>$I{B$IRDn`*+ zy#YTqK|eIfbNypn-&q<<>qp1UOXsu-!Xh<`BL^8`(zZzl0hAC-zG^Z&ICUdJ+@;15 zJ)C3^>dE2z{YCF{SQef6 zA_C;Pk$_}!X5g?d@GSa-TwwH~>9T|~1{R{ghI1GKMn+eNJR3CmG%yFyJVQc5kMWi7 zUe@*I?l`LD^$Q<5=1Mj&(tqcTt6m?&EETeOC z%dC_Wzs#U^bo0!uGxY9AGb~LB{*a591((R}5!TL^dR$U0NZGr>78&CP2q<*#s zIHZM)F3zWoj8CVGUBPP`K?aFCC_F2kD`iT8=YOA7ga@n89bLd!m$LJRd@c+%|9t`QJ1lPxISkW`0a*vtWfSHbsH9AjJ zU@0%hltVHQxPHpz+PFttcVy+UtBw#P*cQsm&l+>f#IvY64S#bC0Xnl`#S(XYEr0n$ z<3P{X9T5kYd=7vH_a6>OFrAyW;l4SxMjw{^o`KO9Vrk*dD!d#SVCfv#5gKgR_QNT_ zr|!63-y)sams(M0qPULYHVRe^J~K4XP|AntCQI=X6F>H(a;et&E{`cq?LeSQ4?zx)01cRzl} zGN~_ip)Ks6e7YKQ;4Gc0KkAfz(D>w0AKD}KqFu1rtZgW3zi(SjVYmhqMRx`RzvqB%Q6z#)D4GZ&RZ6LXgJ@~ zdKtd#JzxuFNH)0u=+LVN3_3V`z-KU_12QmV35Mg)dDDQdt{6}U>;VpZSOG9wQw}fx z(nxV=IQ(09)5TzulS(D7>m}N>;NVQ%kqIfFEOyFL9iExhGYfm*R2(1;M24&(y6C}q z25t>>vZE4DKDKpe>M>q>_x5=C&0E&z)RD#U%tNcT2@e|ax+Xdi4+SxRJ^jex*c4sF zhe6{Hzj;xspfNu8p+n=y!2{!#1`f=S=Ahl<4*>F7kM3ZcDaBv79SQX2GG*8=wj~`J z;;_sBhwrAO@p#sNwulJ)hq_`5=_Nw@qxf>GH&SWXJg+bH4XiSG;6NQFNgNqDjK|dY6nMT9XS#zH|xNYx=4tWyD;Xy<6Y}|q8 z4I5}AXui`p4T1DSWe)Ju2NW8~%rmpX*N#LS2Q#a>_N$KX&K#%X&j|?WShRr$CgH-; zgq({%NpjFGAm^5?x?TYKL0ptRqYIl+=$1Cz)r`LFe+R*CXmjews-HCMRayglu`l|A zi@rcEIkdtN-)?oJq6RO@Ko&6RC6u&DLVSS^U&XEiB$1bukGZA_)(bjpW7`U`eJ)j| zO^~mTXuIu!=rFSpW#~f)A2`gRyRO__0n(XcH*f)Twf_nhzV!V1E{?}7D`vuZMmU+T z7R#Qk0J903Q1iGA;E+Eb0yd7cpz>sSMi&CgVgV{cLopk%eDkii9GstH(n;7zA`VY( zGf{1DaYi~FOBHxgF|PAiuC}AnA8C99frFEh&vIM#_p%732!LSfP=vwZ_71wlaV8Kl z_$Gn3_!1}T5n5cR+OdrlkMLlE@6JvKv*Q;JozRHTvO#x@%_MF=r6FlJ4?rri!wCm0 z4>faqoZy_m;EvJG=8lYhX>ZFdp4OdEdeUYTK#P6Rx0j31P%msxxn()`hk z=tDjD?F6(Oq^*ELx2^c0H~J&fn~M6}x_#H}PCEe}K+yqG_;A=?R4?K`{rDokNGvB& zM2*qcV<^hIr5T0#*Eftlu87$BxnM;-%2+XH`+hQUk{Ez;I=A|(73z@=1a^bnF_p)1 z==$xvD%qo`W#u{t{SeED%#Slt2H!ZOWy6w?C_EIBtRjb;C`-eHjxzFGE}*X1z=l>P z{22t@2t{K#K2@H12D+g+V*VLWFL=}7@Z^m&w!MIdG+4&FDa~8-)X}jSlp2w&Q}Z`o zBa^TAjFvV{+W3mIJ8SNR$G~?)9Vz#Ixg{x^-_@Y2Ht?&Zh0{B-v=KhSrXg+V_9~t2 ze8@+p>S|nvq3yaELs=v_7*qf{$1T}{_wD<#NgtN!{=>lP$l&uPf{l)M_*oi_@Vu~C{HI-y=@#S%m@N8GiR>LsMvl#iIo{#`BLJJl(aa$Wx-Sh zuxTBm)3mF4S!-c(U>PY+no{ZH3IY-zielJ&!!c>AEZx!7E+{3)=QfKwOBvQ33x<4k z&`<$8EX32j@dLP=)==nr07_!;%=+S|x_5`;yte}Zaf%Wd91HX}r=r0z+##sEF$1K~ zk2Fq-O7p;Kgx1KVK~_E1$^7Er=n5T9hYa)72a{tb_>+bQGDyz@HuKYhHG0q<@TFtn z{8n2Z9Eja*er)4^5J3S!Rwju@H+7!s3=QZ4dOJSy(1pDffYllk3^c}|sl*e#y=s#_ zZg*wFg258-gitb}w7twAdoVmq#PXlEgt&?v!qcl*>Gs&kwQ=_RrtJkHb*GF=EdDAu z0~>=4bo}e8T18AihZmiY+pD~sgjEQAh4vucG7YeI5Sx`b%*Sn|c<{60qHXvz#LVeE zM2+7Og6B?36(_2LyHFXzLV)QNr-2#adTBssXJ^<;4wci`URJ2M8xFP}Jk%iEqGCw` zfXkAHunl7cO&6y?1!UqZlzG6xz%n!d9>9}uJgUm|svQhE5woNe$y8DFe(2b$9{}N< zZj5@qo)*n58XJZPore~pd zGGnaJmmSiP0mia)R@808XkJxy8i!|@x+=%BGl?fWXs=o4FZ-I8!qNxGgBau~!BIM( z8POgj$)i)zj;%i?Lr33TU?Nc3e2bp1lhCRFxs^4^w|Og)kn1GfSssk&+OrxRUdq@jeSXJXer|-#4?-xH-5G|Ha)1to}{@}hm$X9U@Kq% zNt^JAk4ik1;AC4dFr~D87+q`S%U4Bys0QB#4WSNw(jjUFGx225!$-M56f5(}KuJPp z$?j+H>Bw;4Koqd>*$=GlvK!n>6>xDH{wItOBXy(kUt-;9@^%xIDai<*#?D3*8vxwv zwUevi-Ju!1G@79h9T3AiERAwKNE0y7WW>xTK*RbuYw_n-QqY1!W6;2Pf=;vy09CoY zkh;Bl`L+$MvB|XHlaCRb44te4{FG=%jw1>I{0C&jaY9Rls5{Fzbc7d6a*EO+a|b}# z9t2`@@Zu1bt3*a;fq4onH}{ffGjBNxF_m4odb{|QzMu#QjiUnx^lds2bya>ep=BlC7vN#eu6Ovie8^_};Du$BG0zWrvWgEK9~-h_xpw2u zIDhSy+XA29&8AqD0T=r+tM>hPXxg4L8LdS}IVrGG$)6t#yh4POaz!&!JEC(2vf}3W zgJ8A-SfUT$5T1&|mw^VqWQ8!PL8VCO+ZU5$b8epSp+m9Z%Jnz&^b0Ow`N=Ssv;8{) zH1!}YTI$91KK%s0#w}qSFFDv;vQ-Xm=(q%U`1rat!scAkA`^V_@MSi{ zs=OK>$HK3F?zs3U<*$|{z8@S@+Q=Xamkka61DOOPF1IaM`cP5mQIrvfj$ULFz}~_t z4tGW!WCEVOOcNpk(-=~b&H%SKIn9|$0uFkpbg?Om6g?AnZi)Ay%wPswV|_EgaKsky z&!7}s>#L~c{tbZjgctfKPGjR#xn~*&Bj4?1Kp~cztOoYl9o<|jqbj_wv~X>9WhOi2 ztTI@2LE3Eq&@V1%)%LDd1vnP(Z9A@ii8d-1No5apdKV_OQ+MZyM41t09S>|_uhkXrGcWtav5!Vw^9utjN-t`;HBrl~o=2Ot9$2(}uJsw(VW{w0ZOvs{2?!k!k&u-+L*5*(*<9 z^YB|$i2%%wC`1MPEo7D;FV%l}L&>!)?=<6@&kg~eITXw#T9A!r?lo|bQaEei4kI@q zk*3jEarq)&sJ!B#fEk;n^8*v2#|hA_aiXTuRR&T!x$UkTcyr5)Yf)n;6LOvg z#DOke-BJg?<(Z2|gn6g}${uXM&9$<4S#Pruz`f5dHdrSPWpo)4JkYe2$pnBMTF*c( zUtB-s6L#kWdJmvs8VnFh@0bVWA+y^uPlV-CLV_Enk76q@F1jP`*MTE z60jn+y`@F$mHI%BVtqMj9TcT46|>B!D~-geZ(ox|IcKr1JZ5xu-m{_X2f+^Kj~ju7 z-&6-TVljlc@R5}3RRzu&%;j52pwMY-KkI-5csei*z@W;tFSqa}1;|(2(ZZjv7X`ilz{jkL zA7>^2c=5E_I`7egmu!DSa{RQKc=bRATg0+S&7ez~89dLWaH*^n_7b<~3jY^AerT5; z0j!^XZk&Ahsqq(o_9x@9Lp$T19F1#6BB`5hTGyOEURy#A$@;(jQF-Y9V0CM)rcLkP_w2LlZinQs{KF%P_n z;fxr=dMfM@KOZo7kzihF2sT_P9p7;T)1y5I4rWiAqM{Ls?iZ*zD@HOlhguY5o8 zv%fvgU$`*-% zN-+r=p1zn-Fap}H%YyL ztW1=vO3rXI!QfY*O}?hX%V>qPT|eOn8usZ;IP}b>b96!Xgm5s0eUyEQsG+dRxh>oO z#9}|y0iSuGeYmggK}vX@v>f7_iqoMb0GVjW>XP4MF!92xhC%b;4he1M)i$`l^Z66I zFP+;QJ4+|WJMW$!w{LBaryt_+zXiWE4xJms*A?OYUTm_lEImCywZlwIL$HzRHR==JP%Cp{X|(D0h*Zu8_-R0DvptZ(GUkFhouZg zm={5(VmA^4(U<`S7s50k^ft6`d#34ur)LQA&@Hl{ao`X=bIT%k8N7T{h%S`*I#ZF1 z+R3AgZ}Wg%&-`{!6`pGLHp*Q^J|K`|+ThX7-iRt1>W*(x-UqWE z^-M=+QYl6PRA4YjJgX@N4e~SiR@*6#Tp#){fKNN+Zl@W519%Wr8GP&~_y{^n-$F4& z8g+7zw7>c&^zAwk@KaKJ+Y`dy`RK+jJKO&>-l>jmF<@iXlV;9!$=8cBWCT)yi_sVauPr5VCJcfdR#?bQDb5nxGr$r8 z$b9p{t#Mwfua7=?$OHB~&`H@~d-uw1w;7ja4D4{EgCOVicW=5rN7uOCSG$V!Opow! zp5ktUmhE~fQ(E8GXozo9e3Be!X5%Nl?8yfiVf9_GIi=@0EgN9WzFTlRu;T-Od1Lj5O zlXDUn`1$Q!KQhaTf%;N+|FG%PPcHAqfc)2XUUE0%5hXD;4Z@OuNAC_DTpXv4t;sos zf<+84<&ndCicOSCEtRBQc(es z+{&U)LKL3Jlhe@{3hi9QmyJSZB|2Q3ai${}*cl*&{UgscwYY^{45GR#&kj$v^$EVo z!Lx3(J-maHBkhDgoYD0+kBzc&dGy4(f0J#wBJf}=I>Nqh%h7V-OZ$~@L&s>_++86D zjcB)o9z5(<*3{5>gn55KqlWD|aff33g@};CpAUdDG>@#)@x~u{lL&1VE(&12i1h z-U!FAI4iS%JAfON5IWjFg6t3_)X<8%64fXe06O@@;EF4sIR3GsfdKw9S;&D=aN!BK z@yv_Tnb85#-c_A8zk+Xl2#pjvV<3Y<>*1`D3vD(^NTZ|cpu-)&QwP|^+mvF%Nw_@{ z8$}^Y$ZSyNTHm^;ko(&P;tpJ6Na`R5+0Kpb(hHg9qXWmw$_hqQCC{TXLsLw9KBUg= zpKMA#oS^N&)gO4lpR?$@eSm00hfX>khH_e3${xO>p^CE_f*Ffj)2p z?+3tT?~#w-=1aP0_h7yc8CuY;w4D`_{is`j0R-Ju8|go_SDzgqH~vlAI|p4{5=_^r zc`#v81?mGw&nM57yHxR1yb{4j7V-ouzH_B9UcL*ntQ5xng@< z6#(#2V06)CkjRX)x1Q(^K1=BQV1s;@H@#v#cr=*A$t=g`iUgT0XaMx}3PysE#cbgj zpRW!(hq=)-IC60KBsbT5{JM&#qR<^){94LS3M$_F70nlz`aXHqzql#zp?KQ_T)%bH zL<@en>Fvw6jEA#5b$Y`qDSQgT)CGC56Mzlo?TVe*_>r}h0q@Xp>E}VtdIdLwmdXj0 z2cGp($X$+J0a_F?2Gtc1DzPzREIWZDQE{OUgRkiM5X79JV9S)yF@w@#`(}Pa)eXAN0y1b(S84%Ev6o!=c}~e0yBKxov)r9A6tpH_@!%n`w1W-}aS-U;fll>4kw);P;e2sF?r*ef{N&j`L5C;QI#@ z3S)#;ZayBK?f{&mOpkB?*9df;t~A%abn-Z}@(>I*7ss+%sF^^6NX~^0LcQ z$MQl%cz6XtM4dtyAyR-G+MUZe^Hw{yZ1}Y8p>b3vE;H;0ilevNRQQ%9Jk+XcPXh}) zvR49K%Fv#=5H`(h6%(Ya6f+ob>kmKlszUkD*yn}k!HAGd=UY{BtY_+?JlohPAahPa znZ_e(|4!R^L(<9E9UdmrL3_?mY*9h-MA5YX(oRMaU?-(z>lvin_rWoO%`GL=R&&L1 z5}f(!jqP#f!lo7GUpV*+-;IOID!1lQutq~0bo$7_!5=d6qa(__k^tK$d?l8$d>J44>9o-Y8UWFL23`@` z9y79PV}Pck(;?j_K@09k)yN9kxPCz%7b8?=YeE35=Ehp{;s3*Q;wg&)^$u) zbeJHCSXT9w9jPPt-Qm=Q;58?#mH4q1% z0m=8hv8T{k0j86I6TQ&c9a!=1cqrue)`R#SfPeKWvUxz`KV*Q!dFYsWz|$*OeS7IW z&${cTvyadyvmiRjW^**~Xq*{lIB>#-H7?b^i_T*SS^@Gq&L6<>TsjO`)*P)@_0TE# z|0u4FXkc%!snSd?{UV8LH}6c>+oa)%65yjE147(2D*$rIW9FuFVj!G}#z0j@dZ2L; zXvlIj$2b*RXLbPxiCQxQ@aD(Y&hZWD{DXs$~ZaLZKa7aa| zOLrJMjw4Pwh)$FxCn1nXryGaEGoth1uCw@4KkH&UT4n1r=~n#G@koK2&NKUkQtD(p z#rZbSNmyxN|JUBW?w1cda$?uiY$8pa?lB#;R!u{2T*q=60f{STR8!N$a~^pgEzjM zO-srwOL+D<%SB$x4^XaRVmcgg9%=MWJ2_(xXwS&vaE{}ebjprDHi!lea11MP{@pL0 z3fCV2FP*!lCEJc2gGW#g>H2#~qb?52TSr@W9M1-p!F%NLtQt0MBxV2xzAVS$2v{E$ zSrU~I%VO~3etKruTwep=TV^v?oRJ#2Jn!DK`C$!6Hm-T?dO5YHX(&>@J9aT79$u*r z4&(tb@rw>_0N$u=ZGXQCX|yn~O<#?OVh4f*V&hxc^w2K2y2*q417Y{#HU zTS&^JX9F+hL>}2Z^2+SSW}Rom0_vPLfxmQ{<3W2;4qrND`yBqm&zj-R3rDafxah_+ z9N_8eOE!K>!A2WpY~XsL5x zv<%H}T)|061Gp6x5XX4nc!q9lS1x>;MrVZ~cC+bg->er3=j?(*aVoZIoB zZ-6Itc6~uG4RfU}p-O!WQTd~)`C7>q<`2)6~9?;M(T&Cp5K-dSm@aGZ( z{{uJvMz`k?T&*S*dburHUb_VF=GL^qzH#i(lIY0Ac=1v6fnQ=}R0T9s`vri{GQctL zkWZZ$cwBcQKo&pcZ65aS4TQ|(gFP@KPz0peng6`Lu9JvJGb<5CM|8=6 zV|H=2RKW6}{?;4U$6HsW*Y4f%p+^snCr%wSE_}IlNjva^6PE1!T=el%Yt91$9!sn1 zch{I>3El}-5y)g13P(TBs}8LR@4fx5i_+r80A&WY{~MQ+kS`m-8I=F;BjGadT^n= zXb<+7*p!7hJQL>5QFw-b+Krcxq7(ZS^brP>&{NoHFdjL+Vmib?NBH_XH~e7I`pTkzDvT5MX4&?={o~U=|J&om z7hfEYJp9o3^RN8DSl_?x*$!1ADGZ?_F9RC0XzD{b9mkyrd(xH~n3mLO{6=0mHrMFB z?9vClxRz%CgAY$tW|un~k6df}w+1R?Y0iUAYb*Inp$zc34^A3c*;vc0k20*R`0WFo z9-SE6((%GS77ow`)Pt8!QZFVa^jv18$_hW}Mbg;8k$D(XP6kvQB z4yVrb6};JhAdG=&6!7|)aWfGQ2O@+&xKb~COD=$IC6z4aqpb%w^HsX*3{BaG(48eE zrK?V+k6bo8H*ekXQj}{!{|ZW~F{3^Dp)ZaL*YA$w$B&Hv@JIiCJbm=GI};NZ{22s+ z*VMsT+8)-W+0jEQ-T>uA9CZ43uWXM)r=J`97FWi(vu})t4&HM8+`;ESYMM8lWqJ-D zJj&F7v~@_b@^DmV>OhB%!)J+&qw^;j?rh3qUx2m&7yqOU*dWBFbPQ}shi6G_TZqTT z!2|w-0(f9YHs7!_da%k^<+t%_Ywy{JM^}dd0@bY`O#CcCY z{|<}puB+_NJKEDwFv^l1+t>${W1ohf09zw7&tITR+TMCdX@`Jm>EP8FqE zzH?<~eClU^ZQR_vF~0t_KOG-D4Fi;n;ymoYNWpG6*X8V@^PBMFhgRKj5K04l{7e7M zxOD0K__x3JPsf3Ud*i9oS?b|ny}1zQhBxUbALGO*G>n~^dQ(>U*?dc0s4@aON0rI! zolcSp$7p?>>BkmjvFyf%-e(7*N1!$@S&lzp%;GQ z0Ydeq&eV@h+}@C7hGm9llIItbbZlO~4Zs7v=zQ>|480h{8IaHg==y>~y9Q!wHVCm7 z9I-bz=t?1jL|Uu;+0n`iQwC`|KJjZ;$4}^!TweH}Yta`*lyOK0 z5B$7z@tdz-8wb`-jgu#ikNr2_7{@o#cvBq&DD%=e8u^|(IM*`A53jl+H0I?SJL5ZN zZ_1$j43jrM?ynaBNQkwDpE`r$$}xW`;3Zn6JxdF*H!{d;Jv!uY2O!ju=h=l@GKA() zkmV9I4H-0K968H=8WuyDU(8GhkRs|?S|1z*C#Mm+qrf(uD2+?LX^RIy7E5*GyS(@- zAm$EQvgg5^ieJu6U)eeca0VJM`3V6!qX!jay9Nb{c<{lt!Y9O8ws!7}a~HQf0J1El zJ~#|N4b94cYu26d{Wq^!ciR1#M`kyZ2d{ zBEf@>ziml!OG!h?5O16*a~PPZj|VwLs6P5x0BPzVz0m`f@UXu@3!yS8Up$bN6nfaA zh@S_<63B?q7kfk7XyG4z{^Tz2Ub}Er%dEA z#LfTLj3I@L;a+~5Uml52-^WhyQbul5WCjGNc(pYs=8ci(0VPut@=Hlowv%z4bU63{ z7OBH>bRxjG*-fHZNyyma8G={Pa$eT^0j7;V|^XpF$*L+8(& zd|29XR3|rF(Xk6$)RPVk&`-P-kGABIMtsWPM`z%YsAp-g`PnBkFFV`*Lkl%Ndj`z3 zIG$Mwm@QaR(uo17Zywwt+tJjAjtm6PJi-i~jl2d}q@JxK_KEZ38-Y232C}Lce*l*q z{XCVJj!7|gXENq*qCGq7aEF!7%5>(tOTpT%c|^ZD>2blW;?{H!`arEY)+Efwx#CboYie;x&NWbu;#gNGF6qa}KR)}>24IKTzIa$IcQIL(ismz(5q zfWtC0mnzUC)Yq)T7jAPPy%`){;hk(CiVyUUpISFBXCXtV*o4`NYuE13(=0DMvGSnU z6~&3IgAQ+0PxXjyQCfkB*nEROf7Zu!Evo`#`+vC0#xTaV*cum{r`Ck zI87Z^xh%y`EmQ}*dLXh|I^kpZdh1J_3Bl#N@Z2e1^-Tr(26+;uj;^H}9g15f01vo8 zeMko8Lf${@kRTejJJy4ck%%4n8kMP(4ir{k0~jT$Ig^G$4Gap;#_L^7Kdd3SM)x`O zK>xfJDf-(1pygDEF+ds&VD`wv2&G%)!<{${-~o`7_<6)tq50EsqK0va_7Z*`tOk`x z%IO9=5cT4r4zmtt zMKEpF69F4sIBgi{P!t}tAHKk~FL`uu^9D@DpU73jBMVvMz4hq$FXF_P%zySY>B8yE#Zl&dwPF<2ZGU&md+hMG3VZ$nc zkWPsW*i=JrcSw=@asxIkvf`bGA7gXZUFq&4$RkZ0T;c%!&Ax)`rJc-p~;Ko?kTya2z^4 zBfbsAA@FSh@ubr*8?wx@QqGS&%8grf49n|1=rzy?KThI3=u`H z_fZGKMvp6-{bzG&e-8i-`Nr^5yc#YkopnMjbLDHk8QtU5`Nz|B%^o0h-IQ&5I|6a# zQ-<2S!m1nIh|0WrYsa$v6d-nmcOB+cdJvBek;M;3$U~3L&m{+8Y)ObRsNznb^c7u4 zcjutP(MDd{%Sf#9xR0F&O5(&IvU&ih3xEvT_d!UhS^MAwZnrN1_GI#~52?-2pI;T? zOP)-~5Fj%)O?@A1cd)#YP}uwdahUSw0TeC;;N=Z2*y*5Ogjwt~54Np7?%1L;n(-hG z83P?XEfXOthMMpc@09@%uL{iT=v*}Pgdq1K zaT1&q#VBV+XQpT)!-NXZ@Ziu{xpxWcIo)p63Frp^{^RTW^3m`N xu4$xGd@Fh8MJT-XnRzfBFh#)Sl=v89{C}jeh%Zc*3TprW002ovPDHLkV1fywFwy`3 literal 0 HcmV?d00001 diff --git a/src/ClassicUO.Assets/gumpartassets/40308.png b/src/ClassicUO.Assets/gumpartassets/40308.png new file mode 100644 index 0000000000000000000000000000000000000000..ccb6e2798e4f23f00fc89819b86a685008f5ab5e GIT binary patch literal 3244 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.button == (byte)SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START && World.InGame) { Gump g = UIManager.GetGump(); - if(g == null) + if (g == null) { UIManager.Add(new ModernOptionsGump()); - } else + } + else { g.Dispose(); } From 12b2be4c8b16ba1c237a4d32ce45e69028fa0406 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 1 Feb 2024 02:29:42 -0700 Subject: [PATCH 11/93] Update version history text --- src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index 89866dc4ef..dcf521b758 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -10,7 +10,10 @@ internal class VersionHistory : Gump "/c[white][3.20.0]/cd\n" + "- Being frozen wont cancel auto follow\n" + "- Fix from CUO for buffs\n" + - "- Add ability to load custom spell definitions from an external file", + "- Add ability to load custom spell definitions from an external file\n" + + "- Customize the options gump via ui file\n" + + "- Added saveposition tag for xml gumps\n" + + "- Can now open multiple journals\n", "/c[white][3.19.0]/cd\n" + "- SOS Gump ID configurable in settings\n" + From 8accb465bf5d8ac3a33ef6749ac925da2581c185 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 1 Feb 2024 03:08:31 -0700 Subject: [PATCH 12/93] Just some minor cleanup --- .../FontStashSharp/RichText/RichTextLayout.cs | 4 +- src/ClassicUO.Assets/PNGLoader.cs | 33 +- .../Game/GameObjects/MobileAnimation.cs | 473 +++++++++--------- .../Game/Managers/TextRenderer.cs | 5 +- .../Game/Managers/ToolTipOverrideManager.cs | 29 +- .../Game/UI/Controls/SimpleBorder.cs | 4 +- .../Game/UI/Gumps/GridContainer.cs | 5 +- .../Game/UI/Gumps/HealthBarGump.cs | 2 +- .../Game/UI/Gumps/MacroButtonGump.cs | 9 +- .../Game/UI/Gumps/ModernOptionsGump.cs | 6 - .../Game/UI/Gumps/MultipleToolTipGump.cs | 13 +- .../Game/UI/Gumps/NameOverHeadHandlerGump.cs | 10 +- .../Game/UI/Gumps/SkillGumpAdvanced.cs | 23 +- .../Game/UI/Gumps/TipNoticeGump.cs | 3 - .../Game/UI/Gumps/WorldMapGump.cs | 8 +- src/ClassicUO.Client/GameController.cs | 4 +- .../Network/PacketHandlers.cs | 2 +- src/ClassicUO.Renderer/Camera.cs | 10 +- 18 files changed, 319 insertions(+), 324 deletions(-) diff --git a/external/FontStashSharp/src/FontStashSharp/RichText/RichTextLayout.cs b/external/FontStashSharp/src/FontStashSharp/RichText/RichTextLayout.cs index 1f0ab2d4e6..3e8af29bde 100644 --- a/external/FontStashSharp/src/FontStashSharp/RichText/RichTextLayout.cs +++ b/external/FontStashSharp/src/FontStashSharp/RichText/RichTextLayout.cs @@ -224,7 +224,7 @@ private void Update() { _size = _layoutBuilder.Layout(Text, Font, Width); } - catch (System.Exception ex) + catch { } @@ -249,7 +249,7 @@ public Point Measure(int? width) result = _layoutBuilder.Layout(Text, Font, width, true); _measures[key] = result; } - catch (System.Exception ex) + catch { } diff --git a/src/ClassicUO.Assets/PNGLoader.cs b/src/ClassicUO.Assets/PNGLoader.cs index 58572ca9cd..7ce6b1a27f 100644 --- a/src/ClassicUO.Assets/PNGLoader.cs +++ b/src/ClassicUO.Assets/PNGLoader.cs @@ -45,13 +45,13 @@ public Texture2D GetImageTexture(string fullImagePath) public GumpInfo LoadGumpTexture(uint graphic) { - Texture2D texture; - if (gump_availableIDs == null) return new GumpInfo(); int index = Array.IndexOf(gump_availableIDs, graphic); if (index == -1) return new GumpInfo(); + Texture2D texture; + gump_textureCache.TryGetValue(graphic, out texture); if (exePath != null && texture == null && GraphicsDevice != null) @@ -160,6 +160,10 @@ public Task Load() uint.TryParse(fname.Substring(0, fname.Length - 4), out gump_availableIDs[i]); } } + else + { + Directory.CreateDirectory(gumpPath); + } string artPath = Path.Combine(exePath, IMAGES_FOLDER, ART_EXTERNAL_FOLDER); if (Directory.Exists(artPath)) @@ -176,6 +180,10 @@ public Task Load() } } } + else + { + Directory.CreateDirectory(artPath); + } }); } @@ -184,7 +192,7 @@ public Task LoadResourceAssets() return Task.Run( () => { - var assembly = this.GetType().Assembly; + var assembly = GetType().Assembly; //Load the custom gump art included with TUO for (uint i = 40303; i <= 40312; i++) @@ -200,6 +208,10 @@ public Task LoadResourceAssets() continue; } } + else + { + continue; + } var resourceName = assembly.GetName().Name + $".gumpartassets.{i}.png"; Console.WriteLine(resourceName); @@ -214,10 +226,17 @@ public Task LoadResourceAssets() //Increase available gump id's - uint[] availableIDs = new uint[gump_availableIDs.Length + 1]; - gump_availableIDs.CopyTo(availableIDs, 0); - availableIDs[availableIDs.Length - 1] = i; - gump_availableIDs = availableIDs; + if (gump_availableIDs != null) + { + uint[] availableIDs = new uint[gump_availableIDs.Length + 1]; + gump_availableIDs.CopyTo(availableIDs, 0); + availableIDs[availableIDs.Length - 1] = i; + gump_availableIDs = availableIDs; + } + else + { + gump_availableIDs = [i]; + } stream.Dispose(); } diff --git a/src/ClassicUO.Client/Game/GameObjects/MobileAnimation.cs b/src/ClassicUO.Client/Game/GameObjects/MobileAnimation.cs index 89723638aa..caf01a3767 100644 --- a/src/ClassicUO.Client/Game/GameObjects/MobileAnimation.cs +++ b/src/ClassicUO.Client/Game/GameObjects/MobileAnimation.cs @@ -30,12 +30,11 @@ #endregion -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using ClassicUO.Game.Data; using ClassicUO.Assets; +using ClassicUO.Game.Data; using ClassicUO.Utility; +using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace ClassicUO.Game.GameObjects { @@ -88,11 +87,11 @@ public override ushort GetGraphicForAnimation() { case 0x0192: case 0x0193: - { - g -= 2; + { + g -= 2; - break; - } + break; + } case 0x02B6: g = 667; @@ -124,7 +123,7 @@ public Direction GetDirectionForAnimation() private static void CalculateHight( ushort graphic, Mobile mobile, - AnimationFlags flags, + AnimationFlags flags, bool isrun, bool iswalking, ref byte result @@ -223,7 +222,7 @@ ref byte result } } - private static void LABEL_222(AnimationFlags flags, ref ushort v13) + private static void LABEL_222(AnimationFlags flags, ref ushort v13) { if ((flags & AnimationFlags.CalculateOffsetLowGroupExtended) != 0) { @@ -365,7 +364,7 @@ private static void LABEL_222(AnimationFlags flags, ref ushort v13) // v13 = 0; } - private static void LABEL_190(AnimationFlags flags, ref ushort v13) + private static void LABEL_190(AnimationFlags flags, ref ushort v13) { if ((flags & AnimationFlags.Unknown80) != 0 && v13 == 4) { @@ -568,7 +567,7 @@ public static byte GetGroupForAnimation( AnimationGroupsType originalType = Client.Game.Animations.GetAnimType(graphic); Client.Game.Animations.ConvertBodyIfNeeded(ref graphic, isParent); AnimationGroupsType type = Client.Game.Animations.GetAnimType(graphic); - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(graphic); bool uop = (flags & AnimationFlags.UseUopAnimation) != 0; @@ -1051,12 +1050,12 @@ public static byte GetGroupForAnimation( return (byte)v13; } - // LABEL_188 - v13 = 2; + //// LABEL_188 + //v13 = 2; - LABEL_190(flags, ref v13); + //LABEL_190(flags, ref v13); - return (byte)v13; + //return (byte)v13; } byte result = mobile._animationGroup; @@ -1162,242 +1161,167 @@ public static byte GetGroupForAnimation( break; default: - { - Item hand2 = mobile.FindItemByLayer(Layer.TwoHanded); - - if (!isWalking) { - if (result == 0xFF) - { - bool haveLightAtHand2 = - hand2 != null - && hand2.ItemData.IsLight - && hand2.ItemData.AnimID == graphic; + Item hand2 = mobile.FindItemByLayer(Layer.TwoHanded); - if (mobile.IsMounted) - { - if (haveLightAtHand2) - { - result = 28; - } - else - { - result = 25; - } - } - else if (mobile.IsGargoyle && mobile.IsFlying) // TODO: what's up when it is dead? - { - if (mobile.InWarMode) - { - result = 65; - } - else - { - result = 64; - } - } - else if (!mobile.InWarMode || mobile.IsDead) + if (!isWalking) + { + if (result == 0xFF) { - if (haveLightAtHand2) + bool haveLightAtHand2 = + hand2 != null + && hand2.ItemData.IsLight + && hand2.ItemData.AnimID == graphic; + + if (mobile.IsMounted) { - // TODO: UOP EQUIPMENT ? - result = 0; + if (haveLightAtHand2) + { + result = 28; + } + else + { + result = 25; + } } - else + else if (mobile.IsGargoyle && mobile.IsFlying) // TODO: what's up when it is dead? { - if ( - uop - && type == AnimationGroupsType.Equipment - && Client.Game.Animations.AnimationExists(graphic, 37) - ) + if (mobile.InWarMode) { - result = 37; + result = 65; } else { - result = 4; + result = 64; } } - } - else if (haveLightAtHand2) - { - // TODO: UOP EQUIPMENT ? - - result = 2; - } - else - { - unsafe + else if (!mobile.InWarMode || mobile.IsDead) { - ushort* handAnimIDs = stackalloc ushort[2]; - Item hand1 = mobile.FindItemByLayer(Layer.OneHanded); - - if (hand1 != null) + if (haveLightAtHand2) { - handAnimIDs[0] = hand1.ItemData.AnimID; + // TODO: UOP EQUIPMENT ? + result = 0; } - - if (hand2 != null) + else { - handAnimIDs[1] = hand2.ItemData.AnimID; + if ( + uop + && type == AnimationGroupsType.Equipment + && Client.Game.Animations.AnimationExists(graphic, 37) + ) + { + result = 37; + } + else + { + result = 4; + } } + } + else if (haveLightAtHand2) + { + // TODO: UOP EQUIPMENT ? - if (hand1 == null) + result = 2; + } + else + { + unsafe { + ushort* handAnimIDs = stackalloc ushort[2]; + Item hand1 = mobile.FindItemByLayer(Layer.OneHanded); + + if (hand1 != null) + { + handAnimIDs[0] = hand1.ItemData.AnimID; + } + if (hand2 != null) { - if ( - uop - && type == AnimationGroupsType.Equipment - && !Client.Game.Animations.AnimationExists( - graphic, - 7 - ) - ) - { - result = 8; - } - else - { - result = 7; - } + handAnimIDs[1] = hand2.ItemData.AnimID; + } - for (int i = 0; i < 2; i++) + if (hand1 == null) + { + if (hand2 != null) { if ( - handAnimIDs[i] >= 0x0263 - && handAnimIDs[i] <= 0x028B + uop + && type == AnimationGroupsType.Equipment + && !Client.Game.Animations.AnimationExists( + graphic, + 7 + ) ) { - for ( - int k = 0; - k < HANDS_BASE_ANIMID.Length; - k++ + result = 8; + } + else + { + result = 7; + } + + for (int i = 0; i < 2; i++) + { + if ( + handAnimIDs[i] >= 0x0263 + && handAnimIDs[i] <= 0x028B ) { - if (handAnimIDs[i] == HANDS_BASE_ANIMID[k]) + for ( + int k = 0; + k < HANDS_BASE_ANIMID.Length; + k++ + ) { - result = 8; - i = 2; + if (handAnimIDs[i] == HANDS_BASE_ANIMID[k]) + { + result = 8; + i = 2; - break; + break; + } } } } } - } - else if (mobile.IsGargoyle && mobile.IsFlying) - { - result = 64; - } - else - { - result = 7; - } - } - else - { - result = 7; - } - } - } - } - } - else if (mobile.IsMounted) - { - if (isRun) - { - result = 24; - } - else - { - result = 23; - } - } - //else if (EquippedGraphic0x3E96) - //{ - - //} - else if (isRun || !mobile.InWarMode || mobile.IsDead) - { - if ((flags & AnimationFlags.UseUopAnimation) != 0) - { - // i'm not sure here if it's necessary the isgargoyle - if (mobile.IsGargoyle && mobile.IsFlying) - { - if (isRun) - { - result = 63; - } - else - { - result = 62; - } - } - else - { - if (isRun && Client.Game.Animations.AnimationExists(graphic, 24)) - { - result = 24; - } - else - { - if (isRun) - { - if ( - uop - && type == AnimationGroupsType.Equipment - && !Client.Game.Animations.AnimationExists(graphic, 2) - ) - { - result = 3; - } - else - { - result = 2; - - if (mobile.IsGargoyle) + else if (mobile.IsGargoyle && mobile.IsFlying) { - hand2 = mobile.FindItemByLayer(Layer.OneHanded); + result = 64; + } + else + { + result = 7; } - } - } - else - { - if ( - uop - && type == AnimationGroupsType.Equipment - && !Client.Game.Animations.AnimationExists(graphic, 0) - ) - { - result = 1; } else { - result = 0; + result = 7; } } } } } - else + else if (mobile.IsMounted) { if (isRun) { - result = (byte)(hand2 != null ? 3 : 2); + result = 24; } else { - result = (byte)(hand2 != null ? 1 : 0); + result = 23; } } + //else if (EquippedGraphic0x3E96) + //{ - if (hand2 != null) + //} + else if (isRun || !mobile.InWarMode || mobile.IsDead) { - ushort hand2Graphic = hand2.ItemData.AnimID; - - if (hand2Graphic < 0x0240 || hand2Graphic > 0x03E1) + if ((flags & AnimationFlags.UseUopAnimation) != 0) { + // i'm not sure here if it's necessary the isgargoyle if (mobile.IsGargoyle && mobile.IsFlying) { if (isRun) @@ -1411,62 +1335,137 @@ public static byte GetGroupForAnimation( } else { - if (isRun) + if (isRun && Client.Game.Animations.AnimationExists(graphic, 24)) { - result = 3; + result = 24; } else { - result = 1; + if (isRun) + { + if ( + uop + && type == AnimationGroupsType.Equipment + && !Client.Game.Animations.AnimationExists(graphic, 2) + ) + { + result = 3; + } + else + { + result = 2; + + if (mobile.IsGargoyle) + { + hand2 = mobile.FindItemByLayer(Layer.OneHanded); + } + } + } + else + { + if ( + uop + && type == AnimationGroupsType.Equipment + && !Client.Game.Animations.AnimationExists(graphic, 0) + ) + { + result = 1; + } + else + { + result = 0; + } + } } } } else { - for (int i = 0; i < HAND2_BASE_ANIMID.Length; i++) + if (isRun) + { + result = (byte)(hand2 != null ? 3 : 2); + } + else + { + result = (byte)(hand2 != null ? 1 : 0); + } + } + + if (hand2 != null) + { + ushort hand2Graphic = hand2.ItemData.AnimID; + + if (hand2Graphic < 0x0240 || hand2Graphic > 0x03E1) { - if (HAND2_BASE_ANIMID[i] == hand2Graphic) + if (mobile.IsGargoyle && mobile.IsFlying) { - if (mobile.IsGargoyle && mobile.IsFlying) + if (isRun) { - if (isRun) - { - result = 63; - } - else - { - result = 62; - } + result = 63; + } + else + { + result = 62; + } + } + else + { + if (isRun) + { + result = 3; } else { - if (isRun) + result = 1; + } + } + } + else + { + for (int i = 0; i < HAND2_BASE_ANIMID.Length; i++) + { + if (HAND2_BASE_ANIMID[i] == hand2Graphic) + { + if (mobile.IsGargoyle && mobile.IsFlying) { - result = 3; + if (isRun) + { + result = 63; + } + else + { + result = 62; + } } else { - result = 1; + if (isRun) + { + result = 3; + } + else + { + result = 1; + } } - } - break; + break; + } } } } } - } - else if (mobile.IsGargoyle && mobile.IsFlying) - { - result = 62; - } - else - { - result = 15; - } + else if (mobile.IsGargoyle && mobile.IsFlying) + { + result = 62; + } + else + { + result = 15; + } - break; - } + break; + } } return result; @@ -1625,7 +1624,7 @@ private static byte GetObjectNewAnimationType_0(Mobile mobile, ushort action, by { if (action <= 10) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -1759,7 +1758,7 @@ private static byte GetObjectNewAnimationType_0(Mobile mobile, ushort action, by [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_1_2(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -1788,7 +1787,7 @@ private static byte GetObjectNewAnimationType_1_2(Mobile mobile, ushort action, [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_3(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -1832,7 +1831,7 @@ private static byte GetObjectNewAnimationType_3(Mobile mobile, ushort action, by [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_4(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -1870,7 +1869,7 @@ private static byte GetObjectNewAnimationType_4(Mobile mobile, ushort action, by [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_5(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -1918,7 +1917,7 @@ private static byte GetObjectNewAnimationType_5(Mobile mobile, ushort action, by [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_6_14(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -1975,7 +1974,7 @@ private static byte GetObjectNewAnimationType_7(Mobile mobile, ushort action, by [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_8(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -2004,7 +2003,7 @@ private static byte GetObjectNewAnimationType_8(Mobile mobile, ushort action, by [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_9(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -2031,7 +2030,7 @@ private static byte GetObjectNewAnimationType_9(Mobile mobile, ushort action, by [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_10(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) @@ -2058,7 +2057,7 @@ private static byte GetObjectNewAnimationType_10(Mobile mobile, ushort action, b [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetObjectNewAnimationType_11(Mobile mobile, ushort action, byte mode) { - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(mobile.Graphic); AnimationGroupsType type = AnimationGroupsType.Monster; if ((flags & AnimationFlags.Found) != 0) diff --git a/src/ClassicUO.Client/Game/Managers/TextRenderer.cs b/src/ClassicUO.Client/Game/Managers/TextRenderer.cs index 1532436337..5ec4c105f9 100644 --- a/src/ClassicUO.Client/Game/Managers/TextRenderer.cs +++ b/src/ClassicUO.Client/Game/Managers/TextRenderer.cs @@ -30,13 +30,12 @@ #endregion -using System.Collections.Generic; using ClassicUO.Configuration; using ClassicUO.Game.GameObjects; -using ClassicUO.Game.UI.Gumps; using ClassicUO.Input; using ClassicUO.Renderer; using Microsoft.Xna.Framework; +using System.Collections.Generic; namespace ClassicUO.Game.Managers { @@ -76,7 +75,7 @@ public virtual void Draw(UltimaBatcher2D batcher, int startX, int startY, bool i continue; } - ushort hue = 0; + //ushort hue = 0; float alpha = o.Alpha / 255f; diff --git a/src/ClassicUO.Client/Game/Managers/ToolTipOverrideManager.cs b/src/ClassicUO.Client/Game/Managers/ToolTipOverrideManager.cs index 964bc3ae5c..86747ab774 100644 --- a/src/ClassicUO.Client/Game/Managers/ToolTipOverrideManager.cs +++ b/src/ClassicUO.Client/Game/Managers/ToolTipOverrideManager.cs @@ -216,6 +216,7 @@ public static void ImportOverrideSettings() } catch (System.Exception e) { + GameActions.Print(e.Message); GameActions.Print("It looks like there was an error trying to import your override settings.", 32); } break; @@ -225,6 +226,10 @@ public static void ImportOverrideSettings() t.SetApartmentState(ApartmentState.STA); t.Start(); } + else + { + GameActions.Print("This feature is not currently supported on Unix.", 32); + } } public static string ProcessTooltipText(uint serial, uint compareTo = uint.MinValue) @@ -271,22 +276,22 @@ public static string ProcessTooltipText(uint serial, uint compareTo = uint.MinVa if (compareTo != uint.MinValue) { tooltip += string.Format( - overrideData.FormattedText, - property.Name, - property.FirstValue.ToString(), - property.SecondValue.ToString(), - property.OriginalString, - property.FirstDiff != 0 ? "("+property.FirstDiff.ToString()+")" : "", - property.SecondDiff != 0 ? "("+property.SecondDiff.ToString()+")" : "" + overrideData.FormattedText, + property.Name, + property.FirstValue.ToString(), + property.SecondValue.ToString(), + property.OriginalString, + property.FirstDiff != 0 ? "(" + property.FirstDiff.ToString() + ")" : "", + property.SecondDiff != 0 ? "(" + property.SecondDiff.ToString() + ")" : "" ) + "\n"; } else { tooltip += string.Format( - overrideData.FormattedText, - property.Name, - property.FirstValue.ToString(), - property.SecondValue.ToString(), + overrideData.FormattedText, + property.Name, + property.FirstValue.ToString(), + property.SecondValue.ToString(), property.OriginalString, "", "" ) + "\n"; } @@ -343,7 +348,7 @@ public static string ProcessTooltipText(string text) handled = true; break; } - catch (System.FormatException e) { } + catch { } } } } diff --git a/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs b/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs index 45912aa0ad..5cb9c7b61d 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs @@ -10,8 +10,8 @@ internal class SimpleBorder : Control private int _width = 0, _height = 0; //Return 0 so this control has a 0, 0 size to not interfere with hitboxes - public int Width { get { return 0; } set { _width = value; } } - public int Height { get { return 0; } set { _height = value; } } + public new int Width { get { return 0; } set { _width = value; } } + public new int Height { get { return 0; } set { _height = value; } } public override bool Draw(UltimaBatcher2D batcher, int x, int y) { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs index a80001f2a7..e822d8e745 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs @@ -488,10 +488,7 @@ private void UpdateItems(bool overrideSort = false) InvalidateContents = false; } - public static bool FindContainer(uint serial, out GridContainer? gridContainer) - { - return (gridContainer = UIManager.GetGump(serial)) == null ? false : true; - } + public static bool FindContainer(uint serial, out GridContainer? gridContainer) => (gridContainer = UIManager.GetGump(serial)) != null; protected override void UpdateContents() { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index 546d80aeb3..e882506029 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -119,7 +119,7 @@ protected set { } public bool IsLastTarget { get; set; } = false; private bool _locked = false; - private bool IsLocked + private new bool IsLocked { get { return _locked; } set diff --git a/src/ClassicUO.Client/Game/UI/Gumps/MacroButtonGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/MacroButtonGump.cs index c12df9b71d..cec8995e33 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/MacroButtonGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/MacroButtonGump.cs @@ -30,17 +30,16 @@ #endregion -using System; -using System.Xml; +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Managers; using ClassicUO.Game.Scenes; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System.Data.SqlClient; +using System; +using System.Xml; namespace ClassicUO.Game.UI.Gumps { @@ -112,7 +111,7 @@ public bool HideLabel _hideLabel = value; } } - public float Scale + public new float Scale { get => _scale; set diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index 5958e0fe6b..8bb6fd8b62 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -4470,7 +4470,6 @@ public void Click(Point pos) public void Drag(Point pos) { pos = new Point((pos.X - ScreenCoordinateX), pos.Y - ScreenCoordinateY); - int p = 0; if (SelectionStart == SelectionEnd) { @@ -4669,8 +4668,6 @@ public override void Update() if (Children.Count != 0) { - int w = 0, h = 0; - for (int i = 0; i < Children.Count; i++) { Control c = Children[i]; @@ -4684,10 +4681,7 @@ public override void Update() } c.Update(); - - } - } } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/MultipleToolTipGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/MultipleToolTipGump.cs index 342eb02dce..2872975a5e 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/MultipleToolTipGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/MultipleToolTipGump.cs @@ -60,27 +60,18 @@ private void RepositionTooltips() SSHeight = Height + 9; } - private int updateTickCount = 0; - public override bool Draw(UltimaBatcher2D batcher, int x, int y) { if (!hoverReference.MouseIsOver) Dispose(); - if(Height == 0) + if (Height == 0) { - foreach(Control c in Children) + foreach (Control c in Children) if (Height < c.Height) Height = c.Height; } - //updateTickCount++; - //if(updateTickCount > 5) - //{ - // RepositionTooltips(); - // updateTickCount = 0; - //} - int z_width = Width + 24; int z_height = Height + 8; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/NameOverHeadHandlerGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/NameOverHeadHandlerGump.cs index 696c4f60d9..b8f0ea2bbb 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/NameOverHeadHandlerGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/NameOverHeadHandlerGump.cs @@ -30,13 +30,12 @@ #endregion -using System; -using System.Collections.Generic; using ClassicUO.Configuration; using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Controls; -using ClassicUO.Resources; using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Gumps { @@ -48,7 +47,6 @@ internal class NameOverHeadHandlerGump : Gump private readonly List _overheadButtons = new List(); private Control _alpha; - private readonly Checkbox _keepOpenCheckbox; private StbTextBox searchBox; public NameOverHeadHandlerGump() : base(0, 0) @@ -148,10 +146,6 @@ public void UpdateCheckboxes() { button.IsChecked = NameOverHeadManager.LastActiveNameOverheadOption == button.Text; } - if (_keepOpenCheckbox != null) - { - _keepOpenCheckbox.IsChecked = NameOverHeadManager.IsPermaToggled; - } } public void RedrawOverheadOptions() diff --git a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs index 16cf745a27..ae7e74c261 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs @@ -30,19 +30,19 @@ #endregion -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using ClassicUO.Assets; +using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Controls; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Resources; using Microsoft.Xna.Framework; using System; -using ClassicUO.Configuration; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; namespace ClassicUO.Game.UI.Gumps { @@ -77,9 +77,8 @@ internal class SkillGumpAdvanced : Gump private Button resizeDrag; private Area BottomArea; private int dragStartH; - private Label total, real, value; + private Label real, value; private AlphaBlendControl background; - private Line bottomLine; private ScrollArea area; @@ -259,11 +258,11 @@ public SkillGumpAdvanced() : base(0, 0) - Add(BottomArea); + Add(BottomArea); Add(_sortOrderIndicator = new GumpPic(0, 0, 0x985, 0)); OnButtonClick((int)Buttons.SortName); - + Add(resizeDrag = new Button(0, 0x837, 0x838, 0x838)); resizeDrag.MouseDown += ResizeDrag_MouseDown; resizeDrag.MouseUp += ResizeDrag_MouseUp; @@ -311,7 +310,7 @@ private void BuildGump() entry.Clear(); entry.Dispose(); } - + _skillListEntries.Clear(); PropertyInfo pi = typeof(Skill).GetProperty(_sortField); @@ -413,7 +412,7 @@ private void BuildGump() } } - + _databox.WantUpdateSize = true; _databox.ReArrangeChildren(); @@ -499,7 +498,7 @@ private enum Buttons SortLock = 5, } } - + internal class SkillListEntry : Control { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/TipNoticeGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/TipNoticeGump.cs index f03eb116f8..0049eb0133 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/TipNoticeGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/TipNoticeGump.cs @@ -31,16 +31,13 @@ #endregion using ClassicUO.Game.UI.Controls; -using ClassicUO.Input; using ClassicUO.Network; -using ClassicUO.Utility.Collections; namespace ClassicUO.Game.UI.Gumps { internal class TipNoticeGump : Gump { private readonly ExpandableScroll _background; - private readonly Button _prev, _next; private readonly ScrollArea _scrollArea; private readonly StbTextBox _textBox; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs index 2bb64b9528..5d3df00264 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs @@ -773,7 +773,7 @@ out int out_y private int GetOffset(int x, int y, int centerX, int centerY) { - const int offset = 0; + const int OFFSET = 0; if (y > centerY) { @@ -787,15 +787,15 @@ private int GetOffset(int x, int y, int centerX, int centerY) if (x > centerX) { - return offset + 4; + return OFFSET + 4; } if (x >= -centerX) { - return offset; + return OFFSET; } - return offset + 8; + return OFFSET + 8; } internal void HandlePositionTarget() diff --git a/src/ClassicUO.Client/GameController.cs b/src/ClassicUO.Client/GameController.cs index 2494b3f68b..473544f086 100644 --- a/src/ClassicUO.Client/GameController.cs +++ b/src/ClassicUO.Client/GameController.cs @@ -189,7 +189,7 @@ protected override void LoadContent() Fonts.Initialize(GraphicsDevice); SolidColorTextureCache.Initialize(GraphicsDevice); PNGLoader.Instance.GraphicsDevice = GraphicsDevice; - PNGLoader.Instance.LoadResourceAssets(); + System.Threading.Tasks.Task loadResourceAssets = PNGLoader.Instance.LoadResourceAssets(); Animations = new Renderer.Animations.Animations(GraphicsDevice); Arts = new Renderer.Arts.Art(GraphicsDevice); @@ -209,6 +209,8 @@ protected override void LoadContent() using var ms = new MemoryStream(bytes); _background = Texture2D.FromStream(GraphicsDevice, ms); + loadResourceAssets.Wait(10000); + SetScene(new LoginScene()); SetWindowPositionBySettings(); } diff --git a/src/ClassicUO.Client/Network/PacketHandlers.cs b/src/ClassicUO.Client/Network/PacketHandlers.cs index 7f893591be..b15a7916a0 100644 --- a/src/ClassicUO.Client/Network/PacketHandlers.cs +++ b/src/ClassicUO.Client/Network/PacketHandlers.cs @@ -7333,7 +7333,7 @@ public static Vector3 ReverseLookup(int xLong, int yLat, int xMins, int yMins, b if (!ySouth) absLat = 360.0 - absLat; - int x, y, z; + int x, y; x = xCenter + (int)((absLong * xWidth) / 360); y = yCenter + (int)((absLat * yHeight) / 360); diff --git a/src/ClassicUO.Renderer/Camera.cs b/src/ClassicUO.Renderer/Camera.cs index dab60bff8a..1f938e4c58 100644 --- a/src/ClassicUO.Renderer/Camera.cs +++ b/src/ClassicUO.Renderer/Camera.cs @@ -109,7 +109,7 @@ public void Update(bool force, float timeDelta, Point mousePos) _updateMatrixes = true; } - _timeDelta= timeDelta; + _timeDelta = timeDelta; _mousePos = mousePos; UpdateMatrices(); @@ -138,8 +138,8 @@ private static void Transform(ref Point position, ref Matrix matrix, out Point r { float x = position.X * matrix.M11 + position.Y * matrix.M21 + matrix.M41; float y = position.X * matrix.M12 + position.Y * matrix.M22 + matrix.M42; - result.X = (int) x; - result.Y = (int) y; + result.X = (int)x; + result.Y = (int)y; } public Point MouseToWorldPosition() @@ -185,8 +185,8 @@ private void CalculateLerpZoom() { float zoom = 1f / Zoom; - const float FADE_TIME = 12.0f; - const float SMOOTHING_FACTOR = (1.0f / FADE_TIME) * 60.0f; + //const float FADE_TIME = 12.0f; + //const float SMOOTHING_FACTOR = (1.0f / FADE_TIME) * 60.0f; _lerpZoom = zoom; // MathHelper.Lerp(_lerpZoom, zoom, SMOOTHING_FACTOR * Time.Delta); } From b41d9ff280d23f35b954bd2a7fb189df1eed56e0 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 5 Feb 2024 14:07:20 -0700 Subject: [PATCH 13/93] Potential crash fix --- src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index 8bb6fd8b62..22a13b338b 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2821,7 +2821,10 @@ public Control GenConditionControl(int key, int width, bool createIfNotExists) _preview.IsSelected = true; _preview.MouseUp += (s, e) => { - CoolDownBarManager.AddCoolDownBar(TimeSpan.FromSeconds(int.Parse(_cooldown.Text)), _name.Text, _hueSelector.Hue, _replaceIfExists.IsChecked); + if (int.TryParse(_cooldown.Text, out int value)) + { + CoolDownBarManager.AddCoolDownBar(TimeSpan.FromSeconds(value), _name.Text, _hueSelector.Hue, _replaceIfExists.IsChecked); + } }; main.Add(_preview); From 472458485fd6e877f1831ac432799b73822cf79c Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 5 Feb 2024 14:20:56 -0700 Subject: [PATCH 14/93] Move child removals to after loop to avoid issues --- .../Game/UI/Controls/Control.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index 9fe737d9b9..90342cfa73 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -308,14 +308,15 @@ public virtual void Update() return; } + if (Children.Count != 0) { - //InitializeControls(); + List removalList = new List(); ; int w = 0, h = 0; for (int i = 0; i < Children.Count; i++) { - if (i < 0) + if (i < 0 || i >= Children.Count) { continue; } @@ -329,10 +330,7 @@ public virtual void Update() if (c.IsDisposed) { - OnChildRemoved(); - //Children.RemoveAt(i); - Children.Remove(c); - i--; + removalList.Add(c); continue; } @@ -355,6 +353,15 @@ public virtual void Update() } } + if (removalList.Count > 0) + { + foreach (Control c in removalList) + { + OnChildRemoved(); + Children.Remove(c); + } + } + if (WantUpdateSize && IsVisible) { if (w != Width) From 4707314898281d24e69f0bf454ddb0a3839c4b5d Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 5 Feb 2024 22:03:21 -0700 Subject: [PATCH 15/93] Fix for P tags in tooltips --- src/ClassicUO.Client/Game/UI/Controls/TextBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs index 8c3c747861..6a880685a0 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs @@ -280,6 +280,7 @@ public static string ConvertHtmlToFontStashSharpCommand(string text) finalString = finalString.Replace("", "").Replace("

", ""); finalString = finalString.Replace("", "").Replace("", ""); finalString = finalString.Replace("", "").Replace("", ""); + finalString = finalString.Replace("

", "").Replace("

", ""); return finalString; } From 5b99284878626044a26c4a114fd9dc6266325aaf Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 20:11:45 -0700 Subject: [PATCH 16/93] Fix file access from CUO --- src/ClassicUO.Client/Game/Managers/ContainerManager.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/ContainerManager.cs b/src/ClassicUO.Client/Game/Managers/ContainerManager.cs index c689ad639f..d56e512778 100644 --- a/src/ClassicUO.Client/Game/Managers/ContainerManager.cs +++ b/src/ClassicUO.Client/Game/Managers/ContainerManager.cs @@ -30,16 +30,14 @@ #endregion -using System.Collections.Generic; -using System.IO; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.GameObjects; using ClassicUO.Game.UI.Gumps; -using ClassicUO.Assets; -using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.IO; namespace ClassicUO.Game.Managers { @@ -257,7 +255,8 @@ public static void BuildContainerFile(bool force) { MakeDefault(); - using (StreamWriter writer = new StreamWriter(File.Create(path))) + using (var stream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) + using (var writer = new StreamWriter(stream)) { writer.WriteLine("# FORMAT"); From 69f117f975cba4041feaac93180a1d3081f50773 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 20:20:17 -0700 Subject: [PATCH 17/93] Change default spell config import url --- src/ClassicUO.Client/Configuration/Language.cs | 2 +- src/ClassicUO.Client/Game/UI/Gumps/InputRequest.cs | 4 ++-- src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index c9449ba1b6..2355590d83 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -497,7 +497,7 @@ public class TazUO public string TextFormat { get; set; } = "Text format"; public string EnableSpellIndicatorSystem { get; set; } = "Enable spell indicator system"; public string ImportFromUrl { get; set; } = "Import from url"; - public string InputRequestUrl { get; set; } = "Enter the url for the spell config. /c[red]This will override your current config."; + public string InputRequestUrl { get; set; } = "Enter the url for the spell config. \n/c[red]This will override your current config."; public string Download { get; set; } = "Download"; public string Cancel { get; set; } = "Cancel"; public string AttemptingToDownloadSpellConfig { get; set; } = "Attempting to download spell config.."; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/InputRequest.cs b/src/ClassicUO.Client/Game/UI/Gumps/InputRequest.cs index ccf939618d..405b404485 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/InputRequest.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/InputRequest.cs @@ -1,5 +1,4 @@ using ClassicUO.Assets; -using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Controls; using System; using static ClassicUO.Game.UI.Gumps.OptionsGump; @@ -8,7 +7,7 @@ namespace ClassicUO.Game.UI.Gumps { internal class InputRequest : Gump { - public InputRequest(string message, string buttonText, string button2Text, Action result) : base(0, 0) + public InputRequest(string message, string buttonText, string button2Text, Action result, string defaultInputValue = "") : base(0, 0) { Width = 400; Height = 0; @@ -31,6 +30,7 @@ public InputRequest(string message, string buttonText, string button2Text, Actio ) { X = 0, Y = _.Y + _.Height + 15 }; Height += input.Height; + input.SetText(defaultInputValue); Add(input); NiceButton button1, button2; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index 22a13b338b..c4630d1e19 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2361,7 +2361,7 @@ private void BuildTazUO() }); } } - }) + }, "https://gist.githubusercontent.com/bittiez/c70ddcb58fc59f74a0c4d2c5b4fc6478/raw/SpellVisualRange.json") { X = (Client.Game.Window.ClientBounds.Width >> 1) - 50, Y = (Client.Game.Window.ClientBounds.Height >> 1) - 50 }); } }; From 49dc1f50872991c61837fe6a844d968e58a97d0e Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 20:26:48 -0700 Subject: [PATCH 18/93] Fix for spell range manager cursor not working when highlight obj is enabled --- .../Game/GameObjects/Views/LandView.cs | 31 +++++++++---------- .../Game/GameObjects/Views/StaticView.cs | 31 +++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs b/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs index f1bf5f753e..63ac9fa33f 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs @@ -30,13 +30,13 @@ #endregion -using ClassicUO.Configuration; using ClassicUO.Assets; +using ClassicUO.Configuration; +using ClassicUO.Game.Managers; using ClassicUO.Renderer; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; -using ClassicUO.Game.Managers; namespace ClassicUO.Game.GameObjects { @@ -68,23 +68,22 @@ public override bool Draw(UltimaBatcher2D batcher, int posX, int posY, float dep { hue = Constants.DEAD_RANGE_COLOR; } - else + + if (SelectedObject.Object == this) { - if (SelectedObject.Object == this) - { - SpellVisualRangeManager.Instance.LastCursorTileLoc = new Vector2(X, Y); - } - if (SpellVisualRangeManager.Instance.IsTargetingAfterCasting()) - { - hue = SpellVisualRangeManager.Instance.ProcessHueForTile(hue, this); - } + SpellVisualRangeManager.Instance.LastCursorTileLoc = new Vector2(X, Y); + } + if (SpellVisualRangeManager.Instance.IsTargetingAfterCasting()) + { + hue = SpellVisualRangeManager.Instance.ProcessHueForTile(hue, this); + } - if (TileMarkerManager.Instance.IsTileMarked(X, Y, World.Map.Index, out var nhue)) - hue = nhue; + if (TileMarkerManager.Instance.IsTileMarked(X, Y, World.Map.Index, out var nhue)) + hue = nhue; + + if (ProfileManager.CurrentProfile.DisplayRadius && Distance == ProfileManager.CurrentProfile.DisplayRadiusDistance) + hue = ProfileManager.CurrentProfile.DisplayRadiusHue; - if (ProfileManager.CurrentProfile.DisplayRadius && Distance == ProfileManager.CurrentProfile.DisplayRadiusDistance) - hue = ProfileManager.CurrentProfile.DisplayRadiusHue; - } Vector3 hueVec; if (hue != 0) diff --git a/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs b/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs index 8563d19e83..789cd6bebc 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs @@ -30,14 +30,13 @@ #endregion +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; +using ClassicUO.Game.Managers; using ClassicUO.Game.Scenes; -using ClassicUO.IO; -using ClassicUO.Assets; using ClassicUO.Renderer; using Microsoft.Xna.Framework; -using ClassicUO.Game.Managers; namespace ClassicUO.Game.GameObjects { @@ -90,24 +89,22 @@ public override bool Draw(UltimaBatcher2D batcher, int posX, int posY, float dep hue = Constants.DEAD_RANGE_COLOR; partial = false; } - else + + if (SelectedObject.Object == this) { - if (SelectedObject.Object == this) - { - SpellVisualRangeManager.Instance.LastCursorTileLoc = new Vector2(X, Y); - } + SpellVisualRangeManager.Instance.LastCursorTileLoc = new Vector2(X, Y); + } - if (SpellVisualRangeManager.Instance.IsTargetingAfterCasting()) - { - hue = SpellVisualRangeManager.Instance.ProcessHueForTile(hue, this); - } + if (SpellVisualRangeManager.Instance.IsTargetingAfterCasting()) + { + hue = SpellVisualRangeManager.Instance.ProcessHueForTile(hue, this); + } - if (TileMarkerManager.Instance.IsTileMarked(X, Y, World.Map.Index, out var nhue)) - hue = nhue; + if (TileMarkerManager.Instance.IsTileMarked(X, Y, World.Map.Index, out var nhue)) + hue = nhue; - if (ProfileManager.CurrentProfile.DisplayRadius && Distance == ProfileManager.CurrentProfile.DisplayRadiusDistance && System.Math.Abs(Z - World.Player.Z) < 11) - hue = ProfileManager.CurrentProfile.DisplayRadiusHue; - } + if (ProfileManager.CurrentProfile.DisplayRadius && Distance == ProfileManager.CurrentProfile.DisplayRadiusDistance && System.Math.Abs(Z - World.Player.Z) < 11) + hue = ProfileManager.CurrentProfile.DisplayRadiusHue; Vector3 hueVec = ShaderHueTranslator.GetHueVector(hue, partial, AlphaHue / 255f); From 6236b4ffb1ea94aa996090ca6c7ff047c0685c3c Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 20:41:49 -0700 Subject: [PATCH 19/93] Settings fix from CUO --- .../Configuration/Settings.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Settings.cs b/src/ClassicUO.Client/Configuration/Settings.cs index 151aae0e59..1f3fcde267 100644 --- a/src/ClassicUO.Client/Configuration/Settings.cs +++ b/src/ClassicUO.Client/Configuration/Settings.cs @@ -30,17 +30,26 @@ #endregion +using ClassicUO.Configuration.Json; +using Microsoft.Xna.Framework; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; -using ClassicUO.Configuration.Json; -using Microsoft.Xna.Framework; namespace ClassicUO.Configuration { [JsonSourceGenerationOptions(WriteIndented = true, GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(Settings), GenerationMode = JsonSourceGenerationMode.Metadata)] - sealed partial class SettingsJsonContext : JsonSerializerContext { } + sealed partial class SettingsJsonContext : JsonSerializerContext + { + // horrible fix: https://github.com/ClassicUO/ClassicUO/issues/1663 + public static SettingsJsonContext RealDefault { get; } = new SettingsJsonContext( + new JsonSerializerOptions() + { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + } internal sealed class Settings @@ -64,7 +73,7 @@ internal sealed class Settings [JsonPropertyName("profilespath")] public string ProfilesPath { get; set; } = string.Empty; [JsonPropertyName("clientversion")] public string ClientVersion { get; set; } = string.Empty; - + [JsonPropertyName("lang")] public string Language { get; set; } = ""; [JsonPropertyName("lastservernum")] public ushort LastServerNum { get; set; } = 1; @@ -73,8 +82,8 @@ internal sealed class Settings [JsonPropertyName("fps")] public int FPS { get; set; } = 60; - [JsonConverter(typeof(NullablePoint2Converter))] [JsonPropertyName("window_position")] public Point? WindowPosition { get; set; } - [JsonConverter(typeof(NullablePoint2Converter))] [JsonPropertyName("window_size")] public Point? WindowSize { get; set; } + [JsonConverter(typeof(NullablePoint2Converter))][JsonPropertyName("window_position")] public Point? WindowPosition { get; set; } + [JsonConverter(typeof(NullablePoint2Converter))][JsonPropertyName("window_size")] public Point? WindowSize { get; set; } [JsonPropertyName("is_win_maximized")] public bool IsWindowMaximized { get; set; } = true; @@ -127,7 +136,7 @@ public void Save() { // Make a copy of the settings object that we will use in the saving process var json = JsonSerializer.Serialize(this, typeof(Settings), SettingsJsonContext.Default); - var settingsToSave = JsonSerializer.Deserialize(json, typeof(Settings), SettingsJsonContext.Default) as Settings; + var settingsToSave = JsonSerializer.Deserialize(json, typeof(Settings), SettingsJsonContext.RealDefault) as Settings; // Make sure we don't save username and password if `saveaccount` flag is not set // NOTE: Even if we pass username and password via command-line arguments they won't be saved @@ -141,7 +150,7 @@ public void Save() // NOTE: We can do any other settings clean-ups here before we save them - ConfigurationResolver.Save(settingsToSave, GetSettingsFilepath(), SettingsJsonContext.Default); + ConfigurationResolver.Save(settingsToSave, GetSettingsFilepath(), SettingsJsonContext.RealDefault); } } } \ No newline at end of file From c07f54864779fe0746b6b16557eaa1e84f6943bf Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 20:47:40 -0700 Subject: [PATCH 20/93] Potential bug fix for buff gump, some minor cleanup and updated version history --- .../Game/GameObjects/Views/ItemView.cs | 6 +++--- .../Game/UI/Gumps/ImprovedBuffGump.cs | 15 +++++++++------ .../Game/UI/Gumps/VersionHistory.cs | 4 ++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs b/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs index d9cf2c1064..3a1dc3eb8e 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs @@ -30,17 +30,17 @@ #endregion -using System; -using System.Collections.Generic; +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.Managers; using ClassicUO.Game.Scenes; using ClassicUO.IO; -using ClassicUO.Assets; using ClassicUO.Renderer; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; using MathHelper = ClassicUO.Utility.MathHelper; namespace ClassicUO.Game.GameObjects diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ImprovedBuffGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ImprovedBuffGump.cs index 0416dddc8a..3d59160d36 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ImprovedBuffGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ImprovedBuffGump.cs @@ -32,10 +32,13 @@ public ImprovedBuffGump() : base(0, 0) public void AddBuff(BuffIcon icon) { - CoolDownBar coolDownBar = new CoolDownBar(TimeSpan.FromMilliseconds(icon.Timer - Time.Ticks), icon.Title.Replace("
", " "), ProfileManager.CurrentProfile.ImprovedBuffBarHue, 0, 0, icon.Graphic, icon.Type, true); - coolDownBar.SetTooltip(icon.Text); - BuffBarManager.AddCoolDownBar(coolDownBar, _direction, _box); - _box.Add(coolDownBar); + if (icon != null) + { + CoolDownBar coolDownBar = new CoolDownBar(TimeSpan.FromMilliseconds(icon.Timer - Time.Ticks), icon.Title.Replace("
", " "), ProfileManager.CurrentProfile.ImprovedBuffBarHue, 0, 0, icon.Graphic, icon.Type, true); + coolDownBar.SetTooltip(icon.Text); + BuffBarManager.AddCoolDownBar(coolDownBar, _direction, _box); + _box.Add(coolDownBar); + } } public void RemoveBuff(BuffIconType graphic) @@ -105,12 +108,12 @@ private void BuildGump() _box = new DataBox(0, 0, Width, HEIGHT); - + Add(_background); Add(_button); Add(_box); - + BuffBarManager.Clear(); if (World.Player != null) { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index dcf521b758..50e000b959 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -7,6 +7,10 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { + "/c[white][3.21.0]/cd\n" + + "- A few bug fixes\n" + + "- A few fixes from CUO", + "/c[white][3.20.0]/cd\n" + "- Being frozen wont cancel auto follow\n" + "- Fix from CUO for buffs\n" + From 6074dc555957f95d71b3b545e3059c6128ab056d Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 20:53:28 -0700 Subject: [PATCH 21/93] Move some strings from version history gump to language file --- src/ClassicUO.Client/Configuration/Language.cs | 5 +++++ src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 2355590d83..f8d00c58fe 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -10,6 +10,11 @@ public class Language public ErrorsLanguage ErrorsLanguage { get; set; } = new ErrorsLanguage(); public MapLanguage MapLanguage { get; set; } = new MapLanguage(); + public string TazuoVersionHistory { get; set; } = "TazUO Version History"; + public string CurrentVersion { get; set; } = "Current Version: "; + public string TazUOWiki { get; set; } = "TazUO Wiki"; + public string TazUODiscord { get; set; } = "TazUO Discord"; + [JsonIgnore] public static Language Instance { get; private set; } = new Language(); diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index 50e000b959..c4d0a4a877 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -1,4 +1,5 @@ using ClassicUO.Assets; +using ClassicUO.Configuration; using ClassicUO.Game.UI.Controls; using Microsoft.Xna.Framework; @@ -216,8 +217,8 @@ public VersionHistory() : base(0, 0) Add(bc); TextBox _; - Add(_ = new TextBox("TazUO Version History", TrueTypeLoader.EMBEDDED_FONT, 30, Width, Color.White, FontStashSharp.RichText.TextHorizontalAlignment.Center, false) { Y = 10 }); - Add(_ = new TextBox("Current Version: " + CUOEnviroment.Version.ToString(), TrueTypeLoader.EMBEDDED_FONT, 20, Width, Color.Orange, FontStashSharp.RichText.TextHorizontalAlignment.Center, false) { Y = _.Y + _.Height + 5 }); + Add(_ = new TextBox(Language.Instance.TazuoVersionHistory, TrueTypeLoader.EMBEDDED_FONT, 30, Width, Color.White, FontStashSharp.RichText.TextHorizontalAlignment.Center, false) { Y = 10 }); + Add(_ = new TextBox(Language.Instance.CurrentVersion + CUOEnviroment.Version.ToString(), TrueTypeLoader.EMBEDDED_FONT, 20, Width, Color.Orange, FontStashSharp.RichText.TextHorizontalAlignment.Center, false) { Y = _.Y + _.Height + 5 }); ScrollArea scroll = new ScrollArea(10, _.Y + _.Height, Width - 20, Height - (_.Y + _.Height) - 20, true) { ScrollbarBehaviour = ScrollbarBehaviour.ShowAlways }; @@ -234,14 +235,14 @@ public VersionHistory() : base(0, 0) HitBox _hit; - Add(_ = new TextBox("TazUO Wiki", TrueTypeLoader.EMBEDDED_FONT, 15, 200, Color.Orange, strokeEffect: false) { X = 25, Y = Height - 20 }); + Add(_ = new TextBox(Language.Instance.TazUOWiki, TrueTypeLoader.EMBEDDED_FONT, 15, 200, Color.Orange, strokeEffect: false) { X = 25, Y = Height - 20 }); Add(_hit = new HitBox(_.X, _.Y, _.MeasuredSize.X, _.MeasuredSize.Y)); _hit.MouseUp += (s, e) => { Utility.Platforms.PlatformHelper.LaunchBrowser("https://github.com/bittiez/ClassicUO/wiki"); }; - Add(_ = new TextBox("TazUO Discord", TrueTypeLoader.EMBEDDED_FONT, 15, 200, Color.Orange, strokeEffect: false) { X = 280, Y = Height - 20 }); + Add(_ = new TextBox(Language.Instance.TazUOWiki, TrueTypeLoader.EMBEDDED_FONT, 15, 200, Color.Orange, strokeEffect: false) { X = 280, Y = Height - 20 }); Add(_hit = new HitBox(_.X, _.Y, _.MeasuredSize.X, _.MeasuredSize.Y)); _hit.MouseUp += (s, e) => { From 20e53b5493fc48c161210c8481283398bee429ec Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 21:06:13 -0700 Subject: [PATCH 22/93] Don't "dirty" the text when changing hue unless hue is actually different from current --- src/ClassicUO.Client/Game/UI/Controls/TextBox.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs index 6a880685a0..0acd51de47 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs @@ -199,8 +199,12 @@ public int Hue get => (int)_color.PackedValue; set { - _color.PackedValue = HuesLoader.Instance.GetHueColorRgba8888(31, (ushort)value); - _dirty = true; + var newVal = HuesLoader.Instance.GetHueColorRgba8888(31, (ushort)value); + if (_color.PackedValue != newVal) + { + _color.PackedValue = newVal; + _dirty = true; + } } } @@ -244,7 +248,6 @@ public float Size /// Set to null to ignore width, taking as much width as needed. public void UpdateText(string text, int? width = null) { - if (width != null && width > 0) { _rtl = new RichTextLayout From 5349bb61ffb071aee06e999d8750dbbcfe3e7c37 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 21:40:18 -0700 Subject: [PATCH 23/93] Converted nameplates to use TTF fonts --- .../Configuration/Language.cs | 1 + src/ClassicUO.Client/Configuration/Profile.cs | 3 + .../Game/UI/Gumps/ModernOptionsGump.cs | 11 ++ .../Game/UI/Gumps/NameOverheadGump.cs | 134 ++++++------------ .../Game/UI/Gumps/VersionHistory.cs | 3 +- 5 files changed, 64 insertions(+), 88 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index f8d00c58fe..5674e05a4d 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -534,6 +534,7 @@ public class TazUO public string TooltipFont { get; set; } = "Tooltip font"; public string OverheadFont { get; set; } = "Overhead font"; public string JournalFont { get; set; } = "Journal font"; + public string NameplateFont { get; set; } = "Nameplate font"; #endregion #region Controller diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index 12bdb9c360..99220d8bf2 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -479,6 +479,9 @@ public int CoolDownConditionCount public int OverheadChatFontSize { get; set; } = 20; public int OverheadChatWidth { get; set; } = 200; + public string NamePlateFont { get; set; } = "avadonian"; + public int NamePlateFontSize { get; set; } = 20; + public string DefaultTTFFont { get; set; } = "Roboto-Regular"; public int TextBorderSize { get; set; } = 1; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index c4630d1e19..a9520e4242 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2491,6 +2491,17 @@ private void BuildTazUO() }), true, page); content.RemoveIndent(); content.BlankLine(); + content.AddToRight(GenerateFontSelector(lang.GetTazUO.NameplateFont, ProfileManager.CurrentProfile.NamePlateFont, (i, s) => + { + ProfileManager.CurrentProfile.NamePlateFont = s; + }), true, page); + content.Indent(); + content.AddToRight(new SliderWithLabel(lang.GetTazUO.SharedSize, 0, Theme.SLIDER_WIDTH, 5, 40, profile.NamePlateFontSize, (i) => + { + profile.NamePlateFontSize = i; + }), true, page); + content.RemoveIndent(); + content.BlankLine(); #endregion #region Controller settings diff --git a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs index 654e7dd4bd..7b2efa8dd7 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs @@ -51,9 +51,12 @@ internal class NameOverheadGump : Gump private Point _lockedPosition, _lastLeftMousePositionDown; private bool _positionLocked, - _leftMouseIsDown; - private readonly RenderedText _renderedText; + _leftMouseIsDown, + _isLastTarget, + _needsNameUpdate; + private TextBox _text; private Texture2D _borderColor = SolidColorTextureCache.GetTexture(Color.Black); + private Vector2 _textDrawOffset = Vector2.Zero; public NameOverheadGump(uint serial) : base(serial, 0) { @@ -70,21 +73,12 @@ public NameOverheadGump(uint serial) : base(serial, 0) return; } - _renderedText = RenderedText.Create( - string.Empty, - entity is Mobile m ? Notoriety.GetHue(m.NotorietyFlag) : (ushort)0x0481, - 0xFF, - true, - FontStyle.BlackBorder, - TEXT_ALIGN_TYPE.TS_CENTER, - 100, - 30, - true - ); + _text = new TextBox(string.Empty, ProfileManager.CurrentProfile.NamePlateFont, ProfileManager.CurrentProfile.NamePlateFontSize, 100, entity is Mobile m ? Notoriety.GetHue(m.NotorietyFlag) : (ushort)0x0481, FontStashSharp.RichText.TextHorizontalAlignment.Center); SetTooltip(entity); BuildGump(); + SetName(); } public bool SetName() @@ -100,6 +94,7 @@ public bool SetName() { if (!World.OPL.TryGetNameAndData(item, out string t, out _)) { + _needsNameUpdate = true; if (!item.IsCorpse && item.Amount > 1) { t = item.Amount.ToString() + ' '; @@ -119,40 +114,22 @@ public bool SetName() ); } } - - if (string.IsNullOrEmpty(t)) + else { - return false; + _needsNameUpdate = false; } - FontsLoader.Instance.SetUseHTML(true); - FontsLoader.Instance.RecalculateWidthByInfo = true; - - int width = FontsLoader.Instance.GetWidthUnicode(_renderedText.Font, t); - - if (width > Constants.OBJECT_HANDLES_GUMP_WIDTH) + if (string.IsNullOrEmpty(t)) { - t = FontsLoader.Instance.GetTextByWidthUnicode( - _renderedText.Font, - t.AsSpan(), - Constants.OBJECT_HANDLES_GUMP_WIDTH, - true, - TEXT_ALIGN_TYPE.TS_CENTER, - (ushort)FontStyle.BlackBorder - ); - - width = Constants.OBJECT_HANDLES_GUMP_WIDTH; + return false; } - _renderedText.MaxWidth = width; - _renderedText.Text = t; - - FontsLoader.Instance.RecalculateWidthByInfo = false; - FontsLoader.Instance.SetUseHTML(false); - - Width = _background.Width = Math.Max(60, _renderedText.Width) + 4; - Height = _background.Height = Constants.OBJECT_HANDLES_GUMP_HEIGHT + 4; + _text.UpdateText(t); + Width = _background.Width = Math.Max(60, _text.Width) + 4; + Height = _background.Height = Math.Max(Constants.OBJECT_HANDLES_GUMP_HEIGHT, _text.Height) + 4; + _textDrawOffset.X = (Width - _text.Width - 4) >> 1; + _textDrawOffset.Y = (Height - _text.Height) >> 1; WantUpdateSize = false; return true; @@ -162,29 +139,12 @@ public bool SetName() { string t = entity.Name; - int width = FontsLoader.Instance.GetWidthUnicode(_renderedText.Font, t); - - if (width > Constants.OBJECT_HANDLES_GUMP_WIDTH) - { - t = FontsLoader.Instance.GetTextByWidthUnicode( - _renderedText.Font, - t.AsSpan(), - Constants.OBJECT_HANDLES_GUMP_WIDTH, - true, - TEXT_ALIGN_TYPE.TS_CENTER, - (ushort)FontStyle.BlackBorder - ); - - width = Constants.OBJECT_HANDLES_GUMP_WIDTH; - } - - _renderedText.MaxWidth = width; - - _renderedText.Text = t; - - Width = _background.Width = Math.Max(60, _renderedText.Width) + 4; - Height = _background.Height = Constants.OBJECT_HANDLES_GUMP_HEIGHT + 4; + _text.UpdateText(t); + Width = _background.Width = Math.Max(60, _text.Width) + 4; + Height = _background.Height = Math.Max(Constants.OBJECT_HANDLES_GUMP_HEIGHT, _text.Height) + 4; + _textDrawOffset.X = (Width - _text.Width - 4) >> 1; + _textDrawOffset.Y = (Height - _text.Height) >> 1; WantUpdateSize = false; return true; @@ -557,25 +517,37 @@ public override void Update() { if (entity == TargetManager.LastTargetInfo.Serial) { - _borderColor = SolidColorTextureCache.GetTexture(Color.Red); - _background.Hue = _renderedText.Hue = entity is Mobile m - ? Notoriety.GetHue(m.NotorietyFlag) - : (ushort)0x0481; + if (!_isLastTarget) //Only set this if it was not already last target + { + _borderColor = SolidColorTextureCache.GetTexture(Color.Red); + _background.Hue = (ushort)(_text.Hue = entity is Mobile m + ? Notoriety.GetHue(m.NotorietyFlag) + : (ushort)0x0481); + _isLastTarget = true; + } } else { - _borderColor = SolidColorTextureCache.GetTexture(Color.Black); - _background.Hue = _renderedText.Hue = entity is Mobile m - ? Notoriety.GetHue(m.NotorietyFlag) - : (ushort)0x0481; - _background.Alpha = ProfileManager.CurrentProfile.NamePlateOpacity / 100f; + if (_isLastTarget)//If we make it here, it is no longer the last target so we update colors and set this to false. + { + _borderColor = SolidColorTextureCache.GetTexture(Color.Black); + _background.Hue = (ushort)(_text.Hue = entity is Mobile m + ? Notoriety.GetHue(m.NotorietyFlag) + : (ushort)0x0481); + _isLastTarget = false; + } + } + + if (_needsNameUpdate) + { + SetName(); } } } public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - if (IsDisposed || !SetName()) + if (IsDisposed) { return false; } @@ -720,7 +692,7 @@ out int height Point p = Client.Game.Scene.Camera.WorldToScreen(new Point(x, y)); x = p.X - (Width >> 1); - y = p.Y - (Height >> 1); + y = p.Y - (Height);// >> 1); var camera = Client.Game.Scene.Camera; x += camera.Bounds.X; @@ -764,24 +736,12 @@ out int height ); } - int renderedTextOffset = Math.Max(0, Width - _renderedText.Width - 4) >> 1; - - return _renderedText.Draw( - batcher, - Width, - Height, - x + 2 + renderedTextOffset, - y + 2, - Width, - Height, - 0, - 0 - ); + return _text.Draw(batcher, (int)(x + 2 + _textDrawOffset.X), (int)(y + 2 + _textDrawOffset.Y)); } public override void Dispose() { - _renderedText?.Destroy(); + _text.Dispose(); base.Dispose(); } } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index c4d0a4a877..c6a8d55e5f 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -10,7 +10,8 @@ internal class VersionHistory : Gump private static string[] updateTexts = { "/c[white][3.21.0]/cd\n" + "- A few bug fixes\n" + - "- A few fixes from CUO", + "- A few fixes from CUO\n" + + "- Converted nameplates to use TTF fonts", "/c[white][3.20.0]/cd\n" + "- Being frozen wont cancel auto follow\n" + From 24e1ff6ae5b87ef4f211aeaf5a4f3c99aa376d6c Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 22:09:30 -0700 Subject: [PATCH 24/93] Fix for context menus showing out of the screen --- src/ClassicUO.Client/Game/UI/Controls/ContextMenuControl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ClassicUO.Client/Game/UI/Controls/ContextMenuControl.cs b/src/ClassicUO.Client/Game/UI/Controls/ContextMenuControl.cs index 6997f77c96..c2ceba145f 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ContextMenuControl.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ContextMenuControl.cs @@ -171,6 +171,11 @@ public ContextMenuShowMenu(List list) : base(0, 0) Y = Client.Game.Window.ClientBounds.Height - _background.Height; } + if (Y < Client.Game.Window.ClientBounds.Y) + { + Y = 0; + } + foreach (ContextMenuItem mitem in FindControls()) { if (mitem.Width < _background.Width) From b49b83223aba2c5335750adc066bc7be1156679d Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 6 Feb 2024 22:15:50 -0700 Subject: [PATCH 25/93] Added an available client commands gump --- .../Configuration/Language.cs | 7 +++ .../Game/Managers/CommandManager.cs | 2 + .../Game/UI/Gumps/CommandsGump.cs | 57 +++++++++++++++++++ .../Game/UI/Gumps/TopBarGump.cs | 11 ++-- .../Game/UI/Gumps/VersionHistory.cs | 3 +- 5 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/ClassicUO.Client/Game/UI/Gumps/CommandsGump.cs diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 5674e05a4d..4f81cc2fa7 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -9,11 +9,13 @@ public class Language public ModernOptionsGumpLanguage GetModernOptionsGumpLanguage { get; set; } = new ModernOptionsGumpLanguage(); public ErrorsLanguage ErrorsLanguage { get; set; } = new ErrorsLanguage(); public MapLanguage MapLanguage { get; set; } = new MapLanguage(); + public TopBarGumpLanguage TopBarGump { get; set; } = new TopBarGumpLanguage(); public string TazuoVersionHistory { get; set; } = "TazUO Version History"; public string CurrentVersion { get; set; } = "Current Version: "; public string TazUOWiki { get; set; } = "TazUO Wiki"; public string TazUODiscord { get; set; } = "TazUO Discord"; + public string CommandGump { get; set; } = "Available Client Commands"; [JsonIgnore] public static Language Instance { get; private set; } = new Language(); @@ -580,4 +582,9 @@ public class MapLanguage public string Follow { get; set; } = "Follow"; public string Yourself { get; set; } = "Yourself"; } + + public class TopBarGumpLanguage + { + public string CommandsEntry { get; set; } = "Client Commands"; + } } diff --git a/src/ClassicUO.Client/Game/Managers/CommandManager.cs b/src/ClassicUO.Client/Game/Managers/CommandManager.cs index a5b39c2481..882c68e14b 100644 --- a/src/ClassicUO.Client/Game/Managers/CommandManager.cs +++ b/src/ClassicUO.Client/Game/Managers/CommandManager.cs @@ -47,6 +47,8 @@ public static class CommandManager { private static readonly Dictionary> _commands = new Dictionary>(); + public static Dictionary> Commands { get { return _commands; } } + public static void Initialize() { Register diff --git a/src/ClassicUO.Client/Game/UI/Gumps/CommandsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/CommandsGump.cs new file mode 100644 index 0000000000..929a035515 --- /dev/null +++ b/src/ClassicUO.Client/Game/UI/Gumps/CommandsGump.cs @@ -0,0 +1,57 @@ +using ClassicUO.Assets; +using ClassicUO.Configuration; +using ClassicUO.Game.Managers; +using ClassicUO.Game.UI.Controls; +using Microsoft.Xna.Framework; + +namespace ClassicUO.Game.UI.Gumps +{ + public class CommandsGump : Gump + { + public CommandsGump() : base(0, 0) + { + X = 300; + Y = 200; + Width = 400; + Height = 500; + CanCloseWithRightClick = true; + CanMove = true; + + BorderControl bc = new BorderControl(0, 0, Width, Height, 36); + bc.T_Left = 39925; + bc.H_Border = 39926; + bc.T_Right = 39927; + bc.V_Border = 39928; + bc.V_Right_Border = 39930; + bc.B_Left = 39931; + bc.B_Right = 39933; + bc.H_Bottom_Border = 39932; + + Add(new GumpPicTiled(39929) { X = bc.BorderSize, Y = bc.BorderSize, Width = Width - (bc.BorderSize * 2), Height = Height - (bc.BorderSize * 2) }); + + Add(bc); + + TextBox t; + Add(t = new TextBox(Language.Instance.CommandGump, TrueTypeLoader.EMBEDDED_FONT, 28, Width, Color.Gold, FontStashSharp.RichText.TextHorizontalAlignment.Center) { Y = 5 }); + + ScrollArea scroll = new ScrollArea(10, 10 + t.Height, Width - 20, Height - t.Height - 40, true) { ScrollbarBehaviour = ScrollbarBehaviour.ShowAlways }; + + Add(new AlphaBlendControl(0.45f) { Width = scroll.Width, Height = scroll.Height, X = scroll.X, Y = scroll.Y }); + + GenerateEntries(scroll); + + Add(scroll); + } + + private void GenerateEntries(ScrollArea scroll) + { + int y = 0; + foreach (var command in CommandManager.Commands) + { + TextBox t = new TextBox(command.Key, TrueTypeLoader.EMBEDDED_FONT, 18, scroll.Width, Color.White) { Y = y, AcceptMouseInput = false }; + scroll.Add(t); + y += t.Height + 10; + } + } + } +} diff --git a/src/ClassicUO.Client/Game/UI/Gumps/TopBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/TopBarGump.cs index 3120cfcae3..c71e747d53 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/TopBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/TopBarGump.cs @@ -30,20 +30,17 @@ #endregion -using System.Collections.Generic; +using ClassicUO.Assets; using ClassicUO.Configuration; -using ClassicUO.Game.Data; -using ClassicUO.Game.GameObjects; using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Controls; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Network; -using ClassicUO.Renderer; using ClassicUO.Resources; using ClassicUO.Utility; using ClassicUO.Utility.Logging; using Microsoft.Xna.Framework; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Gumps { @@ -210,6 +207,10 @@ private TopBarGump() : base(0, 0) ); moreMenu.ContextMenu = new ContextMenuControl(); moreMenu.MouseUp += (s, e) => { moreMenu.ContextMenu?.Show(); }; + moreMenu.ContextMenu.Add(new ContextMenuItemEntry(Language.Instance.TopBarGump.CommandsEntry, () => + { + UIManager.Add(new CommandsGump()); + })); moreMenu.ContextMenu.Add(new ContextMenuItemEntry(cliloc.GetString(1079449, ResGumps.Info), () => { if (TargetManager.IsTargeting) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index c6a8d55e5f..cde6ffdae7 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -11,7 +11,8 @@ internal class VersionHistory : Gump "/c[white][3.21.0]/cd\n" + "- A few bug fixes\n" + "- A few fixes from CUO\n" + - "- Converted nameplates to use TTF fonts", + "- Converted nameplates to use TTF fonts\n" + + "- Added an available client commands gump", "/c[white][3.20.0]/cd\n" + "- Being frozen wont cancel auto follow\n" + From 6856d1dc55ff812bdd995b5a5c4c2ca0f30d3589 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 7 Feb 2024 00:59:17 -0700 Subject: [PATCH 26/93] Progress on fixing scroll bars in modern options gump. Not fixed yet. --- .../Game/UI/Gumps/ModernOptionsGump.cs | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index a9520e4242..cbd4b14919 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -71,10 +71,10 @@ public ModernOptionsGump() : base(0, 0) Add(new ColorBox(Width, 40, Theme.SEARCH_BACKGROUND) { AcceptMouseInput = true, CanMove = true, Alpha = 0.85f }); - Add(new TextBox(lang.OptionsTitle, Theme.FONT, 30, null, Color.White, strokeEffect: false) { X = 10, Y = 7 }); + Add(new TextBox(lang.OptionsTitle, Theme.FONT, 30, null, Color.White, strokeEffect: false) { X = 10, Y = 7, AcceptMouseInput = false }); Control c; - Add(c = new TextBox(lang.Search, Theme.FONT, 30, null, Color.White, strokeEffect: false) { Y = 7 }); + Add(c = new TextBox(lang.Search, Theme.FONT, 30, null, Color.White, strokeEffect: false) { Y = 7, AcceptMouseInput = false }); InputField search; Add(search = new InputField(400, 30) { X = Width - 405, Y = 5 }); @@ -83,6 +83,9 @@ public ModernOptionsGump() : base(0, 0) c.X = search.X - c.Width - 5; Add(mainContent = new LeftSideMenuRightSideContent(Width, Height - 40, (int)(Width * 0.23)) { Y = 40 }); + mainContent.RightArea.ToggleScrollBarVisibility(false); + mainContent.RightArea.GetScrollBar.Dispose(); + mainContent.RightArea.GetScrollBar = null; ModernButton b; mainContent.AddToLeft(b = CategoryButton(lang.ButtonGeneral, (int)PAGE.General, mainContent.LeftWidth)); @@ -2391,7 +2394,7 @@ private void BuildTazUO() profile.UseLandTextures = b; }), true, page); content.BlankLine(); - content.AddToRight(new InputFieldWithLabel(lang.GetTazUO.SOSGumpID, Theme.INPUT_WIDTH, profile.SOSGumpID.ToString(), true, (s, e) => { if (uint.TryParse(((InputField.StbTextBox)s).Text, out uint id)) { profile.SOSGumpID = id; } })); + content.AddToRight(new InputFieldWithLabel(lang.GetTazUO.SOSGumpID, Theme.INPUT_WIDTH, profile.SOSGumpID.ToString(), true, (s, e) => { if (uint.TryParse(((InputField.StbTextBox)s).Text, out uint id)) { profile.SOSGumpID = id; } }), true, page); #endregion #region Tooltips @@ -4988,7 +4991,9 @@ public void OnSearchMatch() private class ScrollArea : Control { - private readonly ScrollBar _scrollBar; + private ScrollBar _scrollBar; + + public ScrollBar GetScrollBar { get { return _scrollBar; } set { _scrollBar = value; } } public ScrollArea ( @@ -5023,7 +5028,13 @@ public ScrollArea public int ScrollMinValue => _scrollBar.MinValue; public int ScrollMaxValue => _scrollBar.MaxValue; - public Rectangle ScissorRectangle; + public void ToggleScrollBarVisibility(bool visible = true) + { + if (_scrollBar != null) + { + _scrollBar.IsVisible = visible; + } + } public override void Update() { @@ -5046,11 +5057,13 @@ public void Scroll(bool isup) public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - _scrollBar.Draw(batcher, x + _scrollBar.X, y + _scrollBar.Y); + _scrollBar?.Draw(batcher, x + _scrollBar.X, y + _scrollBar.Y); + + int sbar = _scrollBar == null ? 0 : _scrollBar.Width; - if (batcher.ClipBegin(x + ScissorRectangle.X, y + ScissorRectangle.Y, Width - _scrollBar.Width + ScissorRectangle.Width, Height + ScissorRectangle.Height)) + if (batcher.ClipBegin(x, y, Width - sbar, Height)) { - for (int i = 1; i < Children.Count; i++) + for (int i = 0; i < Children.Count; i++) { Control child = Children[i]; @@ -5059,7 +5072,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) continue; } - int finalY = y + child.Y - _scrollBar.Value + ScissorRectangle.Y; + int finalY = y + child.Y - (_scrollBar == null ? 0 : _scrollBar.Value); child.Draw(batcher, x + child.X, finalY); } @@ -5105,6 +5118,11 @@ public override void Clear() private void CalculateScrollBarMaxValue() { + if (_scrollBar == null) + { + return; + } + _scrollBar.Height = ScrollMaxHeight >= 0 ? ScrollMaxHeight : Height; bool maxValue = _scrollBar.Value == _scrollBar.MaxValue && _scrollBar.MaxValue != 0; @@ -5129,7 +5147,7 @@ private void CalculateScrollBarMaxValue() } int height = Math.Abs(startY) + Math.Abs(endY) - _scrollBar.Height; - height = Math.Max(0, height - (-ScissorRectangle.Y + ScissorRectangle.Height)); + height = Math.Max(0, height); if (height > 0) { @@ -5149,11 +5167,11 @@ private void CalculateScrollBarMaxValue() for (int i = 1; i < Children.Count; i++) { - Children[i].UpdateOffset(0, -_scrollBar.Value + ScissorRectangle.Y); + Children[i].UpdateOffset(0, -_scrollBar.Value); } } - private class ScrollBar : ScrollBarBase + public class ScrollBar : ScrollBarBase { private Rectangle _rectSlider, _emptySpace; @@ -5185,7 +5203,7 @@ public ScrollBar(int x, int y, int height) public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - if (Height <= 0 || !IsVisible) + if (Height <= 0 || !IsVisible || IsDisposed) { return false; } @@ -5208,7 +5226,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) ); } - return base.Draw(batcher, x, y); + return true;// base.Draw(batcher, x, y); } protected override int GetScrollableArea() From a97013f82ce953ab3bd11b909a8a5336d9126278 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 7 Feb 2024 01:05:08 -0700 Subject: [PATCH 27/93] Cleanup textbox a bit --- .../Game/UI/Controls/TextBox.cs | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs index 0acd51de47..94dca72e49 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs @@ -71,36 +71,8 @@ public TextBox bool supportsCommands = true, bool ignoreColorCommands = false, bool calculateGlyphs = false - ) - { - if (strokeEffect) - text = $"/es[{getStrokeSize}]" + text; - - _rtl = new RichTextLayout - { - Font = TrueTypeLoader.Instance.GetFont(font, size), - Text = text, - IgnoreColorCommand = ignoreColorCommands, - SupportsCommands = supportsCommands, - CalculateGlyphs = calculateGlyphs - }; - - if (width != null) - _rtl.Width = width; + ) : this(text, font, size, width, ConvertHueToColor(hue), align, strokeEffect, supportsCommands, ignoreColorCommands, calculateGlyphs) { } - _font = font; - _size = size; - _color.PackedValue = HuesLoader.Instance.GetHueColorRgba8888(31, (ushort)hue); - - if (hue == 0xFFFF || hue == ushort.MaxValue) - _color = Color.White; - - _align = align; - - AcceptMouseInput = true; - Width = _rtl.Width == null ? _rtl.Size.X : (int)_rtl.Width; - base.Height = _rtl.Size.Y; - } public TextBox ( string text, @@ -140,6 +112,11 @@ public TextBox base.Height = _rtl.Size.Y; } + public static Color ConvertHueToColor(int hue) + { + return new Color() { PackedValue = HuesLoader.Instance.GetHueColorRgba8888(31, (ushort)hue) }; + } + public bool PixelCheck(int x, int y) { if (!AcceptMouseInput || string.IsNullOrWhiteSpace(Text)) From d404aac9a05b25792cc36d0d6b85e9e87d1ec6e6 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 7 Feb 2024 15:24:49 -0700 Subject: [PATCH 28/93] World map can now be locked in place, and middle mouse button will toggle freeview instead of only enabling --- .../Game/UI/Gumps/VersionHistory.cs | 3 ++- .../Game/UI/Gumps/WorldMapGump.cs | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index cde6ffdae7..da5442db10 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -12,7 +12,8 @@ internal class VersionHistory : Gump "- A few bug fixes\n" + "- A few fixes from CUO\n" + "- Converted nameplates to use TTF fonts\n" + - "- Added an available client commands gump", + "- Added an available client commands gump\n" + + "- World map alt lock now works, and middle mouse click will toggle freeview", "/c[white][3.20.0]/cd\n" + "- Being frozen wont cancel auto follow\n" + diff --git a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs index 5d3df00264..66af9eacec 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs @@ -195,7 +195,10 @@ public bool FreeView if (!_freeView) { _isScrolling = false; - CanMove = true; + if (!IsLocked) + { + CanMove = true; + } } } } @@ -3400,7 +3403,10 @@ protected override void OnMouseUp(int x, int y, MouseButtonType button) if (button == MouseButtonType.Left && !Keyboard.Alt) { _isScrolling = false; - CanMove = true; + if (!IsLocked) + { + CanMove = true; + } } if (button == MouseButtonType.Left || button == MouseButtonType.Middle) @@ -3430,15 +3436,18 @@ protected override void OnMouseDown(int x, int y, MouseButtonType button) { if (button == MouseButtonType.Middle) { - FreeView = true; + FreeView = !FreeView; } - _lastScroll.X = _center.X; - _lastScroll.Y = _center.Y; - _isScrolling = true; - CanMove = false; + if (FreeView) + { + _lastScroll.X = _center.X; + _lastScroll.Y = _center.Y; + _isScrolling = true; + CanMove = false; - Client.Game.GameCursor.IsDraggingCursorForced = true; + Client.Game.GameCursor.IsDraggingCursorForced = true; + } } } From 7ae50929f58621e2b696369903ddce80bc5b63c9 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 8 Feb 2024 14:54:00 -0700 Subject: [PATCH 29/93] Search for zones file in UO directory OR TUO data/client directory Also set up for future zone label drawing --- .../Game/UI/Gumps/WorldMapGump.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs index 66af9eacec..e6dab08429 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs @@ -1769,6 +1769,10 @@ public void AddZoneSetByFileName(string filename, bool hidden) catch (Exception ee) { Log.Error($"{ee}"); + if (CUOEnviroment.Debug) + { + GameActions.Print(ee.ToString()); + } } } @@ -1800,7 +1804,10 @@ private void LoadZones() _zoneSets.Clear(); - foreach (String filename in Directory.GetFiles(_mapFilesPath, "*.zones.json")) + List zonefiles = [.. Directory.GetFiles(_mapFilesPath, "*.zones.json")]; + zonefiles.AddRange(Directory.GetFiles(Settings.GlobalSettings.UltimaOnlineDirectory, "*.zones.json")); + + foreach (string filename in zonefiles) { bool shouldHide = !string.IsNullOrEmpty ( @@ -3121,6 +3128,8 @@ float zoom Vector3 hueVector = ShaderHueTranslator.GetHueVector(0); Texture2D texture = SolidColorTextureCache.GetTexture(zone.Color); + //Vector2 topleft = new Vector2(10000, 10000), botright = Vector2.Zero; + for (int i = 0, j = 1; i < zone.Vertices.Count; i++, j++) { if (j >= zone.Vertices.Count) j = 0; @@ -3128,6 +3137,26 @@ float zoom Vector2 start = WorldPointToGumpPoint(zone.Vertices[i].X, zone.Vertices[i].Y, x, y, width, height, zoom); Vector2 end = WorldPointToGumpPoint(zone.Vertices[j].X, zone.Vertices[j].Y, x, y, width, height, zoom); + //if(start.X < topleft.X) + //{ + // topleft.X = start.X; + //} + + //if(start.Y < topleft.Y) + //{ + // topleft.Y = start.Y; + //} + + //if(end.X > botright.X) + //{ + // botright.X = end.X; + //} + //if (end.Y > botright.Y) + //{ + // botright.Y = end.Y; + //} + ////Handle drawing a label here + batcher.DrawLine(texture, start, end, hueVector, 1); } } From 4613f95ab53a534d48c31b325187ef767ee8bbfe Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 8 Feb 2024 23:41:43 -0700 Subject: [PATCH 30/93] Fix missing scroll bar in modern options gump --- .../Game/UI/Gumps/ModernOptionsGump.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index cbd4b14919..dd6f115543 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -5057,13 +5057,18 @@ public void Scroll(bool isup) public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - _scrollBar?.Draw(batcher, x + _scrollBar.X, y + _scrollBar.Y); + int sbar = 0, start = 0; - int sbar = _scrollBar == null ? 0 : _scrollBar.Width; + if (_scrollBar != null) + { + _scrollBar.Draw(batcher, x + _scrollBar.X, y + _scrollBar.Y); + sbar = _scrollBar.Width; + start = 1; + } if (batcher.ClipBegin(x, y, Width - sbar, Height)) { - for (int i = 0; i < Children.Count; i++) + for (int i = start; i < Children.Count; i++) { Control child = Children[i]; From f56e93a09dace836ded0ba2eb491531fa3ec9599 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 8 Feb 2024 23:57:04 -0700 Subject: [PATCH 31/93] Add checkbox color and size to theme settings for options gump --- .../Game/UI/Gumps/ModernOptionsGump.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index dd6f115543..d599ae7689 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -45,6 +45,10 @@ private static ThemeSettings Theme _settings = new ThemeSettings(); ThemeSettings.Save(typeof(ModernOptionsGump).ToString(), _settings); } + else + { //Save changes if things have changed + ThemeSettings.Save(typeof(ModernOptionsGump).ToString(), _settings); + } return _settings; } else @@ -2931,14 +2935,12 @@ public void OnSearchMatch() private class CheckboxWithLabel : Control, SearchableOption { - private const int CHECKBOX_SIZE = 30; - private bool _isChecked; private readonly TextBox _text; public TextBox TextLabel => _text; - private Vector3 hueVector = ShaderHueTranslator.GetHueVector(Theme.SEARCH_BACKGROUND, false, 0.9f); + private Vector3 hueVector = ShaderHueTranslator.GetHueVector(Theme.CHECKBOX, false, 0.9f); public CheckboxWithLabel( string text = "", @@ -2949,10 +2951,10 @@ public CheckboxWithLabel( { _isChecked = isChecked; ValueChanged = valueChanged; - _text = new TextBox(text, Theme.FONT, Theme.STANDARD_TEXT_SIZE, maxWidth == 0 ? null : maxWidth, Theme.TEXT_FONT_COLOR, strokeEffect: false) { X = CHECKBOX_SIZE + 5, AcceptMouseInput = false }; + _text = new TextBox(text, Theme.FONT, Theme.STANDARD_TEXT_SIZE, maxWidth == 0 ? null : maxWidth, Theme.TEXT_FONT_COLOR, strokeEffect: false) { X = Theme.CHECKBOX_SIZE + 5, AcceptMouseInput = false }; - Width = CHECKBOX_SIZE + 5 + _text.Width; - Height = Math.Max(CHECKBOX_SIZE, _text.MeasuredSize.Y); + Width = Theme.CHECKBOX_SIZE + 5 + _text.Width; + Height = Math.Max(Theme.CHECKBOX_SIZE, _text.MeasuredSize.Y); _text.Y = (Height / 2) - (_text.Height / 2); @@ -3010,7 +3012,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) batcher.Draw( SolidColorTextureCache.GetTexture(Color.White), - new Rectangle(x, y, CHECKBOX_SIZE, CHECKBOX_SIZE), + new Rectangle(x, y, Theme.CHECKBOX_SIZE, Theme.CHECKBOX_SIZE), hueVector ); @@ -3018,7 +3020,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) { batcher.Draw( SolidColorTextureCache.GetTexture(Color.Black), - new Rectangle(x + (CHECKBOX_SIZE / 2) / 2, y + (CHECKBOX_SIZE / 2) / 2, CHECKBOX_SIZE / 2, CHECKBOX_SIZE / 2), + new Rectangle(x + (Theme.CHECKBOX_SIZE / 2) / 2, y + (Theme.CHECKBOX_SIZE / 2) / 2, Theme.CHECKBOX_SIZE / 2, Theme.CHECKBOX_SIZE / 2), hueVector ); } @@ -6324,6 +6326,8 @@ private class ThemeSettings : UISettings public ushort BACKGROUND { get; set; } = 897; public ushort SEARCH_BACKGROUND { get; set; } = 899; + public ushort CHECKBOX { get; set; } = 899; + public int CHECKBOX_SIZE { get; set; } = 30; public ushort BLACK { get; set; } = 0; [JsonConverter(typeof(ColorJsonConverter))] From 627c3fe7999a53dc552620a7bd754922e8c117a0 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 9 Feb 2024 00:49:43 -0700 Subject: [PATCH 32/93] Add a slowupdate method to the game loop. This will allow for better performance when all controls have been organized to move things that don't need to be processed as often to the slow update method. --- .../Game/Managers/UIManager.cs | 24 ++++++++- .../Game/UI/Controls/Control.cs | 54 +++++++++++++++++++ src/ClassicUO.Client/GameController.cs | 9 +++- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/UIManager.cs b/src/ClassicUO.Client/Game/Managers/UIManager.cs index 4831d96e86..123e1fe9be 100644 --- a/src/ClassicUO.Client/Game/Managers/UIManager.cs +++ b/src/ClassicUO.Client/Game/Managers/UIManager.cs @@ -52,7 +52,6 @@ public static class UIManager private static Control _keyboardFocusControl, _lastFocus; private static bool _needSort; - public static float ContainerScale { get; set; } = 1f; public static AnchorManager AnchorManager { get; } = new AnchorManager(); @@ -385,6 +384,29 @@ public static void Update() HandleMouseInput(); } + public static void SlowUpdate() + { + SortControlsByInfo(); + + LinkedListNode first = Gumps.First; + + while (first != null) + { + LinkedListNode next = first.Next; + + Control g = first.Value; + + g.SlowUpdate(); + + if (g.IsDisposed) + { + Gumps.Remove(first); + } + + first = next; + } + } + public static void Draw(UltimaBatcher2D batcher) { SortControlsByInfo(); diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index 90342cfa73..60f3b5201d 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -301,6 +301,9 @@ public virtual bool Draw(UltimaBatcher2D batcher, int x, int y) return true; } + ///

+ /// Update is called as often as possible. + /// public virtual void Update() { if (IsDisposed) @@ -379,6 +382,57 @@ public virtual void Update() } } + /// + /// Intended for updates that don't need to occur as frequently as Update() does. + /// SlowUpdate is called twice per second. + /// + public virtual void SlowUpdate() + { + if (IsDisposed) + { + return; + } + + if (Children.Count != 0) + { + List removalList = new List(); ; + + for (int i = 0; i < Children.Count; i++) + { + if (i < 0 || i >= Children.Count) + { + continue; + } + + Control c = Children.ElementAt(i); + + if (c == null) + { + continue; + } + + if (c.IsDisposed) + { + removalList.Add(c); + continue; + } + + c.SlowUpdate(); + + } + + if (removalList.Count > 0) + { + foreach (Control c in removalList) + { + OnChildRemoved(); + Children.Remove(c); + } + } + + } + } + /// /// Scale the width and height of this control. Width/Height * Scale /// diff --git a/src/ClassicUO.Client/GameController.cs b/src/ClassicUO.Client/GameController.cs index 473544f086..2ec3f72bc6 100644 --- a/src/ClassicUO.Client/GameController.cs +++ b/src/ClassicUO.Client/GameController.cs @@ -63,8 +63,7 @@ internal unsafe class GameController : Microsoft.Xna.Framework.Game private readonly Texture2D[] _hueSamplers = new Texture2D[3]; private bool _ignoreNextTextInput; private readonly float[] _intervalFixedUpdate = new float[2]; - private double _totalElapsed, - _currentFpsTime; + private double _totalElapsed, _currentFpsTime, _nextSlowUpdate; private uint _totalFrames; private UltimaBatcher2D _uoSpriteBatch; private bool _suppressedDraw; @@ -464,6 +463,12 @@ protected override void Update(GameTime gameTime) UIManager.Update(); + if (Time.Ticks >= _nextSlowUpdate) + { + _nextSlowUpdate = Time.Ticks + 500; + UIManager.SlowUpdate(); + } + _totalElapsed += gameTime.ElapsedGameTime.TotalMilliseconds; _currentFpsTime += gameTime.ElapsedGameTime.TotalMilliseconds; From 48bde68f215d2d6f07ae18ef763ef2757f870441 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 9 Feb 2024 13:05:00 -0700 Subject: [PATCH 33/93] Add a simple gump closing animation(can be toggled off) --- .../Configuration/Language.cs | 1 + src/ClassicUO.Client/Configuration/Profile.cs | 1 + .../Game/UI/Controls/Control.cs | 45 +++++++++++++++++++ .../Game/UI/Gumps/ModernOptionsGump.cs | 3 ++ 4 files changed, 50 insertions(+) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 4f81cc2fa7..62a997955d 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -516,6 +516,7 @@ public class TazUO public string PlayerOffsetY { get; set; } = "Player Offset Y"; public string UseLandTexturesWhereAvailable { get; set; } = "Use land textures where available(Experimental)"; public string SOSGumpID { get; set; } = "SOS Gump ID"; + public string UseGumpClosingAnims { get; set; } = "Enable gump closing animation"; #endregion #region Tooltips diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index 99220d8bf2..e3af7a2233 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -595,6 +595,7 @@ public int CoolDownConditionCount public bool ModernPaperdollAnchorEnabled { get; set; } = false; public bool JournalAnchorEnabled { get; set; } = false; + public bool EnableGumpCloseAnimation { get; set; } = true; public void Save(string path, bool saveGumps = true) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index 60f3b5201d..72c1456964 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -30,6 +30,7 @@ #endregion +using ClassicUO.Configuration; using ClassicUO.Game.Managers; using ClassicUO.Input; using ClassicUO.Renderer; @@ -55,6 +56,8 @@ public abstract class Control private bool _handlesKeyboardFocus; private Point _offset; private Control _parent; + private uint timetoclose = uint.MaxValue; + private bool delayedDispose = false; protected Control(Control parent = null) { @@ -279,6 +282,34 @@ public virtual bool Draw(UltimaBatcher2D batcher, int x, int y) return false; } + if (delayedDispose) + { + + if (timetoclose == uint.MaxValue) + { + timetoclose = Time.Ticks + 150; + } + + if (Time.Ticks >= timetoclose) + { + Dispose(); + return false; + } + + float prog = (float)(Time.Ticks - timetoclose + 151) / 150; + prog = Math.Max(0, Math.Min(1, prog)); // Ensure prog is within [0, 1] + + int offset = (int)(((float)Math.Min(Width, Height) * prog) / 2f); + + if (offset >= Width - (offset * 2) || offset >= Height - (offset * 2)) + { + Dispose(); + return false; + } + + batcher.ClipBegin(x + offset, y + offset, Width - (offset * 2), Height - (offset * 2)); + } + for (int i = 0; i < Children.Count; i++) { if (Children.Count <= i) @@ -298,6 +329,11 @@ public virtual bool Draw(UltimaBatcher2D batcher, int x, int y) DrawDebug(batcher, x, y); + if (delayedDispose) + { + batcher.ClipEnd(); + } + return true; } @@ -993,6 +1029,15 @@ public virtual void Dispose() return; } + if (this is Gumps.Gump) + { + if (!delayedDispose && World.InGame && Parent == null && ProfileManager.CurrentProfile != null && ProfileManager.CurrentProfile.EnableGumpCloseAnimation) + { + delayedDispose = true; + return; + } + } + if (Children != null) { foreach (Control c in Children) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index d599ae7689..0db0ccfd32 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2399,6 +2399,9 @@ private void BuildTazUO() }), true, page); content.BlankLine(); content.AddToRight(new InputFieldWithLabel(lang.GetTazUO.SOSGumpID, Theme.INPUT_WIDTH, profile.SOSGumpID.ToString(), true, (s, e) => { if (uint.TryParse(((InputField.StbTextBox)s).Text, out uint id)) { profile.SOSGumpID = id; } }), true, page); + content.BlankLine(); + content.AddToRight(new CheckboxWithLabel(lang.GetTazUO.UseGumpClosingAnims, 0, profile.EnableGumpCloseAnimation, (b) => { profile.EnableGumpCloseAnimation = b; }), true, page); + #endregion #region Tooltips From 6efb55d0c26527297b853e711e474ee5acaa77e9 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 9 Feb 2024 14:20:38 -0700 Subject: [PATCH 34/93] Control cleanup and optimizations --- .../Game/UI/Controls/ScrollArea.cs | 16 ++++----- .../Game/UI/Controls/ScrollBar.cs | 7 ++-- .../Game/UI/Controls/ScrollFlag.cs | 8 ++--- .../Game/UI/Controls/SimpleBorder.cs | 23 +++++++++++-- .../Game/UI/Controls/StaticPic.cs | 34 +++++++++++++++---- 5 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/ScrollArea.cs b/src/ClassicUO.Client/Game/UI/Controls/ScrollArea.cs index 45571a8ebe..b8f1ae9822 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ScrollArea.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ScrollArea.cs @@ -30,10 +30,10 @@ #endregion -using System; using ClassicUO.Input; using ClassicUO.Renderer; using Microsoft.Xna.Framework; +using System; namespace ClassicUO.Game.UI.Controls { @@ -72,7 +72,8 @@ public ScrollArea { _scrollBar = new ScrollFlag { - X = Width - 19, Height = h + X = Width - 19, + Height = h }; Width += 15; @@ -100,11 +101,9 @@ public ScrollArea public Rectangle ScissorRectangle; - - public override void Update() + public override void SlowUpdate() { - base.Update(); - + base.SlowUpdate(); CalculateScrollBarMaxValue(); if (ScrollbarBehaviour == ScrollbarBehaviour.ShowAlways) @@ -138,7 +137,7 @@ public void Scroll(bool isup) public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - ScrollBarBase scrollbar = (ScrollBarBase) Children[0]; + ScrollBarBase scrollbar = (ScrollBarBase)Children[0]; scrollbar.Draw(batcher, x + scrollbar.X, y + scrollbar.Y); if (batcher.ClipBegin(x + ScissorRectangle.X, y + ScissorRectangle.Y, Width - 14 + ScissorRectangle.Width, Height + ScissorRectangle.Height)) @@ -153,7 +152,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) } int finalY = y + child.Y - scrollbar.Value + ScissorRectangle.Y; - + child.Draw(batcher, x + child.X, finalY); } @@ -163,7 +162,6 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) return true; } - protected override void OnMouseWheel(MouseEventType delta) { switch (delta) diff --git a/src/ClassicUO.Client/Game/UI/Controls/ScrollBar.cs b/src/ClassicUO.Client/Game/UI/Controls/ScrollBar.cs index 4560c37b63..eb209280ee 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ScrollBar.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ScrollBar.cs @@ -30,11 +30,10 @@ #endregion -using System; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using Microsoft.Xna.Framework; +using System; namespace ClassicUO.Game.UI.Controls { @@ -52,6 +51,8 @@ internal class ScrollBar : ScrollBarBase const ushort BACKGROUND_2 = 255; const ushort SLIDER = 254; + private Vector3 hueVector = ShaderHueTranslator.GetHueVector(0); + public ScrollBar(int x, int y, int height) { Height = height; @@ -91,8 +92,6 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) return false; } - var hueVector = ShaderHueTranslator.GetHueVector(0); - ref readonly var gumpInfoUp0 = ref Client.Game.Gumps.GetGump(BUTTON_UP_0); ref readonly var gumpInfoUp1 = ref Client.Game.Gumps.GetGump(BUTTON_UP_1); ref readonly var gumpInfoDown0 = ref Client.Game.Gumps.GetGump(BUTTON_DOWN_0); diff --git a/src/ClassicUO.Client/Game/UI/Controls/ScrollFlag.cs b/src/ClassicUO.Client/Game/UI/Controls/ScrollFlag.cs index 511ff57d99..5eee36cde2 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ScrollFlag.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ScrollFlag.cs @@ -30,11 +30,9 @@ #endregion -using System; -using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using Microsoft.Xna.Framework; +using System; namespace ClassicUO.Game.UI.Controls { @@ -46,6 +44,8 @@ internal class ScrollFlag : ScrollBarBase const ushort BUTTON_DOWN = 0x0825; const ushort BUTTON_FLAG = 0x0828; + private Vector3 hueVector = ShaderHueTranslator.GetHueVector(0); + public ScrollFlag(int x, int y, int height, bool showbuttons) : this() { X = x; @@ -90,8 +90,6 @@ public ScrollFlag() public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - var hueVector = ShaderHueTranslator.GetHueVector(0); - ref readonly var gumpInfoFlag = ref Client.Game.Gumps.GetGump(BUTTON_FLAG); ref readonly var gumpInfoUp = ref Client.Game.Gumps.GetGump(BUTTON_UP); ref readonly var gumpInfoDown = ref Client.Game.Gumps.GetGump(BUTTON_DOWN); diff --git a/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs b/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs index 5cb9c7b61d..bc7b652771 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs @@ -5,9 +5,19 @@ namespace ClassicUO.Game.UI.Controls { internal class SimpleBorder : Control { - public ushort Hue = 0; + public ushort Hue + { + get => hue; + set + { + hue = value; + hueVector = ShaderHueTranslator.GetHueVector(value, false, Alpha); + } + } private int _width = 0, _height = 0; + private Vector3 hueVector; + private ushort hue = 0; //Return 0 so this control has a 0, 0 size to not interfere with hitboxes public new int Width { get { return 0; } set { _width = value; } } @@ -16,16 +26,23 @@ internal class SimpleBorder : Control public override bool Draw(UltimaBatcher2D batcher, int x, int y) { if (IsDisposed) + { return false; + } + base.Draw(batcher, x, y); - var huevec = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); + if (hueVector == default) + { + GameActions.Print($"{Hue}, {Alpha}"); + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); + } batcher.DrawRectangle( SolidColorTextureCache.GetTexture(Color.White), x, y, _width, _height, - huevec + hueVector ); return true; diff --git a/src/ClassicUO.Client/Game/UI/Controls/StaticPic.cs b/src/ClassicUO.Client/Game/UI/Controls/StaticPic.cs index 8527b43095..a5c2dba8c8 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/StaticPic.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/StaticPic.cs @@ -30,17 +30,20 @@ #endregion -using System.Collections.Generic; using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Controls { public class StaticPic : Control { - private ushort _graphic; + private ushort graphic; + private Vector3 hueVector; + private ushort hue; + private bool isPartialHue; public StaticPic(ushort graphic, ushort hue) { @@ -61,15 +64,29 @@ public StaticPic(List parts) IsFromServer = true; } - public ushort Hue { get; set; } - public bool IsPartialHue { get; set; } + public ushort Hue + { + get => hue; set + { + hue = value; + hueVector = ShaderHueTranslator.GetHueVector(value, IsPartialHue, 1); + } + } + public bool IsPartialHue + { + get => isPartialHue; set + { + isPartialHue = value; + hueVector = ShaderHueTranslator.GetHueVector(Hue, value, 1); + } + } public ushort Graphic { - get => _graphic; + get => graphic; set { - _graphic = value; + graphic = value; ref readonly var artInfo = ref Client.Game.Arts.GetArt(value); @@ -89,7 +106,10 @@ public ushort Graphic public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - Vector3 hueVector = ShaderHueTranslator.GetHueVector(Hue, IsPartialHue, 1); + if (hueVector == default) + { + hueVector = ShaderHueTranslator.GetHueVector(Hue, IsPartialHue, 1); + } ref readonly var artInfo = ref Client.Game.Arts.GetArt(Graphic); From 12ead91aac0070c1ec76c3e818e5263c5e0e8952 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 9 Feb 2024 14:59:16 -0700 Subject: [PATCH 35/93] Added AlphaChanged method to controls --- .../Game/UI/Controls/Control.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index 72c1456964..198aa5cb6b 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -58,6 +58,7 @@ public abstract class Control private Control _parent; private uint timetoclose = uint.MaxValue; private bool delayedDispose = false; + private float alpha = 1.0f; protected Control(Control parent = null) { @@ -126,7 +127,15 @@ public Point Location public bool IsFocused { get; set; } - public float Alpha { get; set; } = 1.0f; + public float Alpha + { + get => alpha; set + { + float old = alpha; + alpha = value; + AlphaChanged(old, value); + } + } public List Children { get; } @@ -273,8 +282,6 @@ public void UpdateOffset(int x, int y) } } - - public virtual bool Draw(UltimaBatcher2D batcher, int x, int y) { if (IsDisposed) @@ -663,6 +670,13 @@ public virtual void OnHitTestSuccess(int x, int y, ref Control res) { } + /// + /// Invoked when alpha is changed on a control + /// + /// + /// + public virtual void AlphaChanged(float oldValue, float newValue) { }; + public Control GetFirstControlAcceptKeyboardInput() { if (_acceptKeyboardInput) From 06ae682f989f92c931c4d3d39b02fbbd54a237ea Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 9 Feb 2024 14:59:43 -0700 Subject: [PATCH 36/93] Fix --- src/ClassicUO.Client/Game/UI/Controls/Control.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index 198aa5cb6b..f418e60e2d 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -675,7 +675,7 @@ public virtual void OnHitTestSuccess(int x, int y, ref Control res) /// /// /// - public virtual void AlphaChanged(float oldValue, float newValue) { }; + public virtual void AlphaChanged(float oldValue, float newValue) { } public Control GetFirstControlAcceptKeyboardInput() { From 644c8a2fd611f652f5caddb2fa58e4a4417a1779 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 9 Feb 2024 15:09:09 -0700 Subject: [PATCH 37/93] More minor control cleanup and optimizations --- .../Game/UI/Controls/GumpPicExternalUrl.cs | 7 +++--- .../Game/UI/Controls/GumpPicTiled.cs | 25 ++++++++++++++++--- .../Game/UI/Controls/HoveredLabel.cs | 3 +-- src/ClassicUO.Client/Game/UI/Controls/Line.cs | 9 +++++-- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/GumpPicExternalUrl.cs b/src/ClassicUO.Client/Game/UI/Controls/GumpPicExternalUrl.cs index b83d846cc8..59e2801947 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/GumpPicExternalUrl.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/GumpPicExternalUrl.cs @@ -42,6 +42,8 @@ namespace ClassicUO.Game.UI.Controls { public class GumpPicExternalUrl : Control { + private Vector3 hueVector; + public GumpPicExternalUrl(int x, int y, string imgUrl, ushort hue, int width, int height, bool resize = false) { Width = width; @@ -53,6 +55,7 @@ public GumpPicExternalUrl(int x, int y, string imgUrl, ushort hue, int width, in getImageTexture(); AcceptMouseInput = true; CanMove = true; + hueVector = ShaderHueTranslator.GetHueVector(Hue); } public string ImgUrl { get; } @@ -96,8 +99,6 @@ private void getImageTexture() public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - Vector3 hueVector = ShaderHueTranslator.GetHueVector(Hue); - if (imageTexture != null) { if (!Resize) @@ -106,7 +107,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) batcher.Draw(imageTexture, new Rectangle(x, y, Width, Height), imageTexture.Bounds, hueVector); } - return base.Draw(batcher, x, y);; + return base.Draw(batcher, x, y); ; } } } \ No newline at end of file diff --git a/src/ClassicUO.Client/Game/UI/Controls/GumpPicTiled.cs b/src/ClassicUO.Client/Game/UI/Controls/GumpPicTiled.cs index 06b2e9768d..0e7c24c5f6 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/GumpPicTiled.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/GumpPicTiled.cs @@ -30,17 +30,18 @@ #endregion -using System.Collections.Generic; -using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Controls { public class GumpPicTiled : Control { private ushort _graphic; + private ushort hue; + Vector3 hueVector; public GumpPicTiled(ushort graphic) { @@ -98,11 +99,27 @@ public ushort Graphic } } - public ushort Hue { get; set; } + public ushort Hue + { + get => hue; set + { + hue = value; + hueVector = ShaderHueTranslator.GetHueVector(value, false, Alpha, true); + } + } + + public override void AlphaChanged(float oldValue, float newValue) + { + base.AlphaChanged(oldValue, newValue); + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, newValue, true); + } public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - Vector3 hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha, true); + if (hueVector == default) + { + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha, true); + } ref readonly var gumpInfo = ref Client.Game.Gumps.GetGump(Graphic); diff --git a/src/ClassicUO.Client/Game/UI/Controls/HoveredLabel.cs b/src/ClassicUO.Client/Game/UI/Controls/HoveredLabel.cs index bd8ffa6512..ba9258f25b 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/HoveredLabel.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/HoveredLabel.cs @@ -39,6 +39,7 @@ namespace ClassicUO.Game.UI.Controls internal class HoveredLabel : Label { private readonly ushort _overHue, _normalHue, _selectedHue; + private Vector3 hueVector = ShaderHueTranslator.GetHueVector(0); public HoveredLabel ( @@ -100,8 +101,6 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) { if (DrawBackgroundCurrentIndex && MouseIsOver && !string.IsNullOrWhiteSpace(Text)) { - Vector3 hueVector = ShaderHueTranslator.GetHueVector(0); - batcher.Draw ( SolidColorTextureCache.GetTexture(Color.Gray), diff --git a/src/ClassicUO.Client/Game/UI/Controls/Line.cs b/src/ClassicUO.Client/Game/UI/Controls/Line.cs index 2963724e22..ad8157b581 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Line.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Line.cs @@ -39,6 +39,7 @@ namespace ClassicUO.Game.UI.Controls internal class Line : Control { private readonly Texture2D _texture; + private Vector3 hueVector = ShaderHueTranslator.GetHueVector(0, false, 1f); public Line(int x, int y, int w, int h, uint color) { @@ -50,10 +51,14 @@ public Line(int x, int y, int w, int h, uint color) _texture = SolidColorTextureCache.GetTexture(new Color { PackedValue = color }); } - public override bool Draw(UltimaBatcher2D batcher, int x, int y) + public override void SlowUpdate() { - Vector3 hueVector = ShaderHueTranslator.GetHueVector(0, false, Alpha); + base.SlowUpdate(); + hueVector = ShaderHueTranslator.GetHueVector(0, false, Alpha); + } + public override bool Draw(UltimaBatcher2D batcher, int x, int y) + { batcher.Draw ( _texture, From 69568d89c4ba14beb5568b6c5cbec175da4e0272 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 9 Feb 2024 22:26:37 -0700 Subject: [PATCH 38/93] More control cleanup and optimizations --- .../Game/UI/Controls/AlphaBlendControl.cs | 19 +++++- .../Game/UI/Controls/Button.cs | 32 +++++++--- .../Game/UI/Controls/ButtonTileArt.cs | 10 +-- .../Game/UI/Controls/Checkbox.cs | 8 +-- .../Game/UI/Controls/CheckerTrans.cs | 63 +------------------ .../Game/UI/Controls/ClickableColorBox.cs | 3 - .../Game/UI/Controls/ColorBox.cs | 27 +++++--- .../Game/UI/Controls/ColorPickerBox.cs | 14 ++--- 8 files changed, 73 insertions(+), 103 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/AlphaBlendControl.cs b/src/ClassicUO.Client/Game/UI/Controls/AlphaBlendControl.cs index 148394c300..92ac00ed17 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/AlphaBlendControl.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/AlphaBlendControl.cs @@ -37,16 +37,33 @@ namespace ClassicUO.Game.UI.Controls { public sealed class AlphaBlendControl : Control { + private Vector3 hueVector; + private ushort hue; + public AlphaBlendControl(float alpha = 0.5f) { Alpha = alpha; AcceptMouseInput = false; + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); } - public ushort Hue { get; set; } + public ushort Hue + { + get => hue; set + { + hue = value; + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); + } + } public Color BaseColor { get; set; } = Color.Black; + public override void AlphaChanged(float oldValue, float newValue) + { + base.AlphaChanged(oldValue, newValue); + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); + } + public override bool Draw(UltimaBatcher2D batcher, int x, int y) { Vector3 hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); diff --git a/src/ClassicUO.Client/Game/UI/Controls/Button.cs b/src/ClassicUO.Client/Game/UI/Controls/Button.cs index 488f2c3647..294bee2ae7 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Button.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Button.cs @@ -30,14 +30,13 @@ #endregion -using System.Collections.Generic; using ClassicUO.Game.Scenes; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Controls { @@ -53,9 +52,9 @@ public class Button : Control private readonly string _caption; private bool _entered; private readonly RenderedText[] _fontTexture; - private ushort _normal, - _pressed, - _over; + private ushort _normal, _pressed, _over; + private Vector3 hueVector; + private int hue; public Button( int buttonID, @@ -103,8 +102,9 @@ public Button( CanMove = false; AcceptMouseInput = true; - //CanCloseWithRightClick = false; CanCloseWithEsc = false; + + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha, true); } public Button(List parts) @@ -182,7 +182,15 @@ public ushort ButtonGraphicOver } } - public int Hue { get; set; } + public int Hue + { + get => hue; set + { + hue = value; + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha, true); + + } + } public ushort FontHue { get; } public ushort HueHover { get; } @@ -201,6 +209,12 @@ protected override void OnMouseExit(int x, int y) _entered = false; } + public override void AlphaChanged(float oldValue, float newValue) + { + base.AlphaChanged(oldValue, newValue); + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha, true); + } + public override bool Draw(UltimaBatcher2D batcher, int x, int y) { Texture2D texture = null; @@ -235,9 +249,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) return false; } - var hue = ShaderHueTranslator.GetHueVector(Hue, false, Alpha, true); - - batcher.Draw(texture, new Rectangle(x, y, Width, Height), bounds, hue); + batcher.Draw(texture, new Rectangle(x, y, Width, Height), bounds, hueVector); if (!string.IsNullOrEmpty(_caption)) { diff --git a/src/ClassicUO.Client/Game/UI/Controls/ButtonTileArt.cs b/src/ClassicUO.Client/Game/UI/Controls/ButtonTileArt.cs index efea00572a..8d81e24a65 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ButtonTileArt.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ButtonTileArt.cs @@ -30,11 +30,11 @@ #endregion -using System.Collections.Generic; using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Controls { @@ -42,9 +42,9 @@ public class ButtonTileArt : Button { private readonly ushort _hue; private readonly bool _isPartial; - private readonly int _tileX, - _tileY; + private readonly int _tileX, _tileY; private ushort _graphic; + private Vector3 hueVector; public ButtonTileArt(List gparams) : base(gparams) { @@ -67,14 +67,14 @@ public ButtonTileArt(List gparams) : base(gparams) } _isPartial = TileDataLoader.Instance.StaticData[_graphic].IsPartialHue; + + hueVector = ShaderHueTranslator.GetHueVector(_hue, _isPartial, 1f); } public override bool Draw(UltimaBatcher2D batcher, int x, int y) { base.Draw(batcher, x, y); - var hueVector = ShaderHueTranslator.GetHueVector(_hue, _isPartial, 1f); - ref readonly var artInfo = ref Client.Game.Arts.GetArt(_graphic); if (artInfo.Texture != null) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Checkbox.cs b/src/ClassicUO.Client/Game/UI/Controls/Checkbox.cs index 3cec16e7ad..976de47bf4 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Checkbox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Checkbox.cs @@ -30,13 +30,12 @@ #endregion -using System; -using System.Collections.Generic; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Controls { @@ -44,8 +43,7 @@ public class Checkbox : Control { private bool _isChecked; private readonly RenderedText _text; - private ushort _inactive, - _active; + private readonly ushort _inactive, _active; public Checkbox( ushort inactive, diff --git a/src/ClassicUO.Client/Game/UI/Controls/CheckerTrans.cs b/src/ClassicUO.Client/Game/UI/Controls/CheckerTrans.cs index a1d60e852d..d22466580e 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/CheckerTrans.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/CheckerTrans.cs @@ -30,58 +30,15 @@ #endregion -using System; -using System.Collections.Generic; using ClassicUO.Renderer; using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Controls { internal class CheckerTrans : Control { - //TODO(deccer): should be moved into Renderer namespace - private static readonly Lazy _checkerStencil = new Lazy - ( - () => - { - DepthStencilState depthStencilState = new DepthStencilState - { - DepthBufferEnable = false, - StencilEnable = true, - StencilFunction = CompareFunction.Always, - ReferenceStencil = 1, - StencilMask = 1, - StencilFail = StencilOperation.Keep, - StencilDepthBufferFail = StencilOperation.Keep, - StencilPass = StencilOperation.Replace - }; - - - return depthStencilState; - } - ); - - - //TODO(deccer): should be moved into Renderer namespace - private static readonly Lazy _checkerBlend = new Lazy - ( - () => - { - BlendState blendState = new BlendState - { - ColorWriteChannels = ColorWriteChannels.None - }; - - return blendState; - } - ); - - //public CheckerTrans(float alpha = 0.5f) - //{ - // _alpha = alpha; - // AcceptMouseInput = false; - //} + private Vector3 hueVector = ShaderHueTranslator.GetHueVector(0, false, 0.5f); public CheckerTrans(List parts) { @@ -93,22 +50,8 @@ public CheckerTrans(List parts) IsFromServer = true; } - public override bool Draw(UltimaBatcher2D batcher, int x, int y) { - //batcher.SetBlendState(_checkerBlend.Value); - //batcher.SetStencil(_checkerStencil.Value); - - //batcher.Draw2D(TransparentTexture, new Rectangle(position.X, position.Y, Width, Height), Vector3.Zero /*ShaderHueTranslator.GetHueVector(0, false, 0.5f, false)*/); - - //batcher.SetBlendState(null); - //batcher.SetStencil(null); - - //return true; - - Vector3 hueVector = ShaderHueTranslator.GetHueVector(0, false, 0.5f); - - //batcher.SetStencil(_checkerStencil.Value); batcher.Draw ( SolidColorTextureCache.GetTexture(Color.Black), @@ -121,8 +64,6 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) ), hueVector ); - - //batcher.SetStencil(null); return true; } } diff --git a/src/ClassicUO.Client/Game/UI/Controls/ClickableColorBox.cs b/src/ClassicUO.Client/Game/UI/Controls/ClickableColorBox.cs index aab0872743..4278a034b7 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ClickableColorBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ClickableColorBox.cs @@ -33,7 +33,6 @@ using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Gumps; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using Microsoft.Xna.Framework; @@ -72,8 +71,6 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) Children[0].Draw(batcher, x, y); } - Vector3 hueVector = ShaderHueTranslator.GetHueVector(Hue); - batcher.Draw ( SolidColorTextureCache.GetTexture(Color.White), diff --git a/src/ClassicUO.Client/Game/UI/Controls/ColorBox.cs b/src/ClassicUO.Client/Game/UI/Controls/ColorBox.cs index 5eba292e7c..ebc67c95c6 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ColorBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ColorBox.cs @@ -30,35 +30,42 @@ #endregion -using ClassicUO.Assets; using ClassicUO.Renderer; -using ClassicUO.Utility; using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; namespace ClassicUO.Game.UI.Controls { public class ColorBox : Control { + private ushort hue; + protected Vector3 hueVector; + public ColorBox(int width, int height, ushort hue) { CanMove = false; - Width = width; Height = height; Hue = hue; - WantUpdateSize = false; } - public ushort Hue { get; set; } - - public override bool Draw(UltimaBatcher2D batcher, int x, int y) + public ushort Hue { - Vector3 hueVector = ShaderHueTranslator.GetHueVector(Hue); + get => hue; set + { + hue = value; + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); + } + } - hueVector.Z = Alpha; + public override void AlphaChanged(float oldValue, float newValue) + { + base.AlphaChanged(oldValue, newValue); + hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); + } + public override bool Draw(UltimaBatcher2D batcher, int x, int y) + { batcher.Draw ( SolidColorTextureCache.GetTexture(Color.White), diff --git a/src/ClassicUO.Client/Game/UI/Controls/ColorPickerBox.cs b/src/ClassicUO.Client/Game/UI/Controls/ColorPickerBox.cs index a3995252fe..1cd66916e2 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/ColorPickerBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/ColorPickerBox.cs @@ -30,15 +30,13 @@ #endregion -using System; -using System.Runtime.InteropServices; using ClassicUO.Game.UI.Gumps; using ClassicUO.Input; -using ClassicUO.Assets; using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System; namespace ClassicUO.Game.UI.Controls { @@ -132,9 +130,9 @@ public int SelectedIndex } } - public ushort SelectedHue => SelectedIndex < 0 || SelectedIndex >= _hues.Length ? (ushort) 0 : _hues[SelectedIndex]; + public ushort SelectedHue => SelectedIndex < 0 || SelectedIndex >= _hues.Length ? (ushort)0 : _hues[SelectedIndex]; + - public override void Update() { if (IsDisposed) @@ -191,7 +189,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) if (_hues.Length > 1) { - rect.X = (int) (x + Width / _columns * (SelectedIndex % _columns + .5f) - 1); + rect.X = (int)(x + Width / _columns * (SelectedIndex % _columns + .5f) - 1); rect.Y = (int)(y + Height / _rows * (SelectedIndex / _columns + .5f) - 1); rect.Width = 2; rect.Height = 2; @@ -240,12 +238,12 @@ private void CreateTexture() { _hues = new ushort[size]; } - + for (int y = 0; y < _rows; y++) { for (int x = 0; x < _columns; x++) { - ushort hue = (ushort) ((_customPallete?[y * _columns + x] ?? startColor) + 1); + ushort hue = (ushort)((_customPallete?[y * _columns + x] ?? startColor) + 1); _hues[y * _columns + x] = hue; From 10e004f6f0e84f0742bd1bfcb3dc7e1365a1c66a Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sat, 10 Feb 2024 03:32:16 -0700 Subject: [PATCH 39/93] Fix for recently black tooltip text --- src/ClassicUO.Client/Game/UI/Controls/TextBox.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs index 94dca72e49..73a6cd214c 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs @@ -114,6 +114,10 @@ public TextBox public static Color ConvertHueToColor(int hue) { + if (hue == 0xFFFF || hue == ushort.MaxValue) + { + return Color.White; + } return new Color() { PackedValue = HuesLoader.Instance.GetHueColorRgba8888(31, (ushort)hue) }; } From 69a98c7ba1201c03c7917f7943eba79569df2d22 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sun, 11 Feb 2024 04:42:45 -0700 Subject: [PATCH 40/93] Remove forgotten debug message --- src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs b/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs index bc7b652771..89cacedbc6 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/SimpleBorder.cs @@ -34,7 +34,6 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) if (hueVector == default) { - GameActions.Print($"{Hue}, {Alpha}"); hueVector = ShaderHueTranslator.GetHueVector(Hue, false, Alpha); } From eeb092d0c052e1177c0f42080b663c790fcdd2e7 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sun, 11 Feb 2024 23:41:53 -0700 Subject: [PATCH 41/93] Fix controller input being tracked when window is not in focus --- src/ClassicUO.Client/GameController.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/GameController.cs b/src/ClassicUO.Client/GameController.cs index 2ec3f72bc6..e4e2864f65 100644 --- a/src/ClassicUO.Client/GameController.cs +++ b/src/ClassicUO.Client/GameController.cs @@ -937,6 +937,10 @@ private int HandleSdlEvent(IntPtr userData, IntPtr ptr) } case SDL_EventType.SDL_CONTROLLERBUTTONDOWN: + if (!IsActive) + { + break; + } Controller.OnButtonDown(sdlEvent->cbutton); UIManager.KeyboardFocusControl?.InvokeControllerButtonDown((SDL_GameControllerButton)sdlEvent->cbutton.button); Scene.OnControllerButtonDown(sdlEvent->cbutton); @@ -970,6 +974,10 @@ private int HandleSdlEvent(IntPtr userData, IntPtr ptr) break; case SDL_EventType.SDL_CONTROLLERBUTTONUP: + if (!IsActive) + { + break; + } Controller.OnButtonUp(sdlEvent->cbutton); UIManager.KeyboardFocusControl?.InvokeControllerButtonUp((SDL_GameControllerButton)sdlEvent->cbutton.button); Scene.OnControllerButtonUp(sdlEvent->cbutton); @@ -991,7 +999,11 @@ private int HandleSdlEvent(IntPtr userData, IntPtr ptr) break; case SDL_EventType.SDL_CONTROLLERAXISMOTION: //Work around because sdl doesn't see trigger buttons as buttons, they are axis probably for pressure support - //GameActions.Print(typeof(SDL_GameControllerButton).GetEnumName((SDL_GameControllerButton)sdlEvent->cbutton.button)); + //GameActions.Print(typeof(SDL_GameControllerButton).GetEnumName((SDL_GameControllerButton)sdlEvent->cbutton.button)); + if (!IsActive) + { + break; + } if (sdlEvent->cbutton.button == (byte)SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK || sdlEvent->cbutton.button == (byte)SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE) //Left trigger BACK Right trigger GUIDE { if (sdlEvent->caxis.axisValue > 32000) From 3722cfde8b756edbc64d435485a099c40daf4f84 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 16 Feb 2024 13:25:50 -0700 Subject: [PATCH 42/93] Update README.md --- README.md | 151 +++++++++++++----------------------------------------- 1 file changed, 36 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 441971eac4..313bde99f3 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,46 @@

- + chat on Discord

------------------------------------------------------------------- +*** -Release: [![Release](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml/badge.svg?branch=main)](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml) Dev: [![Dev](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml/badge.svg?branch=dev)](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml) - - -Join TazUO's discord for ideas/support/updates -> https://discord.gg/SqwtB5g95H - -Check out our [wiki](../../wiki) for details on all of the changes TazUO has made for players! - -This version of CUO adds [grid containers](../../wiki/TazUO.Grid-Containers) and many other features to the regular CUO client - - Searchable - - Resizable - - Scrollable - - Can lock items in specific place - - Quick preview for containers inside *if the client has already cached that bag* - - Item scaling! - -[Cool down bars](../../wiki/TazUO.Cooldown-bars) - -[Follow mode improvements](../../wiki/TazUO.Follow-mode) - -[Improved journal](../../wiki/TazUO.Journal) - -[Nameplate healthbars](../../wiki/TazUO.Nameplate-Healthbars) - -And others in our [wiki](../../wiki) - -![Cooldown](https://user-images.githubusercontent.com/3859393/227056224-ef1c6958-fff5-4698-a21a-c63c5814877c.gif) -![SlottedInv](https://user-images.githubusercontent.com/3859393/226514464-32919a68-ebad-4ec0-8bcf-8614a5055f7d.gif) -![Grid Previe](https://user-images.githubusercontent.com/3859393/222873187-c88ad321-8b19-4cfd-9617-7e23b2443b6a.gif) -![image](https://user-images.githubusercontent.com/3859393/222975241-319e5fa6-2c1e-441d-97e6-b04a5e1f6f3b.png) -![Journal](https://user-images.githubusercontent.com/3859393/222942915-e31d26aa-e9a7-41df-9c99-570bcc00d1fb.gif) -![image](https://user-images.githubusercontent.com/3859393/225168130-5ce83950-853d-43ce-9583-65ec4b0ae9d6.png) -![image](https://user-images.githubusercontent.com/3859393/225307385-c8e8014f-9b84-4fe4-a2cd-f33fbeee9563.png) -![image](https://user-images.githubusercontent.com/3859393/226114408-28c6556d-6ba8-43c7-bf1a-079342aaeacd.png) -![image](https://user-images.githubusercontent.com/3859393/226114417-e68b1653-f719-49b3-b799-0beb07e0a211.png) - - -# Original CUO Readme: ------------------------------------------------------------------- - -An open source implementation of the Ultima Online Classic Client. -Individuals/hobbyists: support continued maintenance and development via the monthly Patreon: -
  [![Patreon](https://raw.githubusercontent.com/wiki/ocornut/imgui/web/patreon_02.png)](http://www.patreon.com/classicuo) - -Individuals/hobbyists: support continued maintenance and development via PayPal: -
  [![PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9ZWJBY6MS99D8) - - - - -[![GitHub Actions Status](https://github.com/ClassicUO/ClassicUO/workflows/Build-Test/badge.svg)](https://github.com/ClassicUO/ClassicUO/actions) -[![GitHub Actions Status](https://github.com/ClassicUO/ClassicUO/workflows/Deploy/badge.svg)](https://github.com/ClassicUO/ClassicUO/actions) - -# Introduction -ClassicUO is an open source implementation of the Ultima Online Classic Client. This client is intended to emulate all standard client versions and is primarily tested against Ultima Online free shards. - -The client is currently under heavy development but is functional. The code is based on the [FNA-XNA](https://fna-xna.github.io/) framework. C# is chosen because there is a large community of developers working on Ultima Online server emulators in C#, because FNA-XNA exists and seems reasonably suitable for creating this type of game. - -![screenshot_2020-07-06_12-29-02](https://user-images.githubusercontent.com/20810422/208747312-04f6782f-3dc8-4951-b0a0-73d2305bbfca.png) - - -ClassicUO is natively cross platform and supports: -* Browser [Chrome] -* Windows [DirectX 11, OpenGL, Vulkan] -* Linux [OpenGL, Vulkan] -* macOS [Metal, OpenGL, MoltenVK] - -# Download & Play! -| Platform | Link | -| --- | --- | -| Browser | [Play!](https://play.classicuo.org) | -| Windows x64 | [Download](https://www.classicuo.eu/launcher/win-x64/ClassicUOLauncher-win-x64-release.zip) | -| Linux x64 | [Download](https://www.classicuo.eu/launcher/linux-x64/ClassicUOLauncher-linux-x64-release.zip) | -| macOS | [Download](https://www.classicuo.eu/launcher/osx/ClassicUOLauncher-osx-x64-release.zip) | - -Or visit the [ClassicUO Website](https://www.classicuo.eu/) - -# How to build the project - -Clone repository with: -``` -git config --global url."https://".insteadOf git:// -git clone --recursive https://github.com/ClassicUO/ClassicUO.git -``` - -Build the project: -``` -dotnet build -c Release -``` - -# Contribute -Everyone is welcome to contribute! The GitHub issues and project tracker are kept up to date with tasks that need work. - -# Legal -The code itself has been written using the following projects as a reference: - -* [OrionUO](https://github.com/hotride/orionuo) -* [Razor](https://github.com/msturgill/razor) -* [UltimaXNA](https://github.com/ZaneDubya/UltimaXNA) -* [ServUO](https://github.com/servuo/servuo) - -Backend: -* [FNA](https://github.com/FNA-XNA/FNA) - -This work is released under the BSD 4 license. This project does not distribute any copyrighted game assets. In order to run this client you'll need to legally obtain a copy of the Ultima Online Classic Client. -Using a custom client to connect to official UO servers is strictly forbidden. We do not assume any responsibility of the usage of this client. +Release: [![Release](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml/badge.svg?branch=main)](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml) Dev: [![Dev](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml/badge.svg?branch=dev)](https://github.com/bittiez/TazUO/actions/workflows/build-test.yml) -Ultima Online(R) © 2022 Electronic Arts Inc. All Rights Reserved. +# What is TazUO? +**TazUO** was originally a fork from ClassicUO with the mindset of adding features requested by users to improve QOL. **TazUO** has since moved away from ClassicUO, we will keep an eye on ClassicUO updates and incorporate changes or fixes as they have a wider user base that provides bug reports, but **TazUO** will no longer be merging all changes from ClassicUO. + +# TazUO features +Check out our [wiki](../../wiki) for details on all of the changes TazUO has made for players! + +***Most*** features can be disabled if you don't want to use said feature. + +- [Launcher](../../wiki/TazUO.Updater-Launcher) - Managing profiles for multiple accounts/servers +- [Grid containers](../../wiki/TazUO.Grid-Containers) - Easily find and move items with out fully customizable grid containers +- [Journal](../../wiki/TazUO.Journal) - Vastly improved journal for readability and organization +- [Alternative paperdoll](../../wiki/TazUO.Alternate-Paperdoll) - A new flavor a your paperdoll +- [Improved buff bar](../../wiki/TazUO.Buff-Bars) +- [Client commands](../../wiki/TazUO.Commands) - Several commands have been added for various features +- [Controller support](../../wiki/TazUO.Controller-Support) - That's right, play with your controller! +- [Cooldown bars](../../wiki/TazUO.Cooldown-bars) - Customizable cooldown bars +- [Grid Highlighting](../../wiki/TazUO.Grid-highlighting-based-on-item-properties) - Grid highlighting of items that have specific properties, easier looting! +- [Tooltip overrides](../../wiki/TazUO.Tooltip-Override) - Customize and override any text in tooltips! +- [Custom fonts](../../wiki/TazUO.TTF-Fonts) - BYOF, Bring your own fonts for better readability. + +There are ***many*** more features to check out in our [wiki](../../wiki) or in game, this list is just a sample! + + +# Screenshots +![Cooldown](https://user-images.githubusercontent.com/3859393/227056224-ef1c6958-fff5-4698-a21a-c63c5814877c.gif) +![SlottedInv](https://user-images.githubusercontent.com/3859393/226514464-32919a68-ebad-4ec0-8bcf-8614a5055f7d.gif) +![Grid Previe](https://user-images.githubusercontent.com/3859393/222873187-c88ad321-8b19-4cfd-9617-7e23b2443b6a.gif) +![image](https://user-images.githubusercontent.com/3859393/222975241-319e5fa6-2c1e-441d-97e6-b04a5e1f6f3b.png) +![Journal](https://user-images.githubusercontent.com/3859393/222942915-e31d26aa-e9a7-41df-9c99-570bcc00d1fb.gif) +![image](https://user-images.githubusercontent.com/3859393/225168130-5ce83950-853d-43ce-9583-65ec4b0ae9d6.png) +![image](https://user-images.githubusercontent.com/3859393/225307385-c8e8014f-9b84-4fe4-a2cd-f33fbeee9563.png) +![image](https://user-images.githubusercontent.com/3859393/226114408-28c6556d-6ba8-43c7-bf1a-079342aaeacd.png) +![image](https://user-images.githubusercontent.com/3859393/226114417-e68b1653-f719-49b3-b799-0beb07e0a211.png) From d87add13d3021564dbda1e97114dd667b8d4ea8e Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 19 Feb 2024 20:42:07 -0700 Subject: [PATCH 43/93] Fix controller button macro bug --- .../Game/Managers/MacroManager.cs | 6 +---- src/ClassicUO.Client/Input/Controller.cs | 27 +++++++++++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/MacroManager.cs b/src/ClassicUO.Client/Game/Managers/MacroManager.cs index 4339ddc9e0..f385badd50 100644 --- a/src/ClassicUO.Client/Game/Managers/MacroManager.cs +++ b/src/ClassicUO.Client/Game/Managers/MacroManager.cs @@ -323,17 +323,13 @@ public Macro FindMacro(SDL_GameControllerButton button) { if (obj.ControllerButtons != null) { - if (obj.ControllerButtons.Length > 1) + if (obj.ControllerButtons.Length > 0) { if (Controller.AreButtonsPressed(obj.ControllerButtons)) { break; } } - else if (obj.ControllerButtons.Contains(button)) - { - break; - } } obj = (Macro)obj.Next; diff --git a/src/ClassicUO.Client/Input/Controller.cs b/src/ClassicUO.Client/Input/Controller.cs index 49bd34a3b0..ad0c468ff7 100644 --- a/src/ClassicUO.Client/Input/Controller.cs +++ b/src/ClassicUO.Client/Input/Controller.cs @@ -1,6 +1,4 @@ -using ClassicUO.Game; -using Microsoft.Xna.Framework.Input; -using SDL2; +using SDL2; namespace ClassicUO.Input { @@ -185,19 +183,32 @@ public static bool IsButtonPressed(SDL.SDL_GameControllerButton button) return false; } - public static bool AreButtonsPressed(SDL.SDL_GameControllerButton[] buttons) + /// + /// Check is the supplied list of buttons are currently pressed. + /// + /// + /// If true, any other buttons pressed will make this return false + /// + public static bool AreButtonsPressed(SDL.SDL_GameControllerButton[] buttons, bool exact = true) { bool finalstatus = true; foreach (var button in buttons) { - if(!IsButtonPressed(button)) + if (!IsButtonPressed(button)) { - finalstatus = false; + finalstatus = false; break; } } + var allPressed = PressedButtons(); + + if (exact && allPressed.Length > buttons.Length) + { + finalstatus = false; + } + return finalstatus; } @@ -277,7 +288,7 @@ public static string GetButtonNames(SDL.SDL_GameControllerButton[] buttons) foreach (var button in buttons) { - switch(button) + switch (button) { case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A: keys += "A, "; @@ -330,7 +341,7 @@ public static string GetButtonNames(SDL.SDL_GameControllerButton[] buttons) } } - if(keys.EndsWith(", ")) + if (keys.EndsWith(", ")) { keys = keys.Substring(0, keys.Length - 2); } From fb544836b57c6f6eec6c9626f531c0c890aa2f86 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 19 Feb 2024 21:10:10 -0700 Subject: [PATCH 44/93] Animation fix from CUO --- src/ClassicUO.Assets/AnimationsLoader.cs | 127 +++++++++--------- .../Animations/Animation.cs | 19 ++- 2 files changed, 75 insertions(+), 71 deletions(-) diff --git a/src/ClassicUO.Assets/AnimationsLoader.cs b/src/ClassicUO.Assets/AnimationsLoader.cs index 2fd708c439..b690fde7c7 100644 --- a/src/ClassicUO.Assets/AnimationsLoader.cs +++ b/src/ClassicUO.Assets/AnimationsLoader.cs @@ -71,7 +71,7 @@ private AnimationsLoader() { } public static AnimationsLoader Instance => _instance ?? (_instance = new AnimationsLoader()); - public IReadOnlyDictionary> EquipConversions => _equipConv; + public IReadOnlyDictionary> EquipConversions => _equipConv; public List<(ushort, byte)>[] GroupReplaces { get; } = new List<(ushort, byte)>[2] @@ -189,7 +189,7 @@ private unsafe void LoadInternal() _mobTypes[id] = new MobTypeInfo() { Type = (AnimationGroupsType)i, - Flags = (AnimationFlags )(0x80000000 | number) + Flags = (AnimationFlags)(0x80000000 | number) }; break; @@ -292,7 +292,7 @@ public ReadOnlySpan GetIndices ClientVersion clientVersion, ushort body, ref ushort hue, - ref AnimationFlags flags, + ref AnimationFlags flags, out int fileIndex, out AnimationGroupsType animType, out sbyte mountHeight @@ -388,7 +388,7 @@ out sbyte mountHeight private long CalculateOffset( ushort graphic, AnimationGroupsType type, - AnimationFlags flags, + AnimationFlags flags, out int groupCount ) { @@ -1071,91 +1071,91 @@ ref SittingInfoData data { case 7: case 0: - { - if (data.Direction1 == -1) { - if (direction == 7) + if (data.Direction1 == -1) { - direction = (byte)data.Direction4; + if (direction == 7) + { + direction = (byte)data.Direction4; + } + else + { + direction = (byte)data.Direction2; + } } else { - direction = (byte)data.Direction2; + direction = (byte)data.Direction1; } - } - else - { - direction = (byte)data.Direction1; - } - break; - } + break; + } case 1: case 2: - { - if (data.Direction2 == -1) { - if (direction == 1) + if (data.Direction2 == -1) { - direction = (byte)data.Direction1; + if (direction == 1) + { + direction = (byte)data.Direction1; + } + else + { + direction = (byte)data.Direction3; + } } else { - direction = (byte)data.Direction3; + direction = (byte)data.Direction2; } - } - else - { - direction = (byte)data.Direction2; - } - break; - } + break; + } case 3: case 4: - { - if (data.Direction3 == -1) { - if (direction == 3) + if (data.Direction3 == -1) { - direction = (byte)data.Direction2; + if (direction == 3) + { + direction = (byte)data.Direction2; + } + else + { + direction = (byte)data.Direction4; + } } else { - direction = (byte)data.Direction4; + direction = (byte)data.Direction3; } - } - else - { - direction = (byte)data.Direction3; - } - break; - } + break; + } case 5: case 6: - { - if (data.Direction4 == -1) { - if (direction == 5) + if (data.Direction4 == -1) { - direction = (byte)data.Direction3; + if (direction == 5) + { + direction = (byte)data.Direction3; + } + else + { + direction = (byte)data.Direction1; + } } else { - direction = (byte)data.Direction1; + direction = (byte)data.Direction4; } - } - else - { - direction = (byte)data.Direction4; - } - break; - } + break; + } } GetSittingAnimDirection(ref direction, ref mirror, ref x, ref y); @@ -1214,7 +1214,7 @@ public AnimationGroups GetGroupIndex(ushort graphic, AnimationGroupsType animTyp [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte GetDeathAction( ushort animID, - AnimationFlags animFlags, + AnimationFlags animFlags, AnimationGroupsType animType, bool second, bool isRunning = false @@ -1249,14 +1249,14 @@ public byte GetDeathAction( ); case AnimationGroupsType.SeaMonster: - { - if (!isRunning) { - return 8; - } + if (!isRunning) + { + return 8; + } - goto case AnimationGroupsType.Monster; - } + goto case AnimationGroupsType.Monster; + } case AnimationGroupsType.Monster: @@ -1354,7 +1354,7 @@ AnimationsLoader.AnimIdxBlock index reader.Seek(dataStart); byte frameCount = (byte)( - type < AnimationGroupsType.Equipment ? Math.Round(fc / (float) MAX_DIRECTIONS) : MAX_DIRECTIONS * 2 + type < AnimationGroupsType.Equipment ? Math.Round(fc / (float)MAX_DIRECTIONS) : MAX_DIRECTIONS * 2 ); if (frameCount > _frames.Length) { @@ -1446,13 +1446,18 @@ public Span ReadMULAnimationFrames(int fileIndex, AnimIdxBlock index) return Span.Empty; } - if (index.Position == 0xFFFF_FFFF || index.Size == 0xFFFF_FFFF) + if (index.Position == 0xFFFF_FFFF || index.Size == 0xFFFF_FFFF || index.Size <= 0) { return Span.Empty; } var file = _files[fileIndex]; + if (index.Position + index.Size > file.Length) + { + return Span.Empty; + } + var reader = new StackDataReader( new ReadOnlySpan( (byte*)file.StartAddress.ToPointer() + index.Position, diff --git a/src/ClassicUO.Renderer/Animations/Animation.cs b/src/ClassicUO.Renderer/Animations/Animation.cs index cea1384df3..b8b6d831d3 100644 --- a/src/ClassicUO.Renderer/Animations/Animation.cs +++ b/src/ClassicUO.Renderer/Animations/Animation.cs @@ -1,8 +1,7 @@ -using System; -using System.Runtime.CompilerServices; using ClassicUO.Assets; using Microsoft.Xna.Framework.Graphics; -using static System.Collections.Specialized.BitVector32; +using System; +using System.Runtime.CompilerServices; namespace ClassicUO.Renderer.Animations { @@ -45,7 +44,7 @@ private ref AnimationDirection GetSprite(int body, int action, int dir) public AnimationGroupsType GetAnimType(ushort graphic) => _dataIndex[graphic]?.Type ?? 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public AnimationFlags GetAnimFlags(ushort graphic) => _dataIndex[graphic]?.Flags ?? 0; + public AnimationFlags GetAnimFlags(ushort graphic) => _dataIndex[graphic]?.Flags ?? 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] public sbyte GetMountedHeightOffset(ushort graphic) => @@ -150,10 +149,10 @@ public Span GetAnimationFrames( index = new IndexAnimation(); var indices = AnimationsLoader.Instance.GetIndices ( - UOFileManager.Version, - id, + UOFileManager.Version, + id, ref hue, - ref index.Flags, + ref index.Flags, out index.FileIndex, out index.Type, out index.MountedHeightOffset @@ -184,7 +183,7 @@ out index.MountedHeightOffset { ref readonly var animIdx = ref indices[i * AnimationsLoader.MAX_DIRECTIONS + d]; index.Groups[i].Direction[d].Address = animIdx.Position; - index.Groups[i].Direction[d].Size = index.FileIndex > 0 ? Math.Max(1, animIdx.Size) : animIdx.Size; + index.Groups[i].Direction[d].Size = /*index.FileIndex > 0 ? Math.Max(1, animIdx.Size) :*/ animIdx.Size; } } } @@ -205,7 +204,7 @@ out index.MountedHeightOffset } } } while (index == null); - + useUOP = index.Flags.HasFlag(AnimationFlags.UseUopAnimation); index.Hue = hue; @@ -379,7 +378,7 @@ private sealed class IndexAnimation { public int FileIndex; public ushort Hue; - public AnimationFlags Flags; + public AnimationFlags Flags; public AnimationGroup[] Groups; public AnimationGroupUop[] UopGroups; public sbyte MountedHeightOffset; From 9fbb0bb3e40529ada585b26dbe516f754fbce3da Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 19 Feb 2024 21:31:50 -0700 Subject: [PATCH 45/93] Expose grid slot manager for scripts --- src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs index e822d8e745..ae1edf14ff 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs @@ -117,6 +117,10 @@ private string sortButtonTooltip #region public vars public readonly bool IsPlayerBackpack = false; + + public GridSlotManager GetGridSlotManager { get { return gridSlotManager; } } + + public List GetContents { get { return gridSlotManager.ContainerContents; } } #endregion public GridContainer(uint local, ushort originalContainerGraphic, bool? useGridStyle = null) : base(GetWidth(), GetHeight(), GetWidth(2), GetHeight(1), local, 0) @@ -781,7 +785,7 @@ public static void ClearInstance() GridSaveSystem.Instance.Clear(); } - private class GridItem : Control + public class GridItem : Control { private readonly HitBox hit; private bool mousePressedWhenEntered = false; @@ -1262,7 +1266,7 @@ public override bool Draw(UltimaBatcher2D batcher, int x, int y) } } - private class GridSlotManager + public class GridSlotManager { private Dictionary gridSlots = new Dictionary(); private Item container; From 9d788f2b88c33807c7bbc43d0c9ca6906134e1fc Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 19 Feb 2024 20:35:38 -0700 Subject: [PATCH 46/93] Make some grid container methods public, add system and system core to scripts system --- .../Game/UI/Gumps/GridContainer.cs | 18 +++++++++++++++--- src/ClassicUO.Client/ScriptCompiler.cs | 17 ++++++----------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs index ae1edf14ff..f067a0c702 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs @@ -52,7 +52,7 @@ namespace ClassicUO.Game.UI.Gumps { - internal class GridContainer : ResizableGump + public class GridContainer : ResizableGump { #region CONSTANTS private const int X_SPACING = 1, Y_SPACING = 1; @@ -85,7 +85,7 @@ internal class GridContainer : ResizableGump private bool autoSortContainer = false; private bool firstItemsLoaded = false; - private readonly bool skipSave = false; + private bool skipSave = false; private readonly ushort originalContainerItemGraphic; private GridScrollArea scrollArea; @@ -121,6 +121,11 @@ private string sortButtonTooltip public GridSlotManager GetGridSlotManager { get { return gridSlotManager; } } public List GetContents { get { return gridSlotManager.ContainerContents; } } + + /// + /// Set to true to avoid saving the current grid slots. + /// + public bool SkipSave { get { return skipSave; } set { skipSave = value; } } #endregion public GridContainer(uint local, ushort originalContainerGraphic, bool? useGridStyle = null) : base(GetWidth(), GetHeight(), GetWidth(2), GetHeight(1), local, 0) @@ -860,6 +865,10 @@ public void Resize() background.Height = gridItemSize; } + /// + /// Set this grid slot's item. Set to null for empty slot. + /// + /// public void SetGridItem(Item item) { if (item == null) @@ -1391,7 +1400,10 @@ public void SetLockedSlot(int slot, bool locked) itemLocks.Add(gridSlots[slot].SlotItem); } - private void SetGridPositions() + /// + /// Set the visual grid items to the current GridSlots dict + /// + public void SetGridPositions() { int x = X_SPACING, y = 0; foreach (var slot in gridSlots) diff --git a/src/ClassicUO.Client/ScriptCompiler.cs b/src/ClassicUO.Client/ScriptCompiler.cs index 0ea000c6db..b144b3005e 100644 --- a/src/ClassicUO.Client/ScriptCompiler.cs +++ b/src/ClassicUO.Client/ScriptCompiler.cs @@ -32,8 +32,9 @@ public static string[] GetReferenceAssemblies() CUOEnviroment.ExecutablePath + "/ClassicUO.Renderer.dll", CUOEnviroment.ExecutablePath + "/ClassicUO.Utility.dll", CUOEnviroment.ExecutablePath + "/FNA.dll", - - CUOEnviroment.ExecutablePath + "/ClassicUO.exe" + CUOEnviroment.ExecutablePath + "/ClassicUO.exe", + "System.dll", + "System.Core.dll" }; var path = Path.Combine(CUOEnviroment.ExecutablePath, "Data/Assemblies.cfg"); @@ -89,14 +90,6 @@ public static string GetCompilerOptions(bool debug) AppendCompilerOption(ref sb, "/d:x64"); } -#if NEWTIMERS - AppendCompilerOption(ref sb, "/d:NEWTIMERS"); -#endif - -#if NEWPARENT - AppendCompilerOption(ref sb, "/d:NEWPARENT"); -#endif - return (sb == null ? null : sb.ToString()); } @@ -220,8 +213,9 @@ public static bool CompileCSScripts(bool debug, bool cache, out Assembly assembl #if !MONO using CodeDomProvider provider = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider(); #else - using (CSharpCodeProvider provider = new CSharpCodeProvider()) + using CSharpCodeProvider provider = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider(); #endif + var path = GetUnusedPath("Scripts.CS"); var parms = new CompilerParameters(GetReferenceAssemblies(), path, debug); @@ -233,6 +227,7 @@ public static bool CompileCSScripts(bool debug, bool cache, out Assembly assembl parms.CompilerOptions = options; } + if (CUOEnviroment.IsUnix) { parms.CompilerOptions = String.Format("{0} /nowarn:169,219,414 /recurse:Scripts/*.cs", parms.CompilerOptions); From 83def12a671e899f63be5a229ab076da4e6a3ec4 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 11:19:42 -0700 Subject: [PATCH 47/93] Added TargetHelper for scripts --- .../Game/Managers/TargetManager.cs | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/Managers/TargetManager.cs b/src/ClassicUO.Client/Game/Managers/TargetManager.cs index 4852dbc421..082a768971 100644 --- a/src/ClassicUO.Client/Game/Managers/TargetManager.cs +++ b/src/ClassicUO.Client/Game/Managers/TargetManager.cs @@ -39,6 +39,9 @@ using ClassicUO.Network; using ClassicUO.Resources; using ClassicUO.Utility; +using System; +using System.Threading; +using System.Threading.Tasks; namespace ClassicUO.Game.Managers { @@ -304,13 +307,17 @@ public static void Target(uint serial) case CursorTarget.Invalid: return; case CursorTarget.Internal: + LastTargetInfo.SetEntity(serial); + ClearTargetingWithoutTargetCancelPacket(); + Mouse.CancelDoubleClick = true; + break; case CursorTarget.MultiPlacement: case CursorTarget.Position: case CursorTarget.Object: case CursorTarget.HueCommandTarget: case CursorTarget.SetTargetClientSide: - if (TargetingState != CursorTarget.Internal && entity != World.Player) + if (entity != World.Player) { LastTargetInfo.SetEntity(serial); } @@ -566,4 +573,86 @@ private static void TargetPacket(ushort graphic, ushort x, ushort y, sbyte z) ClearTargetingWithoutTargetCancelPacket(); } } + + public static class TargetHelper + { + private static CancellationTokenSource _executingSource = new CancellationTokenSource(); + + /// + /// Request the player to target a gump + /// + /// + public static async void TargetGump(Action onTarget) + { + var serial = await TargetAsync(); + if (serial == 0) return; + + var g = UIManager.GetGump(serial); + if (g == null) + { + GameActions.Print($"Failed to find the targeted gump (0x{serial:X})."); + return; + } + + onTarget(g); + } + + /// + /// Request the player target an item or mobile + /// + /// + /// + public static async Task TargetObject(Action onTargeted) + { + var serial = await TargetAsync(); + if (serial == 0) return; + + var untyped = World.Get(serial); + if (untyped == null) + { + GameActions.Print($"Failed to find the targeted entity (0x{serial:X})."); + return; + } + + onTargeted(untyped); + } + + public static async Task TargetAsync() + { + if (TargetManager.IsTargeting) TargetManager.CancelTarget(); + + if (CUOEnviroment.Debug) + { + GameActions.Print($"Waiting for Target."); + } + + // Abort any previous running task + var newSource = new CancellationTokenSource(); + Interlocked.Exchange(ref _executingSource, newSource).Cancel(); + + // Set target + TargetManager.SetTargeting(CursorTarget.Internal, CursorType.Target, TargetType.Neutral); + + // Wait for target + while (!newSource.IsCancellationRequested && TargetManager.IsTargeting) + { + try + { + await Task.Delay(250, newSource.Token); + } + catch + { + // ignored + } + } + + if (newSource.IsCancellationRequested) + { + GameActions.Print($"Target request was cancelled."); + return 0; + } + + return TargetManager.LastTargetInfo.Serial; + } + } } \ No newline at end of file From cf011f9708535ce074ec1401a67958d76cd32b04 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 12:58:23 -0700 Subject: [PATCH 48/93] Add cooldown bar fix and update version --- src/ClassicUO.Client/ClassicUO.Client.csproj | 4 ++-- src/ClassicUO.Client/Game/UI/Gumps/CoolDownBar.cs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index ea3d6745d6..f22236e4cb 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -5,8 +5,8 @@ cuoicon.ico ClassicUO ClassicUO - 3.20.0 - 3.20.0 + 3.21.1 + 3.21.1 diff --git a/src/ClassicUO.Client/Game/UI/Gumps/CoolDownBar.cs b/src/ClassicUO.Client/Game/UI/Gumps/CoolDownBar.cs index fa08ea746a..05a066d278 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/CoolDownBar.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/CoolDownBar.cs @@ -65,6 +65,10 @@ public CoolDownBar(TimeSpan _duration, string _name, ushort _hue, int x, int y, } #region LABELS + if (_name.Length > 17) + { + _name = _name.Substring(0, 16) + ".."; + } textLabel = new Label(_name, true, _hue, background.Width, style: FontStyle.BlackBorder, align: Assets.TEXT_ALIGN_TYPE.TS_CENTER) { X = background.X From 8abb150bd52aac99cf9ac17986e183d08342b685 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 20:03:46 -0700 Subject: [PATCH 49/93] Fix potential crash --- src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index 0db0ccfd32..374ae32954 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -5104,16 +5104,19 @@ protected override void OnMouseEnter(int x, int y) protected override void OnMouseWheel(MouseEventType delta) { + if (IsDisposed || _scrollBar == null) + { + return; + } + switch (delta) { case MouseEventType.WheelScrollUp: _scrollBar.Value -= _scrollBar.ScrollStep; - break; case MouseEventType.WheelScrollDown: _scrollBar.Value += _scrollBar.ScrollStep; - break; } } From 77173a72e5c5b819a099b8f892e2c5a1880f9471 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 20:08:13 -0700 Subject: [PATCH 50/93] Potential crash fix --- src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index e882506029..b602d26380 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -619,7 +619,7 @@ public override void Update() } } - if (_background.Hue != 912) + if (_background != null && _background.Hue != 912) { _background.Hue = 912; } @@ -1903,7 +1903,10 @@ public override void Update() } } - _bars[0].IsVisible = false; + if (_bars[0] != null) + { + _bars[0].IsVisible = false; + } } } From df381553c0514c7d68e718dfa62665932f53349a Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:17:10 -0700 Subject: [PATCH 51/93] Update dev deploy, windows first and dotnet 8 --- .github/workflows/tuo-dev-deploy.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tuo-dev-deploy.yml b/.github/workflows/tuo-dev-deploy.yml index 3e75667653..6553f5d975 100644 --- a/.github/workflows/tuo-dev-deploy.yml +++ b/.github/workflows/tuo-dev-deploy.yml @@ -43,7 +43,7 @@ jobs: strategy: max-parallel: 1 matrix: - os: [ macos-latest, ubuntu-latest, windows-latest ] + os: [ windows-latest, macos-latest, ubuntu-latest ] steps: - uses: actions/checkout@v4 @@ -55,11 +55,11 @@ jobs: git config --global url."https://".insteadOf git:// git submodule update --init --recursive - - name: Setup .NET 7 - uses: actions/setup-dotnet@v3 + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x - include-prerelease: true + dotnet-version: 8.0.x + cache: true - name: Build run: dotnet publish ${{ env.CUO_PROJECT_PATH }} -c Release -o ${{ env.CUO_OUTPUT_PATH }} -p:IS_DEV_BUILD=true From d59b987682a39bcbf6a5ddaf889185ade6e8dda4 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:22:24 -0700 Subject: [PATCH 52/93] Update build-test.yml Update to dotnet 8 --- .github/workflows/build-test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1f5801bdf5..839386633e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,21 +26,21 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ macos-latest, ubuntu-latest, windows-latest ] + os: [ windows-latest, macos-latest, ubuntu-latest ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Get submodules run: | git config --global url."https://".insteadOf git:// git submodule update --init --recursive - - name: Setup .NET 7 - uses: actions/setup-dotnet@v3 + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x - include-prerelease: true + dotnet-version: 8.0.x + cache: true - name: Restore run: dotnet restore From f03729f42d312b4ea8610b0e2fbe8816174c70cc Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:24:28 -0700 Subject: [PATCH 53/93] Update tuo-deploy.yml --- .github/workflows/tuo-deploy.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tuo-deploy.yml b/.github/workflows/tuo-deploy.yml index 3be090fe56..cc4974c5b4 100644 --- a/.github/workflows/tuo-deploy.yml +++ b/.github/workflows/tuo-deploy.yml @@ -38,27 +38,27 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build: - # if: ${{ github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ${{ matrix.os }} strategy: max-parallel: 1 matrix: - os: [ macos-latest, ubuntu-latest, windows-latest ] + os: [ windows-latest, macos-latest, ubuntu-latest ] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Get submodules run: | git config --global url."https://".insteadOf git:// git submodule update --init --recursive - - name: Setup .NET 7 - uses: actions/setup-dotnet@v3 + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x - include-prerelease: true + dotnet-version: 8.0.x + cache: true - name: Build run: dotnet publish ${{ env.CUO_PROJECT_PATH }} -c Release -o ${{ env.CUO_OUTPUT_PATH }} -p:IS_DEV_BUILD=true From b78ae769831a8276cefffa47c89782b0f63070cb Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:26:55 -0700 Subject: [PATCH 54/93] Update build-test.yml --- .github/workflows/build-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 839386633e..fe62de37c7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -40,7 +40,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - cache: true - name: Restore run: dotnet restore From d751710c7cbc5303fdccffebd1d29b6271d7555b Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:28:31 -0700 Subject: [PATCH 55/93] Update tuo-deploy.yml --- .github/workflows/tuo-deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tuo-deploy.yml b/.github/workflows/tuo-deploy.yml index cc4974c5b4..d9f4bf5129 100644 --- a/.github/workflows/tuo-deploy.yml +++ b/.github/workflows/tuo-deploy.yml @@ -58,7 +58,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - cache: true - name: Build run: dotnet publish ${{ env.CUO_PROJECT_PATH }} -c Release -o ${{ env.CUO_OUTPUT_PATH }} -p:IS_DEV_BUILD=true From 6906e66478bf9dab00b1bdb9e5e7b33bbd5ff91a Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:28:45 -0700 Subject: [PATCH 56/93] Update tuo-dev-deploy.yml --- .github/workflows/tuo-dev-deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tuo-dev-deploy.yml b/.github/workflows/tuo-dev-deploy.yml index 6553f5d975..b052c1ec95 100644 --- a/.github/workflows/tuo-dev-deploy.yml +++ b/.github/workflows/tuo-dev-deploy.yml @@ -59,7 +59,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - cache: true - name: Build run: dotnet publish ${{ env.CUO_PROJECT_PATH }} -c Release -o ${{ env.CUO_OUTPUT_PATH }} -p:IS_DEV_BUILD=true From 4512856a84b5b2013aff46dbf0a41964ef004e92 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:28:59 -0700 Subject: [PATCH 57/93] Delete .github/workflows/deploy.yml --- .github/workflows/deploy.yml | 92 ------------------------------------ 1 file changed, 92 deletions(-) delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 7aa2321b12..0000000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: Deploy - -on: - workflow_dispatch: - # workflow_run: - # branches: [main] - # workflows: [Build-Test] - # types: - # - completed - - -env: - CUO_ASSEMBLY_VERSION: '0.1.11.${{ github.run_number }}' - CUO_OUTPUT_PATH: '../../bin/dist' - CUO_PROJECT_PATH: "src/ClassicUO.Client/ClassicUO.Client.csproj" - CUO_ZIP_NAME: "ClassicUO-dev-preview-release.zip" - - DOTNET_NOLOGO: false - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - NUGET_XMLDOC_MODE: skip - -jobs: - build: - #if: ${{ github.event.workflow_run.conclusion == 'success' }} && "${{ env.GITHUB_REPOSITORY }}" == 'ClassicUO/ClassicUO' - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ ubuntu-latest ] - - steps: - - uses: actions/checkout@v2 - - - name: Get submodules - run: | - git config --global url."https://".insteadOf git:// - git submodule update --init --recursive - - - name: Setup .NET 7 - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 7.0.x - include-prerelease: true - - - name: Build - run: dotnet publish ${{ env.CUO_PROJECT_PATH }} -c Release -o ${{ env.CUO_OUTPUT_PATH }} -p:IS_DEV_BUILD=true -p:AssemblyVersion=${{ env.CUO_ASSEMBLY_VERSION }} -p:FileVersion=${{ env.CUO_ASSEMBLY_VERSION }} - - - name: Create manifest - run: | - dotnet run --project tools/ManifestCreator/ManifestCreator.csproj "${{ env.CUO_OUTPUT_PATH }}" "dev-preview" "${{ env.CUO_ZIP_NAME }}" - mkdir upload - mv manifest.xml upload - - - name: Create package - uses: thedoctor0/zip-release@master - with: - type: 'zip' - directory: ${{ env.CUO_OUTPUT_PATH }} - filename: ${{ env.CUO_ZIP_NAME }} - exclusions: '*.zip manifest.xml' - - - name: Move output - run: mv "${{ env.CUO_OUTPUT_PATH }}/${{ env.CUO_ZIP_NAME }}" upload - - - name: Remove old Release - uses: dev-drprasad/delete-tag-and-release@v0.2.1 - with: - delete_release: true - tag_name: ClassicUO-dev-release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload Release - uses: ncipollo/release-action@v1 - with: - artifacts: "upload/${{ env.CUO_ZIP_NAME }}" - name: 'ClassicUO dev preview' - body: 'This build may not be safe: use it at your own risk.' - prerelease: true - tag: ClassicUO-dev-release - token: ${{ secrets.GITHUB_TOKEN }} - - - name: FTP Deploy - uses: SamKirkland/FTP-Deploy-Action@4.3.2 - with: - server: ftp.classicuo.eu - username: ${{ secrets.FTP_USER }} - password: ${{ secrets.FTP_PSW }} - protocol: ftps - server-dir: /www.classicuo.eu/dev/deploy/ - local-dir: upload/ - From 24c6e7abda1b03cd12bb0e04153afc7abe3b333a Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 21 Feb 2024 04:35:11 -0700 Subject: [PATCH 58/93] More potential crash fixes --- src/ClassicUO.Client/ClassicUO.Client.csproj | 4 ++-- src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index f22236e4cb..ef724ed285 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -5,8 +5,8 @@ cuoicon.ico ClassicUO ClassicUO - 3.21.1 - 3.21.1 + 3.21.2 + 3.21.2 diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index b602d26380..416aac2a7f 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -624,7 +624,7 @@ public override void Update() _background.Hue = 912; } - if (_hpLineRed.LineColor != HPB_COLOR_GRAY) + if (_hpLineRed != null && _hpLineRed.LineColor != HPB_COLOR_GRAY) { _hpLineRed.LineColor = HPB_COLOR_GRAY; From 90f14835f0e90a49c4d40bf43d433dac3275627c Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 21:52:59 -0700 Subject: [PATCH 59/93] Potential crash fix --- src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index 416aac2a7f..2f04dfc1cb 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -636,7 +636,10 @@ public override void Update() } } - _bars[0].IsVisible = false; + if (_bars[0] != null) + { + _bars[0].IsVisible = false; + } } } From 3cc08c785b1a8601e8df472b175ab935caa443b9 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 22:46:17 -0700 Subject: [PATCH 60/93] Add afterdispose method --- src/ClassicUO.Client/Game/UI/Controls/Control.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index f418e60e2d..b591bb7ab7 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -1063,6 +1063,12 @@ public virtual void Dispose() } IsDisposed = true; + AfterDispose(); } + + /// + /// Called after the control has been disposed. + /// + public virtual void AfterDispose() { } } } \ No newline at end of file From 3311b29018d15d5fec5304ad66ffb3dcecfbe731 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 22:46:41 -0700 Subject: [PATCH 61/93] Move textbox dispose to afterdispose --- .../Game/UI/Gumps/HealthBarGump.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index 2f04dfc1cb..553fb4fc1b 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -140,17 +140,12 @@ protected set { } protected abstract void BuildGump(); - - public override void Dispose() + public override void AfterDispose() { - /*if (TargetManager.LastAttack != LocalSerial) - { - GameActions.SendCloseStatus(LocalSerial); - }*/ + base.AfterDispose(); _textBox?.Dispose(); _textBox = null; - base.Dispose(); } protected override void OnMove(int x, int y) @@ -410,6 +405,11 @@ protected override void OnMouseOver(int x, int y) public override bool Draw(UltimaBatcher2D batcher, int x, int y) { + if (IsDisposed) + { + return false; + } + base.Draw(batcher, x, y); if (Keyboard.Alt && UIManager.MouseOverControl != null && (UIManager.MouseOverControl == this || UIManager.MouseOverControl.RootParent == this)) @@ -1829,7 +1829,7 @@ public override void Update() { base.Update(); - if (IsDisposed /* || (_textBox != null && _textBox.IsDisposed)*/) + if (IsDisposed) { return; } @@ -2096,7 +2096,6 @@ public override void Update() } } - if (_bars.Length > 0 && _bars[0].Hue != hpForegroundHue) //HP Foreground { _bars[0].Hue = hpForegroundHue; From 0e88a9aab58269b80ed892de43f8700b9cdedf46 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Tue, 20 Feb 2024 23:00:56 -0700 Subject: [PATCH 62/93] Fix healthbar gump causing crashes --- .../Game/UI/Gumps/HealthBarGump.cs | 23 ++++++++++++------- .../Game/UI/Gumps/VersionHistory.cs | 3 +++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index 553fb4fc1b..f222daa0ab 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -54,6 +54,7 @@ internal abstract class BaseHealthBarGump : AnchorableGump public bool IsLastAttackBar { get; set; } = false; public static BaseHealthBarGump LastAttackBar { get; set; } + protected bool HasBeenBuilt { get; set; } = false; protected BaseHealthBarGump(Entity entity) : this(0, 0) { @@ -71,6 +72,7 @@ protected BaseHealthBarGump(Entity entity) : this(0, 0) _isDead = entity is Mobile mm && mm.IsDead; BuildGump(); + HasBeenBuilt = true; } public virtual void SetNewMobile(uint serial) @@ -83,6 +85,7 @@ public virtual void SetNewMobile(uint serial) Children.Clear(); BuildGump(); + HasBeenBuilt = true; } } @@ -140,13 +143,13 @@ protected set { } protected abstract void BuildGump(); - public override void AfterDispose() - { - base.AfterDispose(); + //public override void AfterDispose() + //{ + // base.AfterDispose(); - _textBox?.Dispose(); - _textBox = null; - } + // _textBox?.Dispose(); + // _textBox = null; + //} protected override void OnMove(int x, int y) { @@ -176,6 +179,7 @@ public override void Restore(XmlElement xml) { _name = World.Player.Name; BuildGump(); + HasBeenBuilt = true; } else if (ProfileManager.CurrentProfile.SaveHealthbars) { @@ -196,6 +200,7 @@ public override void Restore(XmlElement xml) _outOfRange = true; BuildGump(); + HasBeenBuilt = true; } else { @@ -535,6 +540,7 @@ protected override void UpdateContents() if (_textBox != null) { _textBox.MouseUp -= TextBoxOnMouseUp; + _textBox.Dispose(); } _textBox = null; @@ -546,7 +552,7 @@ public override void Update() { base.Update(); - if (IsDisposed) + if (IsDisposed || !HasBeenBuilt) { return; } @@ -1580,6 +1586,7 @@ protected override void UpdateContents() if (_textBox != null) { _textBox.MouseUp -= TextBoxOnMouseUp; + _textBox.Dispose(); } _textBox = null; @@ -1829,7 +1836,7 @@ public override void Update() { base.Update(); - if (IsDisposed) + if (IsDisposed || !HasBeenBuilt) { return; } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index da5442db10..623446d798 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -8,6 +8,9 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { + "/c[white][3.21.2]/cd\n" + + "-A bugfix release for 3.21 causing crashes", + "/c[white][3.21.0]/cd\n" + "- A few bug fixes\n" + "- A few fixes from CUO\n" + From ba4fb75edb49912cafb456b073dbd931f0269f12 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 22 Feb 2024 15:42:46 -0700 Subject: [PATCH 63/93] Fix a gump animation bug --- .../Game/UI/Controls/Control.cs | 27 +++++++++++++++---- .../Game/UI/Gumps/VersionHistory.cs | 5 +++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index b591bb7ab7..beba9e6566 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -315,6 +315,28 @@ public virtual bool Draw(UltimaBatcher2D batcher, int x, int y) } batcher.ClipBegin(x + offset, y + offset, Width - (offset * 2), Height - (offset * 2)); + + for (int i = 0; i < Children.Count; i++) + { + if (Children.Count <= i) + { + break; + } + Control c = Children.ElementAt(i); + + if (c != null && (c.Page == 0 || c.Page == ActivePage)) + { + if (c.IsVisible) + { + c.Draw(batcher, c.X + x, c.Y + y); + } + } + } + + DrawDebug(batcher, x, y); + + batcher.ClipEnd(); + return true; } for (int i = 0; i < Children.Count; i++) @@ -336,11 +358,6 @@ public virtual bool Draw(UltimaBatcher2D batcher, int x, int y) DrawDebug(batcher, x, y); - if (delayedDispose) - { - batcher.ClipEnd(); - } - return true; } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index 623446d798..dadec02ae1 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -8,8 +8,11 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { + "/c[white][3.22.0]/cd\n" + + "- Changes to improve gump closing animations", + "/c[white][3.21.2]/cd\n" + - "-A bugfix release for 3.21 causing crashes", + "- A bugfix release for 3.21 causing crashes", "/c[white][3.21.0]/cd\n" + "- A few bug fixes\n" + From d0fc10b75a32e5cc013bbb6d672a1d55363d7bfb Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 22 Feb 2024 15:44:33 -0700 Subject: [PATCH 64/93] Remove gump animations from server-side gumps --- src/ClassicUO.Client/Game/UI/Controls/Control.cs | 2 +- src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index beba9e6566..deb7900914 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -1062,7 +1062,7 @@ public virtual void Dispose() if (this is Gumps.Gump) { - if (!delayedDispose && World.InGame && Parent == null && ProfileManager.CurrentProfile != null && ProfileManager.CurrentProfile.EnableGumpCloseAnimation) + if (!delayedDispose && !IsFromServer && World.InGame && Parent == null && ProfileManager.CurrentProfile != null && ProfileManager.CurrentProfile.EnableGumpCloseAnimation) { delayedDispose = true; return; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index dadec02ae1..25b8400ecc 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -8,7 +8,7 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { - "/c[white][3.22.0]/cd\n" + + "/c[white][3.21.3]/cd\n" + "- Changes to improve gump closing animations", "/c[white][3.21.2]/cd\n" + From 4b18fbce6d151a59094a94d22c8705a177c38b5a Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 22 Feb 2024 15:45:12 -0700 Subject: [PATCH 65/93] Update version --- src/ClassicUO.Client/ClassicUO.Client.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index ef724ed285..2d3f7767c8 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -5,8 +5,8 @@ cuoicon.ico ClassicUO ClassicUO - 3.21.2 - 3.21.2 + 3.21.3 + 3.21.3 From a25db86ccd66d8b1ee9e6e2bd777ce876ddbcd21 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 23 Feb 2024 21:16:18 -0700 Subject: [PATCH 66/93] Fix no draw roof tile option and modern transparency --- .../Game/Scenes/GameSceneDrawingSorting.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs b/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs index 527a343e60..5e939d5a54 100644 --- a/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs +++ b/src/ClassicUO.Client/Game/Scenes/GameSceneDrawingSorting.cs @@ -400,16 +400,16 @@ out bool allowSelection allowSelection = false; return true; } + if (itemData.IsRoof && _noDrawRoofs) + { + return false; + } if (itemData.IsDoor || itemData.IsRoof) { obj.AlphaHue = 65; allowSelection = itemData.IsDoor; return true; } - if (itemData.IsRoof && _noDrawRoofs) - { - return false; - } if (itemData.IsFoliage || obj.Graphic == Constants.TREE_REPLACE_GRAPHIC || StaticFilters.IsTree(obj.Graphic, out var _) || (!itemData.IsMultiMovable && obj is Static stat && stat.IsVegetation) || (!itemData.IsMultiMovable && obj is Multi multi && multi.IsVegetation)) { obj.AlphaHue = 65; From 4f4c20cfc87a908461d68413cb5140877775325e Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Fri, 23 Feb 2024 21:36:54 -0700 Subject: [PATCH 67/93] Public NameOver'HeadManager for scripts --- .../Game/Managers/NameOverHeadManager.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/NameOverHeadManager.cs b/src/ClassicUO.Client/Game/Managers/NameOverHeadManager.cs index 82f79758bb..45643541fd 100644 --- a/src/ClassicUO.Client/Game/Managers/NameOverHeadManager.cs +++ b/src/ClassicUO.Client/Game/Managers/NameOverHeadManager.cs @@ -30,12 +30,6 @@ #endregion -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.GameObjects; @@ -43,11 +37,17 @@ using ClassicUO.Input; using ClassicUO.Utility.Logging; using SDL2; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; namespace ClassicUO.Game.Managers { [Flags] - internal enum NameOverheadOptions + public enum NameOverheadOptions { None = 0, @@ -84,7 +84,7 @@ internal enum NameOverheadOptions MobilesAndCorpses = AllMobiles | MonsterCorpses | HumanoidCorpses, } - internal static class NameOverHeadManager + public static class NameOverHeadManager { private static NameOverHeadHandlerGump _gump; private static SDL.SDL_Keycode _lastKeySym = SDL.SDL_Keycode.SDLK_UNKNOWN; @@ -132,7 +132,7 @@ private static bool HandleMobileOverhead(Entity serial) if (mobile == null) return false; - if(mobile.Equals(World.Player) && ActiveOverheadOptions.HasFlag(NameOverheadOptions.ExcludeSelf)) + if (mobile.Equals(World.Player) && ActiveOverheadOptions.HasFlag(NameOverheadOptions.ExcludeSelf)) return false; // Mobile types @@ -167,7 +167,7 @@ private static bool HandleMobileOverhead(Entity serial) if (ActiveOverheadOptions.HasFlag(NameOverheadOptions.Invulnerable) && mobile.NotorietyFlag == NotorietyFlag.Invulnerable) return true; - if(ActiveOverheadOptions.HasFlag(NameOverheadOptions.Self) && mobile.Equals(World.Player)) + if (ActiveOverheadOptions.HasFlag(NameOverheadOptions.Self) && mobile.Equals(World.Player)) return true; return false; @@ -407,7 +407,7 @@ public static void SetActiveOption(NameOverheadOption option) } } - internal class NameOverheadOption + public class NameOverheadOption { public NameOverheadOption(string name, SDL.SDL_Keycode key, bool alt, bool ctrl, bool shift, int optionflagscode) : this(name) { From 5938841324a8429d04abf3f3057365d2ac338966 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sat, 24 Feb 2024 12:09:51 -0700 Subject: [PATCH 68/93] Map order fix from CUO/broberson --- src/ClassicUO.Assets/MultiMapLoader.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Assets/MultiMapLoader.cs b/src/ClassicUO.Assets/MultiMapLoader.cs index f1cc0e0091..8fa6ab6e91 100644 --- a/src/ClassicUO.Assets/MultiMapLoader.cs +++ b/src/ClassicUO.Assets/MultiMapLoader.cs @@ -71,11 +71,12 @@ public override Task Load() { _file = new UOFile(path, true); } - + var facetFiles = Directory.GetFiles(UOFileManager.BasePath, "*.mul", SearchOption.TopDirectoryOnly) .Select(s => Regex.Match(s, "facet0.*\\.mul", RegexOptions.IgnoreCase)) .Where(s => s.Success) .Select(s => Path.Combine(UOFileManager.BasePath, s.Value)) + .OrderBy(s => s) .ToArray(); _facets = new UOFileMul[facetFiles.Length]; @@ -152,7 +153,7 @@ int endy while (_file.Position < _file.Length) { byte pic = _file.ReadByte(); - byte size = (byte) (pic & 0x7F); + byte size = (byte)(pic & 0x7F); bool colored = (pic & 0x80) != 0; int currentHeight = y * pheight; From 64df25c96460ee8efd552dcc53be48c49c0deb5b Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sat, 24 Feb 2024 12:15:44 -0700 Subject: [PATCH 69/93] Fix corpse graphics from CUO/Spasitjel --- src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs | 8 +++++--- src/ClassicUO.Renderer/Animations/Animation.cs | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs b/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs index 3a1dc3eb8e..bc2af04b31 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Views/ItemView.cs @@ -205,7 +205,7 @@ float depth byte animIndex = (byte)AnimIndex; ushort graphic = GetGraphicForAnimation(); - Client.Game.Animations.ConvertBodyIfNeeded(ref graphic); + Client.Game.Animations.ConvertBodyIfNeeded(ref graphic, isCorpse: IsCorpse); var animGroup = Client.Game.Animations.GetAnimType(graphic); var animFlags = Client.Game.Animations.GetAnimFlags(graphic); byte group = AnimationsLoader.Instance.GetDeathAction( @@ -603,7 +603,7 @@ out Dictionary map continue; } - Client.Game.Animations.ConvertBodyIfNeeded(ref graphic); + Client.Game.Animations.ConvertBodyIfNeeded(ref graphic, isCorpse: IsCorpse); var animGroup = Client.Game.Animations.GetAnimType(graphic); var animFlags = Client.Game.Animations.GetAnimFlags(graphic); byte group = AnimationsLoader.Instance.GetDeathAction( @@ -617,7 +617,9 @@ out Dictionary map group, direction, out _, - out var isUOP + out var isUOP, + false, + IsCorpse ); if (frames.IsEmpty) diff --git a/src/ClassicUO.Renderer/Animations/Animation.cs b/src/ClassicUO.Renderer/Animations/Animation.cs index b8b6d831d3..7dcebb8ba1 100644 --- a/src/ClassicUO.Renderer/Animations/Animation.cs +++ b/src/ClassicUO.Renderer/Animations/Animation.cs @@ -342,7 +342,8 @@ public void UpdateAnimationTable(BodyConvFlags flags) public void ConvertBodyIfNeeded( ref ushort graphic, bool isParent = false, - bool forceUOP = false + bool forceUOP = false, + bool isCorpse = false ) { if (graphic >= _dataIndex.Length) @@ -351,7 +352,9 @@ public void ConvertBodyIfNeeded( ushort hue = 0; if (_dataIndex[graphic] != null && _dataIndex[graphic].FileIndex == 0 && !_dataIndex[graphic].Flags.HasFlag(AnimationFlags.UseUopAnimation)) - AnimationsLoader.Instance.ReplaceBody(ref graphic, ref hue); + { + _ = isCorpse ? AnimationsLoader.Instance.ReplaceCorpse(ref graphic, ref hue) : AnimationsLoader.Instance.ReplaceBody(ref graphic, ref hue); + } } public bool AnimationExists(ushort graphic, byte group, bool isCorpse = false) From 3876fa4cddf3f854cd78df757147b8bd73da78a9 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sat, 24 Feb 2024 12:38:16 -0700 Subject: [PATCH 70/93] Make sure macros file and path exist before saving --- src/ClassicUO.Client/Game/Managers/MacroManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ClassicUO.Client/Game/Managers/MacroManager.cs b/src/ClassicUO.Client/Game/Managers/MacroManager.cs index f385badd50..b025e67d79 100644 --- a/src/ClassicUO.Client/Game/Managers/MacroManager.cs +++ b/src/ClassicUO.Client/Game/Managers/MacroManager.cs @@ -137,6 +137,12 @@ public void Save() string path = Path.Combine(ProfileManager.ProfilePath, "macros.xml"); + if (!File.Exists(path)) + { + Directory.CreateDirectory(ProfileManager.ProfilePath); + File.Create(path).Close(); + } + using (XmlTextWriter xml = new XmlTextWriter(path, Encoding.UTF8) { Formatting = Formatting.Indented, From 7b28c48604d15db2fd83481f6510db0ee8dd17c8 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sat, 24 Feb 2024 13:56:58 -0700 Subject: [PATCH 71/93] A little cleanup in tooltip --- src/ClassicUO.Client/Game/UI/Tooltip.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Tooltip.cs b/src/ClassicUO.Client/Game/UI/Tooltip.cs index e286bbad74..7eb9886166 100644 --- a/src/ClassicUO.Client/Game/UI/Tooltip.cs +++ b/src/ClassicUO.Client/Game/UI/Tooltip.cs @@ -30,16 +30,14 @@ #endregion -using System; -using System.Text; +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.GameObjects; -using ClassicUO.Assets; +using ClassicUO.Game.UI.Controls; using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; -using ClassicUO.Game.UI.Controls; namespace ClassicUO.Game.UI { @@ -128,7 +126,7 @@ public bool Draw(UltimaBatcher2D batcher, int x, int y) fontSize = ProfileManager.CurrentProfile.SelectedToolTipFontSize; } - _textBox = new TextBox( + _textBox = new TextBox( TextBox.ConvertHtmlToFontStashSharpCommand(finalString).Trim(), font, fontSize, @@ -136,7 +134,7 @@ public bool Draw(UltimaBatcher2D batcher, int x, int y) hue, align, true - ); + ); _textBox.Width = _textBox.MeasuredSize.X + 10; if (_textBox.Width > 600) _textBox.Width = 600; From 284cb7a8c46ddf5d28a9e235ddb8c9a67d3e2640 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sat, 24 Feb 2024 23:55:02 -0700 Subject: [PATCH 72/93] Fix tmap crash with gump closing animation --- .../Game/UI/Controls/Control.cs | 4 +-- src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index deb7900914..f8ce263e0f 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -1060,9 +1060,9 @@ public virtual void Dispose() return; } - if (this is Gumps.Gump) + if (!delayedDispose && this is Gumps.Gump) { - if (!delayedDispose && !IsFromServer && World.InGame && Parent == null && ProfileManager.CurrentProfile != null && ProfileManager.CurrentProfile.EnableGumpCloseAnimation) + if (!IsFromServer && World.InGame && Parent == null && ProfileManager.CurrentProfile != null && ProfileManager.CurrentProfile.EnableGumpCloseAnimation) { delayedDispose = true; return; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs index 6e65b533c1..b9ef767e81 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs @@ -30,8 +30,6 @@ #endregion -using System; -using System.Collections.Generic; using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Controls; using ClassicUO.Input; @@ -39,6 +37,8 @@ using ClassicUO.Renderer; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; namespace ClassicUO.Game.UI.Gumps { @@ -93,8 +93,9 @@ public MapGump(uint serial, ushort gumpid, int width, int height) : base(serial, _hit.MouseUp += TextureControlOnMouseUp; MenuButton menu = new MenuButton(25, Color.Black.PackedValue, 0.75f, "Menu") { X = width + 44 - 43, Y = 6 }; - - menu.MouseUp += (s, e) => { + + menu.MouseUp += (s, e) => + { menu.ContextMenu?.Show(); }; menu.ContextMenu = new ContextMenuControl(); @@ -117,12 +118,13 @@ public MapGump(uint serial, ushort gumpid, int width, int height) : base(serial, } } })); - menu.ContextMenu.Add(new ContextMenuItemEntry("Try to pathfind", () => { + menu.ContextMenu.Add(new ContextMenuItemEntry("Try to pathfind", () => + { if (foundMapLoc) { - int distance = Math.Max(Math.Abs(World.Player.X - mapX), Math.Abs(World.Player.Y - mapY)); + int distance = Math.Max(Math.Abs(World.Player.X - mapX), Math.Abs(World.Player.Y - mapY)); - if(distance > 10) + if (distance > 10) { GameActions.Print("You're too far away to try to pathfind, you need to be within 10 tiles.", 32); return; @@ -193,8 +195,8 @@ public void AddPin(int x, int y) // multiplier = 0.666666666f; //if (Width == 600) // multiplier = 2f; - if (CUOEnviroment.Debug) - GameActions.Print($"Width: {Width}, Multiplier: {multiplier}, Facet: {mapFacet}, MapData: {mapX}, {mapY}, {mapEndX}, {mapEndY}"); + if (CUOEnviroment.Debug) + GameActions.Print($"Width: {Width}, Multiplier: {multiplier}, Facet: {mapFacet}, MapData: {mapX}, {mapY}, {mapEndX}, {mapEndY}"); mapX = (int)(mapX + (x * multiplier)); mapY = (int)(mapY + (y * multiplier)); @@ -467,11 +469,13 @@ private int LineUnderMouse(ref int x1, ref int y1, ref int x2, ref int y2) return result; } - public override void Dispose() + public override void AfterDispose() { - _hit.MouseUp -= TextureControlOnMouseUp; - _mapTexture?.Dispose(); - base.Dispose(); + base.AfterDispose(); + if (_hit != null) + { + _hit.MouseUp -= TextureControlOnMouseUp; + } } private enum ButtonType From 429552d61cb3830f78099fd6868ac6df0b90ae65 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sun, 25 Feb 2024 14:01:59 -0700 Subject: [PATCH 73/93] Potential bug fix --- .../Game/UI/Controls/Control.cs | 2 + .../Game/UI/Gumps/GridContainer.cs | 43 ++++++++++--------- .../Game/UI/Gumps/VersionHistory.cs | 3 ++ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index f8ce263e0f..c6c2cf5c19 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -72,6 +72,8 @@ protected Control(Control parent = null) IsEnabled = true; } + public bool IsDelayedDisposed { get { return delayedDispose; } } + public virtual ClickPriority Priority { get; set; } = ClickPriority.Default; public uint ServerSerial { get; set; } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs index f067a0c702..3317a7de0c 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs @@ -491,7 +491,7 @@ private void UpdateItems(bool overrideSort = false) if (autoSortContainer) overrideSort = true; - List sortedContents = ProfileManager.CurrentProfile.GridContainerSearchMode == 0 ? gridSlotManager.SearchResults(searchBox.Text) : GridSlotManager.GetItemsInContainer(container); + List sortedContents = (ProfileManager.CurrentProfile is null || ProfileManager.CurrentProfile.GridContainerSearchMode == 0) ? gridSlotManager.SearchResults(searchBox.Text) : GridSlotManager.GetItemsInContainer(container); gridSlotManager.RebuildContainer(sortedContents, searchBox.Text, overrideSort); InvalidateContents = false; @@ -578,7 +578,7 @@ public override void Update() Item item = container; - if (item == null || item.IsDestroyed) + if (item is null || item.IsDestroyed) { Dispose(); return; @@ -595,24 +595,27 @@ public override void Update() if ((lastWidth != Width || lastHeight != Height) || lastGridItemScale != gridItemSize) { - lastGridItemScale = gridItemSize; - background.Width = Width - (borderWidth * 2); - background.Height = Height - (borderWidth * 2); - scrollArea.Width = background.Width; - scrollArea.Height = background.Height - TOP_BAR_HEIGHT; - openRegularGump.X = Width - openRegularGump.Width - borderWidth; - quickDropBackpack.X = openRegularGump.X - quickDropBackpack.Width; - sortContents.X = quickDropBackpack.X - sortContents.Width; - lastHeight = Height; - lastWidth = Width; - searchBox.Width = Math.Min(Width - (borderWidth * 2) - openRegularGump.Width - quickDropBackpack.Width - sortContents.Width, 150); - backgroundTexture.Width = background.Width; - backgroundTexture.Height = background.Height; - backgroundTexture.Alpha = background.Alpha; - backgroundTexture.Hue = background.Hue; - setLootBag.Y = Height - 20; - if (IsPlayerBackpack) - ProfileManager.CurrentProfile.BackpackGridSize = new Point(Width, Height); + if (!IsDelayedDisposed) + { + lastGridItemScale = gridItemSize; + background.Width = Width - (borderWidth * 2); + background.Height = Height - (borderWidth * 2); + scrollArea.Width = background.Width; + scrollArea.Height = background.Height - TOP_BAR_HEIGHT; + openRegularGump.X = Width - openRegularGump.Width - borderWidth; + quickDropBackpack.X = openRegularGump.X - quickDropBackpack.Width; + sortContents.X = quickDropBackpack.X - sortContents.Width; + lastHeight = Height; + lastWidth = Width; + searchBox.Width = Math.Min(Width - (borderWidth * 2) - openRegularGump.Width - quickDropBackpack.Width - sortContents.Width, 150); + backgroundTexture.Width = background.Width; + backgroundTexture.Height = background.Height; + backgroundTexture.Alpha = background.Alpha; + backgroundTexture.Hue = background.Hue; + setLootBag.Y = Height - 20; + if (IsPlayerBackpack) + ProfileManager.CurrentProfile.BackpackGridSize = new Point(Width, Height); + } RequestUpdateContents(); } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index 25b8400ecc..ad455840e3 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -8,6 +8,9 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { + "/c[white][3.21.4]/cd\n" + + "- Various bug fixes", + "/c[white][3.21.3]/cd\n" + "- Changes to improve gump closing animations", From 4f7312699d87307e0ae8bf13017323669a3675d8 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sun, 25 Feb 2024 14:25:46 -0700 Subject: [PATCH 74/93] Default to convert all html colors to FSS colors --- .../Game/UI/Controls/TextBox.cs | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs index 73a6cd214c..2e2a7ec004 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/TextBox.cs @@ -35,7 +35,6 @@ using ClassicUO.Renderer; using FontStashSharp.RichText; using Microsoft.Xna.Framework; -using System; using System.Text.RegularExpressions; namespace ClassicUO.Game.UI.Controls @@ -55,7 +54,7 @@ private int getStrokeSize { if (ProfileManager.CurrentProfile != null) return ProfileManager.CurrentProfile.TextBorderSize; - return 2; + return 1; } } @@ -70,8 +69,9 @@ public TextBox bool strokeEffect = true, bool supportsCommands = true, bool ignoreColorCommands = false, - bool calculateGlyphs = false - ) : this(text, font, size, width, ConvertHueToColor(hue), align, strokeEffect, supportsCommands, ignoreColorCommands, calculateGlyphs) { } + bool calculateGlyphs = false, + bool converthtmlcolors = true + ) : this(text, font, size, width, ConvertHueToColor(hue), align, strokeEffect, supportsCommands, ignoreColorCommands, calculateGlyphs, converthtmlcolors) { } public TextBox ( @@ -84,11 +84,19 @@ public TextBox bool strokeEffect = true, bool supportsCommands = true, bool ignoreColorCommands = false, - bool calculateGlyphs = false + bool calculateGlyphs = false, + bool converthtmlcolors = true ) { if (strokeEffect) + { text = $"/es[{getStrokeSize}]" + text; + } + + if (converthtmlcolors) + { + text = ConvertHTMLColorsToFSS(text); + } _rtl = new RichTextLayout { @@ -106,7 +114,7 @@ public TextBox _color = color; _align = align; - + ConvertHtmlColors = converthtmlcolors; AcceptMouseInput = true; Width = _rtl.Width == null ? _rtl.Size.X : (int)_rtl.Width; base.Height = _rtl.Size.Y; @@ -169,7 +177,15 @@ public string Text { if (_rtl.Text != value) { - _rtl.Text = value; + if (ConvertHtmlColors) + { + _rtl.Text = ConvertHTMLColorsToFSS(value); + } + else + { + _rtl.Text = value; + } + _dirty = true; } } @@ -222,13 +238,20 @@ public float Size } } + public bool ConvertHtmlColors { get; set; } + /// /// Update the text of the TextBox /// /// New string /// Set to null to ignore width, taking as much width as needed. - public void UpdateText(string text, int? width = null) + public void UpdateText(string text, int? width = null, bool converthtmlcolors = true) { + if (converthtmlcolors) + { + text = ConvertHTMLColorsToFSS(text); + } + if (width != null && width > 0) { _rtl = new RichTextLayout @@ -249,16 +272,30 @@ public void UpdateText(string text, int? width = null) } } - public static string ConvertHtmlToFontStashSharpCommand(string text) + public static string ConvertHTMLColorsToFSS(string text) { string finalString; - if (String.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) return ""; finalString = Regex.Replace(text, ".*?)\"?'?>", " /c[${color}]", RegexOptions.Multiline | RegexOptions.IgnoreCase); finalString = Regex.Replace(finalString, ".*?)\"?'?>", " /c[${color}]", RegexOptions.Multiline | RegexOptions.IgnoreCase); - finalString = finalString.Replace("", "/cd").Replace("", "/cd").Replace("
", "\n").Replace("
", "\n").Replace("\n", "\n/cd"); + finalString = finalString.Replace("", "/cd").Replace("", "/cd").Replace("\n", "\n/cd"); + + return finalString; + } + + public static string ConvertHtmlToFontStashSharpCommand(string text) + { + string finalString; + + if (string.IsNullOrEmpty(text)) + return ""; + + finalString = ConvertHTMLColorsToFSS(text); + + finalString = finalString.Replace("
", "\n").Replace("
", "\n"); finalString = finalString.Replace("", "").Replace("", ""); finalString = finalString.Replace("", "").Replace("", ""); finalString = finalString.Replace("", "").Replace("

", ""); From f07008a603e8650a0dd68bd615e832e0a18a6262 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Sun, 25 Feb 2024 16:47:32 -0700 Subject: [PATCH 75/93] Revery gump closing animations --- src/ClassicUO.Client/ClassicUO.Client.csproj | 4 +- .../Configuration/Language.cs | 1 - .../Game/UI/Controls/Control.cs | 64 ------------------- .../Game/UI/Gumps/GridContainer.cs | 40 ++++++------ .../Game/UI/Gumps/ModernOptionsGump.cs | 3 - .../Game/UI/Gumps/VersionHistory.cs | 3 +- 6 files changed, 23 insertions(+), 92 deletions(-) diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index 2d3f7767c8..580cba8ea0 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -5,8 +5,8 @@ cuoicon.ico ClassicUO ClassicUO - 3.21.3 - 3.21.3 + 3.21.4 + 3.21.4 diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 62a997955d..4f81cc2fa7 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -516,7 +516,6 @@ public class TazUO public string PlayerOffsetY { get; set; } = "Player Offset Y"; public string UseLandTexturesWhereAvailable { get; set; } = "Use land textures where available(Experimental)"; public string SOSGumpID { get; set; } = "SOS Gump ID"; - public string UseGumpClosingAnims { get; set; } = "Enable gump closing animation"; #endregion #region Tooltips diff --git a/src/ClassicUO.Client/Game/UI/Controls/Control.cs b/src/ClassicUO.Client/Game/UI/Controls/Control.cs index c6c2cf5c19..a75a062584 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/Control.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/Control.cs @@ -30,7 +30,6 @@ #endregion -using ClassicUO.Configuration; using ClassicUO.Game.Managers; using ClassicUO.Input; using ClassicUO.Renderer; @@ -56,8 +55,6 @@ public abstract class Control private bool _handlesKeyboardFocus; private Point _offset; private Control _parent; - private uint timetoclose = uint.MaxValue; - private bool delayedDispose = false; private float alpha = 1.0f; protected Control(Control parent = null) @@ -72,8 +69,6 @@ protected Control(Control parent = null) IsEnabled = true; } - public bool IsDelayedDisposed { get { return delayedDispose; } } - public virtual ClickPriority Priority { get; set; } = ClickPriority.Default; public uint ServerSerial { get; set; } @@ -291,56 +286,6 @@ public virtual bool Draw(UltimaBatcher2D batcher, int x, int y) return false; } - if (delayedDispose) - { - - if (timetoclose == uint.MaxValue) - { - timetoclose = Time.Ticks + 150; - } - - if (Time.Ticks >= timetoclose) - { - Dispose(); - return false; - } - - float prog = (float)(Time.Ticks - timetoclose + 151) / 150; - prog = Math.Max(0, Math.Min(1, prog)); // Ensure prog is within [0, 1] - - int offset = (int)(((float)Math.Min(Width, Height) * prog) / 2f); - - if (offset >= Width - (offset * 2) || offset >= Height - (offset * 2)) - { - Dispose(); - return false; - } - - batcher.ClipBegin(x + offset, y + offset, Width - (offset * 2), Height - (offset * 2)); - - for (int i = 0; i < Children.Count; i++) - { - if (Children.Count <= i) - { - break; - } - Control c = Children.ElementAt(i); - - if (c != null && (c.Page == 0 || c.Page == ActivePage)) - { - if (c.IsVisible) - { - c.Draw(batcher, c.X + x, c.Y + y); - } - } - } - - DrawDebug(batcher, x, y); - - batcher.ClipEnd(); - return true; - } - for (int i = 0; i < Children.Count; i++) { if (Children.Count <= i) @@ -1062,15 +1007,6 @@ public virtual void Dispose() return; } - if (!delayedDispose && this is Gumps.Gump) - { - if (!IsFromServer && World.InGame && Parent == null && ProfileManager.CurrentProfile != null && ProfileManager.CurrentProfile.EnableGumpCloseAnimation) - { - delayedDispose = true; - return; - } - } - if (Children != null) { foreach (Control c in Children) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs index 3317a7de0c..6282a0dba2 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/GridContainer.cs @@ -595,27 +595,25 @@ public override void Update() if ((lastWidth != Width || lastHeight != Height) || lastGridItemScale != gridItemSize) { - if (!IsDelayedDisposed) - { - lastGridItemScale = gridItemSize; - background.Width = Width - (borderWidth * 2); - background.Height = Height - (borderWidth * 2); - scrollArea.Width = background.Width; - scrollArea.Height = background.Height - TOP_BAR_HEIGHT; - openRegularGump.X = Width - openRegularGump.Width - borderWidth; - quickDropBackpack.X = openRegularGump.X - quickDropBackpack.Width; - sortContents.X = quickDropBackpack.X - sortContents.Width; - lastHeight = Height; - lastWidth = Width; - searchBox.Width = Math.Min(Width - (borderWidth * 2) - openRegularGump.Width - quickDropBackpack.Width - sortContents.Width, 150); - backgroundTexture.Width = background.Width; - backgroundTexture.Height = background.Height; - backgroundTexture.Alpha = background.Alpha; - backgroundTexture.Hue = background.Hue; - setLootBag.Y = Height - 20; - if (IsPlayerBackpack) - ProfileManager.CurrentProfile.BackpackGridSize = new Point(Width, Height); - } + lastGridItemScale = gridItemSize; + background.Width = Width - (borderWidth * 2); + background.Height = Height - (borderWidth * 2); + scrollArea.Width = background.Width; + scrollArea.Height = background.Height - TOP_BAR_HEIGHT; + openRegularGump.X = Width - openRegularGump.Width - borderWidth; + quickDropBackpack.X = openRegularGump.X - quickDropBackpack.Width; + sortContents.X = quickDropBackpack.X - sortContents.Width; + lastHeight = Height; + lastWidth = Width; + searchBox.Width = Math.Min(Width - (borderWidth * 2) - openRegularGump.Width - quickDropBackpack.Width - sortContents.Width, 150); + backgroundTexture.Width = background.Width; + backgroundTexture.Height = background.Height; + backgroundTexture.Alpha = background.Alpha; + backgroundTexture.Hue = background.Hue; + setLootBag.Y = Height - 20; + if (IsPlayerBackpack) + ProfileManager.CurrentProfile.BackpackGridSize = new Point(Width, Height); + RequestUpdateContents(); } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index 374ae32954..b89e237eb7 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2399,9 +2399,6 @@ private void BuildTazUO() }), true, page); content.BlankLine(); content.AddToRight(new InputFieldWithLabel(lang.GetTazUO.SOSGumpID, Theme.INPUT_WIDTH, profile.SOSGumpID.ToString(), true, (s, e) => { if (uint.TryParse(((InputField.StbTextBox)s).Text, out uint id)) { profile.SOSGumpID = id; } }), true, page); - content.BlankLine(); - content.AddToRight(new CheckboxWithLabel(lang.GetTazUO.UseGumpClosingAnims, 0, profile.EnableGumpCloseAnimation, (b) => { profile.EnableGumpCloseAnimation = b; }), true, page); - #endregion #region Tooltips diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index ad455840e3..a81653df6b 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -9,7 +9,8 @@ internal class VersionHistory : Gump { private static string[] updateTexts = { "/c[white][3.21.4]/cd\n" + - "- Various bug fixes", + "- Various bug fixes\n" + + "- Removed gump closing animation. Too many unforeseen issues with it.", "/c[white][3.21.3]/cd\n" + "- Changes to improve gump closing animations", From 4b50eea9b97604f96aa966fbc0a02a509531c858 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 28 Feb 2024 19:25:45 -0700 Subject: [PATCH 76/93] Fix spell book icons --- src/ClassicUO.Client/Game/UI/Gumps/SpellbookGump.cs | 9 +-------- src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs | 3 +++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/SpellbookGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/SpellbookGump.cs index d62192eade..7b4a72d3d1 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/SpellbookGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/SpellbookGump.cs @@ -759,14 +759,7 @@ out string reagents } else { - if (_spellBookType == SpellBookType.Ninjitsu && i > 0 && i < 9) - { - iconGraphic = (ushort)(iconStartGraphic + i); - } - else - { - iconGraphic = (ushort)spellDef.GumpIconID; //(ushort)(iconStartGraphic + i); - } + iconGraphic = (ushort)spellDef.GumpIconSmallID; GetSpellToolTip(out toolTipCliloc); } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index a81653df6b..b67420f4a2 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -8,6 +8,9 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { + "/c[white][3.21.5]/cd\n" + + "- Spell book icon fix", + "/c[white][3.21.4]/cd\n" + "- Various bug fixes\n" + "- Removed gump closing animation. Too many unforeseen issues with it.", From 9951d416ca0f639f1cd26da5c3696ca7260c49e5 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 28 Feb 2024 21:18:23 -0700 Subject: [PATCH 77/93] Add option for tmap and sos to be added as a marker instead of goto only --- src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs | 16 +++++++ .../Game/UI/Gumps/VersionHistory.cs | 6 ++- .../Game/UI/Gumps/WorldMapGump.cs | 43 +++++++++++++++++++ .../Network/PacketHandlers.cs | 14 +++++- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs index b9ef767e81..13e6aabe8c 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/MapGump.cs @@ -118,6 +118,22 @@ public MapGump(uint serial, ushort gumpid, int width, int height) : base(serial, } } })); + menu.ContextMenu.Add(new ContextMenuItemEntry("Add as marker on world map", () => + { + if (foundMapLoc) + { + WorldMapGump map = UIManager.GetGump(); + if (map != null) + { + if (mapFacet != -1) + { + map.AddUserMarker("TMap", mapX, mapY, mapFacet); + } + else + map.AddUserMarker("TMap", mapX, mapY, World.Map.Index); + } + } + })); menu.ContextMenu.Add(new ContextMenuItemEntry("Try to pathfind", () => { if (foundMapLoc) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index b67420f4a2..6846cac664 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -8,8 +8,10 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { - "/c[white][3.21.5]/cd\n" + - "- Spell book icon fix", + "/c[white][3.22.0]/cd\n" + + "- Spell book icon fix\n" + + "- Add option to add treasure maps as map markers instead of goto only\n" + + "- Added the same option for SOS messages", "/c[white][3.21.4]/cd\n" + "- Various bug fixes\n" + diff --git a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs index e6dab08429..461df726a7 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs @@ -2145,6 +2145,49 @@ private void SaveMakerOnPlayer(string markerName) mapMarkerFile?.Markers.Add(mapMarker); } + public void AddUserMarker(string markerName, int x, int y, int map, string color = "yellow") + { + if (!World.InGame) + { + return; + } + + if (string.IsNullOrWhiteSpace(markerName)) + { + GameActions.Print(ResGumps.InvalidMarkerName, 0x2A); + return; + } + + var markerCsv = $"{x},{y},{map},{markerName}, ,{color},{3}"; + using (var fileStream = File.Open(UserMarkersFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write)) + using (var streamWriter = new StreamWriter(fileStream)) + { + streamWriter.BaseStream.Seek(0, SeekOrigin.End); + streamWriter.WriteLine(markerCsv); + } + + var mapMarker = new WMapMarker + { + X = x, + Y = y, + Color = GetColor(color), + ColorName = color, + MapId = map, + MarkerIconName = "", + Name = markerName, + ZoomIndex = 3 + }; + + if (!string.IsNullOrWhiteSpace(mapMarker.MarkerIconName) && _markerIcons.TryGetValue(mapMarker.MarkerIconName, out Texture2D markerIconTexture)) + { + mapMarker.MarkerIcon = markerIconTexture; + } + + var mapMarkerFile = _markerFiles.FirstOrDefault(x => x.FullPath == UserMarkersFilePath); + + mapMarkerFile?.Markers.Add(mapMarker); + } + /// /// Reload User Markers File after Changes /// diff --git a/src/ClassicUO.Client/Network/PacketHandlers.cs b/src/ClassicUO.Client/Network/PacketHandlers.cs index b15a7916a0..1188b94f49 100644 --- a/src/ClassicUO.Client/Network/PacketHandlers.cs +++ b/src/ClassicUO.Client/Network/PacketHandlers.cs @@ -7246,7 +7246,7 @@ string[] lines if (ProfileManager.CurrentProfile != null) { - if (gumpID == 1426736667 || gumpID == ProfileManager.CurrentProfile.SOSGumpID) //SOS message gump + if (gumpID == ProfileManager.CurrentProfile.SOSGumpID) //SOS message gump { for (int i = 0; i < gump.Children.Count; i++) { @@ -7301,6 +7301,18 @@ string[] lines } gump.GoToMarker((int)location.X, (int)location.Y, true); })); + + menu.ContextMenu.Add(new ContextMenuItemEntry("Add marker on world map", () => + { + WorldMapGump gump = UIManager.GetGump(); + if (gump == null) + { + gump = new WorldMapGump(); + UIManager.Add(gump); + } + gump.AddUserMarker("SOS", (int)location.X, (int)location.Y, World.Map.Index); + })); + menu.ContextMenu.Add(new ContextMenuItemEntry("Close", () => { gump.Dispose(); From 4ff15b1bb10312c2b7c979c98b8edc8f5ac2e47b Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Thu, 29 Feb 2024 11:03:10 -0700 Subject: [PATCH 78/93] Add alt to drag select modifiers --- src/ClassicUO.Client/Configuration/Language.cs | 1 + src/ClassicUO.Client/Configuration/Profile.cs | 2 +- .../Game/Scenes/GameSceneInputHandler.cs | 15 ++++++++++++--- .../Game/UI/Gumps/ModernOptionsGump.cs | 8 ++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 4f81cc2fa7..8d8d5a2c4a 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -100,6 +100,7 @@ public class General public string SharedNone { get; set; } = "None"; public string SharedShift { get; set; } = "Shift"; public string SharedCtrl { get; set; } = "Ctrl"; + public string SharedAlt { get; set; } = "Alt"; #region General->General public string HighlightObjects { get; set; } = "Highlight objects under cursor"; diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index e3af7a2233..e2e24b3cab 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -231,7 +231,7 @@ public sealed class Profile public bool DisableCtrlQWBtn { get; set; } public bool DisableAutoMove { get; set; } public bool EnableDragSelect { get; set; } - public int DragSelectModifierKey { get; set; } // 0 = none, 1 = control, 2 = shift + public int DragSelectModifierKey { get; set; } // 0 = none, 1 = control, 2 = shift, 3 = alt public int DragSelect_PlayersModifier { get; set; } = 0; public int DragSelect_MonstersModifier { get; set; } = 0; public int DragSelect_NameplateModifier { get; set; } = 0; diff --git a/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs b/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs index 181ddb8b1b..a3df72479f 100644 --- a/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs +++ b/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs @@ -204,6 +204,11 @@ private bool DragSelectModifierActive() return true; } + if (ProfileManager.CurrentProfile.DragSelectModifierKey == 3 && Keyboard.Alt) + { + return true; + } + return false; } @@ -211,6 +216,7 @@ private void DoDragSelect() { bool ctrl = Keyboard.Ctrl; bool shift = Keyboard.Shift; + bool alt = Keyboard.Alt; if (_selectionStart.X > Mouse.Position.X) { @@ -263,19 +269,22 @@ private void DoDragSelect() { if (( (ProfileManager.CurrentProfile.DragSelect_PlayersModifier == 1 && ctrl) || - (ProfileManager.CurrentProfile.DragSelect_PlayersModifier == 2 && shift) + (ProfileManager.CurrentProfile.DragSelect_PlayersModifier == 2 && shift) || + (ProfileManager.CurrentProfile.DragSelect_PlayersModifier == 2 && alt) ) && !(mobile.IsHuman || mobile.IsGargoyle)) continue; if (( (ProfileManager.CurrentProfile.DragSelect_MonstersModifier == 1 && ctrl) || - (ProfileManager.CurrentProfile.DragSelect_MonstersModifier == 2 && shift) + (ProfileManager.CurrentProfile.DragSelect_MonstersModifier == 2 && shift) || + (ProfileManager.CurrentProfile.DragSelect_MonstersModifier == 3 && alt) ) && (mobile.IsHuman || mobile.IsGargoyle)) continue; bool skip = false; if (( (ProfileManager.CurrentProfile.DragSelect_NameplateModifier == 1 && ctrl) || - (ProfileManager.CurrentProfile.DragSelect_NameplateModifier == 2 && shift) + (ProfileManager.CurrentProfile.DragSelect_NameplateModifier == 2 && shift) || + (ProfileManager.CurrentProfile.DragSelect_NameplateModifier == 3 && alt) )) { bool _skip = true; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index b89e237eb7..e850524dac 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -346,10 +346,10 @@ private void BuildGeneral() content.AddToRight(new CheckboxWithLabel(lang.GetGeneral.DragSelectHP, isChecked: profile.EnableDragSelect, valueChanged: (b) => { profile.EnableDragSelect = b; }), true, page); content.Indent(); - content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragKeyMod, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift }, profile.DragSelectModifierKey, (s, n) => { profile.DragSelectModifierKey = s; }), true, page); - content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragPlayersOnly, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift }, profile.DragSelect_PlayersModifier, (s, n) => { profile.DragSelect_PlayersModifier = s; }), true, page); - content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragMobsOnly, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift }, profile.DragSelect_MonstersModifier, (s, n) => { profile.DragSelect_MonstersModifier = s; }), true, page); - content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragNameplatesOnly, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift }, profile.DragSelect_NameplateModifier, (s, n) => { profile.DragSelect_NameplateModifier = s; }), true, page); + content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragKeyMod, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift, lang.GetGeneral.SharedAlt }, profile.DragSelectModifierKey, (s, n) => { profile.DragSelectModifierKey = s; }), true, page); + content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragPlayersOnly, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift, lang.GetGeneral.SharedAlt }, profile.DragSelect_PlayersModifier, (s, n) => { profile.DragSelect_PlayersModifier = s; }), true, page); + content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragMobsOnly, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift, lang.GetGeneral.SharedAlt }, profile.DragSelect_MonstersModifier, (s, n) => { profile.DragSelect_MonstersModifier = s; }), true, page); + content.AddToRight(new ComboBoxWithLabel(lang.GetGeneral.DragNameplatesOnly, 0, Theme.COMBO_BOX_WIDTH, new string[] { lang.GetGeneral.SharedNone, lang.GetGeneral.SharedCtrl, lang.GetGeneral.SharedShift, lang.GetGeneral.SharedAlt }, profile.DragSelect_NameplateModifier, (s, n) => { profile.DragSelect_NameplateModifier = s; }), true, page); content.AddToRight(new SliderWithLabel(lang.GetGeneral.DragX, 0, Theme.SLIDER_WIDTH, 0, Client.Game.Scene.Camera.Bounds.Width, profile.DragSelectStartX, (r) => { profile.DragSelectStartX = r; }), true, page); content.AddToRight(new SliderWithLabel(lang.GetGeneral.DragY, 0, Theme.SLIDER_WIDTH, 0, Client.Game.Scene.Camera.Bounds.Width, profile.DragSelectStartY, (r) => { profile.DragSelectStartY = r; }), true, page); content.AddToRight(new CheckboxWithLabel(lang.GetGeneral.DragAnchored, isChecked: profile.DragSelectAsAnchor, valueChanged: (b) => { profile.DragSelectAsAnchor = b; }), true, page); From d0bf0d327816a284824c8272867fee934055be87 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 4 Mar 2024 20:28:01 -0700 Subject: [PATCH 79/93] Fix text height for name plates --- .../Game/GameObjects/EntityTextContainer.cs | 4 +- .../Game/GameObjects/GameObject.cs | 7 +- .../Game/GameObjects/Mobile.cs | 68 +++++++++---------- .../Game/UI/Gumps/NameOverheadGump.cs | 20 +++++- .../Game/UI/Gumps/VersionHistory.cs | 3 +- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/src/ClassicUO.Client/Game/GameObjects/EntityTextContainer.cs b/src/ClassicUO.Client/Game/GameObjects/EntityTextContainer.cs index 694fea242a..4c5406ca59 100644 --- a/src/ClassicUO.Client/Game/GameObjects/EntityTextContainer.cs +++ b/src/ClassicUO.Client/Game/GameObjects/EntityTextContainer.cs @@ -30,10 +30,10 @@ #endregion -using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.Managers; +using ClassicUO.Game.UI.Gumps; using ClassicUO.Renderer; using ClassicUO.Utility.Collections; using Microsoft.Xna.Framework; @@ -180,7 +180,7 @@ public void Draw(UltimaBatcher2D batcher) return; } - int offY = 0; + int offY = -NameOverheadGump.CurrentHeight; Point p = new Point(); diff --git a/src/ClassicUO.Client/Game/GameObjects/GameObject.cs b/src/ClassicUO.Client/Game/GameObjects/GameObject.cs index e1244d54dc..1afeb654a6 100644 --- a/src/ClassicUO.Client/Game/GameObjects/GameObject.cs +++ b/src/ClassicUO.Client/Game/GameObjects/GameObject.cs @@ -30,16 +30,15 @@ #endregion -using System; -using System.Runtime.CompilerServices; +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.Managers; using ClassicUO.Game.Map; -using ClassicUO.Assets; -using ClassicUO.Renderer; using ClassicUO.Utility; using Microsoft.Xna.Framework; +using System; +using System.Runtime.CompilerServices; namespace ClassicUO.Game.GameObjects { diff --git a/src/ClassicUO.Client/Game/GameObjects/Mobile.cs b/src/ClassicUO.Client/Game/GameObjects/Mobile.cs index 2fab6922f0..20917414b4 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Mobile.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Mobile.cs @@ -30,16 +30,16 @@ #endregion -using System; +using ClassicUO.Assets; using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.Managers; using ClassicUO.Game.UI.Gumps; -using ClassicUO.Assets; using ClassicUO.Resources; using ClassicUO.Utility; using ClassicUO.Utility.Collections; using Microsoft.Xna.Framework; +using System; namespace ClassicUO.Game.GameObjects { @@ -427,7 +427,7 @@ public void SetIdleAnimation() //); AnimationGroupsType type = Client.Game.Animations.GetAnimType(graphic); - AnimationFlags flags = Client.Game.Animations.GetAnimFlags(graphic); + AnimationFlags flags = Client.Game.Animations.GetAnimFlags(graphic); AnimationGroups animGroup = AnimationGroups.None; bool isLowExtended = false; @@ -947,7 +947,7 @@ public override void UpdateTextCoordsV() return; } - int offY = 0; + int offY = NameOverheadGump.CurrentHeight; bool health = ProfileManager.CurrentProfile.ShowMobilesHP; int alwaysHP = ProfileManager.CurrentProfile.MobileHPShowWhen; @@ -1025,53 +1025,53 @@ public override void CheckGraphicChange(byte animIndex = 0) { case 0x0190: case 0x0192: - { - IsFemale = false; - Race = RaceType.HUMAN; + { + IsFemale = false; + Race = RaceType.HUMAN; - break; - } + break; + } case 0x0191: case 0x0193: - { - IsFemale = true; - Race = RaceType.HUMAN; + { + IsFemale = true; + Race = RaceType.HUMAN; - break; - } + break; + } case 0x025D: - { - IsFemale = false; - Race = RaceType.ELF; + { + IsFemale = false; + Race = RaceType.ELF; - break; - } + break; + } case 0x025E: - { - IsFemale = true; - Race = RaceType.ELF; + { + IsFemale = true; + Race = RaceType.ELF; - break; - } + break; + } case 0x029A: - { - IsFemale = false; - Race = RaceType.GARGOYLE; + { + IsFemale = false; + Race = RaceType.GARGOYLE; - break; - } + break; + } case 0x029B: - { - IsFemale = true; - Race = RaceType.GARGOYLE; + { + IsFemale = true; + Race = RaceType.GARGOYLE; - break; - } + break; + } } } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs index 7b2efa8dd7..f953e5f24d 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs @@ -57,6 +57,24 @@ internal class NameOverheadGump : Gump private TextBox _text; private Texture2D _borderColor = SolidColorTextureCache.GetTexture(Color.Black); private Vector2 _textDrawOffset = Vector2.Zero; + private static int currentHeight = 22; + + public static int CurrentHeight + { + get + { + if (NameOverHeadManager.IsShowing) + { + return currentHeight; + } + + return 0; + } + private set + { + currentHeight = value; + } + } public NameOverheadGump(uint serial) : base(serial, 0) { @@ -127,7 +145,7 @@ public bool SetName() _text.UpdateText(t); Width = _background.Width = Math.Max(60, _text.Width) + 4; - Height = _background.Height = Math.Max(Constants.OBJECT_HANDLES_GUMP_HEIGHT, _text.Height) + 4; + Height = _background.Height = CurrentHeight = Math.Max(Constants.OBJECT_HANDLES_GUMP_HEIGHT, _text.Height) + 4; _textDrawOffset.X = (Width - _text.Width - 4) >> 1; _textDrawOffset.Y = (Height - _text.Height) >> 1; WantUpdateSize = false; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index 6846cac664..02532ee47d 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -11,7 +11,8 @@ internal class VersionHistory : Gump "/c[white][3.22.0]/cd\n" + "- Spell book icon fix\n" + "- Add option to add treasure maps as map markers instead of goto only\n" + - "- Added the same option for SOS messages", + "- Added the same option for SOS messages\n" + + "- Fix text height for nameplates", "/c[white][3.21.4]/cd\n" + "- Various bug fixes\n" + From 9d23383c5efa358af3771ce3d8cd6347824ddabb Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 4 Mar 2024 20:34:06 -0700 Subject: [PATCH 80/93] Add option to disable auto follow --- .../Configuration/Language.cs | 1 + src/ClassicUO.Client/Configuration/Profile.cs | 1 + .../Game/Scenes/GameSceneInputHandler.cs | 2 +- .../Game/UI/Gumps/HealthBarGump.cs | 27 ++++++++++--------- .../Game/UI/Gumps/ModernOptionsGump.cs | 5 ++++ .../Game/UI/Gumps/VersionHistory.cs | 3 ++- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 8d8d5a2c4a..2d92b78767 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -483,6 +483,7 @@ public class TazUO public string HiddenPlayerHue { get; set; } = "Hidden player hue"; public string RegularPlayerOpacity { get; set; } = "Regular player opacity"; public string AutoFollowDistance { get; set; } = "Auto follow distance"; + public string DisableAutoFollow { get; set; } = "Disable alt click to auto follow"; public string DisableMouseInteractionsForOverheadText { get; set; } = "Disable mouse interactions for overhead text"; public string OverridePartyMemberHues { get; set; } = "Override party member body hues with friendly hue"; #endregion diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index e2e24b3cab..e2c69894c5 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -353,6 +353,7 @@ public sealed class Profile public bool WorldMapAllowPositionalTarget { get; set; } = true; public int AutoFollowDistance { get; set; } = 2; + public bool DisableAutoFollowAlt { get; set; } = false; [JsonConverter(typeof(Point2Converter))] public Point ResizeJournalSize { get; set; } = new Point(410, 350); public bool FollowingMode { get; set; } = false; public uint FollowingTarget { get; set; } diff --git a/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs b/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs index a3df72479f..9d790cb3e8 100644 --- a/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs +++ b/src/ClassicUO.Client/Game/Scenes/GameSceneInputHandler.cs @@ -865,7 +865,7 @@ obj is Item it2 case Entity ent: - if (Keyboard.Alt && ent is Mobile) + if (Keyboard.Alt && !ProfileManager.CurrentProfile.DisableAutoFollowAlt && ent is Mobile) { MessageManager.HandleMessage( World.Player, diff --git a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs index f222daa0ab..d43f02e4db 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/HealthBarGump.cs @@ -292,18 +292,21 @@ protected override void OnMouseUp(int x, int y, MouseButtonType button) } } - MessageManager.HandleMessage - ( - World.Player, - ResGeneral.NowFollowing, - string.Empty, - 0, - MessageType.Regular, - 3, - TextType.CLIENT - ); - ProfileManager.CurrentProfile.FollowingMode = true; - ProfileManager.CurrentProfile.FollowingTarget = LocalSerial; + if (!ProfileManager.CurrentProfile.DisableAutoFollowAlt) + { + MessageManager.HandleMessage + ( + World.Player, + ResGeneral.NowFollowing, + string.Empty, + 0, + MessageType.Regular, + 3, + TextType.CLIENT + ); + ProfileManager.CurrentProfile.FollowingMode = true; + ProfileManager.CurrentProfile.FollowingTarget = LocalSerial; + } } } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index e850524dac..d631d0bbaf 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2244,6 +2244,11 @@ private void BuildTazUO() { profile.AutoFollowDistance = i; }), true, page); + content.Indent(); + content.AddToRight(new CheckboxWithLabel(lang.GetTazUO.DisableAutoFollow, 0, profile.DisableAutoFollowAlt, (i) => + { + profile.DisableAutoFollowAlt = i; + }), true, page); content.BlankLine(); content.AddToRight(c = new CheckboxWithLabel(lang.GetTazUO.DisableMouseInteractionsForOverheadText, 0, profile.DisableMouseInteractionOverheadText, (b) => { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index 02532ee47d..0be2830ff9 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -12,7 +12,8 @@ internal class VersionHistory : Gump "- Spell book icon fix\n" + "- Add option to add treasure maps as map markers instead of goto only\n" + "- Added the same option for SOS messages\n" + - "- Fix text height for nameplates", + "- Fix text height for nameplates\n" + + "- Added option to disable auto follow", "/c[white][3.21.4]/cd\n" + "- Various bug fixes\n" + From 93771635b1168244f94506ede96d32da22cebc63 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Mon, 4 Mar 2024 13:39:59 -0700 Subject: [PATCH 81/93] Update client version --- src/ClassicUO.Client/ClassicUO.Client.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index 580cba8ea0..ab8493698e 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -5,8 +5,8 @@ cuoicon.ico ClassicUO ClassicUO - 3.21.4 - 3.21.4 + 3.22.0 + 3.22.0 From ec76b828d35779b39a702c796d66090e2cea588b Mon Sep 17 00:00:00 2001 From: elderwyn Date: Fri, 29 Mar 2024 22:48:47 -0400 Subject: [PATCH 82/93] Feature > Show Target Indicator https://discord.com/channels/1087124353155608617/1192285684493451404/1192285684493451404 --- .../Configuration/Language.cs | 1 + src/ClassicUO.Client/Configuration/Profile.cs | 1 + .../Game/Managers/HealthLinesManager.cs | 35 +++++++++++++++++-- .../Game/UI/Gumps/ModernOptionsGump.cs | 4 +++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/ClassicUO.Client/Configuration/Language.cs b/src/ClassicUO.Client/Configuration/Language.cs index 2d92b78767..4f88903487 100644 --- a/src/ClassicUO.Client/Configuration/Language.cs +++ b/src/ClassicUO.Client/Configuration/Language.cs @@ -128,6 +128,7 @@ public class General #region General->Mobiles public string ShowMobileHP { get; set; } = "Show mobile's HP"; + public string ShowTargetIndicator { get; set; } = "Show Target Indicator"; public string MobileHPType { get; set; } = "Type"; public string HPTypePerc { get; set; } = "Percentage"; public string HPTypeBar { get; set; } = "Bar"; diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index e2c69894c5..0513f288e1 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -142,6 +142,7 @@ public sealed class Profile public bool HighlightMobilesByPoisoned { get; set; } = true; public bool HighlightMobilesByInvul { get; set; } = true; public bool ShowMobilesHP { get; set; } + public bool ShowTargetIndicator { get; set; } public int MobileHPType { get; set; } // 0 = %, 1 = line, 2 = both public int MobileHPShowWhen { get; set; } // 0 = Always, 1 - <100% public bool DrawRoofs { get; set; } = true; diff --git a/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs b/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs index 916954cd3e..220459e7aa 100644 --- a/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs +++ b/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs @@ -65,6 +65,7 @@ public void Draw(UltimaBatcher2D batcher) camera.Bounds.Width, camera.Bounds.Height ); + DrawTargetIndicator(batcher, TargetManager.LastTargetInfo.Serial); } if (SerialHelper.IsMobile(TargetManager.SelectedTarget)) @@ -75,6 +76,7 @@ public void Draw(UltimaBatcher2D batcher) camera.Bounds.Width, camera.Bounds.Height ); + DrawTargetIndicator(batcher, TargetManager.SelectedTarget); } if (SerialHelper.IsMobile(TargetManager.LastAttack)) @@ -85,6 +87,7 @@ public void Draw(UltimaBatcher2D batcher) camera.Bounds.Width, camera.Bounds.Height ); + DrawTargetIndicator(batcher, TargetManager.LastAttack); } if (!IsEnabled) @@ -214,6 +217,34 @@ out int height } } + private void DrawTargetIndicator(UltimaBatcher2D batcher, uint serial) + { + Entity entity = World.Get(serial); + + if (entity == null) + { + return; + } + if (ProfileManager.CurrentProfile == null || !ProfileManager.CurrentProfile.ShowTargetIndicator) + { + return; + } + ref readonly var indicatorInfo = ref Client.Game.Gumps.GetGump(0x756F); + Point p = entity.RealScreenPosition; + p.Y += (int)(entity.Offset.Y - entity.Offset.Z) + 22 + 5; + + p = Client.Game.Scene.Camera.WorldToScreen(p); + p.Y -= entity.FrameInfo.Height + 25; + if (indicatorInfo.Texture != null) + { + batcher.Draw( + indicatorInfo.Texture, + new Rectangle(p.X - 24, p.Y, indicatorInfo.UV.Width, indicatorInfo.UV.Height), + indicatorInfo.UV, + ShaderHueTranslator.GetHueVector(0, false, 1.0f) + ); + } + } private void DrawHealthLineWithMath( UltimaBatcher2D batcher, uint serial, @@ -246,7 +277,7 @@ int screenH return; } - DrawHealthLine(batcher, entity, p.X, p.Y, false); + DrawHealthLine(batcher, entity, p.X, p.Y, false); } private void DrawHealthLine( @@ -301,7 +332,7 @@ bool passive new Rectangle(x, y, gumpInfo.UV.Width * multiplier, gumpInfo.UV.Height * multiplier), gumpInfo.UV, hueVec - ); + ); hueVec.X = 90; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index d631d0bbaf..5f2f7274c4 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -217,6 +217,10 @@ private void BuildGeneral() content.BlankLine(); + content.AddToRight(new CheckboxWithLabel(lang.GetGeneral.ShowTargetIndicator, isChecked: profile.ShowTargetIndicator, valueChanged: (b) => { profile.ShowTargetIndicator = b; }), true, page); + + content.BlankLine(); + content.AddToRight(new CheckboxWithLabel(lang.GetGeneral.HighlightPoisoned, isChecked: profile.HighlightMobilesByPoisoned, valueChanged: (b) => { profile.HighlightMobilesByPoisoned = b; }), true, page); content.Indent(); content.AddToRight(new ModernColorPickerWithLabel(lang.GetGeneral.PoisonHighlightColor, profile.PoisonHue, (h) => { profile.PoisonHue = h; }), true, page); From 68a727a1c1c4dfa613dab7b94135ffee5ff1a62c Mon Sep 17 00:00:00 2001 From: elderwyn Date: Sun, 31 Mar 2024 20:45:02 -0400 Subject: [PATCH 83/93] Moving Texture null check abot Point declarations. --- .../Game/Managers/HealthLinesManager.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs b/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs index 220459e7aa..3a8fb4e8ee 100644 --- a/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs +++ b/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs @@ -230,13 +230,14 @@ private void DrawTargetIndicator(UltimaBatcher2D batcher, uint serial) return; } ref readonly var indicatorInfo = ref Client.Game.Gumps.GetGump(0x756F); - Point p = entity.RealScreenPosition; - p.Y += (int)(entity.Offset.Y - entity.Offset.Z) + 22 + 5; - - p = Client.Game.Scene.Camera.WorldToScreen(p); - p.Y -= entity.FrameInfo.Height + 25; if (indicatorInfo.Texture != null) { + Point p = entity.RealScreenPosition; + p.Y += (int)(entity.Offset.Y - entity.Offset.Z) + 22 + 5; + + p = Client.Game.Scene.Camera.WorldToScreen(p); + p.Y -= entity.FrameInfo.Height + 25; + batcher.Draw( indicatorInfo.Texture, new Rectangle(p.X - 24, p.Y, indicatorInfo.UV.Width, indicatorInfo.UV.Height), From 7f69b89dba92accc6e5bb3a8611c469ad8b52d3c Mon Sep 17 00:00:00 2001 From: elderwyn Date: Sun, 7 Apr 2024 01:25:46 -0400 Subject: [PATCH 84/93] Feature > Resource Overhead --- .../Game/UI/Gumps/NameOverheadGump.cs | 88 +++++++++++++++++-- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs index f953e5f24d..d372a66783 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs @@ -187,7 +187,7 @@ private void BuildGump() _background = new AlphaBlendControl(ProfileManager.CurrentProfile.NamePlateOpacity / 100f) { WantUpdateSize = false, - Hue = entity is Mobile m ? Notoriety.GetHue(m.NotorietyFlag) : (ushort)0x0481 + Hue = entity is Mobile m ? Notoriety.GetHue(m.NotorietyFlag) : Notoriety.GetHue(NotorietyFlag.Gray) } ); } @@ -745,16 +745,86 @@ out int height if (ProfileManager.CurrentProfile.NamePlateHealthBar && _isMobile) { - batcher.Draw - ( - SolidColorTextureCache.GetTexture(Color.White), - new Vector2(x, y), - new Rectangle(x, y, Math.Min((int)(Width * _hpPercent), Width), Height), - ShaderHueTranslator.GetHueVector(_background.Hue, false, ProfileManager.CurrentProfile.NamePlateHealthBarOpacity / 100f) - ); + Mobile m = World.Mobiles.Get(LocalSerial); + var isPlayer = m is PlayerMobile; + + var _alpha = ProfileManager.CurrentProfile.NamePlateHealthBarOpacity / 100f; + DrawResourceBar(batcher, m, x, y, Height / (isPlayer ? 3 : 1), m => + { + var hpPercent = (double)m.Hits / (double)m.HitsMax; + var _baseHue = hpPercent switch + { + 1 => m is PlayerMobile ? 0x0058 : Notoriety.GetHue(m.NotorietyFlag), + > .8 => 0x0058, + > .4 => 0x0030, + _ => 0x0021 + }; + Vector3 hueVec = ShaderHueTranslator.GetHueVector(_baseHue, false, _alpha); + + if (m.IsPoisoned) + { + hueVec = ShaderHueTranslator.GetHueVector(Notoriety.GetHue(NotorietyFlag.Ally), false, _alpha); + } + else if (m.IsYellowHits || m.IsParalyzed) + { + hueVec = ShaderHueTranslator.GetHueVector(Notoriety.GetHue(NotorietyFlag.Invulnerable), false, _alpha); + } + return (hueVec, hpPercent); + }, out var nY); + if (m is PlayerMobile) + { + DrawResourceBar(batcher, m, x, nY, Height / 3, m => + { + var mpPercent = (double)m.Mana / (double)m.ManaMax; + var _baseHue = mpPercent switch + { + > .6 => 0x0058, + > .2 => 0x0030, + _ => 0x0021 + }; + Vector3 hueVec = ShaderHueTranslator.GetHueVector(_baseHue, false, _alpha); + return (hueVec, mpPercent); + }, out nY); + + DrawResourceBar(batcher, m, x, nY, Height / 3, m => + { + var spPercent = (double)m.Stamina / (double)m.StaminaMax; + var _baseHue = spPercent switch + { + > .8 => 0x0058, + > .5 => 0x0030, + _ => 0x0021 + }; + Vector3 hueVec = ShaderHueTranslator.GetHueVector(_baseHue, false, _alpha); + return (hueVec, spPercent); + }, out nY); + y += 20; + } } - return _text.Draw(batcher, (int)(x + 2 + _textDrawOffset.X), (int)(y + 2 + _textDrawOffset.Y)); + return _text.Draw(batcher, (int)(x + 2 +_textDrawOffset.X), (int)(y + 2 + _textDrawOffset.Y)); + } + + private void DrawResourceBar(UltimaBatcher2D batcher, Mobile m, int x, int y, int height, Func getHueVector, out int nY) + { + var data = getHueVector == null ? (ShaderHueTranslator.GetHueVector(0x0058),0) : getHueVector(m); + batcher.DrawRectangle + ( + _borderColor, + x, + y, + Width, + height, + ShaderHueTranslator.GetHueVector(0) + ); + batcher.Draw + ( + SolidColorTextureCache.GetTexture(Color.White), + new Vector2(x + 1, y + 1), + new Rectangle(x, y, Math.Min((int)(Width * data.Item2), Width), height - 2), + data.Item1 + ); + nY = y + height; } public override void Dispose() From 212670d7666df4655eb673083f06e8ca6bbefffe Mon Sep 17 00:00:00 2001 From: elderwyn Date: Mon, 8 Apr 2024 20:58:49 -0400 Subject: [PATCH 85/93] adding InParty Check --- src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs index d372a66783..76aa2e33bf 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs @@ -747,14 +747,15 @@ out int height { Mobile m = World.Mobiles.Get(LocalSerial); var isPlayer = m is PlayerMobile; + var isInParty = World.Party.Contains(m.Serial); var _alpha = ProfileManager.CurrentProfile.NamePlateHealthBarOpacity / 100f; - DrawResourceBar(batcher, m, x, y, Height / (isPlayer ? 3 : 1), m => + DrawResourceBar(batcher, m, x, y, Height / (isPlayer || isInParty ? 3 : 1), m => { var hpPercent = (double)m.Hits / (double)m.HitsMax; var _baseHue = hpPercent switch { - 1 => m is PlayerMobile ? 0x0058 : Notoriety.GetHue(m.NotorietyFlag), + 1 => (m is PlayerMobile || World.Party.Contains(m.Serial)) ? 0x0058 : Notoriety.GetHue(m.NotorietyFlag), > .8 => 0x0058, > .4 => 0x0030, _ => 0x0021 @@ -771,7 +772,7 @@ out int height } return (hueVec, hpPercent); }, out var nY); - if (m is PlayerMobile) + if (m is PlayerMobile || isInParty) { DrawResourceBar(batcher, m, x, nY, Height / 3, m => { From ea9a4b11c81da7c2eadaf2d567349477666bdff9 Mon Sep 17 00:00:00 2001 From: elderwyn Date: Tue, 9 Apr 2024 00:13:56 -0400 Subject: [PATCH 86/93] Advanced Skill Gump (sorting + renaming + remember maximized grps) --- .../Game/Managers/SkillsGroupManager.cs | 15 ++- .../Game/UI/Gumps/SkillGumpAdvanced.cs | 99 +++++++++++++++---- 2 files changed, 92 insertions(+), 22 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs b/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs index 848ab4bdcc..9207272b30 100644 --- a/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs +++ b/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs @@ -165,6 +165,7 @@ public void Save(XmlTextWriter xml) { xml.WriteStartElement("group"); xml.WriteAttributeString("name", Name); + xml.WriteAttributeString("isMaximized", IsMaximized.ToString()); xml.WriteStartElement("skillids"); for (int i = 0; i < Count; i++) @@ -186,8 +187,13 @@ public void Save(XmlTextWriter xml) internal static class SkillsGroupManager { + private static bool _isActive; public static readonly List Groups = new List(); - + public static bool IsActive { get { return _isActive; } set + { + _isActive = value; + } + } public static void Add(SkillsGroup g) { @@ -247,15 +253,19 @@ public static void Load() return; } - XmlElement root = doc["skillsgroups"]; + + XmlElement root = doc["skillsgroups"]; if (root != null) { + Boolean.TryParse(root.GetAttribute("isActive"), out _isActive); foreach (XmlElement xml in root.GetElementsByTagName("group")) { SkillsGroup g = new SkillsGroup(); g.Name = xml.GetAttribute("name"); + Boolean.TryParse(xml.GetAttribute("isMaximized"), out g.IsMaximized); + XmlElement xmlIdsRoot = xml["skillids"]; if (xmlIdsRoot != null) @@ -286,6 +296,7 @@ public static void Save() { xml.WriteStartDocument(true); xml.WriteStartElement("skillsgroups"); + xml.WriteAttributeString("isActive",IsActive.ToString()); foreach (SkillsGroup k in Groups) { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs index ae7e74c261..225a5c5e43 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs @@ -43,6 +43,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; namespace ClassicUO.Game.UI.Gumps { @@ -67,7 +68,6 @@ internal class SkillGumpAdvanced : Gump public static bool Dragging; - private bool _showGroups; private bool _sortAsc; private string _sortField; private readonly GumpPic _sortOrderIndicator; @@ -248,11 +248,12 @@ public SkillGumpAdvanced() : base(0, 0) 0xFF, 1153 )); - + showGrp.IsChecked = SkillsGroupManager.IsActive; showGrp.ValueChanged += (sender, e) => { - _showGroups = showGrp.IsChecked; + SkillsGroupManager.IsActive = showGrp.IsChecked; ForceUpdate(); + SkillsGroupManager.Save(); }; @@ -314,19 +315,30 @@ private void BuildGump() _skillListEntries.Clear(); PropertyInfo pi = typeof(Skill).GetProperty(_sortField); - if (_showGroups) + if (SkillsGroupManager.IsActive) { - var reversedGroupNames = new List() { - ResGeneral.Miscellaneous, - ResGeneral.Combat, - ResGeneral.TradeSkills, - ResGeneral.Magic, - ResGeneral.Wilderness, - ResGeneral.Thieving, - ResGeneral.Bard, - }; - - foreach (SkillsGroup g in SkillsGroupManager.Groups.OrderBy(g => reversedGroupNames.Contains(g.Name)).ThenBy(g => g.Name)) + SkillsGroupManager.Groups.Sort((s1, s2) => + { + var m1 = Regex.Match(s1.Name, "^\\d+"); + var m2 = Regex.Match(s2.Name, "^\\d+"); + if (!m1.Success || !m2.Success) + { + return s1.Name.CompareTo(s2.Name); + } + + if (!int.TryParse(m1.Value,out var v1) || !int.TryParse(m2.Value,out var v2)) + { + return s1.Name.CompareTo(s2.Name); + } + return v1.CompareTo(v2); + + }); + if (_sortAsc) + { + SkillsGroupManager.Groups.Reverse(); + } + + foreach (SkillsGroup g in SkillsGroupManager.Groups) { var skillEntries = new List(); var a = new Area(); @@ -335,9 +347,10 @@ private void BuildGump() a.CanMove = true; a.Height = 26; a.Width = Width - 26; - a.Tag = true; + a.Tag = g.IsMaximized; a.MouseUp += (sender, e) => { + g.IsMaximized = !g.IsMaximized; var _a = (Area)sender; var newState = !(bool)_a.Tag; _a.Tag = newState; @@ -376,18 +389,64 @@ private void BuildGump() { skillEntries.Add(new SkillListEntry(s)); } + StbTextBox _textbox; + a.Add + ( + _textbox = new StbTextBox + ( + 3, + -1, + 200, + false, + FontStyle.Fixed + ) + { + X = 3, + Y = 3, + Width = 180, + Height = 17, + IsEditable = false + } + ); + + _textbox.SetText(g.Name); + _textbox.IsEditable = false; + + _textbox.MouseDown += (s, e) => + { + if (!g.IsMaximized) + { + a.InvokeMouseUp(e.Location, e.Button); + return; + } + if (_textbox.IsEditable) + { + return; + } + UIManager.KeyboardFocusControl = _textbox; + _textbox.SetKeyboardFocus(); + _textbox.IsEditable = true; + _textbox.AllowSelection = true; + }; - a.Add(new Label(g.Name, true, 1153, font: 3) + _textbox.FocusLost += (s, e) => { - X = 3, - Y = 3 - }); + _textbox.IsEditable = false; + _textbox.AllowSelection = false; + UIManager.KeyboardFocusControl = null; + UIManager.SystemChat.SetFocus(); + }; + _textbox.TextChanged += (s, e) => + { + g.Name = _textbox.Text; + }; a.Add(new Label(grpReal.ToString("F1"), true, 1153) { X = 205, Y = 3 }); a.Add(new Label(grpVal.ToString("F1"), true, 1153) { X = 255, Y = 3 }); _databox.Add(a); foreach (var entry in skillEntries) { + entry.IsVisible = g.IsMaximized; _skillListEntries.Add(entry); _databox.Add(entry); } From 2284dfb1a4bfd1f7e5e7f3aaea86ad3fb8c2726c Mon Sep 17 00:00:00 2001 From: elderwyn Date: Tue, 9 Apr 2024 00:26:35 -0400 Subject: [PATCH 87/93] adding textbox background --- .../Game/UI/Gumps/SkillGumpAdvanced.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs index 225a5c5e43..db94ef0c72 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs @@ -44,6 +44,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using static ClassicUO.Renderer.UltimaBatcher2D; namespace ClassicUO.Game.UI.Gumps { @@ -389,6 +390,16 @@ private void BuildGump() { skillEntries.Add(new SkillListEntry(s)); } + a.Add + ( + new ResizePic(0x0BB8) + { + X = 1, + Y = 3, + Width = 180, + Height = 22 + } + ); StbTextBox _textbox; a.Add ( @@ -401,7 +412,7 @@ private void BuildGump() FontStyle.Fixed ) { - X = 3, + X = 5, Y = 3, Width = 180, Height = 17, @@ -417,11 +428,6 @@ private void BuildGump() if (!g.IsMaximized) { a.InvokeMouseUp(e.Location, e.Button); - return; - } - if (_textbox.IsEditable) - { - return; } UIManager.KeyboardFocusControl = _textbox; _textbox.SetKeyboardFocus(); From e89b1a9c76ddc2df5d9b4e21c1d6b8caf3dd342c Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 10 Apr 2024 08:53:19 -0600 Subject: [PATCH 88/93] Fix some positioning and use poison/invul hues instead of notoriety hues --- .../Game/UI/Gumps/NameOverheadGump.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs index 76aa2e33bf..44a4a895b9 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/NameOverheadGump.cs @@ -734,10 +734,10 @@ out int height batcher.DrawRectangle ( _borderColor, - x - 1, - y - 1, - Width + 1, - Height + 1, + x, + y, + Width, + Height, hueVector ); @@ -764,16 +764,17 @@ out int height if (m.IsPoisoned) { - hueVec = ShaderHueTranslator.GetHueVector(Notoriety.GetHue(NotorietyFlag.Ally), false, _alpha); + hueVec = ShaderHueTranslator.GetHueVector(63, false, _alpha); } else if (m.IsYellowHits || m.IsParalyzed) { - hueVec = ShaderHueTranslator.GetHueVector(Notoriety.GetHue(NotorietyFlag.Invulnerable), false, _alpha); + hueVec = ShaderHueTranslator.GetHueVector(353, false, _alpha); } return (hueVec, hpPercent); }, out var nY); + if (m is PlayerMobile || isInParty) - { + { DrawResourceBar(batcher, m, x, nY, Height / 3, m => { var mpPercent = (double)m.Mana / (double)m.ManaMax; @@ -803,12 +804,12 @@ out int height } } - return _text.Draw(batcher, (int)(x + 2 +_textDrawOffset.X), (int)(y + 2 + _textDrawOffset.Y)); + return _text.Draw(batcher, (int)(x + 2 + _textDrawOffset.X), (int)(y + 2 + _textDrawOffset.Y)); } private void DrawResourceBar(UltimaBatcher2D batcher, Mobile m, int x, int y, int height, Func getHueVector, out int nY) { - var data = getHueVector == null ? (ShaderHueTranslator.GetHueVector(0x0058),0) : getHueVector(m); + var data = getHueVector == null ? (ShaderHueTranslator.GetHueVector(0x0058), 0) : getHueVector(m); batcher.DrawRectangle ( _borderColor, @@ -822,7 +823,7 @@ private void DrawResourceBar(UltimaBatcher2D batcher, Mobile m, int x, int y, in ( SolidColorTextureCache.GetTexture(Color.White), new Vector2(x + 1, y + 1), - new Rectangle(x, y, Math.Min((int)(Width * data.Item2), Width), height - 2), + new Rectangle(x, y, Math.Min((int)((Width - 1) * data.Item2), Width - 1), height - 1), data.Item1 ); nY = y + height; From a5491c1f993bace693d75727c9f8f9f7996cba31 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 10 Apr 2024 08:56:38 -0600 Subject: [PATCH 89/93] Update VersionHistory.cs --- src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index 0be2830ff9..f32525635c 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -8,6 +8,9 @@ namespace ClassicUO.Game.UI.Gumps internal class VersionHistory : Gump { private static string[] updateTexts = { + "/c[white][3.23.0]/cd\n" + + "- Nameplate healthbar poison and invul/paralyzed colors from Elderwyn", + "/c[white][3.22.0]/cd\n" + "- Spell book icon fix\n" + "- Add option to add treasure maps as map markers instead of goto only\n" + From c27e02a6a27e7a6a4fee40d2926a7c3baf7beadc Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 10 Apr 2024 09:26:08 -0600 Subject: [PATCH 90/93] Move option to TUO options & disable if no sprite found --- .../Game/Managers/HealthLinesManager.cs | 10 ++++++---- .../Game/UI/Gumps/ModernOptionsGump.cs | 7 +++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs b/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs index 3a8fb4e8ee..854c4c170d 100644 --- a/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs +++ b/src/ClassicUO.Client/Game/Managers/HealthLinesManager.cs @@ -33,10 +33,8 @@ using ClassicUO.Configuration; using ClassicUO.Game.Data; using ClassicUO.Game.GameObjects; -using ClassicUO.Assets; using ClassicUO.Renderer; using Microsoft.Xna.Framework; -using FontStashSharp; namespace ClassicUO.Game.Managers { @@ -245,6 +243,10 @@ private void DrawTargetIndicator(UltimaBatcher2D batcher, uint serial) ShaderHueTranslator.GetHueVector(0, false, 1.0f) ); } + else + { + ProfileManager.CurrentProfile.ShowTargetIndicator = false; //This sprite doesn't exist for this client, lets avoid checking for it every frame. + } } private void DrawHealthLineWithMath( UltimaBatcher2D batcher, @@ -278,7 +280,7 @@ int screenH return; } - DrawHealthLine(batcher, entity, p.X, p.Y, false); + DrawHealthLine(batcher, entity, p.X, p.Y, false); } private void DrawHealthLine( @@ -333,7 +335,7 @@ bool passive new Rectangle(x, y, gumpInfo.UV.Width * multiplier, gumpInfo.UV.Height * multiplier), gumpInfo.UV, hueVec - ); + ); hueVec.X = 90; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index 5f2f7274c4..fec41d725f 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -217,10 +217,6 @@ private void BuildGeneral() content.BlankLine(); - content.AddToRight(new CheckboxWithLabel(lang.GetGeneral.ShowTargetIndicator, isChecked: profile.ShowTargetIndicator, valueChanged: (b) => { profile.ShowTargetIndicator = b; }), true, page); - - content.BlankLine(); - content.AddToRight(new CheckboxWithLabel(lang.GetGeneral.HighlightPoisoned, isChecked: profile.HighlightMobilesByPoisoned, valueChanged: (b) => { profile.HighlightMobilesByPoisoned = b; }), true, page); content.Indent(); content.AddToRight(new ModernColorPickerWithLabel(lang.GetGeneral.PoisonHighlightColor, profile.PoisonHue, (h) => { profile.PoisonHue = h; }), true, page); @@ -2253,6 +2249,7 @@ private void BuildTazUO() { profile.DisableAutoFollowAlt = i; }), true, page); + content.RemoveIndent(); content.BlankLine(); content.AddToRight(c = new CheckboxWithLabel(lang.GetTazUO.DisableMouseInteractionsForOverheadText, 0, profile.DisableMouseInteractionOverheadText, (b) => { @@ -2263,6 +2260,8 @@ private void BuildTazUO() { profile.OverridePartyAndGuildHue = b; }), true, page); + content.BlankLine(); + content.AddToRight(new CheckboxWithLabel(lang.GetGeneral.ShowTargetIndicator, isChecked: profile.ShowTargetIndicator, valueChanged: (b) => { profile.ShowTargetIndicator = b; }), true, page); #endregion #region Misc From a48297066fa69d07a8703caac3cb4de8ef8b16c2 Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 10 Apr 2024 09:32:07 -0600 Subject: [PATCH 91/93] Little bit of cleanup --- .../Game/Managers/SkillsGroupManager.cs | 28 ++++++++++--------- .../Game/UI/Gumps/SkillGumpAdvanced.cs | 15 +++++----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs b/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs index 9207272b30..ea9bc81a64 100644 --- a/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs +++ b/src/ClassicUO.Client/Game/Managers/SkillsGroupManager.cs @@ -30,17 +30,16 @@ #endregion +using ClassicUO.Assets; +using ClassicUO.Configuration; +using ClassicUO.Game.UI.Gumps; +using ClassicUO.Resources; +using ClassicUO.Utility.Logging; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Xml; -using ClassicUO.Configuration; -using ClassicUO.Game.UI.Gumps; -using ClassicUO.IO; -using ClassicUO.Assets; -using ClassicUO.Resources; -using ClassicUO.Utility.Logging; namespace ClassicUO.Game.Managers { @@ -189,7 +188,10 @@ internal static class SkillsGroupManager { private static bool _isActive; public static readonly List Groups = new List(); - public static bool IsActive { get { return _isActive; } set + public static bool IsActive + { + get { return _isActive; } + set { _isActive = value; } @@ -232,7 +234,7 @@ public static void Load() if (!File.Exists(path)) { Log.Trace("No skillsgroups.xml file. Creating a default file."); - + MakeDefault(); return; @@ -247,13 +249,13 @@ public static void Load() catch (Exception ex) { MakeDefault(); - + Log.Error(ex.ToString()); return; } - + XmlElement root = doc["skillsgroups"]; if (root != null) @@ -296,7 +298,7 @@ public static void Save() { xml.WriteStartDocument(true); xml.WriteStartElement("skillsgroups"); - xml.WriteAttributeString("isActive",IsActive.ToString()); + xml.WriteAttributeString("isActive", IsActive.ToString()); foreach (SkillsGroup k in Groups) { @@ -536,14 +538,14 @@ private static bool LoadMULFile(string path) { while ((strbuild = bin.ReadInt16()) != 0) { - sb.Append((char) strbuild); + sb.Append((char)strbuild); } } else { while ((strbuild = bin.ReadByte()) != 0) { - sb.Append((char) strbuild); + sb.Append((char)strbuild); } } diff --git a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs index db94ef0c72..f9eaed3307 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/SkillGumpAdvanced.cs @@ -44,7 +44,6 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -using static ClassicUO.Renderer.UltimaBatcher2D; namespace ClassicUO.Game.UI.Gumps { @@ -326,8 +325,8 @@ private void BuildGump() { return s1.Name.CompareTo(s2.Name); } - - if (!int.TryParse(m1.Value,out var v1) || !int.TryParse(m2.Value,out var v2)) + + if (!int.TryParse(m1.Value, out var v1) || !int.TryParse(m2.Value, out var v2)) { return s1.Name.CompareTo(s2.Name); } @@ -394,11 +393,11 @@ private void BuildGump() ( new ResizePic(0x0BB8) { - X = 1, - Y = 3, - Width = 180, - Height = 22 - } + X = 1, + Y = 3, + Width = 180, + Height = 22 + } ); StbTextBox _textbox; a.Add From 805810cd0ccebdfc8207364170214e9f4fd1b2ae Mon Sep 17 00:00:00 2001 From: Tad Taylor Date: Wed, 10 Apr 2024 09:39:56 -0600 Subject: [PATCH 92/93] Update version history and number --- src/ClassicUO.Client/ClassicUO.Client.csproj | 4 ++-- src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index ab8493698e..540617d1fc 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -5,8 +5,8 @@ cuoicon.ico ClassicUO ClassicUO - 3.22.0 - 3.22.0 + 3.23.0 + 3.23.0 diff --git a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs index f32525635c..972b89b579 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/VersionHistory.cs @@ -9,7 +9,9 @@ internal class VersionHistory : Gump { private static string[] updateTexts = { "/c[white][3.23.0]/cd\n" + - "- Nameplate healthbar poison and invul/paralyzed colors from Elderwyn", + "- Nameplate healthbar poison and invul/paralyzed colors from Elderwyn\n" + + "- Target indiciator option from original client from Elderwyn\n" + + "- Advanced skill gump improvements from Elderwyn", "/c[white][3.22.0]/cd\n" + "- Spell book icon fix\n" + From 3b6cc472ccd0202d8b8ae781f4a81b69cf19cc04 Mon Sep 17 00:00:00 2001 From: Jeann Sebold Date: Fri, 31 May 2024 09:58:34 -0300 Subject: [PATCH 93/93] fix --- .gitignore | 1 - src/ClassicUO.Client/ClassicUO.Client.csproj | 2 +- src/ClassicUO.Client/Configuration/Profile.cs | 1 - .../Game/Managers/SpellVisualRangeManager.cs | 2 +- .../Game/UI/Gumps/ModernOptionsGump.cs | 5 ----- src/ClassicUO.Client/Game/UI/Gumps/OptionsGump.cs | 7 +------ src/ClassicUO.Client/Network/PacketHandlers.cs | 13 +++++-------- 7 files changed, 8 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index b013f369fe..c25f9b0208 100644 --- a/.gitignore +++ b/.gitignore @@ -266,6 +266,5 @@ __pycache__/ /.svn external/FNA external/MP3Sharp -.external .gitconfig work/.gitconfig \ No newline at end of file diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index d9483f61b9..cecf0942f4 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -1,7 +1,7 @@  - WinExe + Exe cuoicon.ico ClassicUO ClassicUO diff --git a/src/ClassicUO.Client/Configuration/Profile.cs b/src/ClassicUO.Client/Configuration/Profile.cs index 1aa159bcb0..1b510ecadf 100644 --- a/src/ClassicUO.Client/Configuration/Profile.cs +++ b/src/ClassicUO.Client/Configuration/Profile.cs @@ -146,7 +146,6 @@ public sealed class Profile public int MobileHPShowWhen { get; set; } // 0 = Always, 1 - <100% public bool DrawRoofs { get; set; } = true; - public bool AutoOpenHealth { get; set; } = false; public bool SetTargetOut { get; set; } = false; // ## BEGIN - END ## // ART / HUE CHANGES //public bool TreeToStumps { get; set; } diff --git a/src/ClassicUO.Client/Game/Managers/SpellVisualRangeManager.cs b/src/ClassicUO.Client/Game/Managers/SpellVisualRangeManager.cs index d025002b27..3273ef5fab 100644 --- a/src/ClassicUO.Client/Game/Managers/SpellVisualRangeManager.cs +++ b/src/ClassicUO.Client/Game/Managers/SpellVisualRangeManager.cs @@ -74,7 +74,7 @@ private void OnRawMessageReceived(object sender, MessageEventArgs e) if (loaded && e.Parent != null && ReferenceEquals(e.Parent, World.Player)) { // ## BEGIN - END ## // ONCASTINGGUMP - if (ProfileManager.CurrentProfile.OnCastingGump) + if (ProfileManager.CurrentProfile.OnCastingGump && !ProfileManager.CurrentProfile.EnableSpellIndicators) { if (spellRangePowerWordCache.TryGetValue(RemoveContentInBrackets(e.Text.Trim()), out SpellRangeInfo spell)) { diff --git a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs index dc800d3ec7..6e612e8ea0 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/ModernOptionsGump.cs @@ -2273,11 +2273,6 @@ private void BuildDust765() content.AddToRight(new CheckboxWithLabel("Set target with is out range", 0, profile.SetTargetOut, (b) => { profile.SetTargetOut = b; - }), true, page); - content.BlankLine(); - content.AddToRight(new CheckboxWithLabel("Auto Open Health bar gump Get Enemy", 0, profile.AutoOpenHealth, (b) => - { - profile.AutoOpenHealth = b; }), true, page); content.BlankLine(); content.AddToRight(new CheckboxWithLabel("Override container open range", 0, profile.OverrideContainerOpenRange , (b) => diff --git a/src/ClassicUO.Client/Game/UI/Gumps/OptionsGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/OptionsGump.cs index 9595980582..cbe614cb02 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/OptionsGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/OptionsGump.cs @@ -227,7 +227,7 @@ internal class OptionsGump : Gump private HSliderBar _multipleUnderlinesSelfPartyTransparency; // ## BEGIN - END ## // OLDHEALTHLINES // ## BEGIN - END ## // MISC - private Checkbox _offscreenTargeting, _autoOpenHealth, _setTargetOut, _SpecialSetLastTargetCliloc, _blackOutlineStatics, _ignoreStaminaCheck, _blockWoS, _blockWoSFelOnly, _blockWoSArtForceAoS, _blockEnergyF, _blockEnergyFFelOnly, _blockEnergyFArtForceAoS; + private Checkbox _offscreenTargeting, _setTargetOut, _SpecialSetLastTargetCliloc, _blackOutlineStatics, _ignoreStaminaCheck, _blockWoS, _blockWoSFelOnly, _blockWoSArtForceAoS, _blockEnergyF, _blockEnergyFFelOnly, _blockEnergyFArtForceAoS; private InputField _SpecialSetLastTargetClilocText, _blockWoSArt, _blockEnergyFArt; // ## BEGIN - END ## // MISC // ## BEGIN - END ## // MISC2 @@ -6033,11 +6033,6 @@ private void BuildDust() section7.Add(_offscreenTargeting = AddCheckBox(null, "Offscreen targeting (always on)", true, startX, startY)); //has no effect but feature list startY += _offscreenTargeting.Height + 2; - section7.Add(_autoOpenHealth = AddCheckBox(null, "Auto Open Health bar gump Get Enemy", true, startX, startY)); //has no effect but feature list - startY += _autoOpenHealth.Height + 2; - - - section7.Add(_setTargetOut = AddCheckBox(null, "Set target with is out range", true, startX, startY)); //has no effect but feature list startY += _setTargetOut.Height + 2; diff --git a/src/ClassicUO.Client/Network/PacketHandlers.cs b/src/ClassicUO.Client/Network/PacketHandlers.cs index d8143a99d0..933de440f0 100644 --- a/src/ClassicUO.Client/Network/PacketHandlers.cs +++ b/src/ClassicUO.Client/Network/PacketHandlers.cs @@ -3695,16 +3695,13 @@ private static void AttackCharacter(ref StackDataReader p) //} - if (ProfileManager.CurrentProfile.AutoOpenHealth) - { - GameActions.SendCloseStatus(TargetManager.LastAttack); - } + + GameActions.SendCloseStatus(TargetManager.LastAttack); TargetManager.LastAttack = serial; - if (ProfileManager.CurrentProfile.AutoOpenHealth) - { - GameActions.RequestMobileStatus(serial); - } + + GameActions.RequestMobileStatus(serial); + } private static void TextEntryDialog(ref StackDataReader p)