Game Event Broadcast Manipulation & Cancellation (#10)

This commit is contained in:
Michael Wilson
2023-10-23 19:56:42 +10:00
committed by GitHub
parent 37b085a9f5
commit 65bdd0b5ff
17 changed files with 620 additions and 523 deletions

View File

@@ -1,19 +1,21 @@
--- BasedOnStyle: LLVM
Language: Cpp
BasedOnStyle: Google
AccessModifierOffset: -4
Standard: c++11
IndentWidth: 4 IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100 ColumnLimit: 100
DerivePointerAlignment: false
PointerAlignment: Left
AlignAfterOpenBracket: Align AlignAfterOpenBracket: Align
BinPackParameters: false KeepEmptyLinesAtTheStartOfBlocks: false
AlignEscapedNewlines: Left SortIncludes: false
AlwaysBreakTemplateDeclarations: Yes SpaceBeforeParens: ControlStatements
PackConstructorInitializers: Never AllowAllArgumentsOnNextLine: true
BreakConstructorInitializersBeforeComma: false AllowShortIfStatementsOnASingleLine: false
IndentPPDirectives: BeforeHash IndentCaseLabels: false
SortIncludes: Never BreakBeforeBraces: Custom
... BraceWrapping:
AfterClass: true
AfterStruct: true
AfterEnum: true
AfterUnion: true
AfterNamespace: false
AfterFunction: true
IndentBraces: false

View File

@@ -83,6 +83,7 @@ Checks: >
-readability-redundant-declaration, -readability-redundant-declaration,
-readability-function-cognitive-complexity, -readability-function-cognitive-complexity,
-readability-convert-member-functions-to-static, -readability-convert-member-functions-to-static,
-readability-implicit-bool-conversion,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-easily-swappable-parameters, -bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result, -bugprone-implicit-widening-of-multiplication-result,
@@ -92,25 +93,25 @@ Checks: >
WarningsAsErrors: "*" WarningsAsErrors: "*"
CheckOptions: CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines google-readability-braces-around-statements.ShortStatementLines: '1'
value: '1' google-readability-function-size.StatementThreshold: '800'
- key: google-readability-function-size.StatementThreshold google-readability-namespace-comments.ShortNamespaceLines: '10'
value: '800' google-readability-namespace-comments.SpacesBeforeComments: '2'
- key: google-readability-namespace-comments.ShortNamespaceLines readability-identifier-naming.PrivateMemberPrefix: 'm_'
value: '10' readability-identifier-naming.ProtectedMemberPrefix: 'm_'
- key: google-readability-namespace-comments.SpacesBeforeComments readability-identifier-naming.MemberPrefix: 'm_'
value: '2' readability-identifier-naming.ClassCase: CamelCase
- key: readability-identifier-naming.ClassCase readability-identifier-naming.MemberCase: CamelCase
value: CamelCase readability-identifier-naming.EnumCase: CamelCase
- key: readability-identifier-naming.MemberCase readability-identifier-naming.FunctionCase: CamelCase
value: camelBack readability-identifier-naming.ParameterCase: CamelCase
- key: readability-identifier-naming.EnumCase readability-identifier-naming.UnionCase: CamelCase
value: CamelCase readability-identifier-naming.VariableCase: CamelCase
- key: readability-identifier-naming.FunctionCase readability-identifier-naming.LocalConstantPointerPrefix: 'p'
value: CamelCase readability-identifier-naming.VariableHungarianPrefix: On
- key: readability-identifier-naming.ParameterCase readability-identifier-naming.ParameterHungarianPrefix: On
value: camelBack readability-identifier-naming.MemberHungarianPrefix: On
- key: readability-identifier-naming.UnionCase readability-identifier-naming.PointerParameterHungarianPrefix: On
value: CamelCase readability-identifier-naming.PointerParameterCase: CamelCase
- key: readability-identifier-naming.VariableCase readability-identifier-naming.HungarianNotation.UserDefinedType.std::string: s
value: camelBack

View File

@@ -15,10 +15,12 @@ The first parameter type must be a subclass of the `GameEvent` class. The names
```csharp ```csharp
[GameEventHandler] [GameEventHandler]
public void OnPlayerConnect(EventPlayerConnect @event) public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{ {
// Userid will give you a reference to a CCSPlayerController class // Userid will give you a reference to a CCSPlayerController class
Log($"Player {@event.Userid.PlayerName} has connected!"); Log($"Player {@event.Userid.PlayerName} has connected!");
return HookResult.Continue;
} }
``` ```
@@ -29,9 +31,11 @@ It is also possible to bind event listeners in the `OnLoad` (or anywhere you hav
```csharp ```csharp
public override void Load(bool hotReload) public override void Load(bool hotReload)
{ {
RegisterEventHandler<EventRoundStart>(@event => RegisterEventHandler<EventRoundStart>((@event, info) =>
{ {
Console.WriteLine($"Round has started with time limit of {@event.Timelimit}"); Console.WriteLine($"Round has started with time limit of {@event.Timelimit}");
return HookResult.Continue;
}); });
} }
``` ```
@@ -41,3 +45,11 @@ public override void Load(bool hotReload)
The specific subclass of `GameEvent` will provide strongly typed parameters from the event definition. e.g. `event.Timelimit` will be a `long` value, `event.UserId` will be a `CCSPlayerController` and so-on. The specific subclass of `GameEvent` will provide strongly typed parameters from the event definition. e.g. `event.Timelimit` will be a `long` value, `event.UserId` will be a `CCSPlayerController` and so-on.
These event properties are mutable so you can update them as normal and they will update in the event instance. These event properties are mutable so you can update them as normal and they will update in the event instance.
## Preventing Broadcast
You can modify a game event so that it does not get broadcast to clients by modifying the `bool info.DontBroadcast` property. e.g.
## Cancelling an Event
In a pre-event hook, you can prevent the event from continuing to other plugins by returning `HookResult.Handled` or `HookResult.Stop`.

View File

@@ -31,10 +31,12 @@ public class HelloWorldPlugin : BasePlugin
} }
[GameEventHandler] [GameEventHandler]
public void OnPlayerConnect(EventPlayerConnect @event) public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{ {
// Userid will give you a reference to a CCSPlayerController class // Userid will give you a reference to a CCSPlayerController class
Log($"Player {@event.Userid.PlayerName} has connected!"); Log($"Player {@event.Userid.PlayerName} has connected!");
return HookResult.Continue;
} }
[ConsoleCommand("issue_warning", "Issue warning to player")] [ConsoleCommand("issue_warning", "Issue warning to player")]

