Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve HTML Menu #770

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
12 changes: 12 additions & 0 deletions managed/CounterStrikeSharp.API/Core/API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ public static void SetFakeClientConvarValue(int clientindex, string convarname,
}
}

public static void ReplicateConvar(int clientslot, string convarname, string convarvalue){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(clientslot);
ScriptContext.GlobalScriptContext.Push(convarname);
ScriptContext.GlobalScriptContext.Push(convarvalue);
ScriptContext.GlobalScriptContext.SetIdentifier(0xC8728BEC);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}

public static T DynamicHookGetReturn<T>(IntPtr hook, int datatype){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
Expand Down
1 change: 0 additions & 1 deletion managed/CounterStrikeSharp.API/Core/CoreConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ public partial class CoreConfig
public static bool UnlockConCommands => _coreConfig.UnlockConCommands;

public static bool UnlockConVars => _coreConfig.UnlockConVars;

}

public partial class CoreConfig : IStartupService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,9 @@ public VoiceFlags VoiceFlags
{
base.Teleport(position, angles, velocity);
}

public void ReplicateConVar(string conVar, string value)
{
NativeAPI.ReplicateConvar(Slot, conVar, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ public static class PluginConfigExtensions
{
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
WriteIndented = true
WriteIndented = true,
ReadCommentHandling = JsonCommentHandling.Skip
};

public static JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
Expand Down Expand Up @@ -62,7 +63,7 @@ public static class PluginConfigExtensions

var configContent = File.ReadAllText(configPath);

var newConfig = JsonSerializer.Deserialize<T>(configContent)
var newConfig = JsonSerializer.Deserialize<T>(configContent, JsonSerializerOptions)
?? throw new JsonException($"Deserialization failed for configuration file '{configPath}'.");

foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
Expand Down
2 changes: 1 addition & 1 deletion managed/CounterStrikeSharp.API/Modules/Menu/BaseMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ protected BaseMenuInstance(CCSPlayerController player, IMenu menu)
}

protected bool HasPrevButton => Page > 0;
protected bool HasNextButton => Menu.MenuOptions.Count > NumPerPage && CurrentOffset + NumPerPage < Menu.MenuOptions.Count;
protected virtual bool HasNextButton => Menu.MenuOptions.Count > NumPerPage && CurrentOffset + NumPerPage < Menu.MenuOptions.Count;
protected bool HasExitButton => Menu.ExitButton;
protected virtual int MenuItemsPerPage => NumPerPage;

Expand Down
112 changes: 101 additions & 11 deletions managed/CounterStrikeSharp.API/Modules/Menu/CenterHtmlMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,24 @@ public class CenterHtmlMenu : BaseMenu
public string PrevPageColor { get; set; } = "yellow";
public string NextPageColor { get; set; } = "yellow";
public string CloseColor { get; set; } = "red";

public bool InlinePageOptions { get; set; } = true;
public int MaxTitleLength { get; set; } = 0; // defaults to 0 = no limit, if enabled, recommended value is 32
public int MaxOptionLength { get; set; } = 0; // defaults to 0 = no limit, if enabled, recommended value is 26

public CenterHtmlMenu(string title, BasePlugin plugin) : base(title)
public CenterHtmlMenu(string title, BasePlugin plugin, bool inlinePageOptions = true, int maxTitleLength = 0, int maxOptionLength = 0): base(title)
{
Title = title.TruncateHtml(MaxTitleLength);
_plugin = plugin;
InlinePageOptions = inlinePageOptions;
MaxTitleLength = maxTitleLength;
MaxOptionLength = maxOptionLength;
}

[Obsolete("Use the constructor that takes a BasePlugin")]
public CenterHtmlMenu(string title) : base(title)
{
Title = title.TruncateHtml(MaxTitleLength);
}

public override void Open(CCSPlayerController player)
Expand All @@ -53,7 +62,7 @@ public override void Open(CCSPlayerController player)
public override ChatMenuOption AddMenuOption(string display, Action<CCSPlayerController, ChatMenuOption> onSelect,
bool disabled = false)
{
var option = new ChatMenuOption(display, disabled, onSelect);
var option = new ChatMenuOption(display.TruncateHtml(MaxOptionLength), disabled, onSelect);
MenuOptions.Add(option);
return option;
}
Expand All @@ -63,13 +72,40 @@ public class CenterHtmlMenuInstance : BaseMenuInstance
{
private readonly BasePlugin _plugin;
public override int NumPerPage => 5; // one less than the actual number of items per page to avoid truncated options
protected override int MenuItemsPerPage => (Menu.ExitButton ? 0 : 1) + ((HasPrevButton && HasNextButton) ? NumPerPage - 1 : NumPerPage);
protected override bool HasNextButton => Menu.MenuOptions.Count > NumPerPage + 1 && CurrentOffset + NumPerPage < Menu.MenuOptions.Count;
public bool InlinePageOptions { get; set; } = true;
protected override int MenuItemsPerPage
{
get
{
int count = NumPerPage;
if (InlinePageOptions == false)
{
if (!HasPrevButton)
count++;

if (!HasNextButton)
count++;
}
else
{
count++;
if (!HasExitButton && !HasPrevButton && !HasNextButton)
count++;
}

return count;
}
}

public CenterHtmlMenuInstance(BasePlugin plugin, CCSPlayerController player, IMenu menu) : base(player, menu)
{
_plugin = plugin;
RemoveOnTickListener();
plugin.RegisterListener<Core.Listeners.OnTick>(Display);

if (menu is CenterHtmlMenu centerHtmlMenu)
InlinePageOptions = centerHtmlMenu.InlinePageOptions;
}

public override void Display()
Expand Down Expand Up @@ -98,28 +134,82 @@ public override void Display()
builder.Append($"<font color='{color}'>!{keyOffset++}</font> {option.Text}");
builder.AppendLine("<br>");
}

AddPageOptions(centerHtmlMenu, builder);

var currentPageText = builder.ToString();
Player.PrintToCenterHtml(currentPageText);
}

