diff --git a/CSSUniversalMenuAPI.sln b/CSSUniversalMenuAPI.sln index bfe455b..8da8cd8 100644 --- a/CSSUniversalMenuAPI.sln +++ b/CSSUniversalMenuAPI.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProofOfConcepts", "tests\Pr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalMenu.Compat.MenuManagerApi", "src\UniversalMenu.Compat.MenuManagerApi\UniversalMenu.Compat.MenuManagerApi.csproj", "{44AAF649-62D6-1CD8-3F16-028F4D262BE6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalMenu.Compat.ScreenMenuAPI", "src\UniversalMenu.Compat.ScreenMenuAPI\UniversalMenu.Compat.ScreenMenuAPI.csproj", "{B5AB3AB6-5BB6-0154-27DF-61D9EE587806}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalMenu.Driver.ScreenMenuAPI", "src\UniversalMenu.Driver.ScreenMenuAPI\UniversalMenu.Driver.ScreenMenuAPI.csproj", "{E2FD83DD-D7EA-97CA-B6F4-DE2F92E5A00F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Actions", "GitHub Actions", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" @@ -37,6 +39,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Actions", "GitHub Ac .github\shared.yml = .github\shared.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalMenu.Compat.CSSharp", "src\UniversalMenu.Compat.CSSharp\UniversalMenu.Compat.CSSharp.csproj", "{EF2866D7-C411-42ED-B5E0-DCE79CCFCA09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalMenu.Compat.CSSharp.Shared", "src\UniversalMenu.Compat.CSSharp.Shared\UniversalMenu.Compat.CSSharp.Shared.csproj", "{6ADB7AC3-E7F1-4BAB-A323-633932EBB322}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,10 +65,22 @@ Global {44AAF649-62D6-1CD8-3F16-028F4D262BE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {44AAF649-62D6-1CD8-3F16-028F4D262BE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {44AAF649-62D6-1CD8-3F16-028F4D262BE6}.Release|Any CPU.Build.0 = Release|Any CPU + {B5AB3AB6-5BB6-0154-27DF-61D9EE587806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5AB3AB6-5BB6-0154-27DF-61D9EE587806}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5AB3AB6-5BB6-0154-27DF-61D9EE587806}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5AB3AB6-5BB6-0154-27DF-61D9EE587806}.Release|Any CPU.Build.0 = Release|Any CPU {E2FD83DD-D7EA-97CA-B6F4-DE2F92E5A00F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E2FD83DD-D7EA-97CA-B6F4-DE2F92E5A00F}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2FD83DD-D7EA-97CA-B6F4-DE2F92E5A00F}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2FD83DD-D7EA-97CA-B6F4-DE2F92E5A00F}.Release|Any CPU.Build.0 = Release|Any CPU + {EF2866D7-C411-42ED-B5E0-DCE79CCFCA09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF2866D7-C411-42ED-B5E0-DCE79CCFCA09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF2866D7-C411-42ED-B5E0-DCE79CCFCA09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF2866D7-C411-42ED-B5E0-DCE79CCFCA09}.Release|Any CPU.Build.0 = Release|Any CPU + {6ADB7AC3-E7F1-4BAB-A323-633932EBB322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ADB7AC3-E7F1-4BAB-A323-633932EBB322}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ADB7AC3-E7F1-4BAB-A323-633932EBB322}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6ADB7AC3-E7F1-4BAB-A323-633932EBB322}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/package.sh b/package.sh index dff25cd..c39936b 100644 --- a/package.sh +++ b/package.sh @@ -16,6 +16,20 @@ mkdir -p "$dst_plugins/UniversalMenu.Compat.MenuManagerApi" src="./src/UniversalMenu.Compat.MenuManagerApi/bin/Release/net8.0/publish" cp -r "$src/." "$dst_plugins/UniversalMenu.Compat.MenuManagerApi/" +# package UniversalMenu.Compat.CSSharp +mkdir -p "$dst_plugins/UniversalMenu.Compat.CSSharp" +src="./src/UniversalMenu.Compat.CSSharp/bin/Release/net8.0/publish" +cp -r "$src/." "$dst_plugins/UniversalMenu.Compat.CSSharp/" +# shared part: allows modified methods to load the 0Harmony.dll dependency +mkdir -p "$dst_shared/UniversalMenu.Compat.CSSharp.Shared" +src="./src/UniversalMenu.Compat.CSSharp.Shared/bin/Release/net8.0/publish" +cp -r "$src/." "$dst_shared/UniversalMenu.Compat.CSSharp.Shared/" + +# package UniversalMenu.Compat.ScreenMenuAPI # this isn't implemented yet +#mkdir -p "$dst_plugins/UniversalMenu.Compat.ScreenMenuAPI" +#src="./src/UniversalMenu.Compat.ScreenMenuAPI/bin/Release/net8.0/publish" +#cp -r "$src/." "$dst_shared/UniversalMenu.Compat.ScreenMenuAPI/" + # zip CSSUniversalMenuAPI.zip pushd "$dst" 7z a ../CSSUniversalMenuAPI.zip ./ diff --git a/src/UniversalMenu.Compat.CSSharp.Shared/CSSharpCompatPluginShared.cs b/src/UniversalMenu.Compat.CSSharp.Shared/CSSharpCompatPluginShared.cs new file mode 100644 index 0000000..0d146e3 --- /dev/null +++ b/src/UniversalMenu.Compat.CSSharp.Shared/CSSharpCompatPluginShared.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using AngleSharp.Dom; +using AngleSharp.Html.Parser; + +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Menu; + +using CS2ScreenMenuAPI; + +using CSSUniversalMenuAPI; +using CSSUniversalMenuAPI.Extensions; + +using HarmonyLib; + +using IUniversalMenu = CSSUniversalMenuAPI.IMenu; + +namespace UniversalMenu.Compat.CSSharp; + +public static class CSSharpCompatPluginShared +{ + private static Harmony? Harmony { get; set; } + private static Dictionary ActiveMenus { get; } = new(); + + public static void Patch() + { + Harmony = new Harmony("com.universalmenu.compat.cssharp"); + + { + var original = AccessTools.Method(typeof(MenuManager), nameof(MenuManager.OpenCenterHtmlMenu)); + var pre = SymbolExtensions.GetMethodInfo(() => MenuManager_OpenCenterHtmlMenu(null!, null!, null!)); + Harmony.Patch(original, prefix: new HarmonyMethod(pre)); + } + + { + var original = AccessTools.Method(typeof(MenuManager), nameof(MenuManager.OpenChatMenu)); + var pre = SymbolExtensions.GetMethodInfo(() => MenuManager_OpenChatMenu(null!, null!)); + Harmony.Patch(original, prefix: new HarmonyMethod(pre)); + } + + { + var original = AccessTools.Method(typeof(MenuManager), nameof(MenuManager.OpenConsoleMenu)); + var pre = SymbolExtensions.GetMethodInfo(() => MenuManager_OpenConsoleMenu(null!, null!)); + Harmony.Patch(original, prefix: new HarmonyMethod(pre)); + } + + { + var original = AccessTools.Method(typeof(MenuManager), nameof(MenuManager.CloseActiveMenu)); + var pre = SymbolExtensions.GetMethodInfo(() => MenuManager_CloseActiveMenu(null!)); + Harmony.Patch(original, prefix: new HarmonyMethod(pre)); + } + } + + public static void Unpatch() + { + Harmony?.UnpatchAll(); + } + + public static void PlayerDisconnected(CCSPlayerController player) + { + ActiveMenus.Remove(player.Handle); + } + + public static bool BaseMenu_Open(BasePlugin? plugin, CCSPlayerController player, BaseMenu menu) + { + // this API assumes there may only be 1 menu at a time, so tell the implementation + // that this menu is expected to be closed + if (ActiveMenus.TryGetValue(player.Handle, out var activeMenu)) + { + ActiveMenus.Remove(player.Handle); + activeMenu.Close(); + } + + var api = IMenuAPI.PluginCapability.Get(); + + if (api is null) // fall back to builtin menu + return true; + + var newMenu = api.CreateMenu(player); + newMenu.PlayerCanClose = menu.ExitButton; + + var useHtml = false; + if (newMenu is IHtmlSupportMenuExtension htmlMenu) + htmlMenu.UseHtml = useHtml = true; + + if (useHtml) + newMenu.Title = menu.Title; + else + newMenu.Title = StripHtml(menu.Title); + + foreach (var item in menu.MenuOptions) + { + var newItem = newMenu.CreateItem(); + newItem.Enabled = !item.Disabled; + + if (useHtml) + newItem.Title = item.Text; + else + newItem.Title = StripHtml(item.Text); + + if (item.OnSelect is not null) + newItem.Selected += (selectedItem) => + { + item.OnSelect(player, item); + if (menu.PostSelectAction == PostSelectAction.Close) + { + if (ActiveMenus.TryGetValue(player.Handle, out var activeMenu) && activeMenu == newMenu) + ActiveMenus.Remove(player.Handle); + newMenu.Close(); + } + }; + } + + ActiveMenus[player.Handle] = newMenu; + newMenu.Display(); + return false; + } + + public static bool MenuManager_CloseActiveMenu(CCSPlayerController player) + { + Console.WriteLine($"Close active menu 1"); + if (ActiveMenus.TryGetValue(player.Handle, out var activeMenu)) + { + Console.WriteLine($"Close active menu 2"); + ActiveMenus.Remove(player.Handle); + activeMenu.Close(); + } + return true; + } + + public static bool MenuManager_OpenCenterHtmlMenu(BasePlugin plugin, CCSPlayerController player, CenterHtmlMenu menu) + { + return BaseMenu_Open(plugin, player, menu); + } + + public static bool MenuManager_OpenChatMenu(CCSPlayerController player, ChatMenu menu) + { + return BaseMenu_Open(null, player, menu); + } + + public static bool MenuManager_OpenConsoleMenu(CCSPlayerController player, ConsoleMenu menu) + { + return BaseMenu_Open(null, player, menu); + } + + private static readonly HtmlParser HtmlParser = new(new HtmlParserOptions() { IsStrictMode = false }); + private static string StripHtml(string input) + { + var doc = HtmlParser.ParseFragment(input, null!); + return string.Join(string.Empty, doc.Select(x => x.Text())); + } +} diff --git a/src/UniversalMenu.Compat.CSSharp.Shared/UniversalMenu.Compat.CSSharp.Shared.csproj b/src/UniversalMenu.Compat.CSSharp.Shared/UniversalMenu.Compat.CSSharp.Shared.csproj new file mode 100644 index 0000000..a0f4218 --- /dev/null +++ b/src/UniversalMenu.Compat.CSSharp.Shared/UniversalMenu.Compat.CSSharp.Shared.csproj @@ -0,0 +1,20 @@ + + + + UniversalMenu.Compat.CSSharp + Library + True + + + + + False + False + + + + + + + + diff --git a/src/UniversalMenu.Compat.CSSharp/CSSharpCompatPlugin.cs b/src/UniversalMenu.Compat.CSSharp/CSSharpCompatPlugin.cs new file mode 100644 index 0000000..1dbc089 --- /dev/null +++ b/src/UniversalMenu.Compat.CSSharp/CSSharpCompatPlugin.cs @@ -0,0 +1,30 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; + +namespace UniversalMenu.Compat.CSSharp; + +public class CSSharpCompatPlugin : BasePlugin +{ + public override string ModuleName => "UniversalMenu.Compat.CSSharp"; + public override string ModuleVersion => Verlite.Version.Full; + + public override void Load(bool hotReload) + { + CSSharpCompatPluginShared.Patch(); + } + + public override void Unload(bool hotReload) + { + CSSharpCompatPluginShared.Unpatch(); + } + + [GameEventHandler(HookMode.Pre)] + public HookResult OnPlayerDisconnect(EventPlayerDisconnect e, GameEventInfo info) + { + if (e.Userid is null) + return HookResult.Continue; + + CSSharpCompatPluginShared.PlayerDisconnected(e.Userid); + return HookResult.Continue; + } +} diff --git a/src/UniversalMenu.Compat.CSSharp/UniversalMenu.Compat.CSSharp.csproj b/src/UniversalMenu.Compat.CSSharp/UniversalMenu.Compat.CSSharp.csproj new file mode 100644 index 0000000..08246dc --- /dev/null +++ b/src/UniversalMenu.Compat.CSSharp/UniversalMenu.Compat.CSSharp.csproj @@ -0,0 +1,24 @@ + + + + UniversalMenu.Compat.CSSharp + Library + True + + + + + False + False + + + + False + False + runtime + + + + + + diff --git a/src/UniversalMenu.Compat.ScreenMenuAPI/UniversalMenu.Compat.ScreenMenuAPI.csproj b/src/UniversalMenu.Compat.ScreenMenuAPI/UniversalMenu.Compat.ScreenMenuAPI.csproj new file mode 100644 index 0000000..17f9dae --- /dev/null +++ b/src/UniversalMenu.Compat.ScreenMenuAPI/UniversalMenu.Compat.ScreenMenuAPI.csproj @@ -0,0 +1,18 @@ + + + + UniversalMenu.Compat.ScreenMenuAPI + Library + True + + + + + False + False + + + + + +