View File

@@ -30,5 +30,5 @@ SET(
dynload_s dynload_s
dyncall_s dyncall_s
distorm distorm
funchook-shared funchook-static
) )

View File

@@ -103,26 +103,19 @@ namespace CounterStrikeSharp.API.Core
public readonly List<Timer> Timers = new List<Timer>(); public readonly List<Timer> Timers = new List<Timer>();
private void RegisterEventHandlerInternal<T>(string name, Action<T> handler, bool post = false) public delegate HookResult GameEventHandler<T>(T @event, GameEventInfo info) where T : GameEvent;
where T : GameEvent, new()
{
var wrappedHandler = new Action<IntPtr>(pointer =>
{
var @event = new T
{
Handle = pointer
};
handler.Invoke(@event);
});
var subscriber = new CallbackSubscriber(handler, wrappedHandler, private void RegisterEventHandlerInternal<T>(string name, GameEventHandler<T> handler, bool post = false)
where T : GameEvent
{
var subscriber = new CallbackSubscriber(handler, handler,
() => DeregisterEventHandler(name, handler, post)); () => DeregisterEventHandler(name, handler, post));
NativeAPI.HookEvent(name, subscriber.GetInputArgument(), post); NativeAPI.HookEvent(name, subscriber.GetInputArgument(), post);
Handlers[handler] = subscriber; Handlers[handler] = subscriber;
} }
public void RegisterEventHandler<T>(Action<T> handler, bool post = false) where T : GameEvent, new() public void RegisterEventHandler<T>(GameEventHandler<T> handler, bool post = false) where T : GameEvent
{ {
var name = typeof(T).GetCustomAttribute<EventNameAttribute>()?.Name; var name = typeof(T).GetCustomAttribute<EventNameAttribute>()?.Name;
RegisterEventHandlerInternal(name, handler, post); RegisterEventHandlerInternal(name, handler, post);
@@ -276,8 +269,8 @@ namespace CounterStrikeSharp.API.Core
var parameterType = eventHandler.GetParameters().First().ParameterType; var parameterType = eventHandler.GetParameters().First().ParameterType;
var eventName = parameterType.GetCustomAttribute<EventNameAttribute>()?.Name; var eventName = parameterType.GetCustomAttribute<EventNameAttribute>()?.Name;
var actionType = typeof(Action<>).MakeGenericType(parameterType); var actionType = typeof(GameEventHandler<>).MakeGenericType(parameterType);
var action = eventHandler.CreateDelegate(actionType, instance); var action = Delegate.CreateDelegate(actionType, instance, eventHandler);
var generic = method.MakeGenericMethod(parameterType); var generic = method.MakeGenericMethod(parameterType);
generic.Invoke(this, new object[] { eventName, action, false }); generic.Invoke(this, new object[] { eventName, action, false });

View File

@@ -0,0 +1,13 @@
using System;
using System.Runtime.CompilerServices;
namespace CounterStrikeSharp.API.Core;
public class GameEventInfo : NativeObject
{
public GameEventInfo(IntPtr pointer) : base(pointer)
{
}
public unsafe ref bool DontBroadcast => ref Unsafe.AsRef<bool>((void*)Handle);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
namespace CounterStrikeSharp.API.Core;
public enum HookResult
{
Continue = 0,
Changed = 1,
Handled = 3,
Stop = 4,
}

View File

@@ -30,11 +30,9 @@ namespace CounterStrikeSharp.API.Modules.Events
public string Name { get; set; } public string Name { get; set; }
} }
public class GameEvent public class GameEvent : NativeObject
{ {
public IntPtr Handle { get; internal set; } public GameEvent(IntPtr pointer) : base(pointer)
protected GameEvent()
{ {
} }
@@ -42,11 +40,6 @@ namespace CounterStrikeSharp.API.Modules.Events
{ {
} }
internal GameEvent(IntPtr pointer)
{
Handle = pointer;
}
public string EventName => NativeAPI.GetEventName(Handle); public string EventName => NativeAPI.GetEventName(Handle);
public T Get<T>(string name) public T Get<T>(string name)

View File

@@ -54,19 +54,34 @@ namespace TestPlugin
VirtualFunction.CreateVoid<IntPtr, int>(GameData.GetSignature("CCSPlayerController_SwitchTeam")); VirtualFunction.CreateVoid<IntPtr, int>(GameData.GetSignature("CCSPlayerController_SwitchTeam"));
// Register Game Event Handlers // Register Game Event Handlers
RegisterEventHandler<EventPlayerConnect>(GenericEventHandler); RegisterEventHandler<EventPlayerConnect>(GenericEventHandler);
RegisterEventHandler<EventPlayerJump>(@event => RegisterEventHandler<EventPlayerDeath>((@event, info) =>
{
// You can use `info.DontBroadcast` to set the dont broadcast flag on the event.
if (new Random().NextSingle() > 0.5f)
{
@event.Attacker.PrintToChat($"Skipping player_death broadcast at {Server.CurrentTime}");
info.DontBroadcast = true;
}
return HookResult.Continue;
});
RegisterEventHandler<EventPlayerJump>((@event, info) =>
{ {
sigVirtualFunc(@event.Userid.Handle, 2, "Test", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); sigVirtualFunc(@event.Userid.Handle, 2, "Test", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
return HookResult.Continue;
}); });
RegisterEventHandler<EventPlayerSpawn>(@event => RegisterEventHandler<EventPlayerSpawn>((@event, info) =>
{ {
if (!@event.Userid.IsValid) return; if (!@event.Userid.IsValid) return 0;
if (!@event.Userid.PlayerPawn.IsValid) return; if (!@event.Userid.PlayerPawn.IsValid) return 0;
Log($"Player spawned with entity index: {@event.Userid.EntityIndex} & User ID: {@event.Userid.UserId}"); Log($"Player spawned with entity index: {@event.Userid.EntityIndex} & User ID: {@event.Userid.UserId}");
return HookResult.Continue;
}); });
RegisterEventHandler<EventPlayerBlind>(GenericEventHandler); RegisterEventHandler<EventPlayerBlind>(GenericEventHandler);
RegisterEventHandler<EventBulletImpact>(@event => RegisterEventHandler<EventBulletImpact>((@event, info) =>
{ {
var player = @event.Userid; var player = @event.Userid;
var pawn = player.PlayerPawn.Value; var pawn = player.PlayerPawn.Value;
@@ -83,21 +98,26 @@ namespace TestPlugin
VirtualFunctions.GiveNamedItem(pawn.ItemServices.Handle, "weapon_ak47", 0, 0, 0, 0); VirtualFunctions.GiveNamedItem(pawn.ItemServices.Handle, "weapon_ak47", 0, 0, 0, 0);
Log($"Pawn Position: {pawn.CBodyComponent?.SceneNode?.AbsOrigin} @{pawn.CBodyComponent?.SceneNode.Rotation}"); Log(
$"Pawn Position: {pawn.CBodyComponent?.SceneNode?.AbsOrigin} @{pawn.CBodyComponent?.SceneNode.Rotation}");
char randomColourChar = (char)new Random().Next(0, 16); char randomColourChar = (char)new Random().Next(0, 16);
printAllFunc(3, $"Random String with Random Colour: {randomColourChar}{new Random().Next()}", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); printAllFunc(3, $"Random String with Random Colour: {randomColourChar}{new Random().Next()}",
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
pawn.Health += 5; pawn.Health += 5;
Log( Log(
$"Found steamID {new SteamID(player.SteamID)} for player {player.PlayerName}:{pawn.Health}|{pawn.InBuyZone}"); $"Found steamID {new SteamID(player.SteamID)} for player {player.PlayerName}:{pawn.Health}|{pawn.InBuyZone}");
Log($"{@event.Userid}, {@event.X},{@event.Y},{@event.Z}"); Log($"{@event.Userid}, {@event.X},{@event.Y},{@event.Z}");
return HookResult.Continue;
}); });
RegisterEventHandler<EventRoundStart>(@event => RegisterEventHandler<EventRoundStart>((@event, info) =>
{ {
// Grab all cs_player_controller entities and set their cash value to $1337. // Grab all cs_player_controller entities and set their cash value to $1337.
var playerEntities = Utilities.FindAllEntitiesByDesignerName<CCSPlayerController>("cs_player_controller"); var playerEntities =
Utilities.FindAllEntitiesByDesignerName<CCSPlayerController>("cs_player_controller");
Log($"cs_player_controller count: {playerEntities.Count<CCSPlayerController>()}"); Log($"cs_player_controller count: {playerEntities.Count<CCSPlayerController>()}");
foreach (var player in playerEntities) foreach (var player in playerEntities)
@@ -116,6 +136,8 @@ namespace TestPlugin
var gamerulesEnt = new CCSGameRules(entity.Handle); var gamerulesEnt = new CCSGameRules(entity.Handle);
gamerulesEnt.CTTimeOutActive = true; gamerulesEnt.CTTimeOutActive = true;
} }
return HookResult.Continue;
}); });
// Hook global listeners defined by CounterStrikeSharp // Hook global listeners defined by CounterStrikeSharp
@@ -131,7 +153,6 @@ namespace TestPlugin
RegisterListener<Listeners.OnEntitySpawned>(entity => RegisterListener<Listeners.OnEntitySpawned>(entity =>
{ {
var designerName = entity.DesignerName; var designerName = entity.DesignerName;
if (designerName != "smokegrenade_projectile") return; if (designerName != "smokegrenade_projectile") return;
@@ -151,7 +172,6 @@ namespace TestPlugin
$"Test file created by TestPlugin at {DateTime.Now}"); $"Test file created by TestPlugin at {DateTime.Now}");
// Execute a server command as if typed into the server console. // Execute a server command as if typed into the server console.
Server.ExecuteCommand("find \"cssharp\""); Server.ExecuteCommand("find \"cssharp\"");
@@ -160,7 +180,8 @@ namespace TestPlugin
(player, info) => (player, info) =>
{ {
if (player == null) return; if (player == null) return;
Log($"CounterStrikeSharp - a test command was called by {new SteamID(player.SteamID).SteamId2} with {info.ArgString}"); Log(
$"CounterStrikeSharp - a test command was called by {new SteamID(player.SteamID).SteamId2} with {info.ArgString}");
}); });
// Example vfunc call that usually gets the game event manager pointer // Example vfunc call that usually gets the game event manager pointer
@@ -169,14 +190,14 @@ namespace TestPlugin
var virtualFunc = VirtualFunction.Create<IntPtr>(server.Pointer, 91); var virtualFunc = VirtualFunction.Create<IntPtr>(server.Pointer, 91);
var result = virtualFunc() - 8; var result = virtualFunc() - 8;
Log($"Result of virtual func call is {result:X}"); Log($"Result of virtual func call is {result:X}");
} }
[GameEventHandler] [GameEventHandler]
public void OnPlayerConnect(EventPlayerConnect @event) public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{ {
Log($"Player {@event.Name} has connected!"); Log($"Player {@event.Name} has connected!");
return HookResult.Continue;
} }
[ConsoleCommand("cssharp_attribute", "This is a custom attribute event")] [ConsoleCommand("cssharp_attribute", "This is a custom attribute event")]
@@ -185,9 +206,11 @@ namespace TestPlugin
Log("cssharp_attribute called!"); Log("cssharp_attribute called!");
} }
private void GenericEventHandler<T>(T @event) where T : GameEvent private HookResult GenericEventHandler<T>(T @event, GameEventInfo info) where T : GameEvent
{ {
Log($"Event found {@event.Handle:X}, event name: {@event.EventName}"); Log($"Event found {@event.Handle:X}, event name: {@event.EventName} dont broadcast: {info.DontBroadcast}");
return HookResult.Continue;
} }
private void Log(string message) private void Log(string message)