private void AddPageOptions(CenterHtmlMenu centerHtmlMenu, StringBuilder builder)
{
string prevText = $"<font color='{centerHtmlMenu.PrevPageColor}'>!7 &#60;</font> Prev";
string closeText = $"<font color='{centerHtmlMenu.CloseColor}'>!9 X</font> Close";
string nextText = $"<font color='{centerHtmlMenu.NextPageColor}'>!8 ></font> Next";

if (InlinePageOptions)
AddInlinePageOptions(prevText, closeText, nextText, centerHtmlMenu.ExitButton, builder);
else
AddMultilinePageOptions(prevText, closeText, nextText, centerHtmlMenu.ExitButton, builder);
}


private void AddInlinePageOptions(string prevText, string closeText, string nextText, bool hasExitButton, StringBuilder builder)
{
if (HasPrevButton && HasExitButton && HasNextButton)
{
builder.Append($"{prevText} | {closeText} | {nextText}");
return;
}

string doubleOptionSplitString = " \u200e \u200e \u200e \u200e | \u200e \u200e \u200e \u200e "; // empty characters that are not trimmed

int optionsCount = 0;
if (HasPrevButton)
{
builder.AppendFormat($"<font color='{centerHtmlMenu.PrevPageColor}'>!7</font> &#60;- Prev");
builder.AppendLine("<br>");
builder.AppendFormat(prevText);
optionsCount++;
}

if (hasExitButton)
{
if (optionsCount++ > 0)
builder.Append(doubleOptionSplitString);

builder.AppendFormat(closeText);
}

if (HasNextButton)
{
builder.AppendFormat($"<font color='{centerHtmlMenu.NextPageColor}'>!8</font> -> Next");
builder.AppendLine("<br>");
if (optionsCount > 0)
builder.Append(doubleOptionSplitString);

builder.AppendFormat(nextText);
}
}

if (centerHtmlMenu.ExitButton)
private void AddMultilinePageOptions(string prevText, string closeText, string nextText, bool hasExitButton, StringBuilder builder)
{
if (HasPrevButton)
{
builder.AppendFormat($"<font color='{centerHtmlMenu.CloseColor}'>!9</font> -> Close");
builder.AppendFormat(prevText);
builder.AppendLine("<br>");
}

var currentPageText = builder.ToString();
Player.PrintToCenterHtml(currentPageText);
if (HasNextButton)
{
builder.AppendFormat(nextText);
builder.AppendLine("<br>");
}

if (hasExitButton)
{
builder.AppendFormat(closeText);
builder.AppendLine("<br>");
}
}



