diff --git a/regamedll/dlls/API/CSPlayer.cpp b/regamedll/dlls/API/CSPlayer.cpp index 4c7d22e26..f0b773554 100644 --- a/regamedll/dlls/API/CSPlayer.cpp +++ b/regamedll/dlls/API/CSPlayer.cpp @@ -566,6 +566,17 @@ 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; diff --git a/regamedll/dlls/gamerules.h b/regamedll/dlls/gamerules.h index c64e18a42..54efa9a4d 100644 --- a/regamedll/dlls/gamerules.h +++ b/regamedll/dlls/gamerules.h @@ -220,6 +220,10 @@ 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] @@ -245,7 +249,9 @@ enum KillRarity 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_ASSIST_FLASH = 0x020, // The killer player kills the victim with an assistant flashbang grenade + KILLRARITY_DOMINATION = 0x040, // The killer player kills is dominating victim + KILLRARITY_REVENGE = 0x080 // The killer player kills got revenge on victim }; class CItem; diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index fd0b29c3f..fb78706fa 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -4082,7 +4082,7 @@ LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, DeathNotice, void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, entvars_t *pevKiller, entvars_t *pevInflictor) { // by default, the player is killed by the world - CBaseEntity *pKiller = (pevKiller->flags & FL_CLIENT) ? CBaseEntity::Instance(pevKiller) : nullptr; + CBasePlayer *pKiller = (pevKiller->flags & FL_CLIENT) ? CBasePlayer::Instance(pevKiller) : nullptr; const char *killer_weapon_name = pVictim->GetKillerWeaponName(pevInflictor, pevKiller); if (!TheTutor) @@ -4108,6 +4108,16 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, } 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(pKiller ? pKiller->entindex() : 0); // the killer @@ -4129,7 +4139,7 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, else if (pevKiller->flags & FL_CLIENT) { const char *VictimTeam = GetTeam(pVictim->m_iTeam); - const char *KillerTeam = (pKiller && pKiller->IsPlayer()) ? GetTeam(((CBasePlayer *)pKiller)->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(pevKiller->netname), GETPLAYERUSERID(ENT(pevKiller)), GETPLAYERAUTHID(ENT(pevKiller)), KillerTeam, STRING(pVictim->pev->netname), GETPLAYERUSERID(pVictim->edict()), GETPLAYERAUTHID(pVictim->edict()), VictimTeam, killer_weapon_name); @@ -5272,6 +5282,31 @@ int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVict 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; @@ -5319,6 +5354,15 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(SendDeathMessage)(CBaseEntity *pKil 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 diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index de90f3e6b..32235fab0 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -6008,6 +6008,8 @@ void CBasePlayer::Reset() if (CSPlayer()->GetProtectionState() == CCSPlayer::ProtectionSt_Active) { RemoveSpawnProtection(); } + + CSPlayer()->ResetAllStats(); #endif } diff --git a/regamedll/public/regamedll/API/CSPlayer.h b/regamedll/public/regamedll/API/CSPlayer.h index 96955884b..455edae18 100644 --- a/regamedll/public/regamedll/API/CSPlayer.h +++ b/regamedll/public/regamedll/API/CSPlayer.h @@ -59,6 +59,13 @@ class CCSPlayer: public CCSMonster { 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; @@ -110,11 +117,16 @@ class CCSPlayer: public CCSMonster { 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; public: @@ -158,6 +170,8 @@ class CCSPlayer: public CCSMonster { 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 @@ -179,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; +}