Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ CI собирает обе платформы и прикладывает бин
**События**

- `player_death` несёт `weapon` и `distance`.
- `player_hurt`/`player_hurt_post`/`player_trace_attack` несут `weapon` —
classname гранаты/C4 как отдельного инфликтора, иначе активное оружие
атакующего; позволяет писать per-weapon множитель урона в Lua.
- `player_team_change`, `weapon_fire` — опрашиваются каждый кадр, ловят любой
источник смены, включая сторонние моды.
- `map_change`, `plugin_unload` — точки, где плагин снимает свои эффекты.
Expand Down
18 changes: 11 additions & 7 deletions scripts/docs/api/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,14 @@ def event(name, brief, fields, cancel=None, example=None, extra=None,
('e.attacker', 'player \\| nil', '`nil` при падении и уроне мира'),
('e.damage', 'number', 'запись: сколько урона применить'),
('e.bits', 'number', 'маска типа урона: `DMG_FALL`, `DMG_BULLET`'),
('e.hitgroup', 'number \\| nil', 'куда попали: `1` голова, `2` грудь, `3` живот, `4`/`5` руки, `6`/`7` ноги; `nil`, если урон не от попадания')],
('e.hitgroup', 'number \\| nil', 'куда попали: `1` голова, `2` грудь, `3` живот, `4`/`5` руки, `6`/`7` ноги; `nil`, если урон не от попадания'),
('e.weapon', 'string \\| nil', 'classname источника: граната/C4 своим именем, иначе оружие в руках атакующего; `nil` для падения и урона мира')],
slug='player_hurt',
cancel='`e:cancel()` гасит урон полностью: ни звука боли, ни брони, ни смерти. Цепочка на этом обрывается.',
example="""
hook.add("player:hurt", "myplugin.double", function(e)
e.damage = e.damage * 2
hook.add("player:hurt", "myplugin.per_weapon", function(e)
local mult = ({ weapon_awp = 1.5, weapon_deagle = 1.2, hegrenade = 0.8 })[e.weapon]
if mult then e.damage = e.damage * mult end
end)
""",
extra='''Единственное событие, которое меняет игру. Каждый следующий обработчик видит
Expand All @@ -241,7 +243,8 @@ def event(name, brief, fields, cancel=None, example=None, extra=None,
('e.attacker', 'player \\| nil', '`nil` при падении и уроне мира'),
('e.damage', 'number', 'сколько урона применилось'),
('e.bits', 'number', 'маска типа урона'),
('e.hitgroup', 'number \\| nil', 'куда попали: `1` голова, `2` грудь, `3` живот, `4`/`5` руки, `6`/`7` ноги; `nil`, если урон не от попадания')],
('e.hitgroup', 'number \\| nil', 'куда попали: `1` голова, `2` грудь, `3` живот, `4`/`5` руки, `6`/`7` ноги; `nil`, если урон не от попадания'),
('e.weapon', 'string \\| nil', 'то же, что в `player:hurt`')],
slug='player_hurt_post',
extra='`e.victim:health()` здесь уже актуальное. Менять нечего — событие неотменяемое.',
regamedll=True),
Expand All @@ -251,10 +254,11 @@ def event(name, brief, fields, cancel=None, example=None, extra=None,
('e.damage', 'number', 'запись: сырой урон этого попадания, до брони и множителей'),
('e.bits', 'number', 'маска типа урона'),
('e.hitgroup', 'number \\| nil', 'куда попали: `1` голова, `2` грудь, `3` живот, `4`/`5` руки, `6`/`7` ноги'),
('e.x, e.y, e.z', 'number', 'точка попадания')],
('e.x, e.y, e.z', 'number', 'точка попадания'),
('e.weapon', 'string \\| nil', 'оружие в руках атакующего; `nil`, если распознать не удалось')],
slug='player_trace_attack',
cancel='`e:cancel()` (или `e.damage = 0`) убирает это попадание целиком: ни крови, ни вклада в итоговый урон, который дальше увидит `player:hurt`.',
extra='Название — по движковому хуку `CBasePlayer::TraceAttack` (тот же, что `RG_CBasePlayer_TraceAttack` в ReAPI). Дробовик даёт одно `player:trace_attack` на каждую долетевшую дробину и одно `player:hurt`/`weapon:fire` в сумме — здесь урон ещё не сложен.',
extra='Название — по движковому хуку `CBasePlayer::TraceAttack` (тот же, что `RG_CBasePlayer_TraceAttack` в ReAPI). Дробовик даёт одно `player:trace_attack` на каждую долетевшую дробину и одно `player:hurt`/`weapon:fire` в сумме — здесь урон ещё не сложен. В отличие от `player:hurt`, `e.weapon` тут всегда берётся из активного оружия атакующего — у попадания трассировкой нет отдельного инфликтора.',
regamedll=True,
see=[('player:hurt', 'player_hurt.md')]),
event('player:heal', 'Игроку вот-вот дадут здоровье — аптечка, админ-команда; своей регенерации в CS нет.',
Expand All @@ -278,7 +282,7 @@ def event(name, brief, fields, cancel=None, example=None, extra=None,
""",
extra='Суицид приходит как `e.killer.id == e.victim.id`. Вместе с `e.killer` в `nil` уходят `e.weapon` и `e.distance`.',
regamedll=True,
notes=[('note', 'Граната и взрыв C4 приходят как оружие в руках убийцы, а не как\n`hegrenade`. Отличить снаряд можно по `e.bits` в `player:hurt`.')]),
notes=[('note', 'Граната и взрыв C4 приходят как оружие в руках убийцы, а не как\n`hegrenade`. Отличить снаряд точнее можно через `e.weapon` в `player:hurt` —\nтам для гранаты/C4 приходит classname самого снаряда (`hegrenade`, `grenade`,\n`c4`), а не оружие в руках.')]),
event('player:team_change', 'Игрок сменил сторону.',
[PLAYER, ('e.old_team', 'string', '`CT`, `T`, `SPEC`, `NONE`'),
('e.new_team', 'string', 'куда перешёл')],
Expand Down
16 changes: 13 additions & 3 deletions src/lua_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -821,9 +821,10 @@ void LuaEvents::fire_player_spawn(int id)
}

float LuaEvents::fire_player_hurt(int victim, int attacker, float damage, int bits,
int hitgroup)
int hitgroup, const char *weapon)
{
float result = damage;
std::string weapon_name = weapon ? weapon : "";

bool cancelled = run(CSLUA_EVENT_PLAYER_HURT,
[=](lua_State *L) {
Expand All @@ -833,6 +834,8 @@ float LuaEvents::fire_player_hurt(int victim, int attacker, float damage, int bi
lua_pushinteger(L, bits);
lua_setfield(L, -2, "bits");
set_hitgroup(L, hitgroup);
if (!weapon_name.empty())
set_string(L, weapon_name, "weapon");
},
[&result](lua_State *L) {
// Whatever the chain left in e.damage is what the game applies.
Expand All @@ -849,15 +852,19 @@ float LuaEvents::fire_player_hurt(int victim, int attacker, float damage, int bi
}

void LuaEvents::fire_player_hurt_post(int victim, int attacker, float damage, int bits,
int hitgroup)
int hitgroup, const char *weapon)
{
std::string weapon_name = weapon ? weapon : "";

notify(CSLUA_EVENT_PLAYER_HURT_POST, [=](lua_State *L) {
set_player(L, victim, "victim");
set_player_or_nil(L, attacker, "attacker");
set_number(L, damage, "damage");
lua_pushinteger(L, bits);
lua_setfield(L, -2, "bits");
set_hitgroup(L, hitgroup);
if (!weapon_name.empty())
set_string(L, weapon_name, "weapon");
});
}

Expand Down Expand Up @@ -1198,9 +1205,10 @@ void LuaEvents::fire_bomb_defuse_start(int player, bool defuser)
}

float LuaEvents::fire_player_trace_attack(int victim, int attacker, float damage, int bits,
int hitgroup, float x, float y, float z)
int hitgroup, float x, float y, float z, const char *weapon)
{
float result = damage;
std::string weapon_name = weapon ? weapon : "";

bool cancelled = run(CSLUA_EVENT_PLAYER_TRACE_ATTACK,
[=](lua_State *L) {
Expand All @@ -1213,6 +1221,8 @@ float LuaEvents::fire_player_trace_attack(int victim, int attacker, float damage
set_number(L, x, "x");
set_number(L, y, "y");
set_number(L, z, "z");
if (!weapon_name.empty())
set_string(L, weapon_name, "weapon");
},
[&result](lua_State *L) {
lua_getfield(L, -1, "damage");
Expand Down
12 changes: 8 additions & 4 deletions src/lua_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,17 +263,21 @@ class LuaEvents

// damage is by reference in TakeDamage. Returns the final damage: e.damage
// after the chain, or 0 if a handler cancelled. hitgroup is -1 when the
// damage did not come from a hit (fall, world, gas).
float fire_player_hurt(int victim, int attacker, float damage, int bits, int hitgroup);
// damage did not come from a hit (fall, world, gas). weapon is the
// classname behind the hit - the inflictor (grenade, c4) if it is its own
// entity, otherwise the attacker's active item; NULL when neither applies.
float fire_player_hurt(int victim, int attacker, float damage, int bits, int hitgroup,
const char *weapon);

// Post: the damage the game actually applied, for observers.
void fire_player_hurt_post(int victim, int attacker, float damage, int bits, int hitgroup);
void fire_player_hurt_post(int victim, int attacker, float damage, int bits, int hitgroup,
const char *weapon);

// The exact hit before TakeDamage sums same-frame hits. damage is the raw
// per-hit amount before armor/multipliers. x,y,z is the trace position.
// Returns the (possibly changed) damage, 0 if cancelled.
float fire_player_trace_attack(int victim, int attacker, float damage, int bits,
int hitgroup, float x, float y, float z);
int hitgroup, float x, float y, float z, const char *weapon);

// TakeHealth. Returns the (possibly changed) amount, 0 if cancelled.
float fire_player_heal(int player, float amount, int bits);
Expand Down
41 changes: 38 additions & 3 deletions src/regamedll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@ static int attacker_slot(entvars_t *pevAttacker)
return cslua_valid_player_id(idx) ? idx : 0;
}

// The classname behind a hit: the inflictor's own classname when it is a
// distinct entity (grenade, C4 - not the attacker itself), otherwise the
// attacker's active weapon. NULL for fall damage, world damage, and the like.
// Copies into buf since the source string lives inside the game's entity.
static const char *resolve_damage_weapon(entvars_t *pevInflictor, entvars_t *pevAttacker,
char *buf, size_t buf_size)
{
const char *weapon = NULL;

if (pevInflictor && pevInflictor != pevAttacker)
weapon = STRING(pevInflictor->classname);

if ((!weapon || !*weapon) && pevAttacker) {
int aslot = attacker_slot(pevAttacker);
CBasePlayer *attacker = aslot ? cslua_player_entity(aslot) : NULL;
if (attacker && attacker->m_pActiveItem && attacker->m_pActiveItem->pev)
weapon = STRING(attacker->m_pActiveItem->pev->classname);
}

if (!weapon || !*weapon)
return NULL;

cslua_snprintf(buf, buf_size, "%s", weapon);
buf[buf_size - 1] = '\0';
return buf;
}

// TraceAttack fires once per hit (per shotgun pellet) and only accumulates into
// the multi-damage buffer. flDamage is the raw per-hit amount, before armor and
// multipliers. Not by reference, so a changed value is forwarded into callNext.
Expand All @@ -180,7 +207,12 @@ static void hook_trace_attack(IReGameHook_CBasePlayer_TraceAttack *chain, CBaseP
float y = ptr ? ptr->vecEndPos.y : 0.0f;
float z = ptr ? ptr->vecEndPos.z : 0.0f;

flDamage = g_events.fire_player_trace_attack(vslot, aslot, flDamage, bitsDamageType, hitgroup, x, y, z);
// TraceAttack has no separate inflictor - a hitscan trace always comes
// straight from the attacker's own weapon.
char weapon_buf[64];
const char *weapon = resolve_damage_weapon(NULL, pevAttacker, weapon_buf, sizeof weapon_buf);

flDamage = g_events.fire_player_trace_attack(vslot, aslot, flDamage, bitsDamageType, hitgroup, x, y, z, weapon);
if (flDamage <= 0.0f)
return; // fully blocked: no blood, no multidamage

Expand All @@ -203,13 +235,16 @@ static BOOL hook_takedamage(IReGameHook_CBasePlayer_TakeDamage *chain, CBasePlay
int hitgroup = (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))
? victim->m_LastHitGroup : -1;

flDamage = g_events.fire_player_hurt(vslot, aslot, flDamage, bitsDamageType, hitgroup);
char weapon_buf[64];
const char *weapon = resolve_damage_weapon(pevInflictor, pevAttacker, weapon_buf, sizeof weapon_buf);

flDamage = g_events.fire_player_hurt(vslot, aslot, flDamage, bitsDamageType, hitgroup, weapon);
if (flDamage <= 0.0f)
return FALSE; // fully blocked: no pain sound, no armor loss

BOOL result = chain->callNext(victim, pevInflictor, pevAttacker, flDamage, bitsDamageType);

g_events.fire_player_hurt_post(vslot, aslot, flDamage, bitsDamageType, hitgroup);
g_events.fire_player_hurt_post(vslot, aslot, flDamage, bitsDamageType, hitgroup, weapon);
return result;
}

Expand Down
Loading