Add an extended player's DeathMsg message (#858)

* Implemented rarity of kill and assist for extended user message DeathMsg
* Add hookchain CGameRules::SendDeathMessage
* Add domination and revenge
This commit is contained in:
Dmitry Novikov 2023-09-28 16:18:15 +07:00 committed by GitHub
parent 67e7d87423
commit 108db28143
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 815 additions and 151 deletions

View File

@ -110,7 +110,9 @@ This means that plugins that do binary code analysis (Orpheu for example) probab
| mp_give_c4_frags | 3 | - | - | How many bonuses (frags) will get the player who defused or exploded the bomb. |
| mp_hostages_rescued_ratio | 1.0 | 0.0 | 1.0 | Ratio of hostages rescued to win the round. |
| mp_legacy_vehicle_block | 1 | 0 | 1 | Legacy func_vehicle behavior when blocked by another entity.<br/>`0` New behavior <br/>`1` Legacy behavior |
| mp_dying_time | 3.0 | 0.0 | - | Time for switch to free observing after death.<br/>`0` - disable spectating around death.<br/>`>0.00001` - time delay to start spectate.<br/>`NOTE`: The countdown starts when the players death animation is finished.|
| mp_dying_time | 3.0 | 0.0 | - | Time for switch to free observing after death.<br/>`0` - disable spectating around death.<br/>`>0.00001` - time delay to start spectate.<br/>`NOTE`: The countdown starts when the players death animation is finished. |
| mp_deathmsg_flags | 7 | 0 | 7 | Sets a bitsum for extra information in the player's death message.<br/>`0` disabled<br/>`1` position where the victim died<br/>`2` index of the assistant who helped the attacker kill the victim<br/>`4` rarity classification bits, e.g., `blinkill`, `noscope`, `penetrated`, etc. |
| mp_assist_damage_threshold | 40 | 0 | 100 | Sets the percentage of damage needed to score an assist. |
</details>
## How to install zBot for CS 1.6?

16
dist/game.cfg vendored
View File

@ -537,3 +537,19 @@ mp_legacy_vehicle_block "1"
//
// Default value: "3.0"
mp_dying_time "3.0"
// Sets a bitsum for extra information in the player's death message
//
// 1 Position where the victim died
// 2 Index of the assistant who helped the attacker kill the victim
// 4 Rarity classification bits, e.g., blinkill, noscope, penetrated, etc
//
// Set to "0" to send no extra information about death
//
// Default value: "7"
mp_deathmsg_flags "7"
// Sets the percentage of damage needed to score an assist
//
// Default value: "40"
mp_assist_damage_threshold "40"

View File

@ -61,37 +61,37 @@ void EXT_FUNC AddMultiDamage_api(entvars_t *pevInflictor, CBaseEntity *pEntity,
AddMultiDamage(pevInflictor, pEntity, flDamage, bitsDamageType);
}
int EXT_FUNC Cmd_Argc_api()
int EXT_FUNC Cmd_Argc_api()
{
return CMD_ARGC_();
}
const char *EXT_FUNC Cmd_Argv_api(int i)
const char *EXT_FUNC Cmd_Argv_api(int i)
{
return CMD_ARGV_(i);
}
CGrenade *EXT_FUNC PlantBomb_api(entvars_t *pevOwner, Vector &vecStart, Vector &vecVelocity)
CGrenade *EXT_FUNC PlantBomb_api(entvars_t *pevOwner, Vector &vecStart, Vector &vecVelocity)
{
return CGrenade::ShootSatchelCharge(pevOwner, vecStart, vecVelocity);
}
CGib *EXT_FUNC SpawnHeadGib_api(entvars_t *pevVictim)
CGib *EXT_FUNC SpawnHeadGib_api(entvars_t *pevVictim)
{
return CGib::SpawnHeadGib(pevVictim);
}
void EXT_FUNC SpawnRandomGibs_api(entvars_t *pevVictim, int cGibs, int human)
void EXT_FUNC SpawnRandomGibs_api(entvars_t *pevVictim, int cGibs, int human)
{
CGib::SpawnRandomGibs(pevVictim, cGibs, human);
}
void EXT_FUNC UTIL_RestartOther_api(const char *szClassname)
void EXT_FUNC UTIL_RestartOther_api(const char *szClassname)
{
UTIL_RestartOther(szClassname);
}
void EXT_FUNC UTIL_ResetEntities_api()
void EXT_FUNC UTIL_ResetEntities_api()
{
UTIL_ResetEntities();
}
@ -130,11 +130,11 @@ CGrenade *EXT_FUNC SpawnGrenade_api(WeaponIdType weaponId, entvars_t *pevOwner,
{
switch (weaponId)
{
case WEAPON_HEGRENADE:
case WEAPON_HEGRENADE:
return CGrenade::ShootTimed2(pevOwner, vecSrc, vecThrow, time, iTeam, usEvent);
case WEAPON_FLASHBANG:
case WEAPON_FLASHBANG:
return CGrenade::ShootTimed(pevOwner, vecSrc, vecThrow, time);
case WEAPON_SMOKEGRENADE:
case WEAPON_SMOKEGRENADE:
return CGrenade::ShootSmokeGrenade(pevOwner, vecSrc, vecThrow, time, usEvent);
case WEAPON_C4:
return CGrenade::ShootSatchelCharge(pevOwner, vecSrc, vecThrow);
@ -331,6 +331,7 @@ GAMEHOOK_REGISTRY(CBasePlayer_EntSelectSpawnPoint);
GAMEHOOK_REGISTRY(CBasePlayerWeapon_ItemPostFrame);
GAMEHOOK_REGISTRY(CBasePlayerWeapon_KickBack);
GAMEHOOK_REGISTRY(CBasePlayerWeapon_SendWeaponAnim);
GAMEHOOK_REGISTRY(CSGameRules_SendDeathMessage);
int CReGameApi::GetMajorVersion() {
return REGAMEDLL_API_VERSION_MAJOR;

View File

@ -709,6 +709,10 @@ typedef IHookChainRegistryClassEmptyImpl<BOOL, class CHalfLifeMultiplay, int, in
typedef IHookChainClassImpl<void, class CHalfLifeMultiplay, CBasePlayer *, CBasePlayerItem *> CReGameHook_CSGameRules_PlayerGotWeapon;
typedef IHookChainRegistryClassEmptyImpl<void, class CHalfLifeMultiplay, CBasePlayer *, CBasePlayerItem *> CReGameHookRegistry_CSGameRules_PlayerGotWeapon;
// CHalfLifeMultiplay::SendDeathMessage hook
typedef IHookChainClassImpl<void, class CHalfLifeMultiplay, CBaseEntity *, CBasePlayer *, CBasePlayer *, entvars_t *, const char *, int, int> CReGameHook_CSGameRules_SendDeathMessage;
typedef IHookChainRegistryClassEmptyImpl<void, class CHalfLifeMultiplay, CBaseEntity *, CBasePlayer *, CBasePlayer *, entvars_t *, const char *, int, int> CReGameHookRegistry_CSGameRules_SendDeathMessage;
// CBotManager::OnEvent hook
typedef IHookChainClassImpl<void, CBotManager, GameEventType, CBaseEntity *, CBaseEntity *> CReGameHook_CBotManager_OnEvent;
typedef IHookChainRegistryClassEmptyImpl<void, CBotManager, GameEventType, CBaseEntity*, CBaseEntity*> CReGameHookRegistry_CBotManager_OnEvent;
@ -865,7 +869,7 @@ public:
CReGameHookRegistry_CBasePlayer_Pain m_CBasePlayer_Pain;
CReGameHookRegistry_CBasePlayer_DeathSound m_CBasePlayer_DeathSound;
CReGameHookRegistry_CBasePlayer_JoiningThink m_CBasePlayer_JoiningThink;
CReGameHookRegistry_FreeGameRules m_FreeGameRules;
CReGameHookRegistry_PM_LadderMove m_PM_LadderMove;
CReGameHookRegistry_PM_WaterJump m_PM_WaterJump;
@ -889,6 +893,7 @@ public:
CReGameHookRegistry_CBasePlayerWeapon_ItemPostFrame m_CBasePlayerWeapon_ItemPostFrame;
CReGameHookRegistry_CBasePlayerWeapon_KickBack m_CBasePlayerWeapon_KickBack;
CReGameHookRegistry_CBasePlayerWeapon_SendWeaponAnim m_CBasePlayerWeapon_SendWeaponAnim;
CReGameHookRegistry_CSGameRules_SendDeathMessage m_CSGameRules_SendDeathMessage;
public:
virtual IReGameHookRegistry_CBasePlayer_Spawn *CBasePlayer_Spawn();
@ -1044,6 +1049,7 @@ public:
virtual IReGameHookRegistry_CBasePlayerWeapon_ItemPostFrame *CBasePlayerWeapon_ItemPostFrame();
virtual IReGameHookRegistry_CBasePlayerWeapon_KickBack *CBasePlayerWeapon_KickBack();
virtual IReGameHookRegistry_CBasePlayerWeapon_SendWeaponAnim *CBasePlayerWeapon_SendWeaponAnim();
virtual IReGameHookRegistry_CSGameRules_SendDeathMessage *CSGameRules_SendDeathMessage();
};
extern CReGameHookchains g_ReGameHookchains;

View File

@ -550,10 +550,22 @@ void CCSPlayer::ResetVars()
m_bSpawnProtectionEffects = false;
}
// Resets all stats
void CCSPlayer::ResetAllStats()
{
// Resets the kill history for this player
for (int i = 0; i < MAX_CLIENTS; i++)
{
m_iNumKilledByUnanswered[i] = 0;
m_bPlayerDominated[i] = false;
}
}
void CCSPlayer::OnSpawn()
{
m_bGameForcingRespawn = false;
m_flRespawnPending = 0.0f;
m_DamageList.Clear();
}
void CCSPlayer::OnKilled()
@ -571,3 +583,33 @@ void CCSPlayer::OnKilled()
}
#endif
}
void CCSPlayer::OnConnect()
{
ResetVars();
m_iUserID = GETPLAYERUSERID(BasePlayer()->edict());
}
// Remember this amount of damage that we dealt for stats
void CCSPlayer::RecordDamage(CBasePlayer *pAttacker, float flDamage, float flFlashDurationTime)
{
if (!pAttacker || !pAttacker->IsPlayer())
return;
int attackerIndex = pAttacker->entindex() - 1;
if (attackerIndex < 0 || attackerIndex >= MAX_CLIENTS)
return;
CCSPlayer *pCSAttacker = pAttacker->CSPlayer();
// Accumulate damage
CDamageRecord_t &record = m_DamageList[attackerIndex];
if (record.flDamage > 0 && record.userId != pCSAttacker->m_iUserID)
record.flDamage = 0; // reset damage if attacker became another client
record.flDamage += flDamage;
record.userId = pCSAttacker->m_iUserID;
if (flFlashDurationTime > 0)
record.flFlashDurationTime = gpGlobals->time + flFlashDurationTime;
}

