diff --git a/hooks.c b/hooks.c index 1db8e4b..5d7d62f 100644 --- a/hooks.c +++ b/hooks.c @@ -223,6 +223,36 @@ void __cdecl My_G_StartKamikaze(gentity_t* ent) { if (client_id != -1) KamikazeExplodeDispatcher(client_id, is_used_on_demand); } + +void __cdecl My_G_Damage( + gentity_t* target, // entity that is being damaged + gentity_t* inflictor, // entity that is causing the damage + gentity_t* attacker, // entity that caused the inflictor to damage targ + vec3_t dir, // direction of the attack for knockback + vec3_t point, // point at which the damage is being inflicted, used for headshots + int damage, // amount of damage being inflicted + int dflags, // these flags are used to control how T_Damage works + int mod // means_of_death indicator + ) { + int target_id; + int attacker_id = -1; + + G_Damage(target, inflictor, attacker, dir, point, damage, dflags, mod); + + if (!target) { + return; + } + + if (!target->client) { + return; + } + + target_id = target - g_entities; + + attacker_id = attacker - g_entities; + + DamageDispatcher(target_id, attacker_id, damage, dflags, mod); +} #endif // Hook static functions. Can be done before program even runs. @@ -342,6 +372,13 @@ void HookVm(void) { DebugPrint("ERROR: Failed to hook ClientSpawn: %d\n", res); failed = 1; } + count++; + + res = Hook((void*)G_Damage, My_G_Damage, (void*)&G_Damage); + if (res) { + DebugPrint("ERROR: Failed to hook G_Damage: %d\n", res); + failed = 1; + } count++; if (failed) { diff --git a/pyminqlx.h b/pyminqlx.h index ea8874f..493f1d9 100644 --- a/pyminqlx.h +++ b/pyminqlx.h @@ -61,6 +61,7 @@ extern PyObject* client_spawn_handler; extern PyObject* kamikaze_use_handler; extern PyObject* kamikaze_explode_handler; +extern PyObject* damage_handler; // Custom console command handler. These are commands added through Python that can be used // from the console or using RCON. @@ -91,5 +92,6 @@ void ClientSpawnDispatcher(int client_id); void KamikazeUseDispatcher(int client_id); void KamikazeExplodeDispatcher(int client_id, int is_used_on_demand); +void DamageDispatcher(int target_id, int attacker_id, int damage, int dflags, int mod); #endif /* PYMINQLX_H */ diff --git a/python/minqlx/_events.py b/python/minqlx/_events.py index 1e25443..ef572e0 100644 --- a/python/minqlx/_events.py +++ b/python/minqlx/_events.py @@ -30,7 +30,7 @@ class EventDispatcher: to hook into events by registering an event handler. """ - no_debug = ("frame", "set_configstring", "stats", "server_command", "death", "kill", "command", "console_print") + no_debug = ("frame", "set_configstring", "stats", "server_command", "death", "kill", "command", "console_print", "damage") need_zmq_stats_enabled = False def __init__(self): @@ -581,6 +581,14 @@ class KamikazeExplodeDispatcher(EventDispatcher): def dispatch(self, player, is_used_on_demand): return super().dispatch(player, is_used_on_demand) +class DamageDispatcher(EventDispatcher): + """Event that goes off when someone is inflicted with damage.""" + + name = "damage" + need_zmq_stats_enabled = False + + def dispatch(self, target, attacker, damage, dflags, mod): + return super().dispatch(target, attacker, damage, dflags, mod) EVENT_DISPATCHERS = EventDispatcherManager() EVENT_DISPATCHERS.add_dispatcher(ConsolePrintDispatcher) @@ -615,3 +623,4 @@ def dispatch(self, player, is_used_on_demand): EVENT_DISPATCHERS.add_dispatcher(KillDispatcher) EVENT_DISPATCHERS.add_dispatcher(DeathDispatcher) EVENT_DISPATCHERS.add_dispatcher(UserinfoDispatcher) +EVENT_DISPATCHERS.add_dispatcher(DamageDispatcher) diff --git a/python/minqlx/_handlers.py b/python/minqlx/_handlers.py index d915512..7a2dd38 100644 --- a/python/minqlx/_handlers.py +++ b/python/minqlx/_handlers.py @@ -432,6 +432,20 @@ def handle_kamikaze_explode(client_id, is_used_on_demand): minqlx.log_exception() return True +def handle_damage(target_id, attacker_id, damage, dflags, mod): + target_player = minqlx.Player(target_id) if target_id in range(0, 64) else target_id + attacker_player = ( + minqlx.Player(attacker_id) if attacker_id in range(0, 64) else attacker_id + ) + + try: + minqlx.EVENT_DISPATCHERS["damage"].dispatch( + target_player, attacker_player, damage, dflags, mod + ) + except: + minqlx.log_exception() + return True + def handle_console_print(text): """Called whenever the server prints something to the console and when rcon is used.""" try: @@ -510,3 +524,4 @@ def register_handlers(): minqlx.register_handler("kamikaze_use", handle_kamikaze_use) minqlx.register_handler("kamikaze_explode", handle_kamikaze_explode) + minqlx.register_handler("damage", handle_damage) diff --git a/python_dispatchers.c b/python_dispatchers.c index 30ed72d..1d37009 100644 --- a/python_dispatchers.c +++ b/python_dispatchers.c @@ -294,3 +294,21 @@ void KamikazeExplodeDispatcher(int client_id, int is_used_on_demand) { PyGILState_Release(gstate); } + +void DamageDispatcher(int target_id, int attacker_id, int damage, int dflags, int mod) { + if (!damage_handler) + return; // No registered handler + + PyGILState_STATE gstate = PyGILState_Ensure(); + + PyObject* result; + if (attacker_id >= 0) { + result = PyObject_CallFunction(damage_handler, "iiiii", target_id, attacker_id, damage, dflags, mod); + } else { + result = PyObject_CallFunction(damage_handler, "iOiii", target_id, Py_None, damage, dflags, mod); + } + + Py_XDECREF(result); + + PyGILState_Release(gstate); +} diff --git a/python_embed.c b/python_embed.c index 2b48d49..e34d394 100644 --- a/python_embed.c +++ b/python_embed.c @@ -28,6 +28,8 @@ PyObject* client_spawn_handler = NULL; PyObject* kamikaze_use_handler = NULL; PyObject* kamikaze_explode_handler = NULL; +PyObject* damage_handler = NULL; + static PyThreadState* mainstate; static int initialized = 0; @@ -72,6 +74,8 @@ static handler_t handlers[] = { {"kamikaze_use", &kamikaze_use_handler}, {"kamikaze_explode", &kamikaze_explode_handler}, + {"damage", &damage_handler}, + {NULL, NULL} }; @@ -1878,6 +1882,13 @@ static PyObject* PyMinqlx_InitModule(void) { PyModule_AddIntMacro(module, MOD_HMG); PyModule_AddIntMacro(module, MOD_RAILGUN_HEADSHOT); + // damage flags + PyModule_AddIntMacro(module, DAMAGE_RADIUS); + PyModule_AddIntMacro(module, DAMAGE_NO_ARMOR); + PyModule_AddIntMacro(module, DAMAGE_NO_KNOCKBACK); + PyModule_AddIntMacro(module, DAMAGE_NO_PROTECTION); + PyModule_AddIntMacro(module, DAMAGE_NO_TEAM_PROTECTION); + // Initialize struct sequence types. PyStructSequence_InitType(&player_info_type, &player_info_desc); PyStructSequence_InitType(&player_state_type, &player_state_desc); diff --git a/quake_common.h b/quake_common.h index eeeca62..ca49750 100644 --- a/quake_common.h +++ b/quake_common.h @@ -114,7 +114,12 @@ along with this program. If not, see . #define FL_DROPPED_ITEM 0x00001000 -#define DAMAGE_NO_PROTECTION 0x00000008 +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armor does not protect from this damage +#define DAMAGE_NO_KNOCKBACK 0x00000004 // do not affect velocity, just view angles +#define DAMAGE_NO_PROTECTION 0x00000008 // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_NO_TEAM_PROTECTION 0x00000010 // armor, shields, invulnerability, and godmode have no effect typedef enum {qfalse, qtrue} qboolean; typedef unsigned char byte; @@ -1565,6 +1570,7 @@ char* __cdecl My_ClientConnect(int clientNum, qboolean firstTime, qboolean isBot void __cdecl My_ClientSpawn(gentity_t* ent); void __cdecl My_G_StartKamikaze(gentity_t* ent); +void __cdecl My_G_Damage(gentity_t* target, gentity_t* inflictor, gentity_t* attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod); #endif // Custom commands added using Cmd_AddCommand during initialization.