Compare commits

...

10 Commits
v147 ... v153

Author SHA1 Message Date
Dliix66
4f805b18e2 Added canUse virtual method (#282) 2024-01-22 10:23:02 +10:00
roflmuffin
e1f9b5635e chore: update API compatibility version to 151 2024-01-21 21:07:02 +10:00
Michael Wilson
59bff4f500 feat: add discord notify through GH actions 2024-01-21 12:35:49 +10:00
Daniel Wiesendorf
a2581d8e91 Log exception if plugin load fails using the load command (#265) 2024-01-21 12:01:27 +10:00
Ravid-A
e7d190a6f7 Change TerroristsPlanned to TerroristsPlanted in RoundEndReason (#216)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
Co-authored-by: B3none <24966460+B3none@users.noreply.github.com>
2024-01-21 12:01:27 +10:00
B3none
5513d5710a Menu system updates (#268)
Co-authored-by: Stimayk <51941742+Stimayk@users.noreply.github.com>
2024-01-21 12:01:27 +10:00
Michael Poutre
e5c223699c fix(Offsets/Win): CCSPlayer_ItemServices.RemoveWeapons() (#271) 2024-01-21 12:01:27 +10:00
ZoNiCaL
fa37c222d9 Admin manager improvements (#278)
Co-authored-by: B3none <24966460+B3none@users.noreply.github.com>
2024-01-21 12:01:27 +10:00
Michael Wilson
3b633fafc7 Create CODEOWNERS 2024-01-21 11:44:42 +10:00
Abner Santos
765c56a38a Fix css_plugins commands number of args check (#269) 2024-01-19 17:43:21 +00:00
20 changed files with 649 additions and 206 deletions

15
.github/workflows/discord-notify.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Notify Discord on Release
on:
release:
types: [published]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send Notification to Discord
uses: Ilshidur/action-discord@2.4.0
with:
args: "A new release of CS# has been tagged! View it here: ${{ github.event.release.html_url }}"
webhook: ${{ secrets.DISCORD_WEBHOOK }}

1
CODEOWNERS Normal file
View File

@@ -0,0 +1 @@
* @roflmuffin

View File

@@ -74,6 +74,13 @@
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x2A\\x2A\\x48\\x83\\x2A\\x2A\\x2A\\x8D\\x05\\x2A\\x2A\\x2A\\x00\\x48\\x8B\\x30\\x48\\x8B\\x06\\xFF\\x2A\\x2A\\x48\\x8B\\x45\\x2A\\x48\\x8D\\x2A\\x2A\\x4C\\x89\\x2A\\x48\\x89\\x45\\x2A\\x2A\\x68\\xFC"
}
},
"CCSPlayer_WeaponServices_CanUse": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x48\\x89\\x6C\\x24\\x18\\x56\\x57\\x41\\x56\\x48\\x83\\xEC\\x30\\x80\\xB9\\xA8\\x00\\x00\\x00\\x00",
"linux": "\\x48\\x85\\xF6\\x0F\\x84\\x2A\\x2A\\x2A\\x2A\\x55\\x31\\xC9\\x48\\x89\\xE5\\x41\\x55\\x49\\x89\\xFD"
}
},
"CCSPlayer_ItemServices_DropActivePlayerWeapon": {
"offsets": {
"windows": 18,
@@ -82,7 +89,7 @@
},
"CCSPlayer_ItemServices_RemoveWeapons": {
"offsets": {
"windows": 21,
"windows": 19,
"linux": 20
}
},

View File

@@ -27,9 +27,9 @@ public class AdminTests
{
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265731);
Assert.NotNull(adminData);
Assert.Equal(125u, adminData.Immunity); // Group immunity is 125, Admin immunity is 100
Assert.Equal(125u, AdminManager.GetPlayerImmunity((SteamID)76561197960265731)); // Group immunity is 125, Admin immunity is 100
AdminManager.SetPlayerImmunity((SteamID)76561197960265731, 150u);
Assert.Equal(150u, adminData.Immunity); // Group immunity is 125, Admin immunity is 100
Assert.Equal(150u, AdminManager.GetPlayerImmunity((SteamID)76561197960265731)); // Group immunity is 125, Admin immunity is 100
}
[Fact]

Binary file not shown.

View File

@@ -85,11 +85,12 @@ namespace CounterStrikeSharp.API.Core
for (var i = 1; i <= 9; i++)
{
CommandUtils.AddStandaloneCommand("css_" + i, "Command Key Handler", (player, info) =>
CommandUtils.AddStandaloneCommand($"css_{i}", "Command Key Handler", (player, info) =>
{
if (player == null) return;
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
ChatMenus.OnKeyPress(player, key);
MenuManager.OnKeyPress(player, key);
});
}
@@ -151,7 +152,7 @@ namespace CounterStrikeSharp.API.Core
case "start":
case "load":
{
if (info.ArgCount < 2)
if (info.ArgCount < 3)
{
info.ReplyToCommand(
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n",
@@ -182,6 +183,7 @@ namespace CounterStrikeSharp.API.Core
catch (Exception e)
{
info.ReplyToCommand($"Could not load plugin \"{path}\")", true);
Logger.LogError(e, "Could not load plugin \"{Path}\"", path);
}
}
else
@@ -195,7 +197,7 @@ namespace CounterStrikeSharp.API.Core
case "stop":
case "unload":
{
if (info.ArgCount < 2)
if (info.ArgCount < 3)
{
info.ReplyToCommand(
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n",
@@ -218,7 +220,7 @@ namespace CounterStrikeSharp.API.Core
case "restart":
case "reload":
{
if (info.ArgCount < 2)
if (info.ArgCount < 3)
{
info.ReplyToCommand(
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n",
@@ -288,7 +290,7 @@ namespace CounterStrikeSharp.API.Core
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.",
OnCSSPluginCommand);
CommandUtils.AddStandaloneCommand("css_lang", "Set Counter-Strike Sharp language", OnLangCommand);
CommandUtils.AddStandaloneCommand("css_lang", "Set Counter-Strike Sharp language.", OnLangCommand);
}
}
}

View File

@@ -17,7 +17,7 @@
</PropertyGroup>
<PropertyGroup>
<ApiCompatValidateAssemblies>true</ApiCompatValidateAssemblies>
<ApiCompatContractAssembly>.\ApiCompat\v133.dll</ApiCompatContractAssembly>
<ApiCompatContractAssembly>.\ApiCompat\v151.dll</ApiCompatContractAssembly>
</PropertyGroup>
<ItemGroup>
<None Remove="Modules\Commands\CommandInfo" />

View File

@@ -95,7 +95,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of groups.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) { return false; }
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
if (playerData == null) return false;
@@ -136,20 +136,20 @@ namespace CounterStrikeSharp.API.Modules.Admin
return playerData.Groups.IsSupersetOf(playerGroups);
}
/// <summary>
/// Adds a player to a group.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="groups">Groups to add the player to.</param>
public static void AddPlayerToGroup(CCSPlayerController? player, params string[] groups)
/// <summary>
/// Adds a player to a group. This does NOT modify the immunity of the player (see SetPlayerImmunity).
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="groups">Groups to add the player to.</param>
public static void AddPlayerToGroup(CCSPlayerController? player, params string[] groups)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) { return; }
AddPlayerToGroup(player.AuthorizedSteamID, groups);
}
/// <summary>
/// Adds a player to a group.
/// Adds a player to a group. This does NOT modify the immunity of the player (see SetPlayerImmunity).
/// </summary>
/// <param name="steamId">SteamID of the player.</param>
/// <param name="groups">Groups to add the player to.</param>
@@ -187,7 +187,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
public static void RemovePlayerFromGroup(CCSPlayerController? player, bool removeInheritedFlags = true, params string[] groups)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) { return; }
RemovePlayerFromGroup(player.AuthorizedSteamID, true, groups);
}

View File

@@ -167,22 +167,43 @@ namespace CounterStrikeSharp.API.Modules.Admin
}
}
/// <summary>
/// Grabs the admin data for a player that was loaded from "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID object of the player.</param>
/// <returns>AdminData class if data found, null if not.</returns>
public static AdminData? GetPlayerAdminData(SteamID? steamId)
/// <summary>
/// Grabs the admin data for a player that was loaded from "configs/admins.json" and "configs/admins_groups.json".
/// </summary>
/// <param name="player">Player controller</param>
/// <returns>AdminData class if data found, null if not.</returns>
public static AdminData? GetPlayerAdminData(CCSPlayerController? player)
{
if (player == null) return null;
return GetPlayerAdminData(player.AuthorizedSteamID);
}
/// <summary>
/// Grabs the admin data for a player that was loaded from "configs/admins.json" and "configs/admins_groups.json".
/// </summary>
/// <param name="steamId">SteamID object of the player.</param>
/// <returns>AdminData class if data found, null if not.</returns>
public static AdminData? GetPlayerAdminData(SteamID? steamId)
{
if (steamId == null) return null;
return Admins.GetValueOrDefault(steamId);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="steamId">Steam ID remove admin data from.</param>
public static void RemovePlayerAdminData(SteamID? steamId)
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="player">Player controller</param>
public static void RemovePlayerAdminData(CCSPlayerController? player)
{
if (player == null) return;
RemovePlayerAdminData(player.AuthorizedSteamID);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="steamId">Steam ID remove admin data from.</param>
public static void RemovePlayerAdminData(SteamID? steamId)
{
if (steamId == null) return;
Admins.Remove(steamId);
@@ -201,7 +222,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) { return false; }
return PlayerHasPermissions(player.AuthorizedSteamID, flags);
}
@@ -260,7 +281,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) { return false; }
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
@@ -290,7 +311,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) { return false; }
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
@@ -319,7 +340,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// This is here for cases where the server console is attempting to call commands.
// The server console should have access to all commands, regardless of permissions.
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) { return; }
SetPlayerCommandOverride(player.AuthorizedSteamID, command, state);
}
@@ -362,7 +383,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
public static void AddPlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) return;
AddPlayerPermissions(player.AuthorizedSteamID, flags);
}
@@ -400,7 +421,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
public static void RemovePlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) return;
RemovePlayerPermissions(player.AuthorizedSteamID, flags);
}
@@ -427,7 +448,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
public static void ClearPlayerPermissions(CCSPlayerController? player)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) return;
ClearPlayerPermissions(player.AuthorizedSteamID);
}
@@ -457,7 +478,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
public static void SetPlayerImmunity(CCSPlayerController? player, uint value)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) return;
SetPlayerImmunity(player.AuthorizedSteamID, value);
}
@@ -477,13 +498,44 @@ namespace CounterStrikeSharp.API.Modules.Admin
Admins[steamId] = data;
}
/// <summary>
/// Checks to see if a player can target another player based on their immunity value.
/// </summary>
/// <param name="caller">Caller of the command.</param>
/// <param name="target">Target of the command.</param>
/// <returns></returns>
public static bool CanPlayerTarget(CCSPlayerController? caller, CCSPlayerController? target)
/// <summary>
/// Returns the immunity value for a player.
/// </summary>
/// <param name="player">Player controller.</param>
/// <returns> If an immunity value is present in "configs/admins_groups.json"
/// and in "configs/admins.json", the returned value will be the greater of the two.
/// If the value is overriden with SetPlayerImmunity, that value is returned instead.</returns>
public static uint GetPlayerImmunity(CCSPlayerController? player)
{
if (player == null) return 0;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot || player.IsHLTV) return 0;
return GetPlayerImmunity(player.AuthorizedSteamID);
}
/// <summary>
/// Returns the immunity value for a player.
/// </summary>
/// <param name="steamId">Steam ID of the player.</param>
/// <returns> If an immunity value is present in "configs/admins_groups.json"
/// and in "configs/admins.json", the returned value will be the greater of the two.
/// If the value is overriden with SetPlayerImmunity, that value is returned instead.</returns>
public static uint GetPlayerImmunity(SteamID? steamId)
{
if (steamId == null) return 0;
var data = GetPlayerAdminData(steamId);
if (data == null) return 0;
return data.Immunity;
}
/// <summary>
/// Checks to see if a player can target another player based on their immunity value.
/// </summary>
/// <param name="caller">Caller of the command.</param>
/// <param name="target">Target of the command.</param>
/// <returns></returns>
public static bool CanPlayerTarget(CCSPlayerController? caller, CCSPlayerController? target)
{
// The server console should be able to target everyone.
if (caller == null) return true;

View File

@@ -90,11 +90,11 @@ public class Target
case TargetType.TeamSpec:
return player.TeamNum == (byte)CsTeam.Spectator;
case TargetType.GroupAll:
return true;
return !player.IsHLTV;
case TargetType.GroupBots:
return player.IsBot;
case TargetType.GroupHumans:
return !player.IsBot;
return !player.IsBot && !player.IsHLTV;
case TargetType.GroupAlive:
return player.PlayerPawn is { IsValid: true, Value.LifeState: (byte)LifeState_t.LIFE_ALIVE };
case TargetType.GroupDead:

View File

@@ -1,4 +1,4 @@
/*
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,9 +36,12 @@ namespace CounterStrikeSharp.API.Modules.Entities.Constants
TerroristsSurrender = 0x11u, // this also triggers match cancelled
CTsSurrender = 0x12u, // this also triggers match cancelled
TerroristsPlanned = 0x13u,
TerroristsPlanted = 0x13u,
CTsReachedHostage = 0x14u,
SurvivalWin = 0x15u,
SurvivalDraw = 0x16u
SurvivalDraw = 0x16u,
[Obsolete("Use RoundEndReason.TerroristsPlanted instead.")]
TerroristsPlanned = 0x13u
}
}
}

View File

@@ -68,6 +68,9 @@ public static class VirtualFunctions
public static MemoryFunctionVoid<CEntityInstance, CTakeDamageInfo> CBaseEntity_TakeDamageOldFunc = new (GameData.GetSignature("CBaseEntity_TakeDamageOld"));
public static Action<CEntityInstance, CTakeDamageInfo> CBaseEntity_TakeDamageOld = CBaseEntity_TakeDamageOldFunc.Invoke;
public static MemoryFunctionWithReturn<CCSPlayer_WeaponServices, CBasePlayerWeapon, bool> CCSPlayer_WeaponServices_CanUseFunc = new(GameData.GetSignature("CCSPlayer_WeaponServices_CanUse"));
public static Func<CCSPlayer_WeaponServices, CBasePlayerWeapon, bool> CCSPlayer_WeaponServices_CanUse = CCSPlayer_WeaponServices_CanUseFunc.Invoke;
public static MemoryFunctionVoid<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThinkFunc = new (GameData.GetSignature("CCSPlayerPawnBase_PostThink"));
public static Action<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThink = CCSPlayerPawnBase_PostThinkFunc.Invoke;

View File

@@ -0,0 +1,140 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System.Collections.Generic;
namespace CounterStrikeSharp.API.Modules.Menu
{
public abstract class BaseMenu : IMenu
{
public string Title { get; set; }
public List<ChatMenuOption> MenuOptions { get; } = new();
protected BaseMenu(string title)
{
Title = title;
}
public virtual ChatMenuOption AddMenuOption(string display, Action<CCSPlayerController, ChatMenuOption> onSelect, bool disabled = false)
{
var option = new ChatMenuOption(display, disabled, onSelect);
MenuOptions.Add(option);
return option;
}
}
// This must be called ChatMenuOption to maintain backwards compatibility with the old API
public class ChatMenuOption
{
public string Text { get; set; }
public bool Disabled { get; set; }
public Action<CCSPlayerController, ChatMenuOption> OnSelect { get; set; }
public ChatMenuOption(string display, bool disabled, Action<CCSPlayerController, ChatMenuOption> onSelect)
{
Text = display;
Disabled = disabled;
OnSelect = onSelect;
}
}
public abstract class BaseMenuInstance : IMenuInstance
{
public int NumPerPage => 6;
public Stack<int> PrevPageOffsets { get; } = new();
public IMenu Menu { get; }
public CCSPlayerController Player { get; }
public int Page { get; set; }
public int CurrentOffset { get; set; }
protected BaseMenuInstance(CCSPlayerController player, IMenu menu)
{
Menu = menu;
Player = player;
}
protected bool HasPrevButton => Page > 0;
protected bool HasNextButton => CurrentOffset + NumPerPage < Menu.MenuOptions.Count;
protected int MenuItemsPerPage => NumPerPage + 2 - (HasNextButton ? 1 : 0) - (HasPrevButton ? 1 : 0);
public virtual void Display()
{
throw new NotImplementedException();
}
public void OnKeyPress(CCSPlayerController player, int key)
{
if (player.Handle != Player.Handle) return;
if (key == 8 && HasNextButton)
{
NextPage();
return;
}
if (key == 1 && HasPrevButton)
{
PrevPage();
return;
}
if (key == 9)
{
Reset();
return;
}
var desiredValue = key;
if (HasPrevButton) desiredValue = key - 1;
var menuItemIndex = CurrentOffset + desiredValue - 1;
if (menuItemIndex >= 0 && menuItemIndex < Menu.MenuOptions.Count)
{
var menuOption = Menu.MenuOptions[menuItemIndex];
if (!menuOption.Disabled)
{
menuOption.OnSelect(Player, menuOption);
Reset();
}
}
}
public virtual void Reset()
{
CurrentOffset = 0;
Page = 0;
PrevPageOffsets.Clear();
}
public void NextPage()
{
PrevPageOffsets.Push(CurrentOffset);
CurrentOffset += MenuItemsPerPage;
Page++;
Display();
}
public void PrevPage()
{
Page--;
CurrentOffset = PrevPageOffsets.Pop();
Display();
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System.Text;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Modules.Menu
{
public class CenterHtmlMenu : BaseMenu
{
public CenterHtmlMenu(string title) : base(ModifyTitle(title))
{
}
public override ChatMenuOption AddMenuOption(string display, Action<CCSPlayerController, ChatMenuOption> onSelect, bool disabled = false)
{
var option = new ChatMenuOption(ModifyOptionDisplay(display), disabled, onSelect);
MenuOptions.Add(option);
return option;
}
private static string ModifyTitle(string title)
{
if (title.Length > 32)
{
Application.Instance.Logger.LogWarning("Title should not be longer than 32 characters for a CenterHtmlMenu");
return title[..32];
}
return title;
}
private static string ModifyOptionDisplay(string display)
{
if (display.Length > 26)
{
Application.Instance.Logger.LogWarning("Display should not be longer than 26 characters for a CenterHtmlMenu item");
return display[..26];
}
return display;
}
}
public class CenterHtmlMenuInstance : BaseMenuInstance
{
private readonly BasePlugin _plugin;
public CenterHtmlMenuInstance(BasePlugin plugin, CCSPlayerController player, IMenu menu) : base(player, menu)
{
_plugin = plugin;
RemoveOnTickListener();
plugin.RegisterListener<Core.Listeners.OnTick>(Display);
}
public override void Display()
{
if (MenuManager.GetActiveMenu(Player) != this)
{
Reset();
return;
}
var builder = new StringBuilder();
builder.Append($"<b><font color='yellow'>{Menu.Title}</font></b>");
builder.AppendLine("<br>");
var keyOffset = 1;
if (HasPrevButton)
{
builder.AppendFormat("<font color='green'>!1</font> -> Prev");
builder.AppendLine("<br>");
keyOffset++;
}
for (var i = CurrentOffset; i < Math.Min(CurrentOffset + MenuItemsPerPage, Menu.MenuOptions.Count); i++)
{
var option = Menu.MenuOptions[i];
builder.Append($"<font color='green'>!{keyOffset++}</font> {option.Text}");
builder.AppendLine("<br>");
}
if (HasNextButton)
{
builder.AppendFormat("<font color='yellow'>!8</font> -> Next");
builder.AppendLine("<br>");
}
builder.AppendFormat("<font color='red'>!9</font> -> Close");
builder.AppendLine("<br>");
var currentPageText = builder.ToString();
Player.PrintToCenterHtml(currentPageText);
}
public override void Reset()
{
base.Reset();
RemoveOnTickListener();
// Send a blank message to clear the menu
Player.PrintToCenterHtml(" ");
}
private void RemoveOnTickListener()
{
var onTick = new Core.Listeners.OnTick(Display);
_plugin.RemoveListener("OnTick", onTick);
}
}
}

View File

@@ -1,164 +1,78 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Menu;
public class ChatMenuOption
namespace CounterStrikeSharp.API.Modules.Menu
{
public ChatMenuOption(string text, bool disabled, Action<CCSPlayerController, ChatMenuOption> onSelect)
public class ChatMenu: BaseMenu
{
Text = text;
Disabled = disabled;
OnSelect = onSelect;
public ChatMenu(string title) : base(title)
{
}
}
public Action<CCSPlayerController, ChatMenuOption> OnSelect { get; set; }
public string Text { get; set; }
public bool Disabled { get; set; }
}
public class ChatMenu
{
public string Title { get; set; }
public List<ChatMenuOption> MenuOptions { get; } = new();
public ChatMenu(string title)
public class ChatMenuInstance : BaseMenuInstance
{
Title = title;
public ChatMenuInstance(CCSPlayerController player, ChatMenu menu) : base(player, menu)
{
}
public override void Display()
{
Player.PrintToChat(Menu.Title);
Player.PrintToChat("---");
var keyOffset = 1;
if (HasPrevButton)
{
Player.PrintToChat($" {ChatColors.Yellow}!1 {ChatColors.Default}-> Prev");
keyOffset++;
}
for (var i = CurrentOffset;
i < Math.Min(CurrentOffset + MenuItemsPerPage, Menu.MenuOptions.Count);
i++)
{
var option = Menu.MenuOptions[i];
Player.PrintToChat(
$" {(option.Disabled ? ChatColors.Grey : ChatColors.Green)} !{keyOffset++} {ChatColors.Default}{option.Text}");
}
if (HasNextButton)
{
Player.PrintToChat($" {ChatColors.Yellow}!8 {ChatColors.Default}-> Next");
}
}
}
public ChatMenuOption AddMenuOption(string display, Action<CCSPlayerController, ChatMenuOption> onSelect, bool disabled = false)
public static class ChatMenus
{
var option = new ChatMenuOption(display, disabled, onSelect);
MenuOptions.Add(option);
return option;
[Obsolete("Use MenuManager.OpenChatMenu instead")]
public static void OpenMenu(CCSPlayerController player, ChatMenu menu)
{
MenuManager.OpenChatMenu(player, menu);
}
[Obsolete("Use MenuManager.OnKeyPress instead")]
public static void OnKeyPress(CCSPlayerController player, int key)
{
MenuManager.OnKeyPress(player, key);
}
}
}
public class ChatMenuInstance
{
// Six items seems to be able to fit all options in the chat bot without having to open it
readonly int _numPerPage = 6;
private readonly Stack<int> _prevPageOffsets = new();
private readonly ChatMenu _menu;
int _page = 0;
int _currentOffset = 0;
private CCSPlayerController _player;
public ChatMenuInstance(CCSPlayerController player, ChatMenu menu)
{
_menu = menu;
_player = player;
}
private bool HasPrevButton => _page > 0;
private bool HasNextButton => (_currentOffset + _numPerPage) < _menu.MenuOptions.Count;
private int MenuItemsPerPage => _numPerPage + 2 - (HasNextButton ? 1 : 0) - (HasPrevButton ? 1 : 0);
public void Display()
{
_player.PrintToChat(_menu.Title);
_player.PrintToChat("---");
int keyOffset = 1;
if (HasPrevButton)
{
_player.PrintToChat($" {ChatColors.Yellow}[!1] {ChatColors.Default}-> Prev");
keyOffset++;
}
for (int i = _currentOffset;
i < Math.Min(_currentOffset + MenuItemsPerPage, _menu.MenuOptions.Count);
i++)
{
var option = _menu.MenuOptions[i];
_player.PrintToChat(
$" {(option.Disabled ? ChatColors.Grey : ChatColors.Green)} [!{keyOffset++}] {ChatColors.Default}{option.Text}");
}
if (HasNextButton)
{
_player.PrintToChat($" {ChatColors.Yellow}[!8] {ChatColors.Default}-> Next");
}
}
internal void OnKeyPress(CCSPlayerController player, int key)
{
if (_player == null || player.Handle != _player.Handle) return;
if (key == 8 && HasNextButton)
{
NextPage();
return;
}
if (key == 1 && HasPrevButton)
{
PrevPage();
return;
}
var desiredValue = key;
if (HasPrevButton) desiredValue = key - 1;
var menuItemIndex = _currentOffset + desiredValue - 1;
var menuOption = _menu.MenuOptions[menuItemIndex];
if (!menuOption.Disabled)
{
menuOption.OnSelect(_player, menuOption);
Reset();
}
}
public void Reset()
{
_currentOffset = 0;
_page = 0;
_prevPageOffsets.Clear();
_player = null;
}
public void NextPage()
{
_prevPageOffsets.Push(_currentOffset);
_currentOffset += MenuItemsPerPage;
_page++;
Display();
}
public void PrevPage()
{
_page--;
_currentOffset = _prevPageOffsets.Pop();
Display();
}
}
public static class ChatMenus
{
private static readonly Dictionary<IntPtr, ChatMenuInstance> ActiveMenus = new();
public static void OpenMenu(CCSPlayerController player, ChatMenu menu)
{
ActiveMenus[player.Handle] = new ChatMenuInstance(player, menu);
ActiveMenus[player.Handle].Display();
}
public static void OnKeyPress(CCSPlayerController player, int key)
{
if (!ActiveMenus.ContainsKey(player.Handle)) return;
ActiveMenus[player.Handle].OnKeyPress(player, key);
}
}

View File

@@ -0,0 +1,61 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
namespace CounterStrikeSharp.API.Modules.Menu
{
public class ConsoleMenu : BaseMenu
{
public ConsoleMenu(string title) : base(title)
{
}
}
public class ConsoleMenuInstance : BaseMenuInstance
{
public ConsoleMenuInstance(CCSPlayerController player, IMenu menu) : base(player, menu)
{
}
public override void Display()
{
Player.PrintToConsole(Menu.Title);
Player.PrintToConsole("---");
var keyOffset = 1;
if (HasPrevButton)
{
Player.PrintToConsole($"!1 -> Prev");
keyOffset++;
}
for (var i = CurrentOffset;
i < Math.Min(CurrentOffset + MenuItemsPerPage, Menu.MenuOptions.Count);
i++)
{
var option = Menu.MenuOptions[i];
Player.PrintToConsole(
$" {(option.Disabled ? "[Enabled]" : "[Disabled] - ")} !{keyOffset++} {option.Text}");
}
if (HasNextButton)
{
Player.PrintToConsole($"!8 -> Next");
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System.Collections.Generic;
namespace CounterStrikeSharp.API.Modules.Menu
{
public interface IMenu
{
public string Title { get; set; }
public List<ChatMenuOption> MenuOptions { get; }
public ChatMenuOption AddMenuOption(string display, Action<CCSPlayerController, ChatMenuOption> onSelect, bool disabled = false);
}
public interface IMenuInstance
{
protected IMenu Menu { get; }
protected CCSPlayerController? Player { get; }
protected int Page { get; }
protected int CurrentOffset { get; }
protected int NumPerPage { get; }
protected Stack<int> PrevPageOffsets { get; }
public void NextPage();
public void PrevPage();
public void Reset();
public void Display();
public void OnKeyPress(CCSPlayerController player, int key);
}
}

View File

@@ -0,0 +1,76 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System.Collections.Generic;
namespace CounterStrikeSharp.API.Modules.Menu;
public static class MenuManager
{
private static readonly Dictionary<IntPtr, IMenuInstance> ActiveMenus = new();
public static Dictionary<IntPtr, IMenuInstance> GetActiveMenus()
{
return ActiveMenus;
}
public static IMenuInstance? GetActiveMenu(CCSPlayerController player)
{
return !ActiveMenus.TryGetValue(player.Handle, out var value) ? null : value;
}
private static void ResetMenus(CCSPlayerController player)
{
if (ActiveMenus.TryGetValue(player.Handle, out var activeMenu))
{
activeMenu.Reset();
}
ActiveMenus.Remove(player.Handle);
}
public static void OpenChatMenu(CCSPlayerController player, ChatMenu menu)
{
ResetMenus(player);
ActiveMenus[player.Handle] = new ChatMenuInstance(player, menu);
ActiveMenus[player.Handle].Display();
}
public static void OpenCenterHtmlMenu(BasePlugin plugin, CCSPlayerController player, CenterHtmlMenu menu)
{
ResetMenus(player);
ActiveMenus[player.Handle] = new CenterHtmlMenuInstance(plugin, player, menu);
ActiveMenus[player.Handle].Display();
}
public static void OpenConsoleMenu(CCSPlayerController player, ConsoleMenu menu)
{
ResetMenus(player);
ActiveMenus[player.Handle] = new ConsoleMenuInstance(player, menu);
ActiveMenus[player.Handle].Display();
}
public static void OnKeyPress(CCSPlayerController player, int key)
{
if (ActiveMenus.TryGetValue(player.Handle, out var activeMenu))
{
activeMenu.OnKeyPress(player, key);
}
}
}

View File

@@ -331,7 +331,7 @@ namespace TestPlugin
private void SetupMenus()
{
// Chat Menu Example
// [Legacy] Chat Menu Example
var largeMenu = new ChatMenu("Test Menu");
for (int i = 1; i < 26; i++)
{