View File

@ -1242,7 +1242,7 @@ bool EXT_FUNC IsPenetrableEntity_default(Vector &vecSrc, Vector &vecEnd, entvars
LINK_HOOK_CLASS_CHAIN(VectorRef, CBaseEntity, FireBullets3, (VectorRef vecSrc, VectorRef vecDirShooting, float vecSpread, float flDistance, int iPenetration, int iBulletType, int iDamage, float flRangeModifier, entvars_t *pevAttacker, bool bPistol, int shared_rand), vecSrc, vecDirShooting, vecSpread, flDistance, iPenetration, iBulletType, iDamage, flRangeModifier, pevAttacker, bPistol, shared_rand)
// Go to the trouble of combining multiple pellets into a single damage call.
// This version is used by Players, uses the random seed generator to sync client and server side shots.
VectorRef CBaseEntity::__API_HOOK(FireBullets3)(VectorRef vecSrc, VectorRef vecDirShooting, float vecSpread, float flDistance, int iPenetration, int iBulletType, int iDamage, float flRangeModifier, entvars_t *pevAttacker, bool bPistol, int shared_rand)
@ -1340,6 +1340,7 @@ VectorRef CBaseEntity::__API_HOOK(FireBullets3)(VectorRef vecSrc, VectorRef vecD
float flDamageModifier = 0.5;
int iStartPenetration = iPenetration;
while (iPenetration != 0)
{
ClearMultiDamage();
@ -1400,9 +1401,11 @@ VectorRef CBaseEntity::__API_HOOK(FireBullets3)(VectorRef vecSrc, VectorRef vecD
default:
break;
}
if (tr.flFraction != 1.0f)
{
CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit);
int iPenetrationCur = iPenetration;
iPenetration--;
flCurrentDistance = tr.flFraction * flDistance;
@ -1459,6 +1462,7 @@ VectorRef CBaseEntity::__API_HOOK(FireBullets3)(VectorRef vecSrc, VectorRef vecD
flDistance = (flDistance - flCurrentDistance) * flDistanceModifier;
vecEnd = vecSrc + (vecDir * flDistance);
pEntity->SetDmgPenetrationLevel(iStartPenetration - iPenetrationCur);
pEntity->TraceAttack(pevAttacker, iCurrentDamage, vecDir, &tr, (DMG_BULLET | DMG_NEVERGIB));
iCurrentDamage *= flDamageModifier;
}

View File

@ -242,6 +242,10 @@ public:
void SetBlocked(void (T::*pfn)(CBaseEntity *pOther));
void SetBlocked(std::nullptr_t);
void SetDmgPenetrationLevel(int iPenetrationLevel);
void ResetDmgPenetrationLevel();
int GetDmgPenetrationLevel() const;
#ifdef REGAMEDLL_API
CCSEntity *m_pEntity;
CCSEntity *CSEntity() const;

View File

@ -730,7 +730,7 @@ void EXT_FUNC ClientPutInServer(edict_t *pEntity)
}
#ifdef REGAMEDLL_API
pPlayer->CSPlayer()->ResetVars();
pPlayer->CSPlayer()->OnConnect();
#endif
UTIL_ClientPrintAll(HUD_PRINTNOTIFY, "#Game_connected", (sName[0] != '\0') ? sName : "<unconnected>");

View File

@ -16,12 +16,23 @@ void PlayerBlind(CBasePlayer *pPlayer, entvars_t *pevInflictor, entvars_t *pevAt
}
}
pPlayer->Blind(fadeTime * 0.33, fadeHold, fadeTime, alpha);
float flDurationTime = fadeTime * 0.33;
pPlayer->Blind(flDurationTime, fadeHold, fadeTime, alpha);
if (TheBots)
{
TheBots->OnEvent(EVENT_PLAYER_BLINDED_BY_FLASHBANG, pPlayer);
}
#if defined(REGAMEDLL_API) && defined(REGAMEDLL_ADD)
float flAdjustedDamage;
if (alpha > 200)
flAdjustedDamage = fadeTime / 3;
else
flAdjustedDamage = fadeTime / 1.75;
pPlayer->CSPlayer()->RecordDamage(CBasePlayer::Instance(pevAttacker), flAdjustedDamage * 16.0f, flDurationTime);
#endif
}
void RadiusFlash_TraceLine_hook(CBasePlayer *pPlayer, entvars_t *pevInflictor, entvars_t *pevAttacker, Vector &vecSrc, Vector &vecSpot, TraceResult *tr)
@ -101,7 +112,7 @@ void RadiusFlash(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker,
if (pPlayer->pev == pevAttacker || g_pGameRules->PlayerRelationship(pPlayer, CBaseEntity::Instance(pevAttacker)) == GR_TEAMMATE)
continue;
break;
}
}
#endif
if (tr.fStartSolid)
{
@ -110,7 +121,6 @@ void RadiusFlash(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker,
}
flAdjustedDamage = flDamage - (vecSrc - tr.vecEndPos).Length() * falloff;
if (flAdjustedDamage < 0)
flAdjustedDamage = 0;
@ -303,6 +313,8 @@ void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker
if (tr.flFraction != 1.0f)
flAdjustedDamage = 0.0f;
else
pEntity->SetDmgPenetrationLevel(1);
}
#endif
}

View File

