diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj
index 58b9d288a..7aafa0709 100644
--- a/CS2Fixes.vcxproj
+++ b/CS2Fixes.vcxproj
@@ -223,6 +223,7 @@
+
diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters
index 689bf91f3..71e88e173 100644
--- a/CS2Fixes.vcxproj.filters
+++ b/CS2Fixes.vcxproj.filters
@@ -298,5 +298,8 @@
Header Files\cs2_sdk\entity
+
+ Header Files\cs2_sdk\entity
+
\ No newline at end of file
diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h
index 8f77ee09d..70125fd23 100644
--- a/src/cs2_sdk/entity/cbaseentity.h
+++ b/src/cs2_sdk/entity/cbaseentity.h
@@ -31,6 +31,8 @@
extern CGameConfig *g_GameConfig;
+class CGameUI;
+
class CGameSceneNode
{
public:
@@ -134,6 +136,7 @@ class Z_CBaseEntity : public CBaseEntity
SCHEMA_FIELD(float, m_flSpeed)
SCHEMA_FIELD(CUtlString, m_sUniqueHammerID);
SCHEMA_FIELD(CUtlSymbolLarge, m_target);
+ SCHEMA_FIELD(CUtlSymbolLarge, m_iGlobalname);
int entindex() { return m_pEntity->m_EHandle.GetEntryIndex(); }
@@ -239,6 +242,23 @@ class Z_CBaseEntity : public CBaseEntity
}
const char* GetName() const { return m_pEntity->m_name.String(); }
+
+ /* Begin Custom Entities Cast */
+
+ [[nodiscard]] CGameUI *AsGameUI()
+ {
+ if (V_strcasecmp(GetClassname(), "logic_case") != 0)
+ return nullptr;
+
+ const auto tag = m_iszPrivateVScripts.IsValid() ? m_iszPrivateVScripts.String() : nullptr;
+
+ if (tag && V_strcasecmp(tag, "game_ui") == 0)
+ return reinterpret_cast(this);
+
+ return nullptr;
+ }
+
+ /* End Custom Entities Cast */
};
class SpawnPoint : public Z_CBaseEntity
diff --git a/src/cs2_sdk/entity/clogiccase.h b/src/cs2_sdk/entity/clogiccase.h
new file mode 100644
index 000000000..8ac85a0d0
--- /dev/null
+++ b/src/cs2_sdk/entity/clogiccase.h
@@ -0,0 +1,42 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2024 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "../schema.h"
+#include "cbaseentity.h"
+
+class CLogicCase : public Z_CBaseEntity
+{
+public:
+ DECLARE_SCHEMA_CLASS(CLogicCase)
+};
+
+class CGameUI : public CLogicCase
+{
+public:
+ static constexpr int SF_GAMEUI_FREEZE_PLAYER = 32;
+ static constexpr int SF_GAMEUI_JUMP_DEACTIVATE = 256;
+
+ // TODO Hide Weapon requires more RE
+ static constexpr int SF_GAMEUI_HIDE_WEAPON = 64;
+
+ // TODO subtick problem
+ static constexpr int SF_GAMEUI_USE_DEACTIVATE = 128;
+};
\ No newline at end of file
diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp
index d2b448267..5694e88f0 100644
--- a/src/cs2fixes.cpp
+++ b/src/cs2fixes.cpp
@@ -153,7 +153,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool
Message( "Starting plugin.\n" );
- SH_ADD_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameFrame), true);
+ SH_ADD_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameFramePost), true);
SH_ADD_HOOK(IServerGameDLL, GameServerSteamAPIActivated, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameServerSteamAPIActivated), false);
SH_ADD_HOOK(IServerGameDLL, GameServerSteamAPIDeactivated, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameServerSteamAPIDeactivated), false);
SH_ADD_HOOK(IServerGameClients, ClientActive, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientActive), true);
@@ -288,7 +288,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool
bool CS2Fixes::Unload(char *error, size_t maxlen)
{
- SH_REMOVE_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameFrame), true);
+ SH_REMOVE_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameFramePost), true);
SH_REMOVE_HOOK(IServerGameDLL, GameServerSteamAPIActivated, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameServerSteamAPIActivated), false);
SH_REMOVE_HOOK(IServerGameDLL, GameServerSteamAPIDeactivated, g_pSource2Server, SH_MEMBER(this, &CS2Fixes::Hook_GameServerSteamAPIDeactivated), false);
SH_REMOVE_HOOK(IServerGameClients, ClientActive, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientActive), true);
@@ -620,7 +620,7 @@ void CS2Fixes::Hook_ClientDisconnect( CPlayerSlot slot, ENetworkDisconnectionRea
g_playerManager->OnClientDisconnect(slot);
}
-void CS2Fixes::Hook_GameFrame( bool simulating, bool bFirstTick, bool bLastTick )
+void CS2Fixes::Hook_GameFramePost(bool simulating, bool bFirstTick, bool bLastTick)
{
VPROF_ENTER_SCOPE(__FUNCTION__);
/**
@@ -666,6 +666,8 @@ void CS2Fixes::Hook_GameFrame( bool simulating, bool bFirstTick, bool bLastTick
if (g_bEnableZR)
CZRRegenTimer::Tick();
+ EntityHandler_OnGameFramePost(simulating, gpGlobals->tickcount);
+
VPROF_EXIT_SCOPE();
}
diff --git a/src/cs2fixes.h b/src/cs2fixes.h
index 8a2b2ce05..c542e4474 100644
--- a/src/cs2fixes.h
+++ b/src/cs2fixes.h
@@ -46,7 +46,7 @@ class CS2Fixes : public ISmmPlugin, public IMetamodListener
bool loadGame,
bool background );
void OnLevelShutdown();
- void Hook_GameFrame( bool simulating, bool bFirstTick, bool bLastTick );
+ void Hook_GameFramePost(bool simulating, bool bFirstTick, bool bLastTick);
void Hook_ClientActive( CPlayerSlot slot, bool bLoadGame, const char *pszName, uint64 xuid );
void Hook_ClientDisconnect( CPlayerSlot slot, ENetworkDisconnectionReason reason, const char *pszName, uint64 xuid, const char *pszNetworkID );
void Hook_ClientPutInServer( CPlayerSlot slot, char const *pszName, int type, uint64 xuid );
diff --git a/src/customio.cpp b/src/customio.cpp
index fde9392ee..33f61663c 100644
--- a/src/customio.cpp
+++ b/src/customio.cpp
@@ -1,7 +1,7 @@
/**
* =============================================================================
* CS2Fixes
- * Copyright (C) 2023 Source2ZE
+ * Copyright (C) 2023-2024 Source2ZE
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
@@ -31,7 +31,7 @@
#include
#include
-extern CGlobalVars *gpGlobals;
+extern CGlobalVars* gpGlobals;
struct AddOutputKey_t
{
@@ -140,8 +140,8 @@ static void AddOutputCustom_MoveType(Z_CBaseEntity* pInstance,
const std::vector& vecArgs)
{
static Vector stopVelocity(0, 0, 0);
- const auto value = clamp(Q_atoi(vecArgs[1].c_str()), MOVETYPE_NONE, MOVETYPE_LAST);
- const auto type = static_cast(value);
+ const auto value = clamp(Q_atoi(vecArgs[1].c_str()), MOVETYPE_NONE, MOVETYPE_LAST);
+ const auto type = static_cast(value);
pInstance->SetMoveType(type);
@@ -185,14 +185,14 @@ static void AddOutputCustom_BaseVelocity(Z_CBaseEntity* pInstan
#endif
}
-static void AddOutputCustom_AbsVelocity(Z_CBaseEntity *pInstance,
- CEntityInstance *pActivator,
- CEntityInstance *pCaller,
- const std::vector &vecArgs)
+static void AddOutputCustom_AbsVelocity(Z_CBaseEntity* pInstance,
+ CEntityInstance* pActivator,
+ CEntityInstance* pCaller,
+ const std::vector& vecArgs)
{
Vector velocity(clamp(Q_atof(vecArgs[1].c_str()), -4096.f, 4096.f),
- clamp(Q_atof(vecArgs[2].c_str()), -4096.f, 4096.f),
- clamp(Q_atof(vecArgs[3].c_str()), -4096.f, 4096.f));
+ clamp(Q_atof(vecArgs[2].c_str()), -4096.f, 4096.f),
+ clamp(Q_atof(vecArgs[3].c_str()), -4096.f, 4096.f));
pInstance->Teleport(nullptr, nullptr, &velocity);
@@ -201,7 +201,7 @@ static void AddOutputCustom_AbsVelocity(Z_CBaseEntity *pInstance,
#endif
}
-static void AddOutputCustom_Target(Z_CBaseEntity* pInstance,
+static void AddOutputCustom_Target(Z_CBaseEntity* pInstance,
CEntityInstance* pActivator,
CEntityInstance* pCaller,
const std::vector& vecArgs)
@@ -242,7 +242,7 @@ static void AddOutputCustom_Force(Z_CBaseEntity* pInstance,
CEntityInstance* pCaller,
const std::vector& vecArgs)
{
- const auto value = Q_atof(vecArgs[1].c_str());
+ const auto value = Q_atof(vecArgs[1].c_str());
const auto pEntity = reinterpret_cast(pInstance);
if (V_strcasecmp(pEntity->GetClassname(), "phys_thruster") == 0)
{
@@ -254,24 +254,24 @@ static void AddOutputCustom_Force(Z_CBaseEntity* pInstance,
}
}
-static void AddOutputCustom_Gravity(Z_CBaseEntity *pInstance,
- CEntityInstance *pActivator,
- CEntityInstance *pCaller,
- const std::vector &vecArgs)
+static void AddOutputCustom_Gravity(Z_CBaseEntity* pInstance,
+ CEntityInstance* pActivator,
+ CEntityInstance* pCaller,
+ const std::vector& vecArgs)
{
const auto value = Q_atof(vecArgs[1].c_str());
pInstance->m_flGravityScale = value;
#ifdef _DEBUG
- Message("Set gravity to %f for %s\n", value, pInstance->GetName());
+ Message("Set gravity to %f for %s\n", value, pInstance->GetName());
#endif
}
-static void AddOutputCustom_Timescale(Z_CBaseEntity *pInstance,
- CEntityInstance *pActivator,
- CEntityInstance *pCaller,
- const std::vector &vecArgs)
+static void AddOutputCustom_Timescale(Z_CBaseEntity* pInstance,
+ CEntityInstance* pActivator,
+ CEntityInstance* pCaller,
+ const std::vector& vecArgs)
{
const auto value = Q_atof(vecArgs[1].c_str());
@@ -282,10 +282,10 @@ static void AddOutputCustom_Timescale(Z_CBaseEntity *pInstance,
#endif
}
-static void AddOutputCustom_Friction(Z_CBaseEntity *pInstance,
- CEntityInstance *pActivator,
- CEntityInstance *pCaller,
- const std::vector &vecArgs)
+static void AddOutputCustom_Friction(Z_CBaseEntity* pInstance,
+ CEntityInstance* pActivator,
+ CEntityInstance* pCaller,
+ const std::vector& vecArgs)
{
const auto value = Q_atof(vecArgs[1].c_str());
@@ -296,16 +296,16 @@ static void AddOutputCustom_Friction(Z_CBaseEntity *pInstance,
#endif
}
-static void AddOutputCustom_Speed(Z_CBaseEntity *pInstance,
- CEntityInstance *pActivator,
- CEntityInstance *pCaller,
- const std::vector &vecArgs)
+static void AddOutputCustom_Speed(Z_CBaseEntity* pInstance,
+ CEntityInstance* pActivator,
+ CEntityInstance* pCaller,
+ const std::vector& vecArgs)
{
if (!pInstance->IsPawn())
return;
- CCSPlayerPawn *pPawn = (CCSPlayerPawn*)pInstance;
- CCSPlayerController *pController = (CCSPlayerController*)pPawn->GetOriginalController();
+ const auto pPawn = reinterpret_cast(pInstance);
+ const auto pController = pPawn->GetOriginalController();
if (!pController || !pController->IsConnected())
return;
@@ -319,15 +319,15 @@ static void AddOutputCustom_Speed(Z_CBaseEntity *pInstance,
#endif
}
-static void AddOutputCustom_RunSpeed(Z_CBaseEntity *pInstance,
- CEntityInstance *pActivator,
- CEntityInstance *pCaller,
- const std::vector &vecArgs)
+static void AddOutputCustom_RunSpeed(Z_CBaseEntity* pInstance,
+ CEntityInstance* pActivator,
+ CEntityInstance* pCaller,
+ const std::vector& vecArgs)
{
if (!pInstance->IsPawn())
return;
- CCSPlayerPawn *pPawn = reinterpret_cast(pInstance);
+ const auto pPawn = reinterpret_cast(pInstance);
const auto value = Q_atof(vecArgs[1].c_str());
@@ -397,7 +397,6 @@ bool CustomIO_HandleInput(CEntityInstance* pInstance,
return false;
}
-
std::string g_sBurnParticle = "particles/burning_fx/burning_character_b.vpcf";
FAKE_STRING_CVAR(cs2f_burn_particle, "The particle to use for burning entities", g_sBurnParticle, false);
@@ -410,26 +409,26 @@ FAKE_FLOAT_CVAR(cs2f_burn_slowdown, "The slowdown of each burn damage tick as a
float g_flBurnInterval = 0.3f;
FAKE_FLOAT_CVAR(cs2f_burn_interval, "The interval between burn damage ticks", g_flBurnInterval, 0.3f, false);
-bool IgnitePawn(CCSPlayerPawn *pPawn, float flDuration, Z_CBaseEntity *pInflictor, Z_CBaseEntity *pAttacker, Z_CBaseEntity *pAbility, DamageTypes_t nDamageType)
+bool IgnitePawn(CCSPlayerPawn* pPawn, float flDuration, Z_CBaseEntity* pInflictor, Z_CBaseEntity* pAttacker, Z_CBaseEntity* pAbility, DamageTypes_t nDamageType)
{
- CParticleSystem *pParticleEnt = (CParticleSystem*)pPawn->m_hEffectEntity().Get();
+ auto pParticleEnt = reinterpret_cast(pPawn->m_hEffectEntity().Get());
// This guy is already burning, don't ignite again
if (pParticleEnt)
- {
+ {
// Override the end time instead of just adding to it so players who get a ton of ignite inputs don't burn forever
pParticleEnt->m_flDissolveStartTime = gpGlobals->curtime + flDuration;
return true;
}
- Vector vecOrigin = pPawn->GetAbsOrigin();
+ const auto vecOrigin = pPawn->GetAbsOrigin();
- pParticleEnt = (CParticleSystem *)CreateEntityByName("info_particle_system");
+ pParticleEnt = reinterpret_cast(CreateEntityByName("info_particle_system"));
pParticleEnt->m_bStartActive(true);
pParticleEnt->m_iszEffectName(g_sBurnParticle.c_str());
pParticleEnt->m_hControlPointEnts[0] = pPawn;
- pParticleEnt->m_flDissolveStartTime = gpGlobals->curtime + flDuration; // Store the end time in the particle itself so we can increment if needed
+ pParticleEnt->m_flDissolveStartTime = gpGlobals->curtime + flDuration; // Store the end time in the particle itself so we can increment if needed
pParticleEnt->Teleport(&vecOrigin, nullptr, nullptr);
pParticleEnt->DispatchSpawn();
@@ -443,14 +442,13 @@ bool IgnitePawn(CCSPlayerPawn *pPawn, float flDuration, Z_CBaseEntity *pInflicto
CHandle hAttacker(pAttacker);
CHandle hAbility(pAbility);
- new CTimer(0.f, false, [hPawn, hInflictor, hAttacker, hAbility, nDamageType]()
- {
- CCSPlayerPawn *pPawn = hPawn.Get();
+ new CTimer(0.f, false, [hPawn, hInflictor, hAttacker, hAbility, nDamageType]() {
+ CCSPlayerPawn* pPawn = hPawn.Get();
if (!pPawn)
return -1.f;
- CParticleSystem *pParticleEnt = (CParticleSystem*)pPawn->m_hEffectEntity().Get();
+ const auto pParticleEnt = reinterpret_cast(pPawn->m_hEffectEntity().Get());
if (!pParticleEnt)
return -1.f;
@@ -475,7 +473,7 @@ bool IgnitePawn(CCSPlayerPawn *pPawn, float flDuration, Z_CBaseEntity *pInflicto
// Damage doesn't apply if the inflictor is null
if (!hInflictor.Get())
info.m_hInflictor.Set(hAttacker);
-
+
pPawn->TakeDamage(info);
pPawn->m_flVelocityModifier = g_flBurnSlowdown;
diff --git a/src/detours.cpp b/src/detours.cpp
index 3b70cb6ce..3e054fdd3 100644
--- a/src/detours.cpp
+++ b/src/detours.cpp
@@ -369,17 +369,17 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym
ZR_Detour_CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value, nOutputID);
// Handle KeyValue(s)
- if (!V_strnicmp(pInputName->String(), "KeyValue", 8))
- {
- if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString)
- {
- // always const char*, even if it's FIELD_STRING (that is bug string from lua 'EntFire')
- return CustomIO_HandleInput(pThis->m_pInstance, value->m_pszString, pActivator, pCaller);
- }
- Message("Invalid value type for input %s\n", pInputName->String());
- return false;
- }
- else if (!V_strnicmp(pInputName->String(), "IgniteL", 7)) // Override IgniteLifetime
+ if (!V_strnicmp(pInputName->String(), "KeyValue", 8))
+ {
+ if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString)
+ {
+ // always const char*, even if it's FIELD_STRING (that is bug string from lua 'EntFire')
+ return CustomIO_HandleInput(pThis->m_pInstance, value->m_pszString, pActivator, pCaller);
+ }
+ Message("Invalid value type for input %s\n", pInputName->String());
+ return false;
+ }
+ if (!V_strnicmp(pInputName->String(), "IgniteL", 7)) // Override IgniteLifetime
{
float flDuration = 0.f;
@@ -388,13 +388,20 @@ bool FASTCALL Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSym
else
flDuration = value->m_float;
- CCSPlayerPawn *pPawn = (CCSPlayerPawn*)pThis->m_pInstance;
+ CCSPlayerPawn *pPawn = reinterpret_cast(pThis->m_pInstance);
if (pPawn->IsPawn() && IgnitePawn(pPawn, flDuration, pPawn, pPawn))
return true;
}
+ else if (const auto pGameUI = reinterpret_cast(pThis->m_pInstance)->AsGameUI())
+ {
+ if (!V_strcasecmp(pInputName->String(), "Activate"))
+ return CGameUIHandler::OnActivate(pGameUI, reinterpret_cast(pActivator));
+ if (!V_strcasecmp(pInputName->String(), "Deactivate"))
+ return CGameUIHandler::OnDeactivate(pGameUI, reinterpret_cast(pActivator));
+ }
- return CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value, nOutputID);
+ return CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value, nOutputID);
}
bool g_bBlockNavLookup = false;
diff --git a/src/entities.cpp b/src/entities.cpp
index 967ca9b4f..426ba0fc2 100644
--- a/src/entities.cpp
+++ b/src/entities.cpp
@@ -19,9 +19,14 @@
#include "entities.h"
+#include "ctimer.h"
#include "entity.h"
+#include "entity/cbaseplayercontroller.h"
#include "entity/ccsplayerpawn.h"
#include "entity/cgameplayerequip.h"
+#include "entity/clogiccase.h"
+
+// #define ENTITY_HANDLER_ASSERTION
class InputData_t
{
@@ -32,9 +37,9 @@ class InputData_t
int nOutputID;
};
-bool StripPlayer(CCSPlayerPawn* pPawn)
+inline bool StripPlayer(CCSPlayerPawn* pPawn)
{
- const auto pItemServices = pPawn->m_pItemServices();
+ const auto pItemServices = pPawn->m_pItemServices();
if (!pItemServices)
return false;
@@ -44,6 +49,34 @@ bool StripPlayer(CCSPlayerPawn* pPawn)
return true;
}
+// Must be called in GameFramePre
+inline void DelayInput(Z_CBaseEntity* pCaller, const char* input, const char* param = "")
+{
+ const auto eh = pCaller->GetHandle();
+
+ new CTimer(0.f, false, [eh, input, param]() {
+ if (const auto entity = reinterpret_cast(eh.Get()))
+ entity->AcceptInput(input, param, nullptr, entity);
+
+ return -1.f;
+ });
+}
+
+// Must be called in GameFramePre
+inline void DelayInput(Z_CBaseEntity* pCaller, Z_CBaseEntity* pActivator, const char* input, const char* param = "")
+{
+ const auto eh = pCaller->GetHandle();
+ const auto ph = pActivator->GetHandle();
+
+ new CTimer(0.f, false, [eh, ph, input, param]() {
+ const auto player = reinterpret_cast(ph.Get());
+ if (const auto entity = reinterpret_cast(eh.Get()))
+ entity->AcceptInput(input, param, player, entity);
+
+ return -1.f;
+ });
+}
+
namespace CGamePlayerEquipHandler
{
@@ -116,4 +149,239 @@ void TriggerForActivatedPlayer(CGamePlayerEquip* pEntity, InputData_t* pInput)
pItemServices->GiveNamedItem(pszWeapon);
}
-} // namespace CGamePlayerEquipHandler
\ No newline at end of file
+} // namespace CGamePlayerEquipHandler
+
+namespace CGameUIHandler
+{
+constexpr uint64 BAD_BUTTONS = ~0;
+
+struct CGameUIState
+{
+ CHandle m_pPlayer;
+ uint64 m_nButtonState;
+
+ CGameUIState() :
+ m_pPlayer(CBaseHandle()), m_nButtonState(BAD_BUTTONS) {}
+ CGameUIState(const CGameUIState& other) = delete;
+
+ CGameUIState(CCSPlayerPawn* pawn, uint64 buttons) :
+ m_pPlayer(pawn), m_nButtonState(buttons) {}
+
+ [[nodiscard]] CCSPlayerPawn* GetPlayer() const { return m_pPlayer.Get(); }
+
+ void UpdateButtons(uint64 buttons) { m_nButtonState = buttons; }
+};
+
+static std::unordered_map s_repository;
+
+inline uint64 GetButtons(CPlayer_MovementServices* pMovement)
+{
+ const auto buttonStates = pMovement->m_nButtons().m_pButtonStates();
+ const auto buttons = buttonStates[0];
+ return buttons;
+}
+
+inline uint64 GameUIThink(CGameUI* pEntity, CCSPlayerPawn* pPlayer, uint32 lastButtons)
+{
+ const auto pMovement = pPlayer->m_pMovementServices();
+ if (!pMovement)
+ return BAD_BUTTONS;
+
+ const auto spawnFlags = pEntity->m_spawnflags();
+ const auto buttons = GetButtons(pMovement);
+
+ if ((spawnFlags & CGameUI::SF_GAMEUI_JUMP_DEACTIVATE) != 0 && (buttons & IN_JUMP) != 0)
+ {
+ DelayInput(pEntity, pPlayer, "Deactivate");
+ return BAD_BUTTONS;
+ }
+
+ const auto nButtonsChanged = buttons ^ lastButtons;
+
+ // W
+ if ((nButtonsChanged & IN_FORWARD) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_FORWARD) != 0 ? "UnpressedForward" : "PressedForward", pPlayer, pEntity);
+ }
+
+ // A
+ if ((nButtonsChanged & IN_MOVELEFT) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_MOVELEFT) != 0 ? "UnpressedMoveLeft" : "PressedMoveLeft", pPlayer, pEntity);
+ }
+
+ // S
+ if ((nButtonsChanged & IN_BACK) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_BACK) != 0 ? "UnpressedBack" : "PressedBack", pPlayer, pEntity);
+ }
+
+ // D
+ if ((nButtonsChanged & IN_MOVERIGHT) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_MOVERIGHT) != 0 ? "UnpressedMoveRight" : "PressedMoveRight", pPlayer, pEntity);
+ }
+
+ // Attack
+ if ((nButtonsChanged & IN_ATTACK) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_ATTACK) != 0 ? "UnpressedAttack" : "PressedAttack", pPlayer, pEntity);
+ }
+
+ // Attack2
+ if ((nButtonsChanged & IN_ATTACK2) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_ATTACK2) != 0 ? "UnpressedAttack2" : "PressedAttack2", pPlayer, pEntity);
+ }
+
+ // Speed
+ if ((nButtonsChanged & IN_SPEED) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_SPEED) != 0 ? "UnpressedSpeed" : "PressedSpeed", pPlayer, pEntity);
+ }
+
+ // Duck
+ if ((nButtonsChanged & IN_DUCK) != 0)
+ {
+ pEntity->AcceptInput("InValue", (lastButtons & IN_DUCK) != 0 ? "UnpressedDuck" : "PressedDuck", pPlayer, pEntity);
+ }
+
+ return buttons;
+}
+
+// [Kxnrl]: Must be called on game frame pre, and timer done in post!
+void RunThink(int tick)
+{
+ // validate
+ for (auto it = s_repository.begin(); it != s_repository.end();)
+ {
+ const auto entity = CHandle(it->first).Get();
+ if (!entity)
+ {
+ it = s_repository.erase(it);
+#ifdef ENTITY_HANDLER_ASSERTION
+ Message("Remove Entity %d due to invalid.\n", CBaseHandle(it->first).GetEntryIndex());
+#endif
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ // think every 4 tick
+ if ((tick & 4) != 0)
+ return;
+
+ for (auto& [key, state] : s_repository)
+ {
+ const auto entity = CHandle(key).Get();
+ const auto player = state.GetPlayer();
+
+ if (!player || !player->IsPawn())
+ {
+ DelayInput(entity, "Deactivate");
+#ifdef ENTITY_HANDLER_ASSERTION
+ Message("Deactivate Entity %d due to invalid player.\n", entity->entindex());
+#endif
+ continue;
+ }
+
+ if (!player->IsAlive())
+ {
+ DelayInput(entity, player, "Deactivate");
+#ifdef ENTITY_HANDLER_ASSERTION
+ Message("Deactivate Entity %d due to player dead.\n", entity->entindex());
+#endif
+ continue;
+ }
+
+ const auto newButtons = GameUIThink(entity, player, state.m_nButtonState);
+
+ if (newButtons != BAD_BUTTONS)
+ {
+ state.UpdateButtons(newButtons);
+ }
+ }
+}
+
+bool OnActivate(CGameUI* pEntity, Z_CBaseEntity* pActivator)
+{
+ if (!pActivator || !pActivator->IsPawn())
+ return false;
+
+ const auto pPlayer = reinterpret_cast(pActivator);
+
+ const auto pMovement = pPlayer->m_pMovementServices();
+ if (!pMovement)
+ return false;
+
+ if ((pEntity->m_spawnflags() & CGameUI::SF_GAMEUI_FREEZE_PLAYER) != 0)
+ pPlayer->m_fFlags(pPlayer->m_fFlags() | FL_ATCONTROLS);
+
+ const CBaseHandle handle = pEntity->GetHandle();
+ const auto key = static_cast(handle.ToInt());
+
+ DelayInput(pEntity, pPlayer, "InValue", "PlayerOn");
+
+ s_repository[key] = CGameUIState(pPlayer, GetButtons(pMovement) & ~IN_USE);
+
+#ifdef ENTITY_HANDLER_ASSERTION
+ Message("Activate Entity %d<%u> -> %s\n", pEntity->entindex(), key, pPlayer->GetController()->GetPlayerName());
+#endif
+
+ return true;
+}
+
+bool OnDeactivate(CGameUI* pEntity, Z_CBaseEntity* pActivator)
+{
+ const CBaseHandle handle = CHandle(pEntity);
+ const auto key = static_cast(handle.ToInt());
+ const auto it = s_repository.find(key);
+
+ if (it == s_repository.end())
+ {
+#ifdef ENTITY_HANDLER_ASSERTION
+ Message("Deactivate Entity %d -> but does not exists <%u>\n", pEntity->entindex(), key);
+#endif
+ return false;
+ }
+
+ if (const auto pPlayer = it->second.GetPlayer())
+ {
+ if ((pEntity->m_spawnflags() & CGameUI::SF_GAMEUI_FREEZE_PLAYER) != 0)
+ pPlayer->m_fFlags(pPlayer->m_fFlags() & ~FL_ATCONTROLS);
+
+ DelayInput(pEntity, pPlayer, "InValue", "PlayerOff");
+
+#ifdef ENTITY_HANDLER_ASSERTION
+ Message("Deactivate Entity %d -> %s\n", pEntity->entindex(), pPlayer->GetController()->GetPlayerName());
+#endif
+ }
+ else
+ {
+#ifdef ENTITY_HANDLER_ASSERTION
+ Message("Deactivate Entity %d -> nullptr\n", pEntity->entindex());
+#endif
+ }
+
+ s_repository.erase(it);
+
+ return true;
+}
+
+} // namespace CGameUIHandler
+
+void EntityHandler_OnGameFramePre(bool simulate, int tick)
+{
+ if (!simulate)
+ return;
+
+ CGameUIHandler::RunThink(tick);
+}
+
+void EntityHandler_OnGameFramePost(bool simulate, int tick)
+{
+ if (!simulate)
+ return;
+}
diff --git a/src/entities.h b/src/entities.h
index 6b1e1f4ea..2a22424fe 100644
--- a/src/entities.h
+++ b/src/entities.h
@@ -21,6 +21,8 @@
class InputData_t;
class CGamePlayerEquip;
+class Z_CBaseEntity;
+class CGameUI;
namespace CGamePlayerEquipHandler
{
@@ -28,3 +30,13 @@ void Use(CGamePlayerEquip* pEntity, InputData_t* pInput);
void TriggerForAllPlayers(CGamePlayerEquip* pEntity, InputData_t* pInput);
void TriggerForActivatedPlayer(CGamePlayerEquip* pEntity, InputData_t* pInput);
} // namespace CGamePlayerEquipHandler
+
+namespace CGameUIHandler
+{
+bool OnActivate(CGameUI* pEntity, Z_CBaseEntity* pActivator);
+bool OnDeactivate(CGameUI* pEntity, Z_CBaseEntity* pActivator);
+void RunThink(int tick);
+} // namespace CGameUIHandler
+
+void EntityHandler_OnGameFramePre(bool simulate, int tick);
+void EntityHandler_OnGameFramePost(bool simulate, int tick);
\ No newline at end of file
diff --git a/src/gamesystem.cpp b/src/gamesystem.cpp
index 065d5f403..9a284a6b7 100644
--- a/src/gamesystem.cpp
+++ b/src/gamesystem.cpp
@@ -23,9 +23,11 @@
#include "gamesystem.h"
#include "zombiereborn.h"
#include "adminsystem.h"
+#include "entities.h"
#include "tier0/memdbgon.h"
+extern CGlobalVars* gpGlobals;
extern CGameConfig *g_GameConfig;
CBaseGameSystemFactory **CBaseGameSystemFactory::sm_pFirst = nullptr;
@@ -83,6 +85,7 @@ GS_EVENT_MEMBER(CGameSystem, ServerPreEntityThink)
{
// This could've gone into CS2Fixes::Hook_GameFrame but I've kept it here as an example
g_playerManager->FlashLightThink();
+ EntityHandler_OnGameFramePre(gpGlobals->m_bInSimulation, gpGlobals->tickcount);
}
// Called every frame after entities think