Compare commits

...

9 Commits

Author SHA1 Message Date
Michael Wilson
83a341d3cf feat: expose TargetTypeMap 2024-04-11 15:34:01 +10:00
Michael Wilson
534fc42444 fix: bad commit 2024-04-11 14:26:21 +10:00
Michael Wilson
41355d05fa docs: add more comments to base plugin 2024-04-11 14:20:24 +10:00
luxury fabka
d9da15be83 Add teleport overloads (#399)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2024-04-11 00:48:55 +00:00
B3none
75e2f6e8aa [no ci] Rename ACKNOWLEDGEMENTS to ACKNOWLEDGEMENTS.md (#401) 2024-04-10 16:34:15 +10:00
Michael Wilson
37b34e1d41 [no ci] add basic contributing guide 2024-04-08 10:02:18 +10:00
WidovV
5ce04649fd Add CenterHtmlMenu button colors (#398) 2024-04-06 21:43:38 +10:00
Michael Wilson
7b7202fe8a [no ci] Update README.md 2024-04-05 11:54:53 +10:00
Michael Wilson
cadb817ed2 fix: mark center html menu constructor as obsolete, throw error (#396) 2024-04-04 09:33:22 +10:00
15 changed files with 313 additions and 1193 deletions

103
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,103 @@
# Contributing to CounterStrikeSharp
We'd love for you to contribute to CS# to make it better than it is today!
Here are the guidelines we'd like you to follow:
- [Question or Problem?](#question)
- [Issues and Bugs](#issue)
- [Submission Guidelines](#submit)
- [Coding Format](#format)
## <a name="question"></a> Got a Question or Problem?
If you have questions about how to contribute to CounterStrikeSharp, please join our [Discord][discord] server.
## <a name="issue"></a> Found an Issue?
If you find a bug in the source code or a mistake in the documentation, you can help us by
submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request
with a fix.
**Please see the [Submission Guidelines](#submit) below.**
## <a name="submit"></a> Submission Guidelines
### Submitting an Issue
Before you submit your issue please search the archive, maybe your question was already answered.
If your issue appears to be a bug and hasn't been reported, open a new issue. Help us to maximize
the effort we can spend fixing issues and adding new features, by not reporting duplicate issues.
Providing the following information will increase the chances of your issue being dealt with
quickly:
* **Overview of the Issue** - if an error is being thrown a stack trace helps
* **Operating System** - is this a problem with a specific OS (Windows or Linux)?
* **Reproduce the Error** - provide details, if possible, on how to reproduce the error
* **Related Issues** - has a similar issue been reported before?
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit)
### Submitting a Pull Request
Before you submit your pull request consider the following guidelines:
* Search [GitHub](https://github.com/roflmuffin/CounterStrikeSharp/pulls) for an open or closed Pull Request
that relates to your submission. You don't want to duplicate effort.
* If adding a feature or enhancement, we recommend you first [start a discussion for
it](https://github.com/roflmuffin/CounterStrikeSharp/discussions) before submitting a Pull Request.
* [Fork](https://help.github.com/articles/fork-a-repo/) this repo.
* [Clone](https://help.github.com/articles/cloning-a-repository/) your copy.
```shell
git clone https://github.com/YOUR_USERNAME/CounterStrikeSharp.git
cd CounterStrikeSharp/
```
* After cloning, set a new remote [upstream](https://help.github.com/articles/configuring-a-remote-for-a-fork/) (this helps to keep your fork up to date)
```shell
git remote add upstream https://github.com/roflmuffin/CounterStrikeSharp.git
```
* Make your changes in a new git branch:
```shell
git checkout -b my-fix-branch master
```
* Create your patch and run appropriate tests.
* Commit your changes using a descriptive commit message that uses the imperative, present tense: "change" not "changed" nor "changes".
```shell
git commit -a
```
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
* Push your branch to GitHub:
```shell
git push origin my-fix-branch
```
In GitHub, send a pull request to `CounterStrikeSharp:master`.
If we suggest changes, then:
* Make the required updates.
* Re-run CounterStrikeSharp to ensure everything is still working & tests are passing.
* Commit your changes to your branch (e.g. `my-fix-branch`).
* Push the changes to your GitHub repository (this will update your Pull Request).
If the PR gets too outdated we may ask you to rebase and force push to update the PR:
```shell
git fetch upstream
git rebase upstream/master
git push origin my-fix-branch -f
```
That's it! Thank you for your contribution!
#### After your pull request is merged
After your pull request is merged, you can safely delete your branch and pull the changes
from the main (upstream) repository.
[github]: https://github.com/roflmuffin/CounterStrikeSharp
[discord]: https://discord.gg/eAZU3guKWU

View File

@@ -1,6 +1,6 @@
# CounterStrikeSharp
CounterStrikeSharp is a server side modding framework for Counter-Strike: Global Offensive. This project attempts to implement a .NET Core scripting layer on top of a Metamod Source Plugin, allowing developers to create plugins that interact with the game server in a modern language (C#) to facilitate the creation of maintainable and testable code.
CounterStrikeSharp is a server side modding framework for Counter-Strike 2. This project implements a .NET 8 scripting layer on top of a Metamod Source Plugin, allowing developers to create plugins that interact with the game server in a modern language (C#) to facilitate the creation of maintainable and testable code.
[Come and join our Discord](https://discord.gg/eAZU3guKWU)
@@ -18,14 +18,12 @@ Detailed installation instructions can be found in the [docs](https://docs.cssha
## What works?
_(Note, these were features in the previous VSP.NET project, but have not been implemented yet in this project)_
These features are the core of the platform and work pretty well/have a low risk of causing issues.
- [x] Console Commands, Server Commands (e.g. css_mycommand)
- [x] Chat Commands with `!` and `/` prefixes (e.g. !mycommand)
- [ ] **(In Progress)** Console Variables
- [x] Game Event Handlers & Custom Events (e.g. player_death)
- [x] Fake Console Variables (commands which mimic ConVar behaviour as these have not been fully reverse engineered)
- [x] Game Event Handlers & Firing of Events (e.g. player_death)
- [x] Basic event value get/set (string, bool, int32, float)
- [x] Complex event values get/set (ehandle, pawn, player controller)
- [x] Game Tick Based Timers (e.g. repeating map timers)
@@ -34,7 +32,7 @@ These features are the core of the platform and work pretty well/have a low risk
- [x] Client Listeners (e.g. connect, disconnect, put in server)
- [x] OnMapStart
- [x] OnTick
- [x] Server Information (current map, game time, tick rate, model precaching)
- [x] Server Information (current map, game time)
- [x] Schema System Access (access player values like current weapon, money, location etc.)
## Links

File diff suppressed because it is too large Load Diff

View File

@@ -122,9 +122,6 @@ namespace CounterStrikeSharp.API.Core
public readonly Dictionary<Delegate, CallbackSubscriber> CommandListeners =
new Dictionary<Delegate, CallbackSubscriber>();
public readonly Dictionary<Delegate, CallbackSubscriber> ConvarChangeHandlers =
new Dictionary<Delegate, CallbackSubscriber>();
public readonly Dictionary<Delegate, CallbackSubscriber> Listeners =
new Dictionary<Delegate, CallbackSubscriber>();
@@ -159,7 +156,23 @@ namespace CounterStrikeSharp.API.Core
var name = typeof(T).GetCustomAttribute<EventNameAttribute>()?.Name;
RegisterEventHandlerInternal(name, handler, hookMode == HookMode.Post);
}
/// <summary>
/// De-registers a game event handler.
/// </summary>
/// <inheritdoc cref="RegisterEventHandler{T}"/>
public void DeregisterEventHandler<T>(GameEventHandler<T> handler, HookMode hookMode = HookMode.Post) where T : GameEvent
{
var name = typeof(T).GetCustomAttribute<EventNameAttribute>()!.Name;
if (!Handlers.TryGetValue(handler, out var subscriber)) return;
NativeAPI.UnhookEvent(name, subscriber.GetInputArgument(), hookMode == HookMode.Post);
FunctionReference.Remove(subscriber.GetReferenceIdentifier());
Handlers.Remove(handler);
}
[Obsolete("Use the generic version of this method")]
public void DeregisterEventHandler(string name, Delegate handler, bool post)
{
if (!Handlers.TryGetValue(handler, out var subscriber)) return;
@@ -187,6 +200,12 @@ namespace CounterStrikeSharp.API.Core
CommandManager.RegisterCommand(definition);
}
/// <summary>
/// Adds a command listener which will be called before or after the command is executed on the server by a player.
/// </summary>
/// <param name="name">Name of the command, e.g. `jointeam`</param>
/// <param name="handler">Code to run when command is executed. Return <see cref="HookResult.Handled"/> or higher to prevent command execution.</param>
/// <param name="mode">Whether to hook before or after the command is executed.</param>
public void AddCommandListener(string? name, CommandInfo.CommandListenerCallback handler, HookMode mode = HookMode.Pre)
{
var wrappedHandler = new Func<int, IntPtr, HookResult>((i, ptr) =>
@@ -202,6 +221,11 @@ namespace CounterStrikeSharp.API.Core
CommandListeners[handler] = subscriber;
}
/// <summary>
/// Removes a server command.
/// </summary>
/// <param name="name">The name of the command.</param>
/// <param name="handler">The callback function to be invoked when the command is executed.</param>
public void RemoveCommand(string name, CommandInfo.CommandCallback handler)
{
if (CommandHandlers.ContainsKey(handler))
@@ -215,6 +239,10 @@ namespace CounterStrikeSharp.API.Core
}
}
/// <summary>
/// Remove a command listener.
/// </summary>
/// <inheritdoc cref="AddCommandListener"/>
public void RemoveCommandListener(string name, CommandInfo.CommandListenerCallback handler, HookMode mode)
{
if (CommandListeners.ContainsKey(handler))
@@ -228,39 +256,24 @@ namespace CounterStrikeSharp.API.Core
}
}
/*
public void HookConVarChange(ConVar convar, ConVar.ConVarChangedCallback handler)
{
var wrappedHandler = new Action<IntPtr, string, string>((ptr, oldVal, newVal) =>
{
handler?.Invoke(new ConVar(ptr), oldVal, newVal);
});
var subscriber = new CallbackSubscriber(convar, handler, wrappedHandler);
NativeAPI.HookConvarChange(convar.Handle, subscriber.GetInputArgument());
ConvarChangeHandlers[handler] = subscriber;
}
public void UnhookConVarChange(ConVar convar, ConVar.ConVarChangedCallback handler)
{
if (ConvarChangeHandlers.ContainsKey(handler))
{
var subscriber = ConvarChangeHandlers[handler];
NativeAPI.UnhookConvarChange(convar.Handle, subscriber.GetInputArgument());
FunctionReference.Remove(subscriber.GetReferenceIdentifier());
CommandHandlers.Remove(handler);
}
}*/
// Adds global listener, e.g. OnTick, OnClientConnect
/// <summary>
/// Registers a global listener, e.g. <see cref="Listeners.OnTick"/>, <see cref="Listeners.OnClientConnect"/>.
/// </summary>
/// <param name="handler"></param>
/// <typeparam name="T">Listener delegate type</typeparam>
/// <exception cref="ArgumentException">Invalid listener <see cref="T"/> provided</exception>
/// <example>
/// <code lang="C#">
/// RegisterListener&lt;Listeners.OnTick&gt;(OnTick);
/// </code>
/// </example>
public void RegisterListener<T>(T handler) where T : Delegate
{
var listenerName = typeof(T).GetCustomAttribute<ListenerNameAttribute>()?.Name;
if (string.IsNullOrEmpty(listenerName))
{
throw new Exception("Listener of type T is invalid and does not have a name attribute");
throw new ArgumentException("Listener of type T is invalid and does not have a name attribute",
nameof(T));
}
var parameterTypes = typeof(T).GetMethod("Invoke").GetParameters().Select(p => p.ParameterType).ToArray();
@@ -291,6 +304,34 @@ namespace CounterStrikeSharp.API.Core
Listeners[handler] = subscriber;
}
/// <summary>
/// Removes a global listener.
/// </summary>
/// <param name="handler"></param>
/// <typeparam name="T"></typeparam>
/// <exception cref="ArgumentException">Invalid listener <see cref="T"/> provided</exception>
public void RemoveListener<T>(T handler) where T : Delegate
{
var listenerName = typeof(T).GetCustomAttribute<ListenerNameAttribute>()?.Name;
if (string.IsNullOrEmpty(listenerName))
{
throw new ArgumentException("Listener of type T is invalid and does not have a name attribute",
nameof(T));
}
if (!Listeners.TryGetValue(handler, out var subscriber)) return;
NativeAPI.RemoveListener(listenerName, subscriber.GetInputArgument());
FunctionReference.Remove(subscriber.GetReferenceIdentifier());
Listeners.Remove(handler);
}
/// <summary>
/// Removes a global listener.
/// </summary>
/// <param name="name"></param>
/// <param name="handler"></param>
[Obsolete("Use the generic version of this method")]
public void RemoveListener(string name, Delegate handler)
{
if (!Listeners.TryGetValue(handler, out var subscriber)) return;
@@ -300,6 +341,14 @@ namespace CounterStrikeSharp.API.Core
Listeners.Remove(handler);
}
/// <summary>
/// Adds a timer that will call the given callback after the specified amount of seconds.
/// By default will only run once unless the <see cref="TimerFlags.REPEAT"/> flag is set.
/// </summary>
/// <param name="interval">Interval/Delay in seconds</param>
/// <param name="callback">Code to run when timer elapses</param>
/// <param name="flags">Controls if the timer is a one-off, repeat or stops on map change etc.</param>
/// <returns>An instance of the <see cref="Timer"/></returns>
public Timer AddTimer(float interval, Action callback, TimerFlags? flags = null)
{
var timer = new Timer(interval, callback, flags ?? 0);
@@ -307,7 +356,11 @@ namespace CounterStrikeSharp.API.Core
return timer;
}
/// <summary>
/// Registers all attribute handlers on the given instance.
/// Can be used to register event handlers, console commands, entity outputs etc. from classes that are not derived from `BasePlugin`.
/// </summary>
/// <param name="instance"></param>
public void RegisterAllAttributes(object instance)
{
this.RegisterAttributeHandlers(instance);
@@ -342,7 +395,7 @@ namespace CounterStrikeSharp.API.Core
}
/// <summary>
/// Registers all game event handlers that are decorated with the `[GameEventHandler]` attribute.
/// Registers all game event handlers that are decorated with the <see cref="GameEventHandlerAttribute"/> attribute.
/// </summary>
/// <param name="instance">The instance of the object where the event handlers are defined.</param>
public void RegisterAttributeHandlers(object instance)
@@ -372,6 +425,10 @@ namespace CounterStrikeSharp.API.Core
}
}
/// <summary>
/// Registers all console command handlers that are decorated with the <see cref="ConsoleCommandAttribute"/> attribute.
/// </summary>
/// <param name="instance">The instance of the object where the console command handlers are defined.</param>
public void RegisterConsoleCommandAttributeHandlers(object instance)
{
var eventHandlers = instance.GetType()
@@ -399,6 +456,10 @@ namespace CounterStrikeSharp.API.Core
}
}
/// <summary>
/// Registers all entity output handlers that are decorated with the <see cref="EntityOutputHookAttribute"/> attribute.
/// </summary>
/// <param name="instance">The instance of the object where entity output hook handlers are defined.</param>
public void RegisterEntityOutputAttributeHandlers(object instance)
{
var handlers = instance.GetType()
@@ -453,6 +514,12 @@ namespace CounterStrikeSharp.API.Core
RegisterFakeConVars(instance.GetType(), instance);
}
/// <summary>
/// Hooks an <a href="https://developer.valvesoftware.com/wiki/Inputs_and_Outputs">entity output</a>.
/// </summary>
/// <param name="classname">Classname to hook, or `*` for wildcard</param>
/// <param name="outputName">Output name to hook, or `*` for wildcard</param>
/// <param name="handler">Handler to call</param>
public void HookEntityOutput(string classname, string outputName, EntityIO.EntityOutputHandler handler, HookMode mode = HookMode.Pre)
{
var subscriber = new CallbackSubscriber(handler, handler,
@@ -462,6 +529,10 @@ namespace CounterStrikeSharp.API.Core
EntityOutputHooks[handler] = subscriber;
}
/// <summary>
/// Unhooks an entity output.
/// </summary>
/// <inheritdoc cref="HookEntityOutput"/>
public void UnhookEntityOutput(string classname, string outputName, EntityIO.EntityOutputHandler handler, HookMode mode = HookMode.Pre)
{
if (!EntityOutputHooks.TryGetValue(handler, out var subscriber)) return;
@@ -471,6 +542,12 @@ namespace CounterStrikeSharp.API.Core
EntityOutputHooks.Remove(handler);
}
/// <summary>
/// Hooks an entity output for a single entity instance.
/// </summary>
/// <param name="entityInstance">Entity instance to hook</param>
/// <param name="outputName">Output name to hook, or `*` for wildcard</param>
/// <param name="handler">Handler to call</param>
public void HookSingleEntityOutput(CEntityInstance entityInstance, string outputName, EntityIO.EntityOutputHandler handler)
{
// since we wrap around the plugin handler we need to do this to ensure that the plugin callback is only called
@@ -494,6 +571,10 @@ namespace CounterStrikeSharp.API.Core
EntitySingleOutputHooks[handler] = new EntityIO.EntityOutputCallback(entityInstance.DesignerName, outputName, internalHandler);
}
/// <summary>
/// Unhooks an entity output for a single entity instance.
/// </summary>
/// <inheritdoc cref="HookSingleEntityOutput"/>
public void UnhookSingleEntityOutput(CEntityInstance entityInstance, string outputName, EntityIO.EntityOutputHandler handler)
{
UnhookSingleEntityOutputInternal(entityInstance.DesignerName, outputName, handler);
@@ -532,10 +613,6 @@ namespace CounterStrikeSharp.API.Core
subscriber.Dispose();
}
foreach (var kv in ConvarChangeHandlers)
{
}
foreach (var subscriber in Listeners.Values)
{
subscriber.Dispose();

View File

@@ -28,17 +28,23 @@ namespace CounterStrikeSharp.API.Core
public interface IPlugin : IDisposable
{
/// <summary>
/// Name of the plugin.
/// Name of the plugin as it will appear in the plugin list.
/// </summary>
string ModuleName { get; }
/// <summary>
/// Module version.
/// Module version as it will appear in the plugin list.
/// </summary>
string ModuleVersion { get; }
/// <summary>
/// Author of the plugin as it will appear in the plugin list.
/// </summary>
string ModuleAuthor { get; }
/// <summary>
/// Brief description of the plugin as it will appear in the plugin list.
/// </summary>
string ModuleDescription { get; }
/// <summary>
@@ -61,12 +67,15 @@ namespace CounterStrikeSharp.API.Core
/// <param name="hotReload"></param>
void OnAllPluginsLoaded(bool hotReload);
/// <summary>
/// The path to the plugin's DLL file.
/// </summary>
string ModulePath { get; internal set; }
ILogger Logger { get; set; }
IStringLocalizer Localizer { get; set; }
ICommandManager CommandManager { get; set; }
void RegisterAllAttributes(object instance);

View File

@@ -8,10 +8,14 @@ namespace CounterStrikeSharp.API.Core;
public partial class CBaseEntity
{
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
public void Teleport(Vector position, QAngle angles, Vector velocity)
public void Teleport(Vector? position = null, QAngle? angles = null, Vector? velocity = null)
{
Guard.IsValidEntity(this);
position ??= AbsOrigin!;
angles ??= AbsRotation!;
velocity ??= AbsVelocity;
VirtualFunction.CreateVoid<IntPtr, IntPtr, IntPtr, IntPtr>(Handle, GameData.GetOffset("CBaseEntity_Teleport"))(
Handle, position.Handle, angles.Handle, velocity.Handle);
}
@@ -39,6 +43,6 @@ public partial class CBaseEntity
{
Guard.IsValidEntity(this);
return (T) Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 4));
return (T)Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 4));
}
}

View File

@@ -23,6 +23,10 @@ namespace CounterStrikeSharp.API.Modules.Commands
{
public delegate void CommandCallback(CCSPlayerController? player, CommandInfo commandInfo);
/// <summary>
/// Command listener callback.
/// <returns>If returning <see cref="HookResult.Handled"/> or higher, will prevent the command from executing.</returns>
/// </summary>
public delegate HookResult CommandListenerCallback(CCSPlayerController? player, CommandInfo commandInfo);
public CCSPlayerController? CallingPlayer { get; }

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
@@ -12,7 +13,7 @@ public class Target
private string Raw { get; }
private string Slug { get; }
private static readonly Dictionary<string, TargetType> TargetTypeMap = new(StringComparer.OrdinalIgnoreCase)
public static readonly IReadOnlyDictionary<string, TargetType> TargetTypeMap = new Dictionary<string, TargetType>(StringComparer.OrdinalIgnoreCase)
{
{ "@all", TargetType.GroupAll },
{ "@bots", TargetType.GroupBots },
@@ -24,7 +25,7 @@ public class Target
{ "@ct", TargetType.TeamCt },
{ "@t", TargetType.TeamT },
{ "@spec", TargetType.TeamSpec }
};
}.ToFrozenDictionary();
private static bool ConstTargetType(string target, out TargetType targetType)

View File

@@ -28,6 +28,7 @@ public enum PostSelectAction
public abstract class BaseMenu : IMenu
{
public string Title { get; set; }
public List<ChatMenuOption> MenuOptions { get; } = new();
public PostSelectAction PostSelectAction { get; set; } = PostSelectAction.Reset;
public bool ExitButton { get; set; } = true;

View File

@@ -22,19 +22,30 @@ namespace CounterStrikeSharp.API.Modules.Menu;
public class CenterHtmlMenu : BaseMenu
{
private readonly BasePlugin? _plugin;
public string TitleColor { get; set; } = "yellow";
public string EnabledColor { get; set; } = "green";
public string DisabledColor { get; set; } = "grey";
public string PrevPageColor { get; set; } = "yellow";
public string NextPageColor { get; set; } = "yellow";
public string CloseColor { get; set; } = "red";
public CenterHtmlMenu(string title, BasePlugin plugin) : base(ModifyTitle(title))
{
_plugin = plugin;
}
[Obsolete("Use the constructor that takes a BasePlugin")]
public CenterHtmlMenu(string title) : base(ModifyTitle(title))
{
}
public override void Open(CCSPlayerController player)
{
if (_plugin == null) return;
if (_plugin == null)
{
throw new InvalidOperationException("This method is unsupported with the CenterHtmlMenu constructor used." +
"Please provide a BasePlugin in the constructor.");
};
MenuManager.OpenCenterHtmlMenu(_plugin, player, this);
}
@@ -91,35 +102,40 @@ public class CenterHtmlMenuInstance : BaseMenuInstance
return;
}
if (Menu is not CenterHtmlMenu centerHtmlMenu)
{
return;
}
var builder = new StringBuilder();
builder.Append($"<b><font color='yellow'>{Menu.Title}</font></b>");
builder.Append($"<b><font color='{centerHtmlMenu.TitleColor}'>{centerHtmlMenu.Title}</font></b>");
builder.AppendLine("<br>");
var keyOffset = 1;
for (var i = CurrentOffset; i < Math.Min(CurrentOffset + MenuItemsPerPage, Menu.MenuOptions.Count); i++)
for (var i = CurrentOffset; i < Math.Min(CurrentOffset + MenuItemsPerPage, centerHtmlMenu.MenuOptions.Count); i++)
{
var option = Menu.MenuOptions[i];
string color = option.Disabled ? "grey" : "green";
var option = centerHtmlMenu.MenuOptions[i];
string color = option.Disabled ? centerHtmlMenu.DisabledColor : centerHtmlMenu.EnabledColor;
builder.Append($"<font color='{color}'>!{keyOffset++}</font> {option.Text}");
builder.AppendLine("<br>");
}
if (HasPrevButton)
{
builder.AppendFormat("<font color='yellow'>!7</font> &#60;- Prev");
builder.AppendFormat($"<font color='{centerHtmlMenu.PrevPageColor}'>!7</font> &#60;- Prev");
builder.AppendLine("<br>");
}
if (HasNextButton)
{
builder.AppendFormat("<font color='yellow'>!8</font> -> Next");
builder.AppendFormat($"<font color='{centerHtmlMenu.NextPageColor}'>!8</font> -> Next");
builder.AppendLine("<br>");
}
if (Menu.ExitButton)
if (centerHtmlMenu.ExitButton)
{
builder.AppendFormat("<font color='red'>!9</font> -> Close");
builder.AppendFormat($"<font color='{centerHtmlMenu.CloseColor}'>!9</font> -> Close");
builder.AppendLine("<br>");
}

View File

@@ -20,6 +20,12 @@ namespace CounterStrikeSharp.API.Modules.Menu;
public class ChatMenu : BaseMenu
{
public char TitleColor { get; set; } = ChatColors.Yellow;
public char EnabledColor { get; set; } = ChatColors.Green;
public char DisabledColor { get; set; } = ChatColors.Grey;
public char PrevPageColor { get; set; } = ChatColors.Yellow;
public char NextPageColor { get; set; } = ChatColors.Yellow;
public char CloseColor { get; set; } = ChatColors.Red;
public ChatMenu(string title) : base(title)
{
ExitButton = false;
@@ -39,30 +45,35 @@ public class ChatMenuInstance : BaseMenuInstance
public override void Display()
{
Player.PrintToChat(Menu.Title);
if (Menu is not ChatMenu chatMenu)
{
return;
}
Player.PrintToChat($" {chatMenu.TitleColor} {chatMenu.Title}");
Player.PrintToChat("---");
var keyOffset = 1;
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}");
char color = option.Disabled ? chatMenu.DisabledColor : chatMenu.EnabledColor;
Player.PrintToChat($" {color} !{keyOffset++} {ChatColors.Default}{option.Text}");
}
if (HasPrevButton)
{
Player.PrintToChat($" {ChatColors.Yellow}!7 {ChatColors.Default}-> Prev");
Player.PrintToChat($" {chatMenu.PrevPageColor}!7 {ChatColors.Default}-> Prev");
}
if (HasNextButton)
{
Player.PrintToChat($" {ChatColors.Yellow}!8 {ChatColors.Default}-> Next");
Player.PrintToChat($" {chatMenu.NextPageColor}!8 {ChatColors.Default}-> Next");
}
if (Menu.ExitButton)
{
Player.PrintToChat($" {ChatColors.Red}!9 {ChatColors.Default}-> Close");
Player.PrintToChat($" {chatMenu.CloseColor}!9 {ChatColors.Default}-> Close");
}
}
}

View File

@@ -14,9 +14,6 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System;
using CounterStrikeSharp.API.Core;
namespace CounterStrikeSharp.API.Modules.Utils
{
/// <summary>
@@ -30,6 +27,8 @@ namespace CounterStrikeSharp.API.Modules.Utils
/// </summary>
public class Angle : NativeObject
{
public static readonly Angle Zero = new();
public Angle(IntPtr pointer) : base(pointer)
{
}
@@ -46,8 +45,6 @@ namespace CounterStrikeSharp.API.Modules.Utils
this.Y = y ?? 0;
this.Z = z ?? 0;
}
#region Accessors
@@ -366,15 +363,15 @@ namespace CounterStrikeSharp.API.Modules.Utils
return this.IsEqualTol(v, 0.01f);
}
public override string ToString()
{
return $"{X:n2} {Y:n2} {Z:n2}";
}
#endregion
protected override void OnDispose()
{
}*/
public override string ToString()
{
return $"{X:n2} {Y:n2} {Z:n2}";
}
}
}

View File

@@ -14,14 +14,14 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System;
using System.Runtime.CompilerServices;
using CounterStrikeSharp.API.Core;
namespace CounterStrikeSharp.API.Modules.Utils
{
public class QAngle : NativeObject
{
public static readonly QAngle Zero = new();
public QAngle(IntPtr pointer) : base(pointer)
{
}

View File

@@ -14,11 +14,7 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Utils
{
@@ -33,11 +29,12 @@ namespace CounterStrikeSharp.API.Modules.Utils
/// </summary>
public class Vector : NativeObject
{
public static readonly Vector Zero = new();
public Vector(IntPtr pointer) : base(pointer)
{
}
public Vector(float? x = null, float? y = null, float? z = null) : this(NativeAPI.VectorNew())
{
this.X = x ?? 0;