From 3df37bd2f8d13f12bb1f763985b31ddffced6551 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 7 Sep 2023 07:04:00 +0700 Subject: [PATCH] Implemented rarity of kill and assist for extended user message DeathMsg --- README.md | 4 +- dist/game.cfg | 16 ++ regamedll/dlls/API/CSPlayer.cpp | 31 +++ regamedll/dlls/cbase.cpp | 6 +- regamedll/dlls/cbase.h | 4 + regamedll/dlls/client.cpp | 2 +- regamedll/dlls/combat.cpp | 24 ++- regamedll/dlls/game.cpp | 4 + regamedll/dlls/game.h | 2 + regamedll/dlls/gamerules.h | 32 +++ regamedll/dlls/multiplay_gamerules.cpp | 217 +++++++++++++++++-- regamedll/dlls/player.cpp | 8 + regamedll/dlls/weapons.cpp | 2 +- regamedll/public/regamedll/API/CSEntity.h | 28 ++- regamedll/public/regamedll/API/CSPlayer.h | 22 +- regamedll/public/utlarray.h | 248 ++++++++++++++++++++++ 16 files changed, 623 insertions(+), 27 deletions(-) create mode 100644 regamedll/public/utlarray.h diff --git a/README.md b/README.md index 5d521515e..2cbc6f3bd 100644 --- a/README.md +++ b/README.md @@ -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.
`0` New behavior
`1` Legacy behavior | -| mp_dying_time | 3.0 | 0.0 | - | Time for switch to free observing after death.
`0` - disable spectating around death.
`>0.00001` - time delay to start spectate.
`NOTE`: The countdown starts when the player’s death animation is finished.| +| mp_dying_time | 3.0 | 0.0 | - | Time for switch to free observing after death.
`0` - disable spectating around death.
`>0.00001` - time delay to start spectate.
`NOTE`: The countdown starts when the player’s death animation is finished. | +| mp_deathmsg_flags | 7 | 0 | 7 | Sets a bitsum for extra information in the player's death message.
`0` disabled
`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. | +| mp_assist_damage_threshold | 40 | 0 | 100 | Sets the percentage of damage needed to score an assist. | ## How to install zBot for CS 1.6? diff --git a/dist/game.cfg b/dist/game.cfg index fece0de56..ce0b3fc7f 100644 --- a/dist/game.cfg +++ b/dist/game.cfg @@ -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" diff --git a/regamedll/dlls/API/CSPlayer.cpp b/regamedll/dlls/API/CSPlayer.cpp index 36c1d258b..4c7d22e26 100644 --- a/regamedll/dlls/API/CSPlayer.cpp +++ b/regamedll/dlls/API/CSPlayer.cpp @@ -570,6 +570,7 @@ void CCSPlayer::OnSpawn() { m_bGameForcingRespawn = false; m_flRespawnPending = 0.0f; + m_DamageList.Clear(); } void CCSPlayer::OnKilled() @@ -587,3 +588,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; +} diff --git a/regamedll/dlls/cbase.cpp b/regamedll/dlls/cbase.cpp index b1cbe149e..9e14bf43e 100644 --- a/regamedll/dlls/cbase.cpp +++ b/regamedll/dlls/cbase.cpp @@ -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; } diff --git a/regamedll/dlls/cbase.h b/regamedll/dlls/cbase.h index 5199d75b0..986933f52 100644 --- a/regamedll/dlls/cbase.h +++ b/regamedll/dlls/cbase.h @@ -242,6 +242,10 @@ class CBaseEntity { 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; diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index 4f3245af3..0beebf4b1 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -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 : ""); diff --git a/regamedll/dlls/combat.cpp b/regamedll/dlls/combat.cpp index 5e69f213c..8dceda8d4 100644 --- a/regamedll/dlls/combat.cpp +++ b/regamedll/dlls/combat.cpp @@ -16,12 +16,29 @@ 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; + flAdjustedDamage = fadeHold * 1.5; + } + else + { + flAdjustedDamage = fadeTime / 1.75; + flAdjustedDamage = fadeHold * 3.5; + } + + 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 +118,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 +127,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 +319,8 @@ void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker if (tr.flFraction != 1.0f) flAdjustedDamage = 0.0f; + else + pEntity->SetDmgPenetrationLevel(1); } #endif } diff --git a/regamedll/dlls/game.cpp b/regamedll/dlls/game.cpp index 34d9d0ad6..a9e37a8b5 100644 --- a/regamedll/dlls/game.cpp +++ b/regamedll/dlls/game.cpp @@ -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"); diff --git a/regamedll/dlls/game.h b/regamedll/dlls/game.h index bb9d9c1ca..b9b9253b9 100644 --- a/regamedll/dlls/game.h +++ b/regamedll/dlls/game.h @@ -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 diff --git a/regamedll/dlls/gamerules.h b/regamedll/dlls/gamerules.h index 00d0116ff..59415beaa 100644 --- a/regamedll/dlls/gamerules.h +++ b/regamedll/dlls/gamerules.h @@ -220,6 +220,34 @@ enum GR_NEUTRAL, }; +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 +}; + class CItem; class CGameRules @@ -698,6 +726,10 @@ class CHalfLifeMultiplay: public CGameRules VFUNC bool HasRoundTimeExpired(); VFUNC bool IsBombPlanted(); + void SendDeathMessage(CBasePlayer *pAttacker, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill); + int GetRarityOfKill(CBasePlayer *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, bool bAssistWithFlashbang); + CBasePlayer *CheckAssistsToKill(CBasePlayer *pVictim, CBasePlayer *pKiller, bool &bAssistWithFlashbang); + private: void MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int iTeam); diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index 0c45149cb..d1d0418a1 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -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; @@ -4083,7 +4083,7 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, { // by default, the player is killed by the world const char *killer_weapon_name = "world"; - int killer_index = 0; + CBasePlayer *pAttacker = nullptr; #ifndef REGAMEDLL_FIXES // Hack to fix name change @@ -4094,14 +4094,13 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, // Is the killer a client? if (pKiller->flags & FL_CLIENT) { - killer_index = ENTINDEX(ENT(pKiller)); + pAttacker = CBasePlayer::Instance(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) @@ -4142,12 +4141,28 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, if (!TheTutor) { - 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?) - MESSAGE_END(); + int iAllowDeathMessageFlags = (int)deathmsg_flags.value; // allow bitsums for extra information + int iDeathMessageFlags = PLAYERDEATH_POSITION; // set bitsum default + int iRarityOfKill = 0; + + CBasePlayer *pAssister = nullptr; + CBasePlayer *pFlasher = nullptr; + + if (pAttacker && pAttacker->IsPlayer()) + { + // Sets the flag PLAYERDEATH_ASSISTANT indicating the presence of a teammate who assisted in the kill + bool bAssistWithFlashbang; + if ((pAssister = CheckAssistsToKill(pVictim, pAttacker, bAssistWithFlashbang))) + iDeathMessageFlags |= PLAYERDEATH_ASSISTANT; + + iRarityOfKill = GetRarityOfKill(pAttacker, pVictim, pAssister, killer_weapon_name, bAssistWithFlashbang); + if (iRarityOfKill != 0) + iDeathMessageFlags |= PLAYERDEATH_KILLRARITY; // Attacker killed the victim in a rare way + } + + // Apply allowed death message flags to iDeathMessageFlags + iDeathMessageFlags &= iAllowDeathMessageFlags; + SendDeathMessage(pAttacker, pVictim, pAssister, killer_weapon_name, iDeathMessageFlags, iRarityOfKill); } // This weapons from HL isn't it? @@ -4169,8 +4184,6 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, } else if (pKiller->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) : ""; @@ -4345,11 +4358,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 +5217,177 @@ 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 +// +// pVictim - The victim player +// pKiller - The killer player +// bAssistWithFlashbang - 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(CBasePlayer *pVictim, CBasePlayer *pKiller, bool &bAssistWithFlashbang) +{ + CCSPlayer::DamageList_t &victimDamageTakenList = pVictim->CSPlayer()->GetDamageList(); + + float maxDamage = 0.0f; + int maxDamageIndex = -1; + CBasePlayer *maxDamagePlayer = nullptr; + bAssistWithFlashbang = false; + + // 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) + { + bAssistWithFlashbang = victimDamageTakenList[maxDamageIndex - 1].flFlashDurationTime > 0; // if performed the flash assist + return maxDamagePlayer; + } + + 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 player who performed the kill +// pVictim - The player who was killed +// pAssister - The assisting player (if any) +// killerWeaponName - The name of the weapon used by the killer +// bAssistWithFlashbang - 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(CBasePlayer *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, bool bAssistWithFlashbang) +{ + int iRarity = 0; + + // The killer player kills the victim with an assistant flashbang grenade + if (pAssister && bAssistWithFlashbang) + 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->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 && pKiller->m_iClientFOV == DEFAULT_FOV) + iRarity |= KILLRARITY_NOSCOPE; + + // The killer player kills the victim through smoke + const Vector inEyePos = pKiller->EyePosition(); + if (TheCSBots()->IsLineBlockedBySmoke(&inEyePos, &pVictim->pev->origin)) + iRarity |= KILLRARITY_THROUGH_SMOKE; + + return iRarity; +} + +// +// Sends death messages to all players, including info about the killer, victim, weapon used, +// extra death flags, death position, assistant, and kill rarity +// +// +// pAttacker - The player who performed the kill +// 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 CHalfLifeMultiplay::SendDeathMessage(CBasePlayer *pAttacker, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill) +{ + 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 iDeathExtraFlags = 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(pAttacker, pPlayer) != GR_TEAMMATE)) + { + iDeathExtraFlags &= ~PLAYERDEATH_POSITION; + } + + MESSAGE_BEGIN(MSG_ONE, gmsgDeathMsg, nullptr, pPlayer->pev); + WRITE_BYTE(pAttacker ? pAttacker->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 (iDeathExtraFlags > 0) + WRITE_LONG(iDeathExtraFlags); + + // 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 (iDeathExtraFlags & 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 (iDeathExtraFlags & PLAYERDEATH_ASSISTANT) + WRITE_BYTE(pAssister->entindex()); + + // Writes the rarity classification of the kill + if (iDeathExtraFlags & PLAYERDEATH_KILLRARITY) + WRITE_LONG(iRarityOfKill); + + MESSAGE_END(); + } +} diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index fa26e4141..623f3dd12 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -973,6 +973,10 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva TheCareerTasks->HandleEnemyInjury(GetWeaponName(pevInflictor, pevAttacker), pPlayerAttacker->HasShield(), pPlayerAttacker); } } + +#ifdef REGAMEDLL_API + CSPlayer()->RecordDamage(pAttack, flDamage); +#endif } { @@ -1216,6 +1220,10 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva TheCareerTasks->HandleEnemyInjury(GetWeaponName(pevInflictor, pevAttacker), pPlayerAttacker->HasShield(), pPlayerAttacker); } } + +#ifdef REGAMEDLL_API + CSPlayer()->RecordDamage(pAttack, flDamage); +#endif } { diff --git a/regamedll/dlls/weapons.cpp b/regamedll/dlls/weapons.cpp index 097054fc7..7d2ca281e 100644 --- a/regamedll/dlls/weapons.cpp +++ b/regamedll/dlls/weapons.cpp @@ -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) diff --git a/regamedll/public/regamedll/API/CSEntity.h b/regamedll/public/regamedll/API/CSEntity.h index 57e97b9eb..e2c1922c8 100644 --- a/regamedll/public/regamedll/API/CSEntity.h +++ b/regamedll/public/regamedll/API/CSEntity.h @@ -35,6 +35,7 @@ class CCSEntity CCSEntity() : m_pContainingEntity(nullptr) { + m_ucDmgPenetrationLevel = 0; } virtual ~CCSEntity() {} @@ -44,12 +45,14 @@ class CCSEntity 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 @@ class CCSEntity #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: diff --git a/regamedll/public/regamedll/API/CSPlayer.h b/regamedll/public/regamedll/API/CSPlayer.h index f56b1c513..96955884b 100644 --- a/regamedll/public/regamedll/API/CSPlayer.h +++ b/regamedll/public/regamedll/API/CSPlayer.h @@ -30,6 +30,7 @@ #include #include +#include enum WeaponInfiniteAmmoMode { @@ -54,7 +55,8 @@ class CCSPlayer: public CCSMonster { m_flJumpHeight(0), m_flLongJumpHeight(0), m_flLongJumpForce(0), - m_flDuckSpeedMultiplier(0) + m_flDuckSpeedMultiplier(0), + m_iUserID(-1) { m_szModel[0] = '\0'; } @@ -112,7 +114,7 @@ class CCSPlayer: public CCSMonster { void OnSpawn(); void OnKilled(); - + void OnConnect(); CBasePlayer *BasePlayer() const; public: @@ -140,10 +142,22 @@ class CCSPlayer: public CCSMonster { 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; + 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); }; // Inlines diff --git a/regamedll/public/utlarray.h b/regamedll/public/utlarray.h new file mode 100644 index 000000000..0070c19c7 --- /dev/null +++ b/regamedll/public/utlarray.h @@ -0,0 +1,248 @@ +/* +* +* 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 CUtlArray +{ +public: + typedef T ElemType_t; + enum { IsUtlArray = true }; // Used to match this at compiletime + + CUtlArray(); + CUtlArray(T *pMemory, size_t count); + ~CUtlArray(); + + CUtlArray &operator=(const CUtlArray &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; + + void Sort(int(__cdecl *pfnCompare)(const T *, const T *)); + +protected: + T m_Memory[MAX_SIZE]; +}; + +// Constructor +template +inline CUtlArray::CUtlArray() +{ +} + +template +inline CUtlArray::CUtlArray(T *pMemory, size_t count) +{ + CopyArray(pMemory, count); +} + +// Destructor +template +inline CUtlArray::~CUtlArray() +{ +} + +template +inline CUtlArray &CUtlArray::operator=(const CUtlArray &other) +{ + if (this != &other) + { + for (size_t n = 0; n < MAX_SIZE; n++) + m_Memory[n] = other.m_Memory[n]; + } + + return *this; +} + +template +inline CUtlArray::CUtlArray(CUtlArray const &vec) +{ + for (size_t n = 0; n < MAX_SIZE; n++) + m_Memory[n] = vec.m_Memory[n]; +} + +template +inline T *CUtlArray::Base() +{ + return &m_Memory[0]; +} + +template +inline const T *CUtlArray::Base() const +{ + return &m_Memory[0]; +} + +// Element access +template +inline T &CUtlArray::operator[](int i) +{ + Assert(IsValidIndex(i)); + return m_Memory[i]; +} + +template +inline const T &CUtlArray::operator[](int i) const +{ + Assert(IsValidIndex(i)); + return m_Memory[i]; +} + +template +inline T &CUtlArray::Element(int i) +{ + Assert(IsValidIndex(i)); + return m_Memory[i]; +} + +template +inline const T &CUtlArray::Element(int i) const +{ + Assert(IsValidIndex(i)); + return m_Memory[i]; +} + +// Count +template +inline int CUtlArray::Count() const +{ + return (int)MAX_SIZE; +} + +template +inline int CUtlArray::NumAllocated() const +{ + return (int)MAX_SIZE; +} + +// Is element index valid? +template +inline bool CUtlArray::IsValidIndex(int i) const +{ + return (i >= 0) && (i < MAX_SIZE); +} + +// Returns in invalid index +template +inline int CUtlArray::InvalidIndex() +{ + return -1; +} + +// Sorts the vector +template +void CUtlArray::Sort(int(__cdecl *pfnCompare)(const T *, const T *)) +{ + typedef int(__cdecl *QSortCompareFunc_t)(const void *, const void *); + if (Count() <= 1) + return; + + qsort(Base(), Count(), sizeof(T), (QSortCompareFunc_t)(pfnCompare)); +} + +template +void CUtlArray::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 +void CUtlArray::Clear() +{ + Q_memset(m_Memory, 0, MAX_SIZE * sizeof(T)); +} + +template +void CUtlArray::RemoveAll() +{ + Clear(); +} + +template +void CUtlArray::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 +int CUtlArray::Find(const T &src) const +{ + for (int i = 0; i < Count(); i++) + { + if (Element(i) == src) + return i; + } + + return -1; +} + +template +void CUtlArray::FillWithValue(const T &src) +{ + for (int i = 0; i < Count(); i++) + Element(i) = src; +} + +template +bool CUtlArray::HasElement(const T &src) const +{ + return (Find(src) >= 0); +}