@ -166,6 +166,8 @@ cvar_t sv_autobunnyhopping = { "sv_autobunnyhopping", "0", 0, 0.0f
cvar_t sv_enablebunnyhopping = { "sv_enablebunnyhopping", "0", 0, 0.0f, nullptr };
cvar_t plant_c4_anywhere = { "mp_plant_c4_anywhere", "0", 0, 0.0f, nullptr };
cvar_t give_c4_frags = { "mp_give_c4_frags", "3", 0, 3.0f, nullptr };
cvar_t deathmsg_flags = { "mp_deathmsg_flags", "7", 0, 7.0f, nullptr };
cvar_t assist_damage_threshold = { "mp_assist_damage_threshold", "40", 0, 40.0f, nullptr };
cvar_t hostages_rescued_ratio = { "mp_hostages_rescued_ratio", "1.0", 0, 1.0f, nullptr };
@ -423,6 +425,8 @@ void EXT_FUNC GameDLLInit()
CVAR_REGISTER(&legacy_vehicle_block);
CVAR_REGISTER(&dying_time);
CVAR_REGISTER(&deathmsg_flags);
CVAR_REGISTER(&assist_damage_threshold);
// print version
CONSOLE_ECHO("ReGameDLL version: " APP_VERSION "\n");

View File

@ -195,6 +195,8 @@ extern cvar_t give_c4_frags;
extern cvar_t hostages_rescued_ratio;
extern cvar_t legacy_vehicle_block;
extern cvar_t dying_time;
extern cvar_t deathmsg_flags;
extern cvar_t assist_damage_threshold;
#endif

View File

@ -220,6 +220,40 @@ enum
GR_NEUTRAL,
};
// The number of times you must kill a given player to be dominating them
// Should always be more than 1
const int CS_KILLS_FOR_DOMINATION = 4;
enum DeathMessageFlags
{
// float[3]
// Position where the victim died
PLAYERDEATH_POSITION = 0x001,
// byte
// Index of the assistant who helped the attacker kill the victim
PLAYERDEATH_ASSISTANT = 0x002,
// short
// Rarity classification bitsums
// 0x001 - Attacker was blind
// 0x002 - Attacker killed victim from sniper rifle without scope
// 0x004 - Attacker killed victim through walls
PLAYERDEATH_KILLRARITY = 0x004
};
enum KillRarity
{
KILLRARITY_HEADSHOT = 0x001, // The killer player kills the victim with a headshot
KILLRARITY_KILLER_BLIND = 0x002, // The killer player was blind
KILLRARITY_NOSCOPE = 0x004, // The killer player kills the victim with a sniper rifle with no scope
KILLRARITY_PENETRATED = 0x008, // The killer player kills the victim through walls
KILLRARITY_THROUGH_SMOKE = 0x010, // The killer player kills the victim through smoke
KILLRARITY_ASSIST_FLASH = 0x020, // The killer player kills the victim with an assistant flashbang grenade
KILLRARITY_DOMINATION = 0x040, // The killer player dominates the victim
KILLRARITY_REVENGE = 0x080 // The killer player got revenge on the victim
};
class CItem;
class CGameRules
@ -574,6 +608,7 @@ public:
BOOL TeamFull_OrigFunc(int team_id);
BOOL TeamStacked_OrigFunc(int newTeam_id, int curTeam_id);
void PlayerGotWeapon_OrigFunc(CBasePlayer *pPlayer, CBasePlayerItem *pWeapon);
void SendDeathMessage_OrigFunc(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill);
#endif
public:
@ -698,6 +733,10 @@ public:
VFUNC bool HasRoundTimeExpired();
VFUNC bool IsBombPlanted();
void SendDeathMessage(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill);
int GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, bool bFlashAssist);
CBasePlayer *CheckAssistsToKill(CBaseEntity *pKiller, CBasePlayer *pVictim, bool &bFlashAssist);
private:
void MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int iTeam);

View File