View File

@@ -34,147 +34,173 @@
#include "core/log.h" #include "core/log.h"
#include "scripting/callback_manager.h" #include "scripting/callback_manager.h"
SH_DECL_HOOK2(IGameEventManager2, FireEvent, SH_NOATTRIB, 0, bool, IGameEvent *, bool); SH_DECL_HOOK2(IGameEventManager2, FireEvent, SH_NOATTRIB, 0, bool, IGameEvent*, bool);
namespace counterstrikesharp { namespace counterstrikesharp {
EventManager::EventManager() {} EventManager::EventManager() = default;
EventManager::~EventManager() {} EventManager::~EventManager() = default;
void EventManager::OnStartup() {} void EventManager::OnStartup() {}
void EventManager::OnAllInitialized() { void EventManager::OnAllInitialized()
{
SH_ADD_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager, SH_ADD_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager,
SH_MEMBER(this, &EventManager::OnFireEvent), false); SH_MEMBER(this, &EventManager::OnFireEvent), false);
SH_ADD_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager, SH_ADD_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager,
SH_MEMBER(this, &EventManager::OnFireEvent_Post), true); SH_MEMBER(this, &EventManager::OnFireEventPost), true);
} }
void EventManager::OnShutdown() { void EventManager::OnShutdown()
{
SH_REMOVE_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager, SH_REMOVE_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager,
SH_MEMBER(this, &EventManager::OnFireEvent), false); SH_MEMBER(this, &EventManager::OnFireEvent), false);
SH_REMOVE_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager, SH_REMOVE_HOOK(IGameEventManager2, FireEvent, globals::gameEventManager,
SH_MEMBER(this, &EventManager::OnFireEvent_Post), true); SH_MEMBER(this, &EventManager::OnFireEventPost), true);
globals::gameEventManager->RemoveListener(this); globals::gameEventManager->RemoveListener(this);
} }
void EventManager::FireGameEvent(IGameEvent *event) {} void EventManager::FireGameEvent(IGameEvent* pEvent) {}
bool EventManager::HookEvent(const char *name, CallbackT callback, bool post) { bool EventManager::HookEvent(const char* szName, CallbackT fnCallback, bool bPost)
EventHook *p_hook; {
EventHook* pHook;
if (!globals::gameEventManager->FindListener(this, name)) { if (!globals::gameEventManager->FindListener(this, szName)) {
globals::gameEventManager->AddListener(this, name, true); globals::gameEventManager->AddListener(this, szName, true);
} }
CSSHARP_CORE_INFO("Hooking event: {0} with callback pointer: {1}", name, (void *)callback); CSSHARP_CORE_INFO("Hooking event: {0} with callback pointer: {1}", szName, (void*)fnCallback);
auto search = m_hooks.find(name); auto search = m_hooksMap.find(szName);
// If hook struct is not found // If hook struct is not found
if (search == m_hooks.end()) { if (search == m_hooksMap.end()) {
p_hook = new EventHook(); pHook = new EventHook();
if (post) { if (bPost) {
p_hook->PostHook = globals::callbackManager.CreateCallback(name); pHook->m_pPostHook = globals::callbackManager.CreateCallback(szName);
p_hook->PostHook->AddListener(callback); pHook->m_pPostHook->AddListener(fnCallback);
} else { } else {
p_hook->PreHook = globals::callbackManager.CreateCallback(name); pHook->m_pPreHook = globals::callbackManager.CreateCallback(szName);
p_hook->PreHook->AddListener(callback); pHook->m_pPreHook->AddListener(fnCallback);
} }
p_hook->name = std::string(name); pHook->m_Name = std::string(szName);
m_hooks[name] = p_hook; m_hooksMap[szName] = pHook;
return true; return true;
} else { } else {
p_hook = search->second; pHook = search->second;
} }
if (post) { if (bPost) {
if (!p_hook->PostHook) { if (!pHook->m_pPostHook) {
p_hook->PostHook = globals::callbackManager.CreateCallback(""); pHook->m_pPostHook = globals::callbackManager.CreateCallback("");
} }
p_hook->PostHook->AddListener(callback); pHook->m_pPostHook->AddListener(fnCallback);
} else { } else {
if (!p_hook->PreHook) { if (!pHook->m_pPreHook) {
p_hook->PreHook = globals::callbackManager.CreateCallback(""); pHook->m_pPreHook = globals::callbackManager.CreateCallback("");
; ;
} }
p_hook->PreHook->AddListener(callback); pHook->m_pPreHook->AddListener(fnCallback);
} }
return true; return true;
} }
bool EventManager::UnhookEvent(const char *name, CallbackT callback, bool post) { bool EventManager::UnhookEvent(const char* szName, CallbackT fnCallback, bool bPost)
EventHook *p_hook; {
ScriptCallback *p_callback; EventHook* pHook;
ScriptCallback* pCallback;
auto search = m_hooks.find(name); auto search = m_hooksMap.find(szName);
if (search == m_hooks.end()) { if (search == m_hooksMap.end()) {
return false; return false;
} }
p_hook = search->second; pHook = search->second;
if (post) { if (bPost) {
p_callback = p_hook->PostHook; pCallback = pHook->m_pPostHook;
} else { } else {
p_callback = p_hook->PreHook; pCallback = pHook->m_pPreHook;
} }
// Remove from function list // Remove from function list
if (p_callback == nullptr) { if (pCallback == nullptr) {
return false; return false;
} }
p_callback = nullptr; if (bPost) {
if (post) { pHook->m_pPostHook = nullptr;
p_hook->PostHook = nullptr;
} else { } else {
p_hook->PreHook = nullptr; pHook->m_pPreHook = nullptr;
} }
// TODO: Clean up callback if theres noone left attached. // TODO: Clean up callback if theres noone left attached.
CSSHARP_CORE_INFO("Unhooking event: {0} with callback pointer: {1}", name, (void *)callback); CSSHARP_CORE_INFO("Unhooking event: {0} with callback pointer: {1}", szName, (void*)fnCallback);
return true; return true;
} }
bool EventManager::OnFireEvent(IGameEvent *pEvent, bool bDontBroadcast) { bool EventManager::OnFireEvent(IGameEvent* pEvent, bool bDontBroadcast)
EventHook *p_hook; {
const char *name; const char* szName;
bool bLocalDontBroadcast = bDontBroadcast;
if (!pEvent) { if (!pEvent) {
RETURN_META_VALUE(MRES_IGNORED, false); RETURN_META_VALUE(MRES_IGNORED, false);
} }
name = pEvent->GetName(); szName = pEvent->GetName();
auto search = m_hooks.find(name); CSSHARP_CORE_TRACE("OnFireEvent {}", szName);
if (search != m_hooks.end()) {
auto p_callback = search->second->PreHook;
if (p_callback) { auto I = m_hooksMap.find(szName);
CSSHARP_CORE_INFO("Pushing event `{0}` pointer: {1}", name, (void *)pEvent); if (I != m_hooksMap.end()) {
p_callback->ScriptContext().Reset(); auto* pCallback = I->second->m_pPreHook;
p_callback->ScriptContext().SetArgument(0, pEvent);
p_callback->Execute();
RETURN_META_VALUE(MRES_IGNORED, false); if (pCallback) {
CSSHARP_CORE_INFO("Pushing event `{0}` pointer: {1}, dont broadcast: {2}", szName,
(void*)pEvent, bDontBroadcast);
EventOverride override = {bDontBroadcast};
pCallback->Reset();
pCallback->ScriptContext().Push(pEvent);
pCallback->ScriptContext().Push(&override);
for (auto fnMethodToCall : pCallback->GetFunctions()) {
if (!fnMethodToCall)
continue;
fnMethodToCall(&pCallback->ScriptContextStruct());
auto result = pCallback->ScriptContext().GetResult<HookResult>();
bLocalDontBroadcast = override.m_bDontBroadcast;
if (result >= HookResult::Handled) {
globals::gameEventManager->FreeEvent(pEvent);
RETURN_META_VALUE(MRES_SUPERCEDE, false);
}
}
} }
} }
if (bLocalDontBroadcast != bDontBroadcast)
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, true, &IGameEventManager2::FireEvent,
(pEvent, bLocalDontBroadcast));
RETURN_META_VALUE(MRES_IGNORED, true); RETURN_META_VALUE(MRES_IGNORED, true);
} }
bool EventManager::OnFireEvent_Post(IGameEvent *pEvent, bool bDontBroadcast) { bool EventManager::OnFireEventPost(IGameEvent* pEvent, bool bDontBroadcast)
{
RETURN_META_VALUE(MRES_IGNORED, true); RETURN_META_VALUE(MRES_IGNORED, true);
} }
} // namespace counterstrikesharp } // namespace counterstrikesharp

