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 32d729501..e55afd082 100644
--- a/regamedll/dlls/API/CSPlayer.cpp
+++ b/regamedll/dlls/API/CSPlayer.cpp
@@ -550,10 +550,22 @@ void CCSPlayer::ResetVars()
m_bSpawnProtectionEffects = false;
}
+// Resets all stats
+void CCSPlayer::ResetAllStats()
+{
+ // Resets the kill history for this player
+ for (int i = 0; i < MAX_CLIENTS; i++)
+ {
+ m_iNumKilledByUnanswered[i] = 0;
+ m_bPlayerDominated[i] = false;
+ }
+}
+
void CCSPlayer::OnSpawn()
{
m_bGameForcingRespawn = false;
m_flRespawnPending = 0.0f;
+ m_DamageList.Clear();
}
void CCSPlayer::OnKilled()
@@ -571,3 +583,33 @@ void CCSPlayer::OnKilled()
}
#endif
}
+
+void CCSPlayer::OnConnect()
+{
+ ResetVars();
+ m_iUserID = GETPLAYERUSERID(BasePlayer()->edict());
+}
+
+// Remember this amount of damage that we dealt for stats
+void CCSPlayer::RecordDamage(CBasePlayer *pAttacker, float flDamage, float flFlashDurationTime)
+{
+ if (!pAttacker || !pAttacker->IsPlayer())
+ return;
+
+ int attackerIndex = pAttacker->entindex() - 1;
+ if (attackerIndex < 0 || attackerIndex >= MAX_CLIENTS)
+ return;
+
+ CCSPlayer *pCSAttacker = pAttacker->CSPlayer();
+
+ // Accumulate damage
+ CDamageRecord_t &record = m_DamageList[attackerIndex];
+ if (record.flDamage > 0 && record.userId != pCSAttacker->m_iUserID)
+ record.flDamage = 0; // reset damage if attacker became another client
+
+ record.flDamage += flDamage;
+ record.userId = pCSAttacker->m_iUserID;
+
+ if (flFlashDurationTime > 0)
+ record.flFlashDurationTime = gpGlobals->time + flFlashDurationTime;
+}
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 fefc9f42f..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 : "");
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 8bd05ebb3..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
}
@@ -8818,22 +8845,22 @@ void CBasePlayer::SpawnClientSideCorpse()
char *pModel = GET_KEY_VALUE(infobuffer, "model");
float timeDiff = pev->animtime - gpGlobals->time;
-#ifdef REGAMEDLL_ADD
+#ifdef REGAMEDLL_ADD
if (CGameRules::GetDyingTime() < DEATH_ANIMATION_TIME) // a short time, timeDiff estimates to be small
{
float animDuration = GetDyingAnimationDuration();
- // client receives a negative value
- animDuration *= -1.0;
+ // client receives a negative value
+ animDuration *= -1.0;
if (animDuration < timeDiff) // reasonable way to fix client side unfinished sequence bug
{
- // by some reason, if client receives a value less
- // than "(negative current sequence time) * 100"
+ // by some reason, if client receives a value less
+ // than "(negative current sequence time) * 100"
// animation will play visually awkward
- // at this function call time, player death animation
+ // at this function call time, player death animation
// has already finished so we can safely fake it
- timeDiff = animDuration;
+ timeDiff = animDuration;
}
}
#endif
@@ -8856,7 +8883,7 @@ void CBasePlayer::SpawnClientSideCorpse()
#ifndef REGAMEDLL_FIXES
// already defined in StartDeathCam
m_canSwitchObserverModes = true;
-#endif
+#endif
if (TheTutor)
{
@@ -10123,7 +10150,7 @@ void CBasePlayer::RemoveDefuser()
SetProgressBarTime(0);
m_bIsDefusing = false;
}
-#else
+#else
SetProgressBarTime(0);
#endif
}
@@ -10449,10 +10476,10 @@ bool CBasePlayer::Kill()
{
if (GetObserverMode() != OBS_NONE)
return false;
-
+
if (m_iJoiningState != JOINED)
return false;
-
+
m_LastHitGroup = HITGROUP_GENERIC;
// have the player kill himself
@@ -10461,6 +10488,6 @@ bool CBasePlayer::Kill()
if (CSGameRules()->m_pVIP == this)
CSGameRules()->m_iConsecutiveVIP = 10;
-
+
return true;
}
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 cc8a2c4cc..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)
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 1e0978b16..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;
@@ -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);
+}