public override void Close()
{
Expand Down
68 changes: 68 additions & 0 deletions managed/CounterStrikeSharp.API/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Text;
using System.Text.RegularExpressions;

namespace CounterStrikeSharp.API
{
public static class StringExtensions
{
private const string HTML_TAG_REGEX_PATTERN = "<[^>]+>";
private static readonly Regex TagRegex = new(HTML_TAG_REGEX_PATTERN, RegexOptions.Compiled);

public static string TruncateHtml(this string msg, int maxLength)
{
if (maxLength <= 0)
return msg;

if (string.IsNullOrEmpty(msg))
return string.Empty;

string textOnly = Regex.Replace(msg, HTML_TAG_REGEX_PATTERN, "");
if (textOnly.Length <= maxLength)
return msg;

Stack<string> tagStack = new Stack<string>();
StringBuilder result = new System.Text.StringBuilder();
int visibleLength = 0,
i = 0;

while (i < msg.Length && visibleLength < maxLength)
{
if (msg[i] == '<')
{
Match match = TagRegex.Match(msg, i);
if (match.Success && match.Index == i)
{
string tag = match.Value;
result.Append(tag);
i += tag.Length;

if (!tag.StartsWith("</")) // Opening tag
{
string tagName = tag.Split(new[] { ' ', '>' }, StringSplitOptions.RemoveEmptyEntries)[0].Trim('<');
if (!tag.EndsWith("/>") && !tagName.StartsWith("!"))
tagStack.Push(tagName);
}
else if (tagStack.Count > 0)
{
tagStack.Pop();
}

continue;
}
}
else
{
result.Append(msg[i]);
visibleLength++;
}

i++;
}

while (tagStack.Count > 0)
result.Append($"</{tagStack.Pop()}>");

return result.ToString();
}
}
}
24 changes: 24 additions & 0 deletions src/scripting/natives/natives_commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
*/

#include <eiface.h>
#include <networksystem/inetworkmessages.h>

#include "scripting/autonative.h"
#include "scripting/callback_manager.h"
#include "core/managers/con_command_manager.h"
#include "core/managers/player_manager.h"
#include "core/recipientfilters.h"
#include "igameeventsystem.h"
#include "scripting/script_engine.h"
#include "core/log.h"
#include <networkbasetypes.pb.h>

namespace counterstrikesharp {

Expand Down Expand Up @@ -191,6 +195,25 @@ void SetConVarStringValue(ScriptContext& script_context)
pCvar->values = reinterpret_cast<CVValue_t**>((char*)value);
}

void ReplicateConVar(ScriptContext& script_context)
{
auto slot = script_context.GetArgument<int>(0);
auto name = script_context.GetArgument<const char*>(1);
auto value = script_context.GetArgument<const char*>(2);

INetworkMessageInternal* pNetMsg = globals::networkMessages->FindNetworkMessagePartial("SetConVar");
auto msg = pNetMsg->AllocateMessage()->ToPB<CNETMsg_SetConVar>();

CMsg_CVars_CVar* cvarMsg = msg->mutable_convars()->add_cvars();
cvarMsg->set_name(name);
cvarMsg->set_value(value);

CSingleRecipientFilter filter(slot);
globals::gameEventSystem->PostEventAbstract(-1, false, &filter, pNetMsg, msg, 0);

delete msg;
}

REGISTER_NATIVES(commands, {
ScriptEngine::RegisterNativeHandler("ADD_COMMAND", AddCommand);
ScriptEngine::RegisterNativeHandler("REMOVE_COMMAND", RemoveCommand);
Expand All @@ -211,5 +234,6 @@ REGISTER_NATIVES(commands, {
IssueClientCommandFromServer);
ScriptEngine::RegisterNativeHandler("GET_CLIENT_CONVAR_VALUE", GetClientConVarValue);
ScriptEngine::RegisterNativeHandler("SET_FAKE_CLIENT_CONVAR_VALUE", SetFakeClientConVarValue);
ScriptEngine::RegisterNativeHandler("REPLICATE_CONVAR", ReplicateConVar);
})
} // namespace counterstrikesharp
3 changes: 2 additions & 1 deletion src/scripting/natives/natives_commands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ ISSUE_CLIENT_COMMAND_FROM_SERVER: slot:int,command:string -> void
FIND_CONVAR: name:string -> pointer
SET_CONVAR_STRING_VALUE: convar:pointer,value:string -> void
GET_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string -> string
SET_FAKE_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string,convarValue:string -> void
SET_FAKE_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string,convarValue:string -> void
REPLICATE_CONVAR: clientSlot:int,convarName:string,convarValue:string -> void
Loading