View File

@@ -46,43 +46,48 @@ class CUtlString;
namespace counterstrikesharp { namespace counterstrikesharp {
class ScriptCallback; class ScriptCallback;
class PluginFunction; class PluginFunction;
} // namespace counterstrikesharp } // namespace counterstrikesharp
struct EventHook { struct EventHook
EventHook() { {
PreHook = nullptr; EventHook()
PostHook = nullptr; {
m_pPreHook = nullptr;
m_pPostHook = nullptr;
} }
counterstrikesharp::ScriptCallback *PreHook; counterstrikesharp::ScriptCallback* m_pPreHook;
counterstrikesharp::ScriptCallback *PostHook; counterstrikesharp::ScriptCallback* m_pPostHook;
std::string name; std::string m_Name;
};
struct EventOverride {
bool m_bDontBroadcast;
}; };
namespace counterstrikesharp { namespace counterstrikesharp {
class EventManager : public IGameEventListener2, public GlobalClass { class EventManager : public IGameEventListener2, public GlobalClass
public: {
public:
EventManager(); EventManager();
~EventManager(); ~EventManager() override;
public: // GlobalClass // GlobalClass
void OnShutdown() override; void OnShutdown() override;
void OnAllInitialized() override; void OnAllInitialized() override;
void OnStartup() override; void OnStartup() override;
public: // IGameEventListener2 // IGameEventListener2
void FireGameEvent(IGameEvent *event) override; void FireGameEvent(IGameEvent* pEvent) override;
public: bool UnhookEvent(const char* szName, CallbackT fnCallback, bool bPost);
bool UnhookEvent(const char *name, CallbackT callback, bool post); bool HookEvent(const char* szName, CallbackT fnCallback, bool bPost);
bool HookEvent(const char *name, CallbackT callback, bool post);
private: private:
bool OnFireEvent(IGameEvent *pEvent, bool bDontBroadcast); bool OnFireEvent(IGameEvent* pEvent, bool bDontBroadcast);
bool OnFireEvent_Post(IGameEvent *pEvent, bool bDontBroadcast); bool OnFireEventPost(IGameEvent* pEvent, bool bDontBroadcast);
private: std::map<std::string, EventHook*> m_hooksMap;
std::map<std::string, EventHook *> m_hooks;
}; };
} // namespace counterstrikesharp } // namespace counterstrikesharp

View File

@@ -20,95 +20,102 @@
namespace counterstrikesharp { namespace counterstrikesharp {
ScriptCallback::ScriptCallback(const char *name) ScriptCallback::ScriptCallback(const char* szName) : m_root_context(fxNativeContext{})
: m_root_context(fxNativeContext{}) { {
m_script_context_raw = ScriptContextRaw(m_root_context); m_script_context_raw = ScriptContextRaw(m_root_context);
m_name = std::string(name); m_name = std::string(szName);
} }
ScriptCallback::~ScriptCallback() { m_functions.clear(); } ScriptCallback::~ScriptCallback() { m_functions.clear(); }
void ScriptCallback::AddListener(CallbackT plugin_function) { void ScriptCallback::AddListener(CallbackT fnPluginFunction)
m_functions.push_back(plugin_function); {
m_functions.push_back(fnPluginFunction);
} }
bool ScriptCallback::RemoveListener(CallbackT pluginFunction) { bool ScriptCallback::RemoveListener(CallbackT fnPluginFunction)
bool success; {
bool bSuccess;
m_functions.erase(std::remove(m_functions.begin(), m_functions.end(), pluginFunction), m_functions.erase(std::remove(m_functions.begin(), m_functions.end(), fnPluginFunction),
m_functions.end()); m_functions.end());
return success; return bSuccess;
} }
void ScriptCallback::Execute(bool resetContext) { void ScriptCallback::Execute(bool bResetContext)
for (auto method_to_call : m_functions) { {
if (method_to_call) { for (auto fnMethodToCall : m_functions) {
method_to_call(&ScriptContextStruct()); if (fnMethodToCall) {
fnMethodToCall(&ScriptContextStruct());
} }
} }
if (resetContext) { if (bResetContext) {
ResetContext(); Reset();
} }
} }
void ScriptCallback::ResetContext() { ScriptContext().Reset(); } void ScriptCallback::Reset() { ScriptContext().Reset(); }
CallbackManager::CallbackManager() {} CallbackManager::CallbackManager() = default;
ScriptCallback *CallbackManager::CreateCallback(const char *name) { ScriptCallback* CallbackManager::CreateCallback(const char* szName)
CSSHARP_CORE_TRACE("Creating callback {0}", name); {
auto *callback = new ScriptCallback(name); CSSHARP_CORE_TRACE("Creating callback {0}", szName);
m_managed.push_back(callback); auto* pCallback = new ScriptCallback(szName);
m_managed.push_back(pCallback);
return callback; return pCallback;
} }
ScriptCallback *CallbackManager::FindCallback(const char *name) { ScriptCallback* CallbackManager::FindCallback(const char* szName)
for (auto it = m_managed.begin(); it != m_managed.end(); ++it) { {
ScriptCallback *marshal = *it; for (auto* pMarshal : m_managed) {
if (strcmp(marshal->GetName().c_str(), name) == 0) { if (strcmp(pMarshal->GetName().c_str(), szName) == 0) {
return marshal; return pMarshal;
} }
} }
return nullptr; return nullptr;
} }
void CallbackManager::ReleaseCallback(ScriptCallback *callback) { void CallbackManager::ReleaseCallback(ScriptCallback* pCallback)
bool success; {
auto it = std::remove_if(m_managed.begin(), m_managed.end(), auto I = std::remove_if(m_managed.begin(), m_managed.end(),
[callback](ScriptCallback *i) { return callback == i; }); [pCallback](ScriptCallback* pI) { return pCallback == pI; });
if ((success = it != m_managed.end())) m_managed.erase(it, m_managed.end()); if (I != m_managed.end())
delete callback; m_managed.erase(I, m_managed.end());
delete pCallback;
} }
bool CallbackManager::TryAddFunction(const char *name, CallbackT pCallable) { bool CallbackManager::TryAddFunction(const char* szName, CallbackT fnCallable)
auto *fwd = FindCallback(name); {
if (fwd) { auto* pCallback = FindCallback(szName);
fwd->AddListener(pCallable); if (pCallback) {
pCallback->AddListener(fnCallable);
return true; return true;
} }
return false; return false;
} }
bool CallbackManager::TryRemoveFunction(const char *name, CallbackT pCallable) { bool CallbackManager::TryRemoveFunction(const char* szName, CallbackT fnCallable)
auto *fwd = FindCallback(name); {
if (fwd) { auto* pCallback = FindCallback(szName);
bool success = fwd->RemoveListener(pCallable); if (pCallback) {
return success; return pCallback->RemoveListener(fnCallable);
} }
return false; return false;
} }
void CallbackManager::PrintCallbackDebug() { void CallbackManager::PrintCallbackDebug()
{
CSSHARP_CORE_INFO("----CALLBACKS----"); CSSHARP_CORE_INFO("----CALLBACKS----");
for (auto it : m_managed) { for (auto* pCallback : m_managed) {
CSSHARP_CORE_INFO("{0} ({0})\n", it->GetName().c_str(), 1); CSSHARP_CORE_INFO("{0} ({0})\n", pCallback->GetName().c_str(), 1);
} }
} }
} // namespace counterstrikesharp } // namespace counterstrikesharp

View File

@@ -24,40 +24,44 @@
namespace counterstrikesharp { namespace counterstrikesharp {
class ScriptCallback { class ScriptCallback
public: {
ScriptCallback(const char *name); public:
ScriptCallback(const char* szName);
~ScriptCallback(); ~ScriptCallback();
void AddListener(CallbackT plugin_function); void AddListener(CallbackT fnPluginFunction);
bool RemoveListener(CallbackT plugin_function); bool RemoveListener(CallbackT fnPluginFunction);
std::string GetName() { return m_name; } std::string GetName() { return m_name; }
unsigned int GetFunctionCount() { return m_functions.size(); } unsigned int GetFunctionCount() { return m_functions.size(); }
void Execute(bool resetContext = true); std::vector<CallbackT> GetFunctions() { return m_functions; }
void ResetContext();
ScriptContextRaw &ScriptContext() { return m_script_context_raw; }
fxNativeContext &ScriptContextStruct() { return m_root_context; }
private:
void Execute(bool bResetContext = true);
void Reset();
ScriptContextRaw& ScriptContext() { return m_script_context_raw; }
fxNativeContext& ScriptContextStruct() { return m_root_context; }
private:
std::vector<CallbackT> m_functions; std::vector<CallbackT> m_functions;
std::string m_name; std::string m_name;
ScriptContextRaw m_script_context_raw; ScriptContextRaw m_script_context_raw;
fxNativeContext m_root_context; fxNativeContext m_root_context;
}; };
class CallbackManager : public GlobalClass { class CallbackManager : public GlobalClass
public: {
public:
CallbackManager(); CallbackManager();
public: ScriptCallback* CreateCallback(const char* szName);
ScriptCallback *CreateCallback(const char *name); ScriptCallback* FindCallback(const char* szName);
ScriptCallback *FindCallback(const char *name); void ReleaseCallback(ScriptCallback* pCallback);
void ReleaseCallback(ScriptCallback *callback); bool TryAddFunction(const char* szName, CallbackT fnCallable);
bool TryAddFunction(const char *name, CallbackT pCallable); bool TryRemoveFunction(const char* szName, CallbackT fnCallable);
bool TryRemoveFunction(const char *name, CallbackT pCallable);
void PrintCallbackDebug(); void PrintCallbackDebug();
private: private:
std::vector<ScriptCallback *> m_managed; std::vector<ScriptCallback*> m_managed;
}; };
} // namespace counterstrikesharp } // namespace counterstrikesharp

View File

@@ -33,6 +33,13 @@
namespace counterstrikesharp { namespace counterstrikesharp {
enum HookResult {
Continue = 0,
Changed = 1,
Handled = 3,
Stop = 4,
};
inline uint32_t hash_string(const char *string) { inline uint32_t hash_string(const char *string) {
unsigned long result = 5381; unsigned long result = 5381;

View File

@@ -108,7 +108,7 @@ public partial class Generators
[EventName(""{gameEvent.Name}"")] [EventName(""{gameEvent.Name}"")]
public class Event{gameEvent.NamePascalCase} : GameEvent public class Event{gameEvent.NamePascalCase} : GameEvent
{{ {{
public Event{gameEvent.NamePascalCase}() : base(){{}} public Event{gameEvent.NamePascalCase}(IntPtr pointer) : base(pointer){{}}
public Event{gameEvent.NamePascalCase}(bool force) : base(""{gameEvent.Name}"", force){{}} public Event{gameEvent.NamePascalCase}(bool force) : base(""{gameEvent.Name}"", force){{}}
{string.Join("\n", propertyDefinition)} {string.Join("\n", propertyDefinition)}