@ -2035,7 +2035,7 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(RestartRound)()
#endif
pPlayer->RoundRespawn();
#ifdef REGAMEDLL_ADD
FireTargets("game_entity_restart", pPlayer, nullptr, USE_TOGGLE, 0.0);
#endif
@ -3922,7 +3922,7 @@ LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, PlayerKilled,
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(PlayerKilled)(CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor)
{
DeathNotice(pVictim, pKiller, pInflictor);
#ifdef REGAMEDLL_FIXES
#ifdef REGAMEDLL_FIXES
pVictim->pev->flags &= ~FL_FROZEN;
#endif
pVictim->m_afPhysicsFlags &= ~PFLAG_ONTRAIN;
@ -4079,102 +4079,69 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(PlayerKilled)(CBasePlayer *pVictim,
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, DeathNotice, (CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor), pVictim, pKiller, pevInflictor)
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor)
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, entvars_t *pevKiller, entvars_t *pevInflictor)
{
// by default, the player is killed by the world
const char *killer_weapon_name = "world";
int killer_index = 0;
#ifndef REGAMEDLL_FIXES
// Hack to fix name change
char *tau = "tau_cannon";
char *gluon = "gluon gun";
#endif
// Is the killer a client?
if (pKiller->flags & FL_CLIENT)
{
killer_index = ENTINDEX(ENT(pKiller));
if (pevInflictor)
{
if (pevInflictor == pKiller)
{
// If the inflictor is the killer, then it must be their current weapon doing the damage
CBasePlayer *pAttacker = CBasePlayer::Instance(pKiller);
if (pAttacker && pAttacker->IsPlayer())
{
if (pAttacker->m_pActiveItem)
{
killer_weapon_name = pAttacker->m_pActiveItem->pszName();
}
}
}
else
{
// it's just that easy
killer_weapon_name = STRING(pevInflictor->classname);
}
}
}
else
#ifdef REGAMEDLL_FIXES
if (pevInflictor)
#endif
{
killer_weapon_name = STRING(pevInflictor->classname);
}
// strip the monster_* or weapon_* from the inflictor's classname
const char cut_weapon[] = "weapon_";
const char cut_monster[] = "monster_";
const char cut_func[] = "func_";
// replace the code names with the 'real' names
if (!Q_strncmp(killer_weapon_name, cut_weapon, sizeof(cut_weapon) - 1))
killer_weapon_name += sizeof(cut_weapon) - 1;
else if (!Q_strncmp(killer_weapon_name, cut_monster, sizeof(cut_monster) - 1))
killer_weapon_name += sizeof(cut_monster) - 1;
else if (!Q_strncmp(killer_weapon_name, cut_func, sizeof(cut_func) - 1))
killer_weapon_name += sizeof(cut_func) - 1;
CBasePlayer *pKiller = (pevKiller->flags & FL_CLIENT) ? CBasePlayer::Instance(pevKiller) : nullptr;
const char *killer_weapon_name = pVictim->GetKillerWeaponName(pevInflictor, pevKiller);
if (!TheTutor)
{
#ifdef REGAMEDLL_ADD
int iRarityOfKill = 0;
int iDeathMessageFlags = PLAYERDEATH_POSITION; // set default bit
CBasePlayer *pAssister = nullptr;
bool bFlashAssist = false;
if ((pAssister = CheckAssistsToKill(pKiller, pVictim, bFlashAssist)))
{
// Add a flag indicating the presence of an assistant who assisted in the kill
iDeathMessageFlags |= PLAYERDEATH_ASSISTANT;
}
iRarityOfKill = GetRarityOfKill(pKiller, pVictim, pAssister, killer_weapon_name, bFlashAssist);
if (iRarityOfKill != 0)
{
// Add a flag indicating that the attacker killed the victim in a rare way
iDeathMessageFlags |= PLAYERDEATH_KILLRARITY;
}
SendDeathMessage(pKiller, pVictim, pAssister, pevInflictor, killer_weapon_name, iDeathMessageFlags, iRarityOfKill);
// Updates the stats of who has killed whom
if (pKiller && pKiller->IsPlayer() && PlayerRelationship(pVictim, pKiller) != GR_TEAMMATE)
{
int iPlayerIndexKiller = pKiller->entindex();
int iPlayerIndexVictim = pVictim->entindex();
pKiller->CSPlayer()->m_iNumKilledByUnanswered[iPlayerIndexVictim - 1] = 0;
pVictim->CSPlayer()->m_iNumKilledByUnanswered[iPlayerIndexKiller - 1]++;
}
#else
MESSAGE_BEGIN(MSG_ALL, gmsgDeathMsg);
WRITE_BYTE(killer_index); // the killer
WRITE_BYTE(ENTINDEX(pVictim->edict())); // the victim
WRITE_BYTE(pVictim->m_bHeadshotKilled); // is killed headshot
WRITE_STRING(killer_weapon_name); // what they were killed by (should this be a string?)
WRITE_BYTE(pKiller ? pKiller->entindex() : 0); // the killer
WRITE_BYTE(ENTINDEX(pVictim->edict())); // the victim
WRITE_BYTE(pVictim->m_bHeadshotKilled); // is killed headshot
WRITE_STRING(killer_weapon_name); // what they were killed by (should this be a string?)
MESSAGE_END();
#endif
}
// This weapons from HL isn't it?
#ifndef REGAMEDLL_FIXES
if (!Q_strcmp(killer_weapon_name, "egon"))
killer_weapon_name = gluon;
else if (!Q_strcmp(killer_weapon_name, "gauss"))
killer_weapon_name = tau;
#endif
// Did he kill himself?
if (pVictim->pev == pKiller)
if (pVictim->pev == pevKiller)
{
// killed self
char *team = GetTeam(pVictim->m_iTeam);
UTIL_LogPrintf("\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n", STRING(pVictim->pev->netname), GETPLAYERUSERID(pVictim->edict()),
GETPLAYERAUTHID(pVictim->edict()), team, killer_weapon_name);
}
else if (pKiller->flags & FL_CLIENT)
else if (pevKiller->flags & FL_CLIENT)
{
CBasePlayer *pAttacker = CBasePlayer::Instance(pKiller);
const char *VictimTeam = GetTeam(pVictim->m_iTeam);
const char *KillerTeam = (pAttacker && pAttacker->IsPlayer()) ? GetTeam(pAttacker->m_iTeam) : "";
const char *KillerTeam = (pKiller && pKiller->IsPlayer()) ? GetTeam(pKiller->m_iTeam) : "";
UTIL_LogPrintf("\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", STRING(pKiller->netname), GETPLAYERUSERID(ENT(pKiller)), GETPLAYERAUTHID(ENT(pKiller)),
UTIL_LogPrintf("\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", STRING(pevKiller->netname), GETPLAYERUSERID(ENT(pevKiller)), GETPLAYERAUTHID(ENT(pevKiller)),
KillerTeam, STRING(pVictim->pev->netname), GETPLAYERUSERID(pVictim->edict()), GETPLAYERAUTHID(pVictim->edict()), VictimTeam, killer_weapon_name);
}
else
@ -4197,7 +4164,7 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim,
if (pevInflictor)
WRITE_SHORT(ENTINDEX(ENT(pevInflictor))); // index number of secondary entity
else
WRITE_SHORT(ENTINDEX(ENT(pKiller))); // index number of secondary entity
WRITE_SHORT(ENTINDEX(ENT(pevKiller))); // index number of secondary entity
if (pVictim->m_bHeadshotKilled)
WRITE_LONG(9 | DRC_FLAG_DRAMATIC | DRC_FLAG_SLOWMOTION);
@ -4345,11 +4312,11 @@ int EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeadPlayerWeapons)(CBasePlayer *pPla
{
case 3:
return GR_PLR_DROP_GUN_ALL;
case 2:
case 2:
break;
case 1:
return GR_PLR_DROP_GUN_BEST;
default:
default:
return GR_PLR_DROP_GUN_NO;
}
#endif
@ -5204,3 +5171,226 @@ bool CHalfLifeMultiplay::CanPlayerBuy(CBasePlayer *pPlayer) const
return true;
}
//
// Checks for assists in a kill situation
//
// This function analyzes damage records and player actions to determine the player who contributed the most to a kill,
// considering factors such as damage dealt and the use of flashbang grenades
//
// pKiller - The killer entity (Note: The killer may be a non-player)
// pVictim - The victim player
// bFlashAssist - A flag indicating whether a flashbang was used in the assist
// Returns - A pointer to the player who gave the most assistance, or NULL if appropriate assistant is not found
//
CBasePlayer *CHalfLifeMultiplay::CheckAssistsToKill(CBaseEntity *pKiller, CBasePlayer *pVictim, bool &bFlashAssist)
{
#ifdef REGAMEDLL_ADD
CCSPlayer::DamageList_t &victimDamageTakenList = pVictim->CSPlayer()->GetDamageList();
float maxDamage = 0.0f;
int maxDamageIndex = -1;
CBasePlayer *maxDamagePlayer = nullptr;
// Find the best assistant
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
const CCSPlayer::CDamageRecord_t &record = victimDamageTakenList[i - 1];
if (record.flDamage == 0)
continue; // dealt no damage
CBasePlayer *pAttackerPlayer = UTIL_PlayerByIndex(i);
if (!pAttackerPlayer || pAttackerPlayer->IsDormant())
continue; // ignore idle clients
CCSPlayer *pCSAttackerPlayer = pAttackerPlayer->CSPlayer();
if (record.userId != pCSAttackerPlayer->m_iUserID)
continue; // another client?
if (pAttackerPlayer == pKiller || pAttackerPlayer == pVictim)
continue; // ignore involved as killer or victim
if (record.flDamage > maxDamage)
{
// If the assistant used a flash grenade to aid in the kill,
// make sure that the victim was blinded, and that the duration of the flash effect is still preserved
if (record.flFlashDurationTime > 0 && (!pVictim->IsBlind() || record.flFlashDurationTime <= gpGlobals->time))
continue;
maxDamage = record.flDamage;
maxDamagePlayer = pAttackerPlayer;
maxDamageIndex = i;
}
}
// Note: Only the highest damaging player can be an assistant
// The condition checks if the damage dealt by the player exceeds a certain percentage of the victim's max health
// Default threshold is 40%, meaning the assistant must deal at least 40% of the victim's max health as damage
if (maxDamagePlayer && maxDamage > (assist_damage_threshold.value / 100.0f) * pVictim->pev->max_health)
{
bFlashAssist = victimDamageTakenList[maxDamageIndex - 1].flFlashDurationTime > 0; // if performed the flash assist
return maxDamagePlayer;
}
#endif
return nullptr;
}
//
// Check the rarity estimation for a kill
//
// Estimation to represent the rarity of a kill based on various factors, including assists with flashbang grenades,
// headshot kills, kills through walls, the killer's blindness, no-scope sniper rifle kills, and kills through smoke
//
// pKiller - The entity who committed the kill (Note: The killer may be a non-player)
// pVictim - The player who was killed
// pAssister - The assisting player (if any)
// killerWeaponName - The name of the weapon used by the killer
// bFlashAssist - A flag indicating whether an assist was made with a flashbang
// Returns an integer estimation representing the rarity of the kill
// Use with PLAYERDEATH_KILLRARITY flag to indicate a rare kill in death messages
//
int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, bool bFlashAssist)
{
int iRarity = 0;
// The killer player kills the victim with an assistant flashbang grenade
if (pAssister && bFlashAssist)
iRarity |= KILLRARITY_ASSIST_FLASH;
// The killer player kills the victim with a headshot
if (pVictim->m_bHeadshotKilled)
iRarity |= KILLRARITY_HEADSHOT;
// The killer player kills the victim through the walls
if (pVictim->GetDmgPenetrationLevel() > 0)
iRarity |= KILLRARITY_PENETRATED;
// The killer player was blind
if (pKiller && pKiller->IsPlayer())
{
CBasePlayer *pKillerPlayer = static_cast<CBasePlayer *>(pKiller);
if (pKillerPlayer->IsBlind())
iRarity |= KILLRARITY_KILLER_BLIND;
// The killer player kills the victim with a sniper rifle with no scope
WeaponClassType weaponClass = AliasToWeaponClass(killerWeaponName);
if (weaponClass == WEAPONCLASS_SNIPERRIFLE && pKillerPlayer->m_iClientFOV == DEFAULT_FOV)
iRarity |= KILLRARITY_NOSCOPE;
// The killer player kills the victim through smoke
const Vector inEyePos = pKillerPlayer->EyePosition();
if (TheCSBots()->IsLineBlockedBySmoke(&inEyePos, &pVictim->pev->origin))
iRarity |= KILLRARITY_THROUGH_SMOKE;
// Calculate # of unanswered kills between killer & victim
// This is plus 1 as this function gets called before the stat is updated
// That is done so that the domination and revenge will be calculated prior
// to the death message being sent to the clients
int iAttackerEntityIndex = pKillerPlayer->entindex();
assert(iAttackerEntityIndex >= 0 && iAttackerEntityIndex < MAX_CLIENTS);
int iKillsUnanswered = pVictim->CSPlayer()->m_iNumKilledByUnanswered[iAttackerEntityIndex - 1] + 1;
if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION || pKillerPlayer->CSPlayer()->IsPlayerDominated(pVictim->entindex() - 1))
{
// this is the Nth unanswered kill between killer and victim, killer is now dominating victim
iRarity |= KILLRARITY_DOMINATION;
// set victim to be dominated by killer
pKillerPlayer->CSPlayer()->SetPlayerDominated(pVictim, true);
}
else if (pVictim->CSPlayer()->IsPlayerDominated(pKillerPlayer->entindex() - 1))
{
// the killer killed someone who was dominating him, gains revenge
iRarity |= KILLRARITY_REVENGE;
// set victim to no longer be dominating the killer
pVictim->CSPlayer()->SetPlayerDominated(pKillerPlayer, false);
}
}
return iRarity;
}
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, SendDeathMessage, (CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill), pKiller, pVictim, pAssister, pevInflictor, killerWeaponName, iDeathMessageFlags, iRarityOfKill)
//
// Sends death messages to all players, including info about the killer, victim, weapon used,
// extra death flags, death position, assistant, and kill rarity
//
//
// pKiller - The entity who performed the kill (Note: The killer may be a non-player)
// pVictim - The player who was killed
// pAssister - The assisting player (if any)
// killerWeaponName - The name of the weapon used by the killer
// iDeathMessageFlags - Flags indicating extra death message info
// iRarityOfKill - An bitsums representing the rarity classification of the kill
//
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(SendDeathMessage)(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill)
{
#ifdef REGAMEDLL_ADD
iDeathMessageFlags &= (int)deathmsg_flags.value; // leave only allowed bitsums for extra info
#endif
CBasePlayer *pKillerPlayer = (pKiller && pKiller->IsPlayer()) ? static_cast<CBasePlayer *>(pKiller) : nullptr;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
if (!pPlayer || FNullEnt(pPlayer->edict()))
continue;
if (pPlayer->IsBot() || pPlayer->IsDormant())
continue;
int iSendDeathMessageFlags = iDeathMessageFlags;
// Send the victim's death position only
// if the attacker or victim is a teammate of the recipient player
if (pPlayer == pVictim || (
PlayerRelationship(pVictim, pPlayer) != GR_TEAMMATE &&
PlayerRelationship(pKillerPlayer, pPlayer) != GR_TEAMMATE))
{
iSendDeathMessageFlags &= ~PLAYERDEATH_POSITION;
}
// An recipient a client is a victim that involved in this kill
if (pPlayer == pVictim && pVictim != pKillerPlayer)
{
// Sets a domination kill for recipient of the victim once until revenge
int iKillsUnanswered = pVictim->CSPlayer()->m_iNumKilledByUnanswered[pKillerPlayer->entindex() - 1];
if (iKillsUnanswered >= CS_KILLS_FOR_DOMINATION)
iRarityOfKill &= ~KILLRARITY_DOMINATION;
}
MESSAGE_BEGIN(MSG_ONE, gmsgDeathMsg, nullptr, pPlayer->pev);
WRITE_BYTE((pKiller && pKiller->IsPlayer()) ? pKiller->entindex() : 0); // the killer
WRITE_BYTE(pVictim->entindex()); // the victim
WRITE_BYTE(pVictim->m_bHeadshotKilled); // is killed headshot
WRITE_STRING(killerWeaponName); // what they were killed by (should this be a string?)
if (iSendDeathMessageFlags > 0)
{
WRITE_LONG(iSendDeathMessageFlags);
// Writes the coordinates of the place where the victim died
// The victim has just been killed, so this usefully display 'X' dead icon on the HUD radar
if (iSendDeathMessageFlags & PLAYERDEATH_POSITION)
{
WRITE_COORD(pVictim->pev->origin.x);
WRITE_COORD(pVictim->pev->origin.y);
WRITE_COORD(pVictim->pev->origin.z);
}
// Writes the index of the teammate who assisted in the kill
if (iSendDeathMessageFlags & PLAYERDEATH_ASSISTANT)
WRITE_BYTE(pAssister->entindex());
// Writes the rarity classification of the kill
if (iSendDeathMessageFlags & PLAYERDEATH_KILLRARITY)
WRITE_LONG(iRarityOfKill);
}
MESSAGE_END();
}
}

