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/CAPI_Impl.cpp b/regamedll/dlls/API/CAPI_Impl.cpp index 792bc3ef0..3075c841d 100644 --- a/regamedll/dlls/API/CAPI_Impl.cpp +++ b/regamedll/dlls/API/CAPI_Impl.cpp @@ -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; diff --git a/regamedll/dlls/API/CAPI_Impl.h b/regamedll/dlls/API/CAPI_Impl.h index b6bc45dd0..a66d9e91e 100644 --- a/regamedll/dlls/API/CAPI_Impl.h +++ b/regamedll/dlls/API/CAPI_Impl.h @@ -709,6 +709,10 @@ typedef IHookChainRegistryClassEmptyImpl CReGameHook_CSGameRules_PlayerGotWeapon; typedef IHookChainRegistryClassEmptyImpl CReGameHookRegistry_CSGameRules_PlayerGotWeapon; +// CHalfLifeMultiplay::SendDeathMessage hook +typedef IHookChainClassImpl CReGameHook_CSGameRules_SendDeathMessage; +typedef IHookChainRegistryClassEmptyImpl CReGameHookRegistry_CSGameRules_SendDeathMessage; + // CBotManager::OnEvent hook typedef IHookChainClassImpl CReGameHook_CBotManager_OnEvent; typedef IHookChainRegistryClassEmptyImpl CReGameHookRegistry_CBotManager_OnEvent; @@ -865,7 +869,7 @@ class CReGameHookchains: public IReGameHookchains { 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 @@ class CReGameHookchains: public IReGameHookchains { 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 @@ class CReGameHookchains: public IReGameHookchains { 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; diff --git a/regamedll/dlls/API/CSPlayer.cpp b/regamedll/dlls/API/CSPlayer.cpp index 36c1d258b..e55afd082 100644 --- a/regamedll/dlls/API/CSPlayer.cpp +++ b/regamedll/dlls/API/CSPlayer.cpp @@ -43,8 +43,10 @@ EXT_FUNC bool CCSPlayer::JoinTeam(TeamName team) pPlayer->pev->deadflag = DEAD_DEAD; pPlayer->pev->health = 0; + if (pPlayer->m_bHasC4) + pPlayer->DropPlayerItem("weapon_c4"); + pPlayer->RemoveAllItems(TRUE); - pPlayer->m_bHasC4 = false; pPlayer->m_iTeam = SPECTATOR; pPlayer->m_iJoiningState = JOINED; @@ -217,38 +219,20 @@ EXT_FUNC bool CCSPlayer::RemovePlayerItemEx(const char* pszItemName, bool bRemov return true; // ammo was reduced, this will be considered a successful result } - if (pItem == pPlayer->m_pActiveItem) { - ((CBasePlayerWeapon *)pItem)->RetireWeapon(); - } - if (bRemoveAmmo) { - pPlayer->m_rgAmmo[ pItem->PrimaryAmmoIndex() ] = 0; - } - } - - if (pPlayer->RemovePlayerItem(pItem)) - { - if (FClassnameIs(pItem->pev, "weapon_c4")) { - pPlayer->m_bHasC4 = false; - pPlayer->pev->body = 0; - pPlayer->SetBombIcon(FALSE); - pPlayer->SetProgressBarTime(0); - } - - pPlayer->pev->weapons &= ~(1 << pItem->m_iId); - // No more weapon - if ((pPlayer->pev->weapons & ~(1 << WEAPON_SUIT)) == 0) { - pPlayer->m_iHideHUD |= HIDEHUD_WEAPONS; + pPlayer->m_rgAmmo[pItem->PrimaryAmmoIndex()] = 0; } - pItem->Kill(); - - if (!pPlayer->m_rgpPlayerItems[PRIMARY_WEAPON_SLOT]) { - pPlayer->m_bHasPrimary = false; + if (pItem == pPlayer->m_pActiveItem) { + ((CBasePlayerWeapon *)pItem)->RetireWeapon(); + + if (pItem->CanHolster() && pItem != pPlayer->m_pActiveItem && !(pPlayer->pev->weapons &(1 << pItem->m_iId))) { + return true; + } } - - return true; } + + return pItem->DestroyItem(); } return false; @@ -310,14 +294,14 @@ EXT_FUNC void CCSPlayer::GiveShield(bool bDeploy) BasePlayer()->GiveShield(bDeploy); } -EXT_FUNC void CCSPlayer::DropShield(bool bDeploy) +EXT_FUNC CBaseEntity *CCSPlayer::DropShield(bool bDeploy) { - BasePlayer()->DropShield(bDeploy); + return BasePlayer()->DropShield(bDeploy); } -EXT_FUNC void CCSPlayer::DropPlayerItem(const char *pszItemName) +EXT_FUNC CBaseEntity *CCSPlayer::DropPlayerItem(const char *pszItemName) { - BasePlayer()->DropPlayerItem(pszItemName); + return BasePlayer()->DropPlayerItem(pszItemName); } EXT_FUNC bool CCSPlayer::RemoveShield() @@ -566,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() @@ -587,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; +} 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..771538571 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 : ""); @@ -4822,6 +4822,16 @@ int EXT_FUNC GetWeaponData(edict_t *pEdict, struct weapon_data_s *info) item->fuser2 = weapon->m_flStartThrow; item->fuser3 = weapon->m_flReleaseThrow; item->iuser1 = weapon->m_iSwing; + +#ifdef REGAMEDLL_FIXES + if (pPlayerItem == pPlayer->m_pActiveItem && !weapon->m_fInReload && weapon->m_iClip == II.iMaxClip) + { + const WeaponInfoStruct *wpnInfo = GetDefaultWeaponInfo(II.iId); + + if (wpnInfo && wpnInfo->gunClipSize != II.iMaxClip) + item->m_iClip = wpnInfo->gunClipSize; + } +#endif } } diff --git a/regamedll/dlls/combat.cpp b/regamedll/dlls/combat.cpp index 5e69f213c..4cbc485dc 100644 --- a/regamedll/dlls/combat.cpp +++ b/regamedll/dlls/combat.cpp @@ -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 } 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..70d67c224 100644 --- a/regamedll/dlls/gamerules.h +++ b/regamedll/dlls/gamerules.h @@ -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 @@ class CHalfLifeMultiplay: public CGameRules 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 @@ class CHalfLifeMultiplay: public CGameRules 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); diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index 0c45149cb..fb78706fa 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; @@ -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; + CBasePlayer *pKiller = (pevKiller->flags & FL_CLIENT) ? CBasePlayer::Instance(pevKiller) : nullptr; + const char *killer_weapon_name = pVictim->GetKillerWeaponName(pevInflictor, pevKiller); -#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) + if (!TheTutor) { - killer_index = ENTINDEX(ENT(pKiller)); +#ifdef REGAMEDLL_ADD + int iRarityOfKill = 0; + int iDeathMessageFlags = PLAYERDEATH_POSITION; // set default bit - if (pevInflictor) + CBasePlayer *pAssister = nullptr; + + bool bFlashAssist = false; + if ((pAssister = CheckAssistsToKill(pKiller, pVictim, bFlashAssist))) { - 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); - } + // Add a flag indicating the presence of an assistant who assisted in the kill + iDeathMessageFlags |= PLAYERDEATH_ASSISTANT; } - } - 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; + 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; + } - else if (!Q_strncmp(killer_weapon_name, cut_monster, sizeof(cut_monster) - 1)) - killer_weapon_name += sizeof(cut_monster) - 1; + SendDeathMessage(pKiller, pVictim, pAssister, pevInflictor, killer_weapon_name, iDeathMessageFlags, iRarityOfKill); - else if (!Q_strncmp(killer_weapon_name, cut_func, sizeof(cut_func) - 1)) - killer_weapon_name += sizeof(cut_func) - 1; + // 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(); - if (!TheTutor) - { + 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(); - } - - // 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(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(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(); + } +} diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index fa26e4141..a0b0d744b 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -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 } @@ -8494,7 +8521,6 @@ void CStripWeapons::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE if (slot == ALL_OTHER_ITEMS) { - pPlayer->CSPlayer()->RemovePlayerItem("item_thighpack"); pPlayer->CSPlayer()->RemovePlayerItem("item_longjump"); pPlayer->CSPlayer()->RemovePlayerItem("item_assaultsuit"); pPlayer->CSPlayer()->RemovePlayerItem("item_kevlar"); @@ -8819,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 @@ -8857,7 +8883,7 @@ void CBasePlayer::SpawnClientSideCorpse() #ifndef REGAMEDLL_FIXES // already defined in StartDeathCam m_canSwitchObserverModes = true; -#endif +#endif if (TheTutor) { @@ -10124,7 +10150,7 @@ void CBasePlayer::RemoveDefuser() SetProgressBarTime(0); m_bIsDefusing = false; } -#else +#else SetProgressBarTime(0); #endif } @@ -10450,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 @@ -10462,6 +10488,6 @@ bool CBasePlayer::Kill() if (CSGameRules()->m_pVIP == this) CSGameRules()->m_iConsecutiveVIP = 10; - + return true; } diff --git a/regamedll/dlls/player.h b/regamedll/dlls/player.h index 9d9aa67c2..625e60fd6 100644 --- a/regamedll/dlls/player.h +++ b/regamedll/dlls/player.h @@ -637,6 +637,7 @@ class CBasePlayer: public CBaseMonster { 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); diff --git a/regamedll/dlls/weapons.cpp b/regamedll/dlls/weapons.cpp index 097054fc7..2d5b5ed57 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) @@ -1128,8 +1128,10 @@ void EXT_FUNC CBasePlayerWeapon::__API_HOOK(ItemPostFrame)() } } -void CBasePlayerItem::DestroyItem() +bool CBasePlayerItem::DestroyItem() { + bool success = false; + if (m_pPlayer) { // if attached to a player, remove. @@ -1137,18 +1139,31 @@ void CBasePlayerItem::DestroyItem() { #ifdef REGAMEDLL_FIXES + if (m_iId == WEAPON_C4) { + m_pPlayer->m_bHasC4 = false; + m_pPlayer->pev->body = 0; + m_pPlayer->SetBombIcon(FALSE); + m_pPlayer->SetProgressBarTime(0); + } + m_pPlayer->pev->weapons &= ~(1 << m_iId); // No more weapon if ((m_pPlayer->pev->weapons & ~(1 << WEAPON_SUIT)) == 0) { m_pPlayer->m_iHideHUD |= HIDEHUD_WEAPONS; } -#endif + if (!m_pPlayer->m_rgpPlayerItems[PRIMARY_WEAPON_SLOT]) { + m_pPlayer->m_bHasPrimary = false; + } +#endif + success = true; } } Kill(); + + return success; } int CBasePlayerItem::AddToPlayer(CBasePlayer *pPlayer) diff --git a/regamedll/dlls/weapons.h b/regamedll/dlls/weapons.h index da2c9be6d..431284fb9 100644 --- a/regamedll/dlls/weapons.h +++ b/regamedll/dlls/weapons.h @@ -296,7 +296,7 @@ class CBasePlayerItem: public CBaseAnimating virtual int iItemSlot() { return 0; } // return 0 to MAX_ITEMS_SLOTS, used in hud public: - void EXPORT DestroyItem(); + bool EXPORT DestroyItem(); void EXPORT DefaultTouch(CBaseEntity *pOther); void EXPORT FallThink(); void EXPORT Materialize(); diff --git a/regamedll/dlls/weapontype.cpp b/regamedll/dlls/weapontype.cpp index 209c7b89d..2b8ae4458 100644 --- a/regamedll/dlls/weapontype.cpp +++ b/regamedll/dlls/weapontype.cpp @@ -529,6 +529,17 @@ WeaponInfoStruct *GetWeaponInfo(const char *weaponName) return nullptr; } +WeaponInfoStruct *GetDefaultWeaponInfo(int weaponID) +{ + for (auto& info : g_weaponInfo_default) { + if (info.id == weaponID) { + return &info; + } + } + + return nullptr; +} + AmmoInfoStruct *GetAmmoInfo(const char *ammoName) { for (auto& info : g_ammoInfo) { diff --git a/regamedll/dlls/weapontype.h b/regamedll/dlls/weapontype.h index 97752ec8f..2005b6af7 100644 --- a/regamedll/dlls/weapontype.h +++ b/regamedll/dlls/weapontype.h @@ -445,6 +445,8 @@ void WeaponInfoReset(); WeaponInfoStruct *GetWeaponInfo(int weaponID); WeaponInfoStruct *GetWeaponInfo(const char *weaponName); +WeaponInfoStruct *GetDefaultWeaponInfo(int weaponID); + AmmoInfoStruct *GetAmmoInfo(AmmoType ammoID); AmmoInfoStruct *GetAmmoInfo(const char *ammoName); diff --git a/regamedll/msvc/ReGameDLL.vcxproj b/regamedll/msvc/ReGameDLL.vcxproj index b3227cd45..c31abc6e9 100644 --- a/regamedll/msvc/ReGameDLL.vcxproj +++ b/regamedll/msvc/ReGameDLL.vcxproj @@ -46,7 +46,7 @@ - + @@ -787,6 +787,7 @@ + @@ -1124,4 +1125,4 @@ - + \ No newline at end of file diff --git a/regamedll/msvc/ReGameDLL.vcxproj.filters b/regamedll/msvc/ReGameDLL.vcxproj.filters index 3e9c67f15..84536f321 100644 --- a/regamedll/msvc/ReGameDLL.vcxproj.filters +++ b/regamedll/msvc/ReGameDLL.vcxproj.filters @@ -1052,5 +1052,8 @@ dlls\addons + + public + \ No newline at end of file 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..476b20013 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,9 +55,17 @@ 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'; + + // 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; @@ -66,8 +75,8 @@ class CCSPlayer: public CCSMonster { virtual CBaseEntity *GiveNamedItemEx(const char *pszName); virtual void GiveDefaultItems(); virtual void GiveShield(bool bDeploy = true); - virtual void DropShield(bool bDeploy = true); - virtual void DropPlayerItem(const char *pszItemName); + virtual CBaseEntity *DropShield(bool bDeploy = true); + virtual CBaseEntity* DropPlayerItem(const char *pszItemName); virtual bool RemoveShield(); virtual void RemoveAllItems(bool bRemoveSuit); virtual bool RemovePlayerItem(const char* pszItemName); @@ -108,10 +117,15 @@ 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; @@ -140,10 +154,24 @@ 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); + 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; +} diff --git a/regamedll/public/regamedll/regamedll_api.h b/regamedll/public/regamedll/regamedll_api.h index 57da8d290..70162c259 100644 --- a/regamedll/public/regamedll/regamedll_api.h +++ b/regamedll/public/regamedll/regamedll_api.h @@ -38,7 +38,7 @@ #include #define REGAMEDLL_API_VERSION_MAJOR 5 -#define REGAMEDLL_API_VERSION_MINOR 23 +#define REGAMEDLL_API_VERSION_MINOR 24 // CBasePlayer::Spawn hook typedef IHookChainClass IReGameHook_CBasePlayer_Spawn; @@ -588,6 +588,10 @@ typedef IHookChainRegistry IReGameHookRegistry_CSGameRules_TeamS typedef IHookChain IReGameHook_CSGameRules_PlayerGotWeapon; typedef IHookChainRegistry IReGameHookRegistry_CSGameRules_PlayerGotWeapon; +// CHalfLifeMultiplay::SendDeathMessage hook +typedef IHookChain IReGameHook_CSGameRules_SendDeathMessage; +typedef IHookChainRegistry IReGameHookRegistry_CSGameRules_SendDeathMessage; + // CBotManager::OnEvent hook typedef IHookChain IReGameHook_CBotManager_OnEvent; typedef IHookChainRegistry IReGameHookRegistry_CBotManager_OnEvent; @@ -759,7 +763,7 @@ class IReGameHookchains { 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 @@ class IReGameHookchains { 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 { diff --git a/regamedll/public/utlarray.h b/regamedll/public/utlarray.h new file mode 100644 index 000000000..6bdb4136c --- /dev/null +++ b/regamedll/public/utlarray.h @@ -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 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; + +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; +} + +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); +}