View File

@ -745,24 +745,30 @@ void EXT_FUNC CBasePlayer::__API_HOOK(TraceAttack)(entvars_t *pevAttacker, float
AddMultiDamage(pevAttacker, this, flDamage, bitsDamageType);
}
const char *GetWeaponName(entvars_t *pevInflictor, entvars_t *pKiller)
const char *CBasePlayer::GetKillerWeaponName(entvars_t *pevInflictor, entvars_t *pevKiller) const
{
// by default, the player is killed by the world
const char *killer_weapon_name = "world";
// Is the killer a client?
if (pKiller->flags & FL_CLIENT)
if (pevKiller->flags & FL_CLIENT)
{
if (pevInflictor)
{
if (pevInflictor == pKiller)
if (pevInflictor == pevKiller)
{
// If the inflictor is the killer, then it must be their current weapon doing the damage
CBasePlayer *pAttacker = CBasePlayer::Instance(pKiller);
if (pAttacker && pAttacker->IsPlayer())
#ifdef REGAMEDLL_FIXES
// Ignore the inflictor's weapon if victim killed self
if (pevKiller != pev)
#endif
{
if (pAttacker->m_pActiveItem)
killer_weapon_name = pAttacker->m_pActiveItem->pszName();
// If the inflictor is the killer, then it must be their current weapon doing the damage
CBasePlayer *pAttacker = CBasePlayer::Instance(pevKiller);
if (pAttacker && pAttacker->IsPlayer())
{
if (pAttacker->m_pActiveItem)
killer_weapon_name = pAttacker->m_pActiveItem->pszName();
}
}
}
else
@ -781,10 +787,11 @@ const char *GetWeaponName(entvars_t *pevInflictor, entvars_t *pKiller)
}
// strip the monster_* or weapon_* from the inflictor's classname
const char cut_weapon[] = "weapon_";
const char cut_weapon[] = "weapon_";
const char cut_monster[] = "monster_";
const char cut_func[] = "func_";
const char cut_func[] = "func_";
// replace the code names with the 'real' names
if (!Q_strncmp(killer_weapon_name, cut_weapon, sizeof(cut_weapon) - 1))
killer_weapon_name += sizeof(cut_weapon) - 1;
@ -955,7 +962,7 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva
m_bKilledByGrenade = true;
}
LogAttack(pAttack, this, bTeamAttack, int(flDamage), armorHit, pev->health - flDamage, pev->armorvalue, GetWeaponName(pevInflictor, pevAttacker));
LogAttack(pAttack, this, bTeamAttack, int(flDamage), armorHit, pev->health - flDamage, pev->armorvalue, GetKillerWeaponName(pevInflictor, pevAttacker));
bTookDamage = CBaseMonster::TakeDamage(pevInflictor, pevAttacker, int(flDamage), bitsDamageType);
if (bTookDamage)
@ -970,9 +977,13 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva
CBasePlayer *pPlayerAttacker = CBasePlayer::Instance(pevAttacker);
if (pPlayerAttacker && !pPlayerAttacker->IsBot() && pPlayerAttacker->m_iTeam != m_iTeam)
{
TheCareerTasks->HandleEnemyInjury(GetWeaponName(pevInflictor, pevAttacker), pPlayerAttacker->HasShield(), pPlayerAttacker);
TheCareerTasks->HandleEnemyInjury(GetKillerWeaponName(pevInflictor, pevAttacker), pPlayerAttacker->HasShield(), pPlayerAttacker);
}
}
#ifdef REGAMEDLL_API
CSPlayer()->RecordDamage(pAttack, flDamage);
#endif
}
{
@ -1191,11 +1202,11 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva
{
Pain(m_LastHitGroup, false);
}
// keep track of amount of damage last sustained
m_lastDamageAmount = flDamage;
LogAttack(pAttack, this, bTeamAttack, flDamage, armorHit, pev->health - flDamage, pev->armorvalue, GetWeaponName(pevInflictor, pevAttacker));
LogAttack(pAttack, this, bTeamAttack, flDamage, armorHit, pev->health - flDamage, pev->armorvalue, GetKillerWeaponName(pevInflictor, pevAttacker));
// this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that
// as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc)
@ -1213,9 +1224,13 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva
CBasePlayer *pPlayerAttacker = CBasePlayer::Instance(pevAttacker);
if (pPlayerAttacker && !pPlayerAttacker->IsBot() && pPlayerAttacker->m_iTeam != m_iTeam)
{
TheCareerTasks->HandleEnemyInjury(GetWeaponName(pevInflictor, pevAttacker), pPlayerAttacker->HasShield(), pPlayerAttacker);
TheCareerTasks->HandleEnemyInjury(GetKillerWeaponName(pevInflictor, pevAttacker), pPlayerAttacker->HasShield(), pPlayerAttacker);
}
}
#ifdef REGAMEDLL_API
CSPlayer()->RecordDamage(pAttack, flDamage);
#endif
}
{
@ -1415,12 +1430,12 @@ void CBasePlayer::PackDeadPlayerItems()
int nBestWeight = 0;
CBasePlayerItem *pBestItem = nullptr;
#ifdef REGAMEDLL_ADD
#ifdef REGAMEDLL_ADD
int iGunsPacked = 0;
if (iPackGun == GR_PLR_DROP_GUN_ACTIVE)
if (iPackGun == GR_PLR_DROP_GUN_ACTIVE)
{
// check if we've just already dropped our active gun
// check if we've just already dropped our active gun
if (!bSkipPrimSec && m_pActiveItem && m_pActiveItem->CanDrop() && m_pActiveItem->iItemSlot() < KNIFE_SLOT)
{
pBestItem = m_pActiveItem;
@ -1429,7 +1444,7 @@ void CBasePlayer::PackDeadPlayerItems()
}
// are we allowing nade drop?
if ((int)nadedrops.value >= 1)
if ((int)nadedrops.value >= 1)
{
// goto item loop but skip guns
iPackGun = GR_PLR_DROP_GUN_ALL;
@ -1460,7 +1475,7 @@ void CBasePlayer::PackDeadPlayerItems()
#endif
)
{
#ifdef REGAMEDLL_ADD
#ifdef REGAMEDLL_ADD
if (iPackGun == GR_PLR_DROP_GUN_ALL)
{
CBasePlayerItem *pNext = pPlayerItem->m_pNext;
@ -1469,10 +1484,10 @@ void CBasePlayer::PackDeadPlayerItems()
if (pWeaponBox)
{
// just push a few units in forward to separate them
pWeaponBox->pev->velocity = pWeaponBox->pev->velocity * (1.0 + (iGunsPacked * 0.2));
pWeaponBox->pev->velocity = pWeaponBox->pev->velocity * (1.0 + (iGunsPacked * 0.2));
iGunsPacked++;
}
pPlayerItem = pNext;
continue;
}
@ -2113,7 +2128,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Killed)(entvars_t *pevAttacker, int iGib)
if (IsBot() && IsBlind()) // dystopm: shouldn't be !IsBot() ?
wasBlind = true;
TheCareerTasks->HandleEnemyKill(wasBlind, GetWeaponName(g_pevLastInflictor, pevAttacker), m_bHeadshotKilled, killerHasShield, pAttacker, this); // last 2 param swapped to match function definition
TheCareerTasks->HandleEnemyKill(wasBlind, GetKillerWeaponName(g_pevLastInflictor, pevAttacker), m_bHeadshotKilled, killerHasShield, pAttacker, this); // last 2 param swapped to match function definition
}
}
#endif
@ -2144,7 +2159,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Killed)(entvars_t *pevAttacker, int iGib)
{
if (TheCareerTasks)
{
TheCareerTasks->HandleEnemyKill(wasBlind, GetWeaponName(g_pevLastInflictor, pevAttacker), m_bHeadshotKilled, killerHasShield, this, pPlayer);
TheCareerTasks->HandleEnemyKill(wasBlind, GetKillerWeaponName(g_pevLastInflictor, pevAttacker), m_bHeadshotKilled, killerHasShield, this, pPlayer);
}
}
}
@ -2426,7 +2441,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Killed)(entvars_t *pevAttacker, int iGib)
#ifndef REGAMEDLL_FIXES
// NOTE: moved to RemoveDefuser
m_bIsDefusing = false;
m_bIsDefusing = false;
#endif
BuyZoneIcon_Clear(this);
@ -3621,7 +3636,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(JoiningThink)()
#ifndef REGAMEDLL_FIXES
// NOTE: client already clears StatusIcon on join
MESSAGE_BEGIN(MSG_ONE, gmsgStatusIcon, nullptr, pev);
MESSAGE_BEGIN(MSG_ONE, gmsgStatusIcon, nullptr, pev);
WRITE_BYTE(STATUSICON_HIDE);
WRITE_STRING("defuser");
MESSAGE_END();
@ -4629,7 +4644,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(PreThink)()
m_afPhysicsFlags &= ~PFLAG_ONTRAIN;
m_iTrain = (TRAIN_NEW | TRAIN_OFF);
#ifdef REGAMEDLL_FIXES
#ifdef REGAMEDLL_FIXES
if (pTrain && pTrain->Classify() == CLASS_VEHICLE) // ensure func_vehicle's m_pDriver assignation
#endif
{
@ -4648,7 +4663,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(PreThink)()
m_afPhysicsFlags &= ~PFLAG_ONTRAIN;
m_iTrain = (TRAIN_NEW | TRAIN_OFF);
#ifdef REGAMEDLL_FIXES
#ifdef REGAMEDLL_FIXES
if (pTrain->Classify() == CLASS_VEHICLE) // ensure func_vehicle's m_pDriver assignation
#endif
{
@ -5154,7 +5169,17 @@ void EXT_FUNC CBasePlayer::__API_HOOK(PostThink)()
#endif
{
m_LastHitGroup = HITGROUP_GENERIC;
TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL);
// FIXED: The player falling to the ground,
// the damage caused by the fall is initiated by himself (and not by the world)
entvars_t *pevAttacker =
#ifdef REGAMEDLL_FIXES
pev;
#else
VARS(eoNullEntity);
#endif
TakeDamage(pevAttacker, pevAttacker, flFallDamage, DMG_FALL);
pev->punchangle.x = 0;
if (TheBots)
{
@ -5983,6 +6008,8 @@ void CBasePlayer::Reset()
if (CSPlayer()->GetProtectionState() == CCSPlayer::ProtectionSt_Active) {
RemoveSpawnProtection();
}
CSPlayer()->ResetAllStats();
#endif
}
@ -8818,22 +8845,22 @@ void CBasePlayer::SpawnClientSideCorpse()
char *pModel = GET_KEY_VALUE(infobuffer, "model");
float timeDiff = pev->animtime - gpGlobals->time;
#ifdef REGAMEDLL_ADD
#ifdef REGAMEDLL_ADD
if (CGameRules::GetDyingTime() < DEATH_ANIMATION_TIME) // a short time, timeDiff estimates to be small
{
float animDuration = GetDyingAnimationDuration();
// client receives a negative value
animDuration *= -1.0;
// client receives a negative value
animDuration *= -1.0;
if (animDuration < timeDiff) // reasonable way to fix client side unfinished sequence bug
{
// by some reason, if client receives a value less
// than "(negative current sequence time) * 100"
// by some reason, if client receives a value less
// than "(negative current sequence time) * 100"
// animation will play visually awkward
// at this function call time, player death animation
// at this function call time, player death animation
// has already finished so we can safely fake it
timeDiff = animDuration;
timeDiff = animDuration;
}
}
#endif
@ -8856,7 +8883,7 @@ void CBasePlayer::SpawnClientSideCorpse()
#ifndef REGAMEDLL_FIXES
// already defined in StartDeathCam
m_canSwitchObserverModes = true;
#endif
#endif
if (TheTutor)
{
@ -10123,7 +10150,7 @@ void CBasePlayer::RemoveDefuser()
SetProgressBarTime(0);
m_bIsDefusing = false;
}
#else
#else
SetProgressBarTime(0);
#endif
}
@ -10449,10 +10476,10 @@ bool CBasePlayer::Kill()
{
if (GetObserverMode() != OBS_NONE)
return false;
if (m_iJoiningState != JOINED)
return false;
m_LastHitGroup = HITGROUP_GENERIC;
// have the player kill himself
@ -10461,6 +10488,6 @@ bool CBasePlayer::Kill()
if (CSGameRules()->m_pVIP == this)
CSGameRules()->m_iConsecutiveVIP = 10;
return true;
}

View File

@ -637,6 +637,7 @@ public:
bool GetIntoGame();
bool ShouldToShowAccount(CBasePlayer *pReceiver) const;
bool ShouldToShowHealthInfo(CBasePlayer *pReceiver) const;
const char *GetKillerWeaponName(entvars_t *pevInflictor, entvars_t *pevKiller) const;
CBasePlayerItem *GetItemByName(const char *itemName);
CBasePlayerItem *GetItemById(WeaponIdType weaponID);
@ -1000,7 +1001,6 @@ void SendItemStatus(CBasePlayer *pPlayer);
const char *GetCSModelName(int item_id);
Vector VecVelocityForDamage(float flDamage);
int TrainSpeed(int iSpeed, int iMax);
const char *GetWeaponName(entvars_t *pevInflictor, entvars_t *pKiller);
void LogAttack(CBasePlayer *pAttacker, CBasePlayer *pVictim, int teamAttack, int healthHit, int armorHit, int newHealth, int newArmor, const char *killer_weapon_name);
bool CanSeeUseable(CBasePlayer *me, CBaseEntity *pEntity);
void FixPlayerCrouchStuck(edict_t *pPlayer);

View File

@ -93,7 +93,7 @@ void EXT_FUNC __API_HOOK(ApplyMultiDamage)(entvars_t *pevInflictor, entvars_t *p
return;
gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type);
gMultiDamage.pEntity->ResetDmgPenetrationLevel();
}
LINK_HOOK_VOID_CHAIN(AddMultiDamage, (entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType), pevInflictor, pEntity, flDamage, bitsDamageType)

View File

@ -46,7 +46,7 @@
<ClCompile Include="..\dlls\API\CSPlayerItem.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Tests|Win32'">
</ExcludedFromBuild>
</ClCompile>
</ClCompile>
<ClCompile Include="..\dlls\API\CSPlayerWeapon.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Tests|Win32'">
</ExcludedFromBuild>
@ -787,6 +787,7 @@
<ClInclude Include="..\public\regamedll\regamedll_api.h" />
<ClInclude Include="..\public\tier0\dbg.h" />
<ClInclude Include="..\public\tier0\platform.h" />
<ClInclude Include="..\public\utlarray.h" />
<ClInclude Include="..\public\utlmemory.h" />
<ClInclude Include="..\public\utlrbtree.h" />
<ClInclude Include="..\public\utlsymbol.h" />
@ -1124,4 +1125,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1052,5 +1052,8 @@
<ClInclude Include="..\dlls\addons\point_command.h">
<Filter>dlls\addons</Filter>
</ClInclude>
<ClInclude Include="..\public\utlarray.h">
<Filter>public</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -35,6 +35,7 @@ public:
CCSEntity() :
m_pContainingEntity(nullptr)
{
m_ucDmgPenetrationLevel = 0;
}
virtual ~CCSEntity() {}
@ -44,12 +45,14 @@ public:
public:
CBaseEntity *m_pContainingEntity;
unsigned char m_ucDmgPenetrationLevel; // penetration level of the damage caused by the inflictor
private:
#if defined(_MSC_VER)
#pragma region reserve_data_Region
#endif
int CCSEntity_Reserve[0x1000];
char CCSEntity_Reserve[0x3FFF];
virtual void func_reserve1() {};
virtual void func_reserve2() {};
virtual void func_reserve3() {};
@ -85,6 +88,29 @@ private:
#endif
};
inline void CBaseEntity::SetDmgPenetrationLevel(int iPenetrationLevel)
{
#ifdef REGAMEDLL_API
m_pEntity->m_ucDmgPenetrationLevel = iPenetrationLevel;
#endif
}
inline void CBaseEntity::ResetDmgPenetrationLevel()
{
#ifdef REGAMEDLL_API
m_pEntity->m_ucDmgPenetrationLevel = 0;
#endif
}
inline int CBaseEntity::GetDmgPenetrationLevel() const
{
#ifdef REGAMEDLL_API
return m_pEntity->m_ucDmgPenetrationLevel;
#else
return 0;
#endif
}
class CCSDelay: public CCSEntity
{
public:

View File

@ -30,6 +30,7 @@
#include <API/CSPlayerItem.h>
#include <API/CSPlayerWeapon.h>
#include <utlarray.h>
enum WeaponInfiniteAmmoMode
{
@ -54,9 +55,17 @@ public:
m_flJumpHeight(0),
m_flLongJumpHeight(0),
m_flLongJumpForce(0),
m_flDuckSpeedMultiplier(0)
m_flDuckSpeedMultiplier(0),
m_iUserID(-1)
{
m_szModel[0] = '\0';
// Resets the kill history for this player
for (int i = 0; i < MAX_CLIENTS; i++)
{
m_iNumKilledByUnanswered[i] = 0;
m_bPlayerDominated[i] = false;
}
}
virtual bool IsConnected() const;
@ -108,10 +117,15 @@ public:
virtual void OnSpawnEquip(bool addDefault = true, bool equipGame = true);
virtual void SetScoreboardAttributes(CBasePlayer *destination = nullptr);
bool IsPlayerDominated(int iPlayerIndex) const;
void SetPlayerDominated(CBasePlayer *pPlayer, bool bDominated);
void ResetVars();
void ResetAllStats();
void OnSpawn();
void OnKilled();
void OnConnect();
CBasePlayer *BasePlayer() const;
@ -140,10 +154,24 @@ public:
bool m_bMegaBunnyJumping;
bool m_bPlantC4Anywhere;
bool m_bSpawnProtectionEffects;
double m_flJumpHeight;
double m_flLongJumpHeight;
double m_flJumpHeight;
double m_flLongJumpHeight;
double m_flLongJumpForce;
double m_flDuckSpeedMultiplier;
int m_iUserID;
struct CDamageRecord_t
{
float flDamage = 0.0f;
float flFlashDurationTime = 0.0f;
int userId = -1;
};
using DamageList_t = CUtlArray<CDamageRecord_t, MAX_CLIENTS>;
DamageList_t m_DamageList; // A unified array of recorded damage that includes giver and taker in each entry
DamageList_t &GetDamageList() { return m_DamageList; }
void RecordDamage(CBasePlayer *pAttacker, float flDamage, float flFlashDurationTime = -1);
int m_iNumKilledByUnanswered[MAX_CLIENTS]; // [0-31] how many unanswered kills this player has been dealt by each other player
bool m_bPlayerDominated[MAX_CLIENTS]; // [0-31] array of state per other player whether player is dominating other players
};
// Inlines
@ -165,3 +193,20 @@ inline CCSPlayer::EProtectionState CCSPlayer::GetProtectionState() const
// has expired
return ProtectionSt_Expired;
}
// Returns whether this player is dominating the specified other player
inline bool CCSPlayer::IsPlayerDominated(int iPlayerIndex) const
{
if (iPlayerIndex < 0 || iPlayerIndex >= MAX_CLIENTS)
return false;
return m_bPlayerDominated[iPlayerIndex];
}
// Sets whether this player is dominating the specified other player
inline void CCSPlayer::SetPlayerDominated(CBasePlayer *pPlayer, bool bDominated)
{
int iPlayerIndex = pPlayer->entindex();
assert(iPlayerIndex >= 0 && iPlayerIndex < MAX_CLIENTS);
m_bPlayerDominated[iPlayerIndex - 1] = bDominated;
}

View File

@ -38,7 +38,7 @@
#include <API/CSInterfaces.h>
#define REGAMEDLL_API_VERSION_MAJOR 5
#define REGAMEDLL_API_VERSION_MINOR 23
#define REGAMEDLL_API_VERSION_MINOR 24
// CBasePlayer::Spawn hook
typedef IHookChainClass<void, class CBasePlayer> IReGameHook_CBasePlayer_Spawn;
@ -588,6 +588,10 @@ typedef IHookChainRegistry<BOOL, int, int> IReGameHookRegistry_CSGameRules_TeamS
typedef IHookChain<void, CBasePlayer *, CBasePlayerItem *> IReGameHook_CSGameRules_PlayerGotWeapon;
typedef IHookChainRegistry<void, CBasePlayer *, CBasePlayerItem *> IReGameHookRegistry_CSGameRules_PlayerGotWeapon;
// CHalfLifeMultiplay::SendDeathMessage hook
typedef IHookChain<void, class CBaseEntity *, class CBasePlayer *, class CBasePlayer *, struct entvars_s *, const char *, int, int> IReGameHook_CSGameRules_SendDeathMessage;
typedef IHookChainRegistry<void, class CBaseEntity *, class CBasePlayer *, class CBasePlayer *, struct entvars_s *, const char *, int, int> IReGameHookRegistry_CSGameRules_SendDeathMessage;
// CBotManager::OnEvent hook
typedef IHookChain<void, GameEventType, CBaseEntity *, CBaseEntity *> IReGameHook_CBotManager_OnEvent;
typedef IHookChainRegistry<void, GameEventType, CBaseEntity*, CBaseEntity*> IReGameHookRegistry_CBotManager_OnEvent;
@ -759,7 +763,7 @@ public:
virtual IReGameHookRegistry_AddMultiDamage *AddMultiDamage() = 0;
virtual IReGameHookRegistry_ApplyMultiDamage *ApplyMultiDamage() = 0;
virtual IReGameHookRegistry_BuyItem *BuyItem() = 0;
virtual IReGameHookRegistry_CSGameRules_Think *CSGameRules_Think() = 0;
virtual IReGameHookRegistry_CSGameRules_Think *CSGameRules_Think() = 0;
virtual IReGameHookRegistry_CSGameRules_TeamFull *CSGameRules_TeamFull() = 0;
virtual IReGameHookRegistry_CSGameRules_TeamStacked *CSGameRules_TeamStacked() = 0;
virtual IReGameHookRegistry_CSGameRules_PlayerGotWeapon *CSGameRules_PlayerGotWeapon() = 0;
@ -769,6 +773,7 @@ public:
virtual IReGameHookRegistry_CBasePlayerWeapon_ItemPostFrame *CBasePlayerWeapon_ItemPostFrame() = 0;
virtual IReGameHookRegistry_CBasePlayerWeapon_KickBack *CBasePlayerWeapon_KickBack() = 0;
virtual IReGameHookRegistry_CBasePlayerWeapon_SendWeaponAnim *CBasePlayerWeapon_SendWeaponAnim() = 0;
virtual IReGameHookRegistry_CSGameRules_SendDeathMessage *CSGameRules_SendDeathMessage() = 0;
};
struct ReGameFuncs_t {

235
regamedll/public/utlarray.h Normal file
View File

@ -0,0 +1,235 @@
/*
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
* Use, distribution, and modification of this source code and/or resulting
* object code is restricted to non-commercial enhancements to products from
* Valve LLC. All other use, distribution, or modification is prohibited
* without written permission from Valve LLC.
*
*/
#pragma once
// A growable array class that maintains a free list and keeps elements
// in the same location
#include "tier0/platform.h"
#include "tier0/dbg.h"
#define FOR_EACH_ARRAY(vecName, iteratorName)\
for (int iteratorName = 0; (vecName).IsUtlArray && iteratorName < (vecName).Count(); iteratorName++)
#define FOR_EACH_ARRAY_BACK(vecName, iteratorName)\
for (int iteratorName = (vecName).Count() - 1; (vecName).IsUtlArray && iteratorName >= 0; iteratorName--)
template <class T, size_t MAX_SIZE>
class CUtlArray
{
public:
typedef T ElemType_t;
enum { IsUtlArray = true }; // Used to match this at compiletime
CUtlArray();
CUtlArray(T *pMemory, size_t count);
~CUtlArray();
CUtlArray<T, MAX_SIZE> &operator=(const CUtlArray<T, MAX_SIZE> &other);
CUtlArray(CUtlArray const &vec);
// element access
T &operator[](int i);
const T &operator[](int i) const;
T &Element(int i);
const T &Element(int i) const;
T &Random();
const T &Random() const;
T *Base();
const T *Base() const;
// Returns the number of elements in the array, NumAllocated() is included for consistency with UtlVector
int Count() const;
int NumAllocated() const;
// Is element index valid?
bool IsValidIndex(int i) const;
static int InvalidIndex();
void CopyArray(const T *pArray, size_t count);
void Clear();
void RemoveAll();
void Swap(CUtlArray< T, MAX_SIZE> &vec);
// Finds an element (element needs operator== defined)
int Find(const T &src) const;
void FillWithValue(const T &src);
bool HasElement(const T &src) const;
protected:
T m_Memory[MAX_SIZE];
};
// Constructor
template <typename T, size_t MAX_SIZE>
inline CUtlArray<T, MAX_SIZE>::CUtlArray()
{
}
template <typename T, size_t MAX_SIZE>
inline CUtlArray<T, MAX_SIZE>::CUtlArray(T *pMemory, size_t count)
{
CopyArray(pMemory, count);
}
// Destructor
template <typename T, size_t MAX_SIZE>
inline CUtlArray<T, MAX_SIZE>::~CUtlArray()
{
}
template <typename T, size_t MAX_SIZE>
inline CUtlArray<T, MAX_SIZE> &CUtlArray<T, MAX_SIZE>::operator=(const CUtlArray<T, MAX_SIZE> &other)
{
if (this != &other)
{
for (size_t n = 0; n < MAX_SIZE; n++)
m_Memory[n] = other.m_Memory[n];
}
return *this;
}
template <typename T, size_t MAX_SIZE>
inline CUtlArray<T, MAX_SIZE>::CUtlArray(CUtlArray const &vec)
{
for (size_t n = 0; n < MAX_SIZE; n++)
m_Memory[n] = vec.m_Memory[n];
}
template <typename T, size_t MAX_SIZE>
inline T *CUtlArray<T, MAX_SIZE>::Base()
{
return &m_Memory[0];
}
template <typename T, size_t MAX_SIZE>
inline const T *CUtlArray<T, MAX_SIZE>::Base() const
{
return &m_Memory[0];
}
// Element access
template <typename T, size_t MAX_SIZE>
inline T &CUtlArray<T, MAX_SIZE>::operator[](int i)
{
Assert(IsValidIndex(i));
return m_Memory[i];
}
template <typename T, size_t MAX_SIZE>
inline const T &CUtlArray<T, MAX_SIZE>::operator[](int i) const
{
Assert(IsValidIndex(i));
return m_Memory[i];
}
template <typename T, size_t MAX_SIZE>
inline T &CUtlArray<T, MAX_SIZE>::Element(int i)
{
Assert(IsValidIndex(i));
return m_Memory[i];
}
template <typename T, size_t MAX_SIZE>
inline const T &CUtlArray<T, MAX_SIZE>::Element(int i) const
{
Assert(IsValidIndex(i));
return m_Memory[i];
}
// Count
template <typename T, size_t MAX_SIZE>
inline int CUtlArray<T, MAX_SIZE>::Count() const
{
return (int)MAX_SIZE;
}
template <typename T, size_t MAX_SIZE>
inline int CUtlArray<T, MAX_SIZE>::NumAllocated() const
{
return (int)MAX_SIZE;
}
// Is element index valid?
template <typename T, size_t MAX_SIZE>
inline bool CUtlArray<T, MAX_SIZE>::IsValidIndex(int i) const
{
return (i >= 0) && (i < MAX_SIZE);
}
// Returns in invalid index
template <typename T, size_t MAX_SIZE>
inline int CUtlArray<T, MAX_SIZE>::InvalidIndex()
{
return -1;
}
template <typename T, size_t MAX_SIZE>
void CUtlArray<T, MAX_SIZE>::CopyArray(const T *pArray, size_t count)
{
Assert(count < MAX_SIZE);
for (size_t n = 0; n < count; n++)
m_Memory[n] = pArray[n];
}
template <typename T, size_t MAX_SIZE>
void CUtlArray<T, MAX_SIZE>::Clear()
{
Q_memset(m_Memory, 0, MAX_SIZE * sizeof(T));
}
template <typename T, size_t MAX_SIZE>
void CUtlArray<T, MAX_SIZE>::RemoveAll()
{
Clear();
}
template <typename T, size_t MAX_SIZE>
void CUtlArray<T, MAX_SIZE>::Swap(CUtlArray< T, MAX_SIZE> &vec)
{
for (size_t n = 0; n < MAX_SIZE; n++)
SWAP(m_Memory[n], vec.m_Memory[n]);
}
// Finds an element (element needs operator== defined)
template <typename T, size_t MAX_SIZE>
int CUtlArray<T, MAX_SIZE>::Find(const T &src) const
{
for (int i = 0; i < Count(); i++)
{
if (Element(i) == src)
return i;
}
return -1;
}
template <typename T, size_t MAX_SIZE>
void CUtlArray<T, MAX_SIZE>::FillWithValue(const T &src)
{
for (int i = 0; i < Count(); i++)
Element(i) = src;
}
template <typename T, size_t MAX_SIZE>
bool CUtlArray<T, MAX_SIZE>::HasElement(const T &src) const
{
return (Find(src) >= 0);
}