Initial commit

This commit is contained in:
Michal 2026-01-11 18:57:42 +04:00 • committed by GitHub
commit 3dd53cf457
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
105 changed files with 35670 additions and 0 deletions

86
src/EntityData.h Normal file
View file

@ -0,0 +1,86 @@
//
// Created by Michal Přikryl on 15.09.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <random>
#include <string>
#include "schema/CCSPlayerController.h"
namespace TemplatePlugin {
enum class EntityType {
None = 0,
};
template<typename T>
struct EntityCustomData {
T *Value = nullptr;
explicit EntityCustomData(T *v = nullptr) : Value(v) {
}
};
enum class ActionType_t {
None = 0,
EPush = 1,
EPushPower = 2,
IgnoreKnife = 3,
FToPickup = 4,
NoDoorFix = 5,
};
struct EntityData_t {
EntityType Type = EntityType::None;
std::function<void(CHandle<CEntityInstance>, CHandle<CCSPlayerController>)> OnTouch;
std::function<void(CHandle<CEntityInstance>, CHandle<CCSPlayerController>)> OnUse;
std::unique_ptr<EntityCustomData<void> > CustomData;
CHandle<CCSPlayerController> GrabHolder = nullptr;
float GrabDistance = 0.0f;
CHandle<CBaseEntity> TouchedTo = nullptr;
int NextSoundBlockTick = 0;
ActionType_t ActionType = ActionType_t::None;
bool SkinInitialized = false;
void Init() {
Type = EntityType::None;
OnTouch = nullptr;
OnUse = nullptr;
CustomData.reset();
GrabHolder = nullptr;
GrabDistance = 0.0f;
TouchedTo = nullptr;
NextSoundBlockTick = 0;
ActionType = ActionType_t::None;
SkinInitialized = false;
}
};
struct ActionEntity {
std::string HammerId;
int Number = 0;
ActionEntity(const std::string &hammerId, int number)
: HammerId(hammerId), Number(number) {
}
};
struct HandleHasher {
size_t operator()(const CHandle<CBaseEntity> &h) const noexcept {
return std::hash<int>()(h.GetEntryIndex());
}
};
struct HandleEqual {
bool operator()(const CHandle<CBaseEntity> &a, const CHandle<CBaseEntity> &b) const noexcept {
return a.GetEntryIndex() == b.GetEntryIndex();
}
};
inline std::unordered_map<CHandle<CBaseEntity>, EntityData_t, HandleHasher, HandleEqual> EntityData;
}

56
src/PlayersData.cpp Normal file
View file

@ -0,0 +1,56 @@
//
// Created by Michal Přikryl on 12.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "PlayersData.h"
namespace TemplatePlugin {
bool PlayerDataHandler::Add(uint64_t steamid, const PlayerData &data) {
if (steamid == 0 || players_.contains(steamid)) return false;
players_[steamid] = data;
return true;
}
PlayerData* PlayerDataHandler::TryGet(uint64_t steamid) {
auto it = players_.find(steamid);
return it != players_.end() ? &it->second : nullptr;
}
bool PlayerDataHandler::Remove(uint64_t steamid) {
return players_.erase(steamid) > 0;
}
PlayerData& PlayerDataHandler::Ensure(uint64_t steamid) {
return players_[steamid];
}
bool PlayerDataHandler::Add(CCSPlayerController* player, const PlayerData& data) {
if (!player) return false;
uint64_t steamid = player->GetSteamID();
if (steamid == 0) return false;
return Add(steamid, data);
}
PlayerData* PlayerDataHandler::TryGet(CCSPlayerController* player) {
if (!player) return nullptr;
uint64_t steamid = player->GetSteamID();
return steamid ? TryGet(steamid) : nullptr;
}
bool PlayerDataHandler::Remove(CCSPlayerController* player) {
if (!player) return false;
uint64_t steamid = player->GetSteamID();
return steamid ? Remove(steamid) : false;
}
PlayerData& PlayerDataHandler::Ensure(CCSPlayerController* player) {
static PlayerData dummy{};
if (!player) return dummy;
uint64_t steamid = player->GetSteamID();
return steamid ? Ensure(steamid) : dummy;
}
void PlayerDataHandler::ClearAll() {
players_.clear();
}
}

30
src/PlayersData.h Normal file
View file

@ -0,0 +1,30 @@
//
// Created by Michal Přikryl on 12.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <unordered_map>
#include "schema/CCSPlayerController.h"
namespace TemplatePlugin {
struct PlayerData {
};
class PlayerDataHandler {
public:
static bool Add(uint64_t steamid, const PlayerData &data = {});
static PlayerData* TryGet(uint64_t steamid);
static bool Remove(uint64_t steamid);
static PlayerData& Ensure(uint64_t steamid);
static bool Add(CCSPlayerController* player, const PlayerData& data = {});
static PlayerData* TryGet(CCSPlayerController* player);
static bool Remove(CCSPlayerController* player);
static PlayerData& Ensure(CCSPlayerController* player);
static void ClearAll();
private:
static inline std::unordered_map<uint64_t, PlayerData> players_;
};
}

139
src/RayTrace.cpp Normal file
View file

@ -0,0 +1,139 @@
//
// Created by Michal Přikryl on 30.08.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "RayTrace.h"
#include <Shared.h>
#include "schema/CBaseModelEntity.h"
#include "vectorextends.h"
#include "dynlibutils/memaddr.h"
#include "dynlibutils/module.h"
#include "colors.h"
#include "log.h"
namespace TemplatePlugin::RayTrace
{
using TraceShapeFn = bool(*)(void* pThis,
Ray_t& ray,
Vector& start,
Vector& end,
CTraceFilter* filter,
CGameTrace* trace);
static TraceShapeFn s_TraceShape = nullptr;
bool Initialize()
{
void* pCNavPhysicsInterfaceVTable =
DynLibUtils::CModule(shared::g_pServer).GetVirtualTableByName("CNavPhysicsInterface");
if (!pCNavPhysicsInterfaceVTable)
{
FP_ERROR("Failed to find CNavPhysicsInterface vtable!");
return false;
}
auto table = static_cast<void**>(pCNavPhysicsInterfaceVTable);
s_TraceShape = reinterpret_cast<TraceShapeFn>(table[shared::g_pGameConfig->GetOffset("CNavPhysicsInterface_TraceShape")]);
return true;
}
static void DrawBeam(const Vector& start, const Vector& end, const Color& color)
{
CBeam* beam = UTIL_CreateEntityByName<CBeam>("env_beam");
if (!beam) return;
beam->m_clrRender().SetColor(color.r(), color.g(), color.b(), color.a());
beam->m_fWidth() = 1.5f;
beam->m_nRenderMode() = kRenderGlow;
beam->m_nRenderFX() = kRenderFxNone;
beam->Teleport(&start, &VectorExtends::RotationZero, &VectorExtends::VectorZero);
beam->m_vecEndPos() = end;
beam->DispatchSpawn();
}
std::optional<TraceResult> TraceShapeEx(
const Vector& start,
const Vector& end,
CTraceFilter& filterInc,
Ray_t rayInc)
{
if (!s_TraceShape) return std::nullopt;
CGameTrace tr{};
Vector startCopy = start;
Vector endCopy = end;
s_TraceShape(nullptr, rayInc, startCopy, endCopy,
&filterInc, &tr);
TraceResult r{};
r.EndPos = tr.m_vEndPos;
r.HitEntity = tr.m_pEnt;
r.Fraction = tr.m_flFraction;
r.AllSolid = tr.m_bStartInSolid;
r.Normal = tr.m_vHitNormal;
return r;
}
std::optional<TraceResult> TraceShape(
const Vector& origin,
const QAngle& viewangles,
CBaseEntity* ignorePlayer,
const TraceOptions* opts)
{
Vector forward;
AngleVectors(viewangles, &forward);
Vector endOrigin{
origin.x + forward.x * 8192.f,
origin.y + forward.y * 8192.f,
origin.z + forward.z * 8192.f
};
CTraceFilterEx filter = ignorePlayer ? CTraceFilterEx(ignorePlayer) : CTraceFilterEx();
if (opts)
{
if (opts->InteractsWith) filter.m_nInteractsWith = static_cast<uint64_t>(*opts->InteractsWith);
if (opts->InteractsExclude) filter.m_nInteractsExclude = static_cast<uint64_t>(*opts->InteractsExclude);
}
Ray_t ray;
auto res = TraceShapeEx(origin, endOrigin, filter, ray);
if (opts && opts->DrawBeam)
{
Color col = res.has_value() ? colors::Red().ToValveColor() : colors::Green().ToValveColor();
DrawBeam(origin, res ? res->EndPos : endOrigin, col);
}
return res;
}
std::optional<TraceResult> TraceEndShape(
const Vector& origin,
const Vector& endOrigin,
CBaseEntity* ignorePlayer,
const TraceOptions* opts)
{
CTraceFilterEx filter = ignorePlayer ? CTraceFilterEx(ignorePlayer) : CTraceFilterEx();
if (opts)
{
if (opts->InteractsWith) filter.m_nInteractsWith = static_cast<uint64_t>(*opts->InteractsWith);
if (opts->InteractsExclude) filter.m_nInteractsExclude = static_cast<uint64_t>(*opts->InteractsExclude);
}
Ray_t ray;
auto res = TraceShapeEx(origin, endOrigin, filter, ray);
if (opts && opts->DrawBeam)
{
Color col = res.has_value() ? colors::Red().ToValveColor() : colors::Green().ToValveColor();
DrawBeam(origin, res ? res->EndPos : endOrigin, col);
}
return res;
}
}

126
src/RayTrace.h Normal file
View file

@ -0,0 +1,126 @@
//
// Created by Michal Přikryl on 30.08.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <optional>
#include <cstdint>
#include "vector.h"
#include "gametrace.h"
#include "trace.h"
#include "cmodel.h"
#include "schema/CBaseEntity.h"
namespace TemplatePlugin::RayTrace
{
enum class InteractionLayers : uint64_t
{
Solid = 0x1,
Hitboxes = 0x2,
Trigger = 0x4,
Sky = 0x8,
PlayerClip = 0x10,
NPCClip = 0x20,
BlockLOS = 0x40,
BlockLight = 0x80,
Ladder = 0x100,
Pickup = 0x200,
BlockSound = 0x400,
NoDraw = 0x800,
Window = 0x1000,
PassBullets = 0x2000,
WorldGeometry = 0x4000,
Water = 0x8000,
Slime = 0x10000,
TouchAll = 0x20000,
Player = 0x40000,
NPC = 0x80000,
Debris = 0x100000,
Physics_Prop = 0x200000,
NavIgnore = 0x400000,
NavLocalIgnore = 0x800000,
PostProcessingVolume = 0x1000000,
UnusedLayer3 = 0x2000000,
CarriedObject = 0x4000000,
PushAway = 0x8000000,
ServerEntityOnClient = 0x10000000,
CarriedWeapon = 0x20000000,
StaticLevel = 0x40000000,
csgo_team1 = 0x80000000,
csgo_team2 = 0x100000000,
csgo_grenadeclip = 0x200000000,
csgo_droneclip = 0x400000000,
csgo_moveable = 0x800000000,
csgo_opaque = 0x1000000000,
csgo_monster = 0x2000000000,
csgo_thrown_grenade = 0x8000000000,
FUNPLAY_IGNORE_PLAYER = (0x8000000000ull << 1)
};
inline InteractionLayers operator|(InteractionLayers a, InteractionLayers b)
{
return static_cast<InteractionLayers>(
static_cast<uint64_t>(a) | static_cast<uint64_t>(b)
);
}
inline InteractionLayers& operator|=(InteractionLayers& a, InteractionLayers b)
{
a = a | b;
return a;
}
class CTraceFilterEx : public CTraceFilter
{
public:
explicit CTraceFilterEx(CBaseEntity* entityToIgnore)
: CTraceFilter(static_cast<CEntityInstance*>(entityToIgnore),
entityToIgnore ? entityToIgnore->m_hOwnerEntity.Get() : nullptr,
entityToIgnore ? entityToIgnore->m_pCollision()->m_collisionAttribute().m_nHierarchyId() : static_cast<uint16>(0xFFFFFFFF),
0x2c3011,
COLLISION_GROUP_DEFAULT, true)
{
}
CTraceFilterEx() : CTraceFilter(0x2c3011, COLLISION_GROUP_DEFAULT, true)
{
}
};
struct TraceOptions
{
std::optional<InteractionLayers> InteractsWith{static_cast<InteractionLayers>(0x2c3011)};
std::optional<InteractionLayers> InteractsExclude{};
bool DrawBeam{false};
};
struct TraceResult
{
Vector EndPos{};
CEntityInstance* HitEntity{};
float Fraction{};
bool AllSolid{};
Vector Normal{};
};
bool Initialize();
std::optional<TraceResult> TraceShape(
const Vector& origin,
const QAngle& viewangles,
CBaseEntity* ignorePlayer = nullptr,
const TraceOptions* opts = nullptr);
std::optional<TraceResult> TraceEndShape(
const Vector& origin,
const Vector& endOrigin,
CBaseEntity* ignorePlayer = nullptr,
const TraceOptions* opts = nullptr);
std::optional<TraceResult> TraceShapeEx(
const Vector& vecStart,
const Vector& vecEnd,
CTraceFilter& filterInc,
Ray_t rayInc);
}

62
src/Shared.cpp Normal file
View file

@ -0,0 +1,62 @@
//
// Created by Michal Přikryl on 20.09.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "Shared.h"
#include <icvar.h>
#include <iserver.h>
#include <schemasystem.h>
#include <sourcehook/sourcehook.h>
#include <sourcehook/sourcehook_impl.h>
namespace TemplatePlugin::shared
{
ICvar* g_pCVar = nullptr;
IServerGameDLL* g_pServer = nullptr;
ISource2Server* g_pSource2Server = nullptr;
IVEngineServer* g_pEngine = nullptr;
CSchemaSystem* g_pSchemaSystem = nullptr;
IGameEventManager2* g_pGameEventManager = nullptr;
IGameEventSystem* g_pGameEventSystem = nullptr;
ISource2GameEntities* g_pGameEntities = nullptr;
INetworkMessages* g_pNetworkMessages = nullptr;
INetworkServerService* g_pNetworkServerService = nullptr;
CGameEntitySystem* g_pEntitySystem = nullptr;
IServerGameClients* g_pGameClients = nullptr;
CGlobalVars *g_pGlobalVars = nullptr;
CGameResourceService *g_pGameResourceServiceServer = nullptr;
CGameConfig *g_pGameConfig = nullptr;
SourceHook::Impl::CSourceHookImpl source_hook_impl;
SourceHook::ISourceHook* source_hook = &source_hook_impl;
int source_hook_pluginid = 0;
CGlobalVars *getGlobalVars() {
INetworkGameServer *server = g_pNetworkServerService->GetIGameServer();
if (!server) return nullptr;
if (!g_pGlobalVars) g_pGlobalVars = server->GetGlobals();
return g_pNetworkServerService->GetIGameServer()->GetGlobals();
}
constexpr float engine_fixed_tick_interval = 0.015625f;
const char* GetMapName()
{
if (getGlobalVars() == nullptr) return nullptr;
return getGlobalVars()->mapname.ToCStr();
}
void ServerCommand(const char* command)
{
auto clean_command = std::string(command);
clean_command.append("\n\0");
g_pEngine->ServerCommand(clean_command.c_str());
}
double GetEngineTime() { return Plat_FloatTime(); }
float GetTickInterval() { return engine_fixed_tick_interval; }
float GetCurrentTime() { return getGlobalVars()->curtime; }
int GetTickCount() { return getGlobalVars()->tickcount; }
float GetGameFrameTime() { return getGlobalVars()->frametime; }
bool g_bHasTicked = false;
bool g_bDetoursLoaded = false;
}

55
src/Shared.h Normal file
View file

@ -0,0 +1,55 @@
//
// Created by Michal Přikryl on 20.09.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <icvar.h>
#include <memory>
#include <schemasystem.h>
#include <vector>
#include <eiface.h>
#include <gameconfig.h>
#include <igameeventsystem.h>
#include "schema/cgameresourceserviceserver.h"
#include <sourcehook/sourcehook.h>
class CGameEntitySystem;
namespace TemplatePlugin::shared
{
extern ICvar* g_pCVar;
extern IServerGameDLL* g_pServer;
extern ISource2Server* g_pSource2Server;
extern IVEngineServer* g_pEngine;
extern CSchemaSystem* g_pSchemaSystem;
extern IGameEventManager2* g_pGameEventManager;
extern IGameEventSystem* g_pGameEventSystem;
extern ISource2GameEntities* g_pGameEntities;
extern INetworkMessages* g_pNetworkMessages;
extern INetworkServerService* g_pNetworkServerService;
extern CGameEntitySystem* g_pEntitySystem;
extern IServerGameClients* g_pGameClients;
extern CGlobalVars* g_pGlobalVars;
extern CGameResourceService* g_pGameResourceServiceServer;
extern CGameConfig *g_pGameConfig;
extern SourceHook::ISourceHook *source_hook;
extern int source_hook_pluginid;
CGlobalVars* getGlobalVars();
extern const char *GetMapName();
extern void ServerCommand(const char *command);
extern double GetEngineTime();
extern float GetTickInterval();
extern float GetCurrentTime();
extern int GetTickCount();
extern float GetGameFrameTime();
extern bool g_bHasTicked;
extern bool g_bDetoursLoaded;
}
#undef SH_GLOB_SHPTR
#define SH_GLOB_SHPTR TemplatePlugin::shared::source_hook
#undef SH_GLOB_PLUGPTR
#define SH_GLOB_PLUGPTR TemplatePlugin::shared::source_hook_pluginid

133
src/TemplatePlugin.cpp Normal file
View file

@ -0,0 +1,133 @@
#include "TemplatePlugin.h"
#include "path.h"
#include "Shared.h"
#include "dynlibutils/module.h"
#include <entitysystem.h>
#include "igameevents.h"
#include <iserver.h>
#include "game_system.h"
#include "schemasystem/schemasystem.h"
#include "schema/CCSPlayerController.h"
#include "schema/cgameresourceserviceserver.h"
#include "schema/plat.h"
#include <filesystem>
#include <cstdio>
#include <detours.h>
#include <fstream>
#include <gameconfig.h>
#include <regex>
#include <listeners/Listeners.h>
#include "EntityData.h"
#include "log.h"
#include "PlayersData.h"
#include "tasks.h"
#include "commands/Commands.h"
#include "events/Events.h"
#include "hooks/Hooks.h"
#include "schema/CGameRules.h"
#define VERSION_STRING SEMVER " @ " GITHUB_SHA
#define BUILD_TIMESTAMP __DATE__ " " __TIME__
PLUGIN_EXPOSE(Template, TemplatePlugin::g_iPlugin);
CGameEntitySystem* GameEntitySystem()
{
return *reinterpret_cast<CGameEntitySystem**>((uintptr_t)(g_pGameResourceServiceServer) +
TemplatePlugin::shared::g_pGameConfig->GetOffset("GameEntitySystem"));
}
class GameSessionConfiguration_t
{
};
namespace TemplatePlugin
{
ITemplatePlugin g_iPlugin;
bool ITemplatePlugin::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool late)
{
PLUGIN_SAVEVARS();
GET_V_IFACE_CURRENT(GetEngineFactory, shared::g_pCVar, ICvar, CVAR_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetServerFactory, shared::g_pSource2Server, ISource2Server, SOURCE2SERVER_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetServerFactory, shared::g_pServer, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL);
GET_V_IFACE_CURRENT(GetEngineFactory, shared::g_pEngine, IVEngineServer, INTERFACEVERSION_VENGINESERVER);
GET_V_IFACE_ANY(GetEngineFactory, shared::g_pSchemaSystem, CSchemaSystem, SCHEMASYSTEM_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, shared::g_pGameEventSystem, IGameEventSystem,
GAMEEVENTSYSTEM_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetServerFactory, shared::g_pGameEntities, ISource2GameEntities,
SOURCE2GAMEENTITIES_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetServerFactory, shared::g_pGameClients, IServerGameClients,
SOURCE2GAMECLIENTS_INTERFACE_VERSION);
GET_V_IFACE_CURRENT(GetEngineFactory, g_pGameResourceServiceServer, IGameResourceService,
GAMERESOURCESERVICESERVER_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, shared::g_pNetworkMessages, INetworkMessages,
NETWORKMESSAGES_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, shared::g_pNetworkServerService, INetworkServerService,
NETWORKSERVERSERVICE_INTERFACE_VERSION);
g_pCVar = shared::g_pCVar;
g_pSource2GameEntities = shared::g_pGameEntities;
shared::g_pGameResourceServiceServer = (CGameResourceService*)g_pGameResourceServiceServer;
if (!shared::g_pGameResourceServiceServer)
return false;
Tasks::Init();
auto gamedata_path = std::string(Paths::GetRootDirectory() + "/gamedata.json");
shared::g_pGameConfig = new CGameConfig(gamedata_path);
char conf_error[255] = "";
if (!shared::g_pGameConfig->Init(conf_error, sizeof(conf_error)))
{
FP_ERROR("Could not read '{}'. Error: {}", gamedata_path, conf_error);
return false;
}
if (!InitGameSystems())
return false;
g_SMAPI->AddListener(this, this);
Listeners::InitListeners();
g_pCVar = shared::g_pCVar;
ConVar_Register(FCVAR_RELEASE | FCVAR_CLIENT_CAN_EXECUTE | FCVAR_GAMEDLL);
if (late)
{
shared::g_pEntitySystem = GameEntitySystem();
shared::g_pEntitySystem->AddListenerEntity(&Detours::entityListener);
Commands::InitCommands();
Events::InitEvents();
Hooks::InitHooks();
Detours::InitHooks();
RayTrace::Initialize();
shared::g_bDetoursLoaded = true;
}
FP_INFO("<<< Load() success! >>>");
return true;
}
bool ITemplatePlugin::Unload(char* error, size_t maxlen)
{
Listeners::DestructListeners();
Detours::ShutdownHooks();
Detours::Shutdown();
shared::g_pEntitySystem->RemoveListenerEntity(&Detours::entityListener);
Tasks::Shutdown();
FP_INFO("<<< Unload() success! >>>");
return true;
}
const char* ITemplatePlugin::GetAuthor() { return "Slynx"; }
const char* ITemplatePlugin::GetName() { return "TemplatePlugin"; }
const char* ITemplatePlugin::GetDescription() { return "TemplatePlugin Metamod plugin for CS2 servers."; }
const char* ITemplatePlugin::GetURL() { return "https://slynxdev.cz"; }
const char* ITemplatePlugin::GetLicense() { return "GPLv3"; }
const char* ITemplatePlugin::GetVersion() { return VERSION_STRING; }
const char* ITemplatePlugin::GetDate() { return BUILD_TIMESTAMP; }
const char* ITemplatePlugin::GetLogTag() { return "TemplatePlugin"; }
}

30
src/TemplatePlugin.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef _INCLUDE_METAMOD_SOURCE_STUB_PLUGIN_H_
#define _INCLUDE_METAMOD_SOURCE_STUB_PLUGIN_H_
#include <igameevents.h>
#include <ISmmPlugin.h>
#include "entitysystem.h"
namespace TemplatePlugin
{
class ITemplatePlugin : public ISmmPlugin, public IMetamodListener
{
public:
bool Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late) override;
bool Unload(char *error, size_t maxlen) override;
const char *GetAuthor() override;
const char *GetName() override;
const char *GetDescription() override;
const char *GetURL() override;
const char *GetLicense() override;
const char *GetVersion() override;
const char *GetDate() override;
const char *GetLogTag() override;
};
extern ITemplatePlugin g_iPlugin;
}
#endif //_INCLUDE_METAMOD_SOURCE_STUB_PLUGIN_H_
PLUGIN_GLOBALVARS();

137
src/colors.h Normal file
View file

@ -0,0 +1,137 @@
#pragma once
#include <string>
#include <sstream>
#include <cstdio>
#include <Color.h>
#include <cmath>
#include <chrono>
namespace TemplatePlugin {
struct colors {
float r, g, b, a;
colors(float red, float green, float blue, float alpha = 1.0f)
: r(red), g(green), b(blue), a(alpha) {}
static colors Red() { return colors(1.f, 0.f, 0.f, 1.f); }
static colors Green() { return colors(0.f, 1.f, 0.f, 1.f); }
static colors Blue() { return colors(0.f, 0.f, 1.f, 1.f); }
static colors Yellow() { return colors(1.f, 1.f, 0.f, 1.f); }
static colors Purple() { return colors(0.5f, 0.f, 0.5f, 1.f); }
static colors Orange() { return colors(1.f, 0.5f, 0.f, 1.f); }
static colors Rainbow() { return colors(0.247f, 0.655f, 0.839f, 1.0f); }
static colors FromHex(const std::string& hex) {
unsigned int rgb = 0;
std::string clean = hex;
if (clean.starts_with("#"))
clean = clean.substr(1);
std::stringstream ss;
ss << std::hex << clean;
ss >> rgb;
float rf = ((rgb >> 16) & 0xFF) / 255.0f;
float gf = ((rgb >> 8) & 0xFF) / 255.0f;
float bf = (rgb & 0xFF) / 255.0f;
return colors(rf, gf, bf);
}
static colors FromHSV(double h, double s, double v, double a = 1.0) {
h = fmod(h, 360.0);
double c = v * s;
double x = c * (1 - std::fabs(fmod(h / 60.0, 2) - 1));
double m = v - c;
double rf = 0, gf = 0, bf = 0;
if (h < 60) {
rf = c; gf = x; bf = 0;
} else if (h < 120) {
rf = x; gf = c; bf = 0;
} else if (h < 180) {
rf = 0; gf = c; bf = x;
} else if (h < 240) {
rf = 0; gf = x; bf = c;
} else if (h < 300) {
rf = x; gf = 0; bf = c;
} else {
rf = c; gf = 0; bf = x;
}
return colors(
static_cast<float>(rf + m),
static_cast<float>(gf + m),
static_cast<float>(bf + m),
static_cast<float>(a)
);
}
bool IsEmpty() const {
return r == 0.0f && g == 0.0f && b == 0.0f && a == 0.0f;
}
bool IsRainbow() const {
return ToHexString() == "#3FA7D6";
}
std::string ToHexString() const {
int ir = static_cast<int>(r * 255);
int ig = static_cast<int>(g * 255);
int ib = static_cast<int>(b * 255);
char buf[8];
std::snprintf(buf, sizeof(buf), "#%02X%02X%02X", ir, ig, ib);
return std::string(buf);
}
uint32_t ToARGB() const {
return (static_cast<uint32_t>(a * 255) << 24) |
(static_cast<uint32_t>(b * 255) << 16) |
(static_cast<uint32_t>(g * 255) << 8) |
(static_cast<uint32_t>(r * 255));
}
static colors FromARGB(uint32_t packed) {
float rf = (packed & 0xFF) / 255.0f;
float gf = ((packed >> 8) & 0xFF) / 255.0f;
float bf = ((packed >> 16) & 0xFF) / 255.0f;
float af = ((packed >> 24) & 0xFF) / 255.0f;
return colors(rf, gf, bf, af);
}
Color ToValveColor() const {
return Color(
static_cast<int>(r * 255.0f),
static_cast<int>(g * 255.0f),
static_cast<int>(b * 255.0f),
static_cast<int>(a * 255.0f)
);
}
static inline const char* MapColorToCode(const colors& c) {
if (c.IsEmpty())
return "\x01";
const std::string hex = c.ToHexString();
if (hex == "#FF0000") return "\x07"; // red
if (hex == "#008000") return "\x04"; // green
if (hex == "#0000FF") return "\x0B"; // blue
if (hex == "#FFFF00") return "\x09"; // yellow
if (hex == "#800080") return "\x0E"; // purple
if (hex == "#FFA500") return "\x10"; // orange
if (hex == "#FFFFFF") return "\x01"; // white
return "\x01";
}
static colors GetRainbowColor() {
using namespace std::chrono;
auto now = system_clock::now();
auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count();
double hue = fmod((ms % 1000) / 1000.0 * 360.0, 360.0);
return FromHSV(hue, 1.0, 1.0);
}
};
}

14
src/commands/Commands.cpp Normal file
View file

@ -0,0 +1,14 @@
//
// Created by Michal Přikryl on 21.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "Commands.h"
#include <detours.h>
namespace TemplatePlugin::Commands
{
void InitCommands()
{
}
}

11
src/commands/Commands.h Normal file
View file

@ -0,0 +1,11 @@
//
// Created by Michal Přikryl on 21.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "detourtypes.h"
namespace TemplatePlugin::Commands
{
void InitCommands();
}

369
src/detours.cpp Normal file
View file

@ -0,0 +1,369 @@
//
// Created by Michal Přikryl on 09.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include <funchook/include/funchook.h>
#include <igameevents.h>
#include <sourcehook.h>
#include <cstring>
#include <unordered_set>
#include "schema/CCSPlayerController.h"
#include "detours.h"
#include <Shared.h>
#include <TemplatePlugin.h>
#include "log.h"
namespace TemplatePlugin {
SH_DECL_HOOK2(IGameEventManager2, FireEvent, SH_NOATTRIB, 0, bool, IGameEvent*, bool);
SH_DECL_HOOK3_void(ICvar, DispatchConCommand, SH_NOATTRIB, 0, ConCommandRef, const CCommandContext&,
const CCommand&);
namespace Detours {
std::vector<std::unique_ptr<ConCommand> > registeredCommands;
static std::unordered_map<std::string, std::vector<CommandEntry> > consoleListeners;
static std::unordered_map<std::string, std::vector<EventEntry> > gameEvents;
static EventManager eventManager;
static std::vector<IGameEvent *> eventStack;
static std::vector<EntityEventHandler> entitySpawnedListeners;
static std::vector<EntityEventHandler> entityCreatedListeners;
static std::vector<EntityEventHandler> entityDeletedListeners;
static std::vector<EntityParentChangedHandler> entityParentChangerListeners;
static std::unordered_set<std::string> registeredNames;
static std::unordered_map<std::string, CommandHandler> commandCallbacks;
struct OutputHookKey {
CEntityInstance *entity;
std::string output;
HookMode mode;
bool operator==(const OutputHookKey &o) const noexcept {
return entity == o.entity && output == o.output && mode == o.mode;
}
};
struct OutputHookKeyHasher {
size_t operator()(const OutputHookKey &k) const noexcept {
return std::hash<void *>{}(k.entity) ^ (std::hash<std::string>{}(k.output) << 1)
^ (std::hash<int>{}(static_cast<int>(k.mode)) << 2);
}
};
static std::unordered_map<OutputHookKey, std::vector<EntityOutputHandler>, OutputHookKeyHasher>
g_entityOutputHooks;
void ConCommandRouter(const CCommandContext &ctx, const CCommand &args) {
if (args.ArgC() < 1)
return;
std::string name = args.Arg(0);
auto it = commandCallbacks.find(name);
if (it == commandCallbacks.end())
return;
(void) it->second(ctx, args, HookMode::Post);
}
void RegisterChatListener(const std::string &name,
const std::function<void(const CCommandContext &, const CCommand &, HookMode)> &
handler) {
CommandHandler nativeHandler = WrapVoidHandler(handler);
RegisterConsoleListener(name, nativeHandler, HookMode::Pre);
RegisterConsoleListener("/" + name, nativeHandler, HookMode::Pre);
RegisterConsoleListener("!" + name, nativeHandler, HookMode::Pre);
}
void RegisterConsoleCommand(const std::string &name,
const std::function<void(const CCommandContext &, const CCommand &, HookMode)> &
handler) {
CommandHandler nativeHandler = WrapVoidHandler(handler);
if (shared::g_pCVar && shared::g_pCVar->FindConCommand(name.c_str()).IsValidRef()) {
RegisterConsoleListener(name, nativeHandler, HookMode::Pre);
RegisterConsoleListener("/" + name, nativeHandler, HookMode::Pre);
RegisterConsoleListener("!" + name, nativeHandler, HookMode::Pre);
return;
}
if (!registeredNames.contains(name)) {
auto cmd = std::make_unique<ConCommand>(
name.c_str(),
ConCommandRouter,
("Registered command: " + name).c_str(),
FCVAR_NONE
);
registeredCommands.push_back(std::move(cmd));
registeredNames.insert(name);
}
RegisterConsoleListener(name, nativeHandler, HookMode::Pre);
RegisterConsoleListener("/" + name, nativeHandler, HookMode::Pre);
RegisterConsoleListener("!" + name, nativeHandler, HookMode::Pre);
}
void InitHooks() {
SH_ADD_HOOK(IGameEventManager2, FireEvent, shared::g_pGameEventManager, Detours::OnFireEvent, false);
SH_ADD_HOOK(IGameEventManager2, FireEvent, shared::g_pGameEventManager, Detours::OnFireEventPost, true);
SH_ADD_HOOK(ICvar, DispatchConCommand, shared::g_pCVar, Hook_DispatchConCommand, false);
}
void ShutdownHooks() {
SH_REMOVE_HOOK(IGameEventManager2, FireEvent, shared::g_pGameEventManager, Detours::OnFireEvent, false);
SH_REMOVE_HOOK(IGameEventManager2, FireEvent, shared::g_pGameEventManager, Detours::OnFireEventPost, true);
SH_REMOVE_HOOK(ICvar, DispatchConCommand, shared::g_pCVar, Hook_DispatchConCommand, false);
}
void RegisterConsoleListener(const std::string &name, CommandHandler handler, HookMode mode) {
consoleListeners[name].push_back({handler, mode});
}
void RegisterGameEvent(const std::string &name, GameEventHandler handler, HookMode mode) {
gameEvents[name].push_back({handler, mode});
if (!shared::g_pGameEventManager->FindListener(&eventManager, name.c_str()))
{
shared::g_pGameEventManager->AddListener(&eventManager, name.c_str(), true);
}
}
void EventManager::FireGameEvent(IGameEvent* pEvent) {}
void RegisterEntityListener(EntityEventHandler handler, EntityEventType type) {
switch (type) {
case EntityEventType::ENTITY_SPAWNED:
entitySpawnedListeners.push_back(handler);
break;
case EntityEventType::ENTITY_CREATED:
entityCreatedListeners.push_back(handler);
break;
case EntityEventType::ENTITY_DELETED:
entityDeletedListeners.push_back(handler);
break;
}
}
void *FindModuleSignature(const DynLibUtils::CModule& module, const char *name) {
return module.FindPattern(
shared::g_pGameConfig->GetSignature(name)
);
}
HookResult DispatchConsoleListener(const CCommandContext &ctx, const CCommand &args, HookMode mode) {
std::string name = args.Arg(0);
std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
auto it = consoleListeners.find(name);
if (it == consoleListeners.end())
return HookResult::Continue;
HookResult result = HookResult::Continue;
for (const auto &entry: it->second) {
if (entry.mode != mode)
continue;
HookResult thisResult = entry.handler(ctx, args, mode);
if (thisResult == HookResult::Stop)
return HookResult::Stop;
if (thisResult == HookResult::Handled && mode == HookMode::Pre)
return HookResult::Handled;
if (static_cast<int>(thisResult) > static_cast<int>(result))
result = thisResult;
}
return result;
}
bool DispatchGameEvent(IGameEvent *event, HookMode mode, bool &dontBroadcast) {
const char *name = event->GetName();
auto it = gameEvents.find(name);
if (it == gameEvents.end())
return true;
EventOverride override{dontBroadcast};
for (const auto &hook: it->second) {
if (hook.mode != mode)
continue;
HookResult result = hook.handler(event, mode, override);
if (result == HookResult::Handled || result == HookResult::Stop)
return false;
}
dontBroadcast = override.dontBroadcast;
return true;
}
void DispatchEntitySpawn(CEntityInstance *ent) {
for (auto &fn: entitySpawnedListeners) {
fn(ent);
}
}
void DispatchEntityCreate(CEntityInstance *ent) {
for (auto &fn: entityCreatedListeners) {
fn(ent);
}
}
void DispatchEntityDelete(CEntityInstance *ent) {
for (auto &fn: entityDeletedListeners) {
fn(ent);
}
}
void HookSingleEntityOutput(CEntityInstance *entity,
const std::string &outputName,
EntityOutputHandler handler,
HookMode mode) {
g_entityOutputHooks[{entity, outputName, mode}].push_back(std::move(handler));
}
void UnhookSingleEntityOutput(CEntityInstance *entity,
const std::string &outputName,
EntityOutputHandler handler,
HookMode mode) {
auto key = OutputHookKey{entity, outputName, mode};
auto it = g_entityOutputHooks.find(key);
if (it == g_entityOutputHooks.end()) return;
auto &vec = it->second;
vec.erase(std::remove_if(vec.begin(), vec.end(),
[&](const EntityOutputHandler &h) {
return h.target<void>() == handler.target<void>();
}),
vec.end());
if (vec.empty())
g_entityOutputHooks.erase(it);
}
HookResult DispatchEntityOutput(CEntityIOOutput *output,
const char *name,
CEntityInstance *activator,
CEntityInstance *caller,
const CVariant *value,
float delay,
HookMode mode) {
HookResult result = HookResult::Continue;
for (auto &[key, handlers]: g_entityOutputHooks) {
if (key.entity != caller) continue;
if (key.output != name) continue;
if (key.mode != mode) continue;
for (auto &fn: handlers) {
HookResult r = fn(output, name, activator, caller, value, delay);
if (r == HookResult::Stop) return HookResult::Stop;
if (static_cast<int>(r) > static_cast<int>(result))
result = r;
}
}
return result;
}
bool OnFireEvent(IGameEvent *event, bool bDontBroadcast) {
if (!event)
RETURN_META_VALUE(MRES_IGNORED, false);
bool localDontBroadcast = bDontBroadcast;
if (!DispatchGameEvent(event, HookMode::Pre, localDontBroadcast))
RETURN_META_VALUE(MRES_SUPERCEDE, false);
if (IGameEvent *copy = shared::g_pGameEventManager->DuplicateEvent(event)) eventStack.push_back(copy);
if (localDontBroadcast != bDontBroadcast)
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, true, &IGameEventManager2::FireEvent,
(event, localDontBroadcast));
RETURN_META_VALUE(MRES_IGNORED, true);
}
bool OnFireEventPost(IGameEvent *event, bool bDontBroadcast) {
if (!event)
RETURN_META_VALUE(MRES_IGNORED, false);
if (!eventStack.empty()) {
IGameEvent *copy = eventStack.back();
eventStack.pop_back();
bool dummy = bDontBroadcast;
DispatchGameEvent(copy, HookMode::Post, dummy);
shared::g_pGameEventManager->FreeEvent(copy);
}
RETURN_META_VALUE(MRES_IGNORED, true);
}
void Hook_DispatchConCommand(ConCommandRef, const CCommandContext &ctx, const CCommand &args) {
if (args.ArgC() >= 2) {
const char *cmd = args.Arg(0);
const char *msg = args.Arg(1);
if (V_strcmp(cmd, "say") == 0 || V_strcmp(cmd, "say_team") == 0) {
std::string message = msg;
if (message.size() >= 2 && message.front() == '"' && message.back() == '"')
message = message.substr(1, message.size() - 2);
if (!message.empty() && (message[0] == '!' || message[0] == '/')) {
std::string cleaned = message.substr(1);
CCommand parsed;
parsed.Tokenize(cleaned.c_str());
if (parsed.ArgC() > 0) {
HookResult r = DispatchConsoleListener(ctx, parsed, HookMode::Pre);
if (r != HookResult::Stop)
DispatchConsoleListener(ctx, parsed, HookMode::Post);
}
RETURN_META(MRES_SUPERCEDE);
}
}
}
HookResult result = DispatchConsoleListener(ctx, args, HookMode::Pre);
if (result == HookResult::Handled || result == HookResult::Stop)
RETURN_META(MRES_SUPERCEDE);
DispatchConsoleListener(ctx, args, HookMode::Post);
}
void Shutdown() {
consoleListeners.clear();
shared::g_pGameEventManager->RemoveListener(&eventManager);
gameEvents.clear();
entitySpawnedListeners.clear();
entityCreatedListeners.clear();
entityDeletedListeners.clear();
entityParentChangerListeners.clear();
commandCallbacks.clear();
registeredNames.clear();
registeredCommands.clear();
for (auto hook: hookHandles) {
if (!hook) continue;
int rc = funchook_uninstall(hook, 0);
if (rc != 0) {
FP_ERROR("Failed to uninstall hook: {}", rc);
}
rc = funchook_destroy(hook);
if (rc != 0) {
FP_ERROR("Failed to destroy hook: {}", rc);
}
}
hookHandles.clear();
signatureHooks.clear();
shared::g_pEntitySystem->RemoveListenerEntity(&entityListener);
}
}
}

293
src/detours.h Normal file
View file

@ -0,0 +1,293 @@
//
// Created by Michal Přikryl on 20.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <any>
#include <functional>
#include <string>
#include <igameevents.h>
#include <convar.h>
#include <features.h>
#include <ISmmPlugin.h>
#include <TemplatePlugin.h>
#include <funchook/include/funchook.h>
#include <type_traits>
#include "detourtypes.h"
#include "entitysystem.h"
#include "log.h"
#include "dynlibutils/module.h"
namespace TemplatePlugin::Detours
{
struct CommandEntry
{
CommandHandler handler;
HookMode mode;
};
struct EventEntry
{
GameEventHandler handler;
HookMode mode;
};
class EventManager : public IGameEventListener2
{
void FireGameEvent(IGameEvent* pEvent) override;
};
static std::unordered_map<std::string, std::vector<SignatureEntry>> signatureHooks;
static std::mutex signatureHooksMutex;
static std::vector<funchook_t*> hookHandles;
extern std::unordered_map<std::string, std::any> overriddenReturns;
extern std::mutex overriddenReturnsMutex;
void Shutdown();
void InitHooks();
void ShutdownHooks();
bool OnFireEvent(IGameEvent* event, bool bDontBroadcast);
bool OnFireEventPost(IGameEvent* event, bool bDontBroadcast);
void ConCommandRouter(const CCommandContext& ctx, const CCommand& args);
void Hook_DispatchConCommand(ConCommandRef, const CCommandContext&, const CCommand&);
void RegisterConsoleListener(const std::string& name, CommandHandler handler, HookMode mode = HookMode::Post);
void RegisterChatListener(const std::string& name,
const std::function<void(const CCommandContext&, const CCommand&, HookMode)>& handler);
void RegisterConsoleCommand(const std::string& name,
const std::function<void(const CCommandContext&, const CCommand&,
HookMode)>& handler);
void RegisterGameEvent(const std::string& name, GameEventHandler handler, HookMode mode = HookMode::Post);
void RegisterEntityListener(EntityEventHandler handler, EntityEventType type);
inline void RegisterSignatureHandler(const std::string& name,
SignatureHandler handler,
HookMode mode = HookMode::Post)
{
std::scoped_lock lock(signatureHooksMutex);
signatureHooks[name].push_back({handler, mode});
}
void* FindModuleSignature(const DynLibUtils::CModule& module, const char* name);
template <typename T>
inline void* ToVoidArg(T&& v)
{
return const_cast<void*>(reinterpret_cast<const void*>(&v));
}
template <typename T>
inline void* ToVoidArg(T* v)
{
return const_cast<void*>(reinterpret_cast<const void*>(v));
}
template <typename Fn, typename... Args>
auto DispatchSignatureCall(const std::string& name, Fn original, Args... args)
-> decltype(original(args...))
{
using Ret = decltype(original(args...));
std::vector<SignatureEntry> hooksCopy;
{
std::scoped_lock lock(signatureHooksMutex);
auto it = signatureHooks.find(name);
if (it != signatureHooks.end())
hooksCopy = it->second;
}
void* argArray[] = {ToVoidArg(args)...};
constexpr size_t argCount = sizeof...(Args);
void* self = argCount > 0 ? argArray[0] : nullptr;
bool skipOriginal = false;
bool allowReturnOverride = false;
if constexpr (!std::is_void_v<Ret>)
{
Ret result{};
DynamicHook hook{argArray, argCount, self, &result};
// --- PRE hooks ---
for (auto& entry : hooksCopy)
{
if (entry.mode != HookMode::Pre) continue;
HookResult r = entry.handler(&hook, HookMode::Pre);
if (r == HookResult::Stop) return result;
if (r == HookResult::Handled)
{
skipOriginal = true;
return result;
}
if (r == HookResult::Changed) allowReturnOverride = true;
}
// --- Originál ---
if (!skipOriginal && original)
{
if (!(allowReturnOverride && hook.returnOverridden))
{
result = original(args...);
}
}
// --- POST hooks ---
for (auto& entry : hooksCopy)
{
if (entry.mode != HookMode::Post) continue;
HookResult r = entry.handler(&hook, HookMode::Post);
if (r == HookResult::Stop) break;
}
return result;
}
else
{
DynamicHook hook{argArray, argCount, self, nullptr};
// --- PRE hooks ---
for (auto& entry : hooksCopy)
{
if (entry.mode != HookMode::Pre) continue;
HookResult r = entry.handler(&hook, HookMode::Pre);
if (r == HookResult::Stop) return;
if (r == HookResult::Handled)
{
skipOriginal = true;
return;
}
}
// --- Originál ---
if (!skipOriginal && original)
{
original(args...);
}
// --- POST hooks ---
for (auto& entry : hooksCopy)
{
if (entry.mode != HookMode::Post) continue;
HookResult r = entry.handler(&hook, HookMode::Post);
if (r == HookResult::Stop) break;
}
}
}
template <typename R, typename... Args>
void RegisterSignatureDetour(
const std::string& sigName,
DynLibUtils::CModule module,
R (**ppOriginal)(Args...),
SignatureHandler handler,
HookMode mode
)
{
std::scoped_lock lock(signatureHooksMutex);
auto it = signatureHooks.find(sigName);
if (it != signatureHooks.end())
{
it->second.push_back({handler, mode});
return;
}
void* addr = FindModuleSignature(std::move(module), sigName.c_str());
if (!addr)
{
FP_ERROR("Failed to find signature for '{}'", sigName);
return;
}
*ppOriginal = reinterpret_cast<R(*)(Args...)>(addr);
SignatureStorage<R(*)(Args...)>::name = sigName;
auto detour = [](Args... args) -> R
{
auto orig = SignatureStorage<R(*)(Args...)>::original;
return DispatchSignatureCall(SignatureStorage<R(*)(Args...)>::name, orig, args...);
};
auto hook = funchook_create();
using FnType = R(*)(Args...);
if (funchook_prepare(
hook,
reinterpret_cast<void**>(ppOriginal),
reinterpret_cast<void*>(static_cast<FnType>(detour))
) != 0 ||
funchook_install(hook, 0) != 0)
{
FP_ERROR("Failed to hook '{}'", sigName);
return;
}
SignatureStorage<R(*)(Args...)>::original = *ppOriginal;
signatureHooks[sigName] = {};
signatureHooks[sigName].push_back({handler, mode});
hookHandles.push_back(hook);
}
HookResult DispatchConsoleListener(const CCommandContext& ctx, const CCommand& args, HookMode mode);
bool DispatchGameEvent(IGameEvent* event, HookMode mode, bool& dontBroadcast);
void DispatchEntitySpawn(CEntityInstance* entity);
void DispatchEntityCreate(CEntityInstance* entity);
void DispatchEntityDelete(CEntityInstance* entity);
class CEntityListener : public IEntityListener
{
void OnEntitySpawned(CEntityInstance* pEntity) override { DispatchEntitySpawn(pEntity); }
void OnEntityCreated(CEntityInstance* pEntity) override { DispatchEntityCreate(pEntity); }
void OnEntityDeleted(CEntityInstance* pEntity) override { DispatchEntityDelete(pEntity); }
void OnEntityParentChanged(CEntityInstance* pEntity, CEntityInstance* pNewParent) override
{
}
};
void HookSingleEntityOutput(CEntityInstance* entity,
const std::string& outputName,
EntityOutputHandler handler,
HookMode mode = HookMode::Post);
void UnhookSingleEntityOutput(CEntityInstance* entity,
const std::string& outputName,
EntityOutputHandler handler,
HookMode mode = HookMode::Post);
HookResult DispatchEntityOutput(CEntityIOOutput* output,
const char* name,
CEntityInstance* activator,
CEntityInstance* caller,
const CVariant* value,
float delay,
HookMode mode);
inline CEntityListener entityListener;
inline CommandHandler WrapVoidHandler(
const std::function<void(const CCommandContext&, const CCommand&, HookMode)>& fn)
{
return [fn](const CCommandContext& ctx, const CCommand& args, HookMode mode) -> HookResult
{
fn(ctx, args, mode);
return HookResult::Continue;
};
}
}

207
src/detourtypes.h Normal file
View file

@ -0,0 +1,207 @@
//
// Created by Michal Přikryl on 23.08.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <functional>
#include <igameevents.h>
#include <entitysystem.h>
#include <convar.h>
#include <string>
namespace TemplatePlugin
{
class CBaseEntity;
class CTakeDamageInfo;
class CTakeDamageResult;
enum class HookResult
{
Continue = 0,
Changed = 1,
Handled = 3,
Stop = 4,
};
enum class HookMode
{
Pre,
Post
};
struct DynamicHook
{
void** args;
size_t argCount;
void* self;
void* returnValue;
bool returnOverridden = false;
template <typename T>
T GetParam(size_t index) const
{
if (index >= argCount)
throw std::out_of_range("arg index");
if constexpr (std::is_pointer_v<T>)
{
return reinterpret_cast<T>(args[index]);
}
else
{
return *reinterpret_cast<T*>(args[index]);
}
}
template <typename T>
void SetParam(size_t index, const T& value)
{
if (index >= argCount)
throw std::out_of_range("arg index");
if constexpr (std::is_pointer_v<T>)
{
if constexpr (std::is_pointer_v<std::remove_pointer_t<T>>)
{
**reinterpret_cast<T*>(args[index]) = *value;
}
else
{
args[index] = const_cast<std::remove_const_t<T>>(value);
}
}
else
{
*reinterpret_cast<T*>(args[index]) = value;
}
}
template <typename T>
T GetReturn() const
{
if (!returnValue)
return T{};
if constexpr (std::is_pointer_v<T>)
{
return *reinterpret_cast<T*>(returnValue);
}
else
{
return *reinterpret_cast<T*>(returnValue);
}
}
template <typename T>
void SetReturn(const T& value)
{
if (!returnValue)
return;
if constexpr (std::is_pointer_v<T>)
{
*reinterpret_cast<T*>(returnValue) = value;
}
else
{
*reinterpret_cast<T*>(returnValue) = value;
}
returnOverridden = true;
}
};
struct EventOverride
{
bool dontBroadcast = false;
};
enum class EntityEventType
{
ENTITY_SPAWNED,
ENTITY_CREATED,
ENTITY_DELETED
};
struct EntityIOConnectionDesc_t
{
string_t m_targetDesc;
string_t m_targetInput;
string_t m_valueOverride;
CEntityHandle m_hTarget;
EntityIOTargetType_t m_nTargetType;
int32 m_nTimesToFire;
float m_flDelay;
};
struct EntityIOConnection_t : EntityIOConnectionDesc_t
{
bool m_bMarkedForRemoval;
EntityIOConnection_t* m_pNext;
};
struct EntityIOOutputDesc_t
{
const char* m_pName;
uint32 m_nFlags;
uint32 m_nOutputOffset;
};
class CEntityIOOutput
{
public:
void* vtable;
EntityIOConnection_t* m_pConnections;
EntityIOOutputDesc_t* m_pDesc;
};
using CommandHandler = std::function<HookResult(const CCommandContext&, const CCommand&, HookMode)>;
using GameEventHandler = std::function<HookResult(IGameEvent* event, HookMode mode, EventOverride& override)>;
using EntityEventHandler = void(*)(CEntityInstance*);
using EntityParentChangedHandler = void(*)(CEntityInstance*, CEntityInstance*);
using EntityOutputHandler = std::function<HookResult(CEntityIOOutput*, const char*, CEntityInstance*,
CEntityInstance*, const CVariant*, float)>;
using SignatureHandler = std::function<HookResult(DynamicHook* hook, HookMode mode)>;
using CBaseEntity_TakeDamageOld_t = int64_t(*)(CBaseEntity* pThis, CTakeDamageInfo* info, CTakeDamageResult* unk3);
struct SignatureEntry
{
SignatureHandler handler;
HookMode mode;
SignatureEntry(SignatureHandler h, HookMode m)
: handler(std::move(h)), mode(m)
{
}
SignatureEntry(const SignatureEntry& other)
: handler(other.handler), mode(other.mode)
{
}
SignatureEntry& operator=(const SignatureEntry& other)
{
if (this != &other)
{
handler = other.handler;
mode = other.mode;
}
return *this;
}
SignatureEntry(SignatureEntry&&) noexcept = default;
SignatureEntry& operator=(SignatureEntry&&) noexcept = default;
};
template <typename T>
struct SignatureStorage;
template <typename R, typename... Args>
struct SignatureStorage<R(*)(Args...)>
{
static inline std::string name;
static inline R (*original)(Args...) = nullptr;
};
}

13
src/events/Events.cpp Normal file
View file

@ -0,0 +1,13 @@
//
// Created by Michal Přikryl on 21.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "Events.h"
#include <detours.h>
namespace TemplatePlugin::Events
{
void InitEvents()
{
}
}

11
src/events/Events.h Normal file
View file

@ -0,0 +1,11 @@
//
// Created by Michal Přikryl on 21.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "detourtypes.h"
namespace TemplatePlugin::Events
{
void InitEvents();
}

46
src/game_system.cpp Normal file
View file

@ -0,0 +1,46 @@
//
// Created by Michal Přikryl on 21.09.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "Shared.h"
#include "game_system.h"
#include <tier0/vprof.h>
#include "log.h"
#include "dynlibutils/module.h"
CBaseGameSystemFactory** CBaseGameSystemFactory::sm_pFirst = nullptr;
CGameSystem g_GameSystem;
IGameSystemFactory* CGameSystem::sm_Factory = nullptr;
IEntityResourceManifest* m_exportResourceManifest = nullptr;
// This mess is needed to get the pointer to sm_pFirst so we can insert game systems
bool InitGameSystems()
{
DynLibUtils::CModule libserver(TemplatePlugin::shared::g_pServer);
auto result = libserver.FindPattern(TemplatePlugin::shared::g_pGameConfig->GetSignature("IGameSystem_InitAllSystems_pFirst"));
if (!result)
{
FP_ERROR("Failed to find IGameSystem_InitAllSystems_pFirst!");
return false;
}
uint8_t* ptr = reinterpret_cast<uint8_t*>(result.GetPtr()) + 3;
uint32 offset = *(uint32*)ptr;
ptr += 4;
CBaseGameSystemFactory::sm_pFirst = (CBaseGameSystemFactory**)(ptr + offset);
CGameSystem::sm_Factory = new CGameSystemStaticFactory<CGameSystem>("Template_GameSystem", &g_GameSystem);
return true;
}
GS_EVENT_MEMBER(CGameSystem, BuildGameSessionManifest)
{
IEntityResourceManifest* pResourceManifest = msg->m_pResourceManifest;
m_exportResourceManifest = pResourceManifest;
}

30
src/game_system.h Normal file
View file

@ -0,0 +1,30 @@
//
// Created by Michal Přikryl on 21.09.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "entitysystem.h"
#include "igamesystemfactory.h"
bool InitGameSystems();
class CGameSystem : public CBaseGameSystem
{
public:
GS_EVENT(BuildGameSessionManifest);
void Shutdown() override
{
delete sm_Factory;
}
void SetGameSystemGlobalPtrs(void* pValue) override
{
if (sm_Factory) sm_Factory->SetGlobalPtr(pValue);
}
bool DoesGameSystemReallocate() override { return sm_Factory->ShouldAutoAdd(); }
static IGameSystemFactory* sm_Factory;
};

102
src/gameconfig.cpp Normal file
View file

@ -0,0 +1,102 @@
//
// Created by Michal Přikryl on 20.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "gameconfig.h"
#include <fstream>
#include <TemplatePlugin.h>
namespace TemplatePlugin
{
CGameConfig::CGameConfig(const std::string& path) { m_sPath = path; }
CGameConfig::~CGameConfig() = default;
bool CGameConfig::Init(char* conf_error, int conf_error_size)
{
std::ifstream ifs(m_sPath);
if (!ifs)
{
V_snprintf(conf_error, conf_error_size, "Gamedata file not found.");
return false;
}
m_json = json::parse(ifs);
#if _WIN32
constexpr auto platform = "windows";
#else
constexpr auto platform = "linux";
#endif
try
{
for (auto& [k, v] : m_json.items())
{
if (v.contains("signatures"))
{
if (auto library = v["signatures"]["library"]; library.is_string())
{
m_umLibraries[k] = library.get<std::string>();
}
if (auto signature = v["signatures"][platform]; signature.is_string())
{
m_umSignatures[k] = signature.get<std::string>();
}
}
if (v.contains("offsets"))
{
if (auto offset = v["offsets"][platform]; offset.is_number_integer())
{
m_umOffsets[k] = offset.get<std::int64_t>();
}
}
if (v.contains("patches"))
{
if (auto patch = v["patches"][platform]; patch.is_string())
{
m_umPatches[k] = patch.get<std::string>();
}
}
}
}
catch (const std::exception& ex)
{
V_snprintf(conf_error, conf_error_size, "Failed to parse gamedata file: %s", ex.what());
return false;
}
return true;
}
const std::string CGameConfig::GetPath() { return m_sPath; }
const char* CGameConfig::GetSignature(const std::string& name)
{
auto it = m_umSignatures.find(name);
if (it == m_umSignatures.end())
{
return nullptr;
}
return it->second.c_str();
}
const char* CGameConfig::GetPatch(const std::string& name)
{
auto it = m_umPatches.find(name);
if (it == m_umPatches.end())
{
return nullptr;
}
return it->second.c_str();
}
int CGameConfig::GetOffset(const std::string& name)
{
auto it = m_umOffsets.find(name);
if (it == m_umOffsets.end())
{
return -1;
}
return it->second;
}
}

41
src/gameconfig.h Normal file
View file

@ -0,0 +1,41 @@
//
// Created by Michal Přikryl on 20.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <KeyValues.h>
#include <string>
#include <unordered_map>
#undef snprintf
#include <nlohmann/json.hpp>
namespace TemplatePlugin {
class CGameConfig
{
public:
using json = nlohmann::json;
CGameConfig(const std::string& path);
~CGameConfig();
bool Init(char* conf_error, int conf_error_size);
const std::string GetPath();
const char* GetLibrary(const std::string& name);
const char* GetSignature(const std::string& name);
const char* GetSymbol(const char* name);
const char* GetPatch(const std::string& name);
int GetOffset(const std::string& name);
private:
std::string m_sPath;
// use Valve KeyValues in the future.
// since we'd better make '\' easier.
json m_json;
std::unordered_map<std::string, int> m_umOffsets;
std::unordered_map<std::string, std::string> m_umSignatures;
std::unordered_map<std::string, void*> m_umAddresses;
std::unordered_map<std::string, std::string> m_umLibraries;
std::unordered_map<std::string, std::string> m_umPatches;
};
} // namespace Core

18
src/hooks/Hooks.cpp Normal file
View file

@ -0,0 +1,18 @@
//
// Created by Michal Přikryl on 21.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "Hooks.h"
#include "detours.h"
#include "log.h"
#include "PlayersData.h"
#include "PluginData.h"
#include "schema/CCSPlayerController.h"
#include "schema/CUserCmd.h"
namespace TemplatePlugin::Hooks
{
void InitHooks()
{
}
}

11
src/hooks/Hooks.h Normal file
View file

@ -0,0 +1,11 @@
//
// Created by Michal Přikryl on 21.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "detourtypes.h"
namespace TemplatePlugin::Hooks
{
void InitHooks();
}

View file

@ -0,0 +1,86 @@
//
// Created by Michal Přikryl on 10.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "Listeners.h"
#include <detours.h>
#include <RayTrace.h>
#include <Shared.h>
#include <tasks.h>
#include <commands/Commands.h>
#include <dynlibutils/module.h>
#include <events/Events.h>
#include <schema/CGameRules.h>
class GameSessionConfiguration_t
{
};
namespace TemplatePlugin::Listeners {
SourceHooks sourceHooks;
SH_DECL_HOOK3_void(IServerGameDLL, GameFrame, SH_NOATTRIB, 0, bool, bool, bool);
SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0, const GameSessionConfiguration_t&,
ISource2WorldSession*, const char*);
SH_DECL_HOOK2(IGameEventManager2, LoadEventsFromFile, SH_NOATTRIB, 0, int, const char*, bool);
int g_iLoadEventsFromFileId = -1;
void InitListeners() {
SH_ADD_HOOK(IServerGameDLL, GameFrame, shared::g_pServer,
SH_MEMBER(&sourceHooks,&SourceHooks::Hook_GameFrame), false);
SH_ADD_HOOK(INetworkServerService, StartupServer, shared::g_pNetworkServerService,
SH_MEMBER(&sourceHooks, &SourceHooks::Hook_StartupServer), true);
auto pCGameEventManagerVTable = DynLibUtils::CModule(shared::g_pServer).
GetVirtualTableByName("CGameEventManager").RCast<IGameEventManager2*>();
g_iLoadEventsFromFileId = SH_ADD_DVPHOOK(IGameEventManager2, LoadEventsFromFile, pCGameEventManagerVTable,
SH_MEMBER(&sourceHooks, &SourceHooks::Hook_LoadEventsFromFile), false);
}
void DestructListeners() {
SH_REMOVE_HOOK(IServerGameDLL, GameFrame, shared::g_pServer,
SH_MEMBER(&sourceHooks,&SourceHooks::Hook_GameFrame), false);
SH_REMOVE_HOOK(INetworkServerService, StartupServer, shared::g_pNetworkServerService,
SH_MEMBER(&sourceHooks, &SourceHooks::Hook_StartupServer), true);
SH_REMOVE_HOOK_ID(g_iLoadEventsFromFileId);
}
void SourceHooks::Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick)
{
Tasks::Tick(simulating);
if (!shared::getGlobalVars())
return;
shared::g_bHasTicked = true;
if (CCSGameRules::FindGameRules())
CCSGameRules::FindGameRules()->m_bGameRestart =
CCSGameRules::FindGameRules()->m_flRestartRoundTime < shared::GetCurrentTime();
}
void SourceHooks::Hook_StartupServer(const GameSessionConfiguration_t& config,
ISource2WorldSession*, const char*)
{
if (!shared::g_bDetoursLoaded)
{
shared::g_pEntitySystem = GameEntitySystem();
shared::g_pEntitySystem->AddListenerEntity(&Detours::entityListener);
Commands::InitCommands();
Events::InitEvents();
Detours::InitHooks();
RayTrace::Initialize();
shared::g_bDetoursLoaded = true;
}
if (shared::g_bHasTicked)
{
Tasks::RemoveMapChangeTimers();
}
shared::g_bHasTicked = false;
}
int SourceHooks::Hook_LoadEventsFromFile(const char* filename, bool bSearchAll)
{
ExecuteOnce(shared::g_pGameEventManager = META_IFACEPTR(IGameEventManager2));
RETURN_META_VALUE(MRES_IGNORED, 0);
}
}

23
src/listeners/Listeners.h Normal file
View file

@ -0,0 +1,23 @@
//
// Created by Michal Přikryl on 09.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <eiface.h>
#include <sourcehook.h>
#include <iserver.h>
namespace TemplatePlugin::Listeners {
void InitListeners();
void DestructListeners();
class SourceHooks {
public:
void Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick);
void Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession*, const char*);
int Hook_LoadEventsFromFile(const char* filename, bool bSearchAll);
};
extern SourceHooks sourceHooks;
}

49
src/log.cpp Normal file
View file

@ -0,0 +1,49 @@
//
// Created by Michal Přikryl on 22.12.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "log.h"
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/cfg/env.h>
#if defined(_WIN32)
#include <windows.h>
#endif
namespace TemplatePlugin {
std::shared_ptr<spdlog::logger> Log::m_FP_logger;
void Log::Init() {
#if defined(_WIN32)
HANDLE hOut = GetStdHandle(STD_ERROR_HANDLE);
DWORD dwMode = 0;
if (GetConsoleMode(hOut, &dwMode))
SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
#endif
std::vector<spdlog::sink_ptr> sinks;
auto color_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
color_sink->set_pattern("%^[%T.%e] %n: %v%$");
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("TemplatePlugin.log", true);
file_sink->set_pattern("[%T.%e] [%^%l%$] %n: %v");
sinks.emplace_back(color_sink);
sinks.emplace_back(file_sink);
m_FP_logger = std::make_shared<spdlog::logger>("TemplatePlugin", sinks.begin(), sinks.end());
register_logger(m_FP_logger);
m_FP_logger->set_level(spdlog::level::trace);
m_FP_logger->flush_on(spdlog::level::info);
spdlog::cfg::load_env_levels();
}
void Log::Close() {
spdlog::drop("TemplatePlugin");
m_FP_logger.reset();
}
}

28
src/log.h Normal file
View file

@ -0,0 +1,28 @@
//
// Created by Michal Přikryl on 22.12.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <memory>
#include <spdlog/spdlog.h>
namespace TemplatePlugin {
class Log {
public:
static void Init();
static void Close();
static std::shared_ptr<spdlog::logger>& GetLogger() { return m_FP_logger; }
private:
static std::shared_ptr<spdlog::logger> m_FP_logger;
};
}
#define FP_TRACE(fmt, ...) ::TemplatePlugin::Log::GetLogger()->trace("- [ " fmt " ] -", ##__VA_ARGS__)
#define FP_DEBUG(fmt, ...) ::TemplatePlugin::Log::GetLogger()->debug("- [ " fmt " ] -", ##__VA_ARGS__)
#define FP_INFO(fmt, ...) ::TemplatePlugin::Log::GetLogger()->info("- [ " fmt " ] -", ##__VA_ARGS__)
#define FP_WARN(fmt, ...) ::TemplatePlugin::Log::GetLogger()->warn("- [ " fmt " ] -", ##__VA_ARGS__)
#define FP_ERROR(fmt, ...) ::TemplatePlugin::Log::GetLogger()->error("- [ " fmt " ] -", ##__VA_ARGS__)
#define FP_CRITICAL(fmt, ...) ::TemplatePlugin::Log::GetLogger()->critical("- [ " fmt " ] -", ##__VA_ARGS__)

29
src/path.h Normal file
View file

@ -0,0 +1,29 @@
//
// Created by Michal Přikryl on 19.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <eiface.h>
#include <string>
#include "Shared.h"
namespace TemplatePlugin::Paths {
static std::string gameDirectory;
inline std::string GameDirectory() {
if (gameDirectory.empty()) {
CBufferStringGrowable<255> gamePath;
shared::g_pEngine->GetGameDir(gamePath);
gameDirectory = std::string(gamePath.Get());
}
return gameDirectory;
}
inline std::string GetRootDirectory() { return GameDirectory() + "/addons/TemplatePlugin"; }
inline std::string EnginePath() { return GameDirectory() + "../bin/linuxsteamrt64/libengine2.so"; }
inline std::string Tier0Path() { return GameDirectory() + "../bin/linuxsteamrt64/libtier0.so"; }
inline std::string ServerPath() { return GameDirectory() + "/bin/linuxsteamrt64/libserver.so"; }
inline std::string SchemaSystemPath() { return GameDirectory() + "../bin/linuxsteamrt64/libschemasystem.so"; }
inline std::string VScriptPath() { return GameDirectory() + "../bin/linuxsteamrt64/libvscript.so"; }
} // namespace TemplatePlugin::Paths

65
src/prints.cpp Normal file
View file

@ -0,0 +1,65 @@
//
// Created by Michal Přikryl on 10.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "prints.h"
#include "PlayersData.h"
#include <igameeventsystem.h>
#include <regex>
#include <networksystem/inetworkmessages.h>
#include "usermessages.pb.h"
#include "tier0/memdbgon.h"
namespace TemplatePlugin::Prints
{
std::string ReplaceColorTags(const std::string &input) {
static const std::vector<std::pair<std::string, std::string> > tags = {
{"[[DEFAULT]]", "\x01"}, {"[[DARKRED]]", "\x02"}, {"[[LIGHTPURPLE]]", "\x03"}, {"[[GREEN]]", "\x04"},
{"[[OLIVE]]", "\x05"}, {"[[LIME]]", "\x06"}, {"[[RED]]", "\x07"}, {"[[GREY]]", "\x08"},
{"[[YELLOW]]", "\x09"}, {"[[SILVER]]", "\x0A"}, {"[[BLUE]]", "\x0B"}, {"[[DARKBLUE]]", "\x0C"},
{"[[ORANGE]]", "\x10"}, {"[[PURPLE]]", "\x0E"}
};
std::string result = input;
for (const auto &[tag, code]: tags) {
size_t pos;
while ((pos = result.find(tag)) != std::string::npos)
result.replace(pos, tag.length(), code);
}
return result;
}
std::string ReplaceFormatTags(const std::string &input) {
std::string result = ReplaceColorTags(input);
return result;
}
void ChatToPlayer(CCSPlayerController* player, const std::string& msg)
{
if (!player || !player->CheckValid())
return;
ClientPrintNative(player->GetPlayerSlot(), HudDestination::Chat, ReplaceColorTags(" " + msg).c_str());
}
void ClientPrintNative(CPlayerSlot slot, HudDestination dest, const char* msg)
{
INetworkMessageInternal* pNetMsg = shared::g_pNetworkMessages->FindNetworkMessagePartial("TextMsg");
auto data = pNetMsg->AllocateMessage()->ToPB<CUserMessageTextMsg>();
data->set_dest(static_cast<google::protobuf::uint32>(dest));
data->add_param(msg);
CPlayerBitVec recipients;
recipients.Set(slot.Get());
shared::g_pGameEventSystem->PostEventAbstract(
CSplitScreenSlot(-1), false, ABSOLUTE_PLAYER_LIMIT,
reinterpret_cast<const uint64*>(recipients.Base()), pNetMsg, data,
0, BUF_RELIABLE
);
delete data;
}
}

35
src/prints.h Normal file
View file

@ -0,0 +1,35 @@
//
// Created by Michal Přikryl on 10.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <string>
#include <vector>
#include "schema/CCSPlayerController.h"
#include "log.h"
namespace TemplatePlugin::Prints
{
enum class HudDestination : int
{
Center = 1,
Alert = 2,
Chat = 3
};
void ClientPrintNative(CPlayerSlot slot, HudDestination dest, const char* msg);
std::string ReplaceColorTags(const std::string& input);
std::string ReplaceFormatTags(const std::string &input);
template<typename... Args>
std::string FormatMessage(fmt::format_string<Args...> fmtStr, Args &&... args) {
std::string formatted = fmt::format(fmtStr, std::forward<Args>(args)...);
return ReplaceFormatTags(formatted);
}
inline const char* PrefixCl() { return "[[DARKRED]][!][[DEFAULT]] "; }
void ChatToPlayer(CCSPlayerController* player, const std::string& msg);
}

View file

@ -0,0 +1,45 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
#include "CBaseModelEntity.h"
namespace
TemplatePlugin
{
class IChoreoServices
{
DECLARE_SCHEMA_CLASS(IChoreoServices)
};
class PhysicsRagdollPose_t
{
DECLARE_SCHEMA_CLASS(PhysicsRagdollPose_t)
SCHEMA_FIELD_POINTER(CUtlVector<CTransform>, m_Transforms)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hOwner)
SCHEMA_FIELD(bool, m_bSetFromDebugHistory)
};
class CBaseAnimGraph : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CBaseAnimGraph)
SCHEMA_FIELD(bool, m_bInitiallyPopulateInterpHistory)
SCHEMA_FIELD(IChoreoServices *, m_pChoreoServices)
SCHEMA_FIELD(bool, m_bAnimGraphUpdateEnabled)
SCHEMA_FIELD(float, m_flMaxSlopeDistance)
SCHEMA_FIELD(Vector, m_vLastSlopeCheckPos)
SCHEMA_FIELD(bool, m_bAnimationUpdateScheduled)
SCHEMA_FIELD(Vector, m_vecForce)
SCHEMA_FIELD(int32, m_nForceBone)
SCHEMA_FIELD(PhysicsRagdollPose_t, m_RagdollPose)
SCHEMA_FIELD(bool, m_bRagdollEnabled)
SCHEMA_FIELD(bool, m_bRagdollClientSide)
SCHEMA_FIELD(CTransform, m_xParentedRagdollRootInEntitySpace)
};
}

53
src/schema/CBaseButton.h Normal file
View file

@ -0,0 +1,53 @@
//
// Created by Michal Přikryl on 07.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseTrigger.h"
#include "globaltypes.h"
namespace
TemplatePlugin
{
class locksound_t
{
public:
DECLARE_SCHEMA_CLASS(locksound_t);
SCHEMA_FIELD_POINTER(char, sLockedSound)
SCHEMA_FIELD_POINTER(char, sUnlockedSound)
SCHEMA_FIELD(float, flwaitSound)
};
class CBaseButton : public CBaseToggle
{
public:
DECLARE_SCHEMA_CLASS(CBaseButton);
SCHEMA_FIELD(QAngle, m_angMoveEntitySpace)
SCHEMA_FIELD(bool, m_fStayPushed)
SCHEMA_FIELD(bool, m_fRotating)
SCHEMA_FIELD(locksound_t, m_ls)
SCHEMA_FIELD_POINTER(char, m_sUseSound)
SCHEMA_FIELD_POINTER(char, m_sLockedSound)
SCHEMA_FIELD_POINTER(char, m_sUnlockedSound)
SCHEMA_FIELD_POINTER(char, m_sOverrideAnticipationName)
SCHEMA_FIELD(bool, m_bLocked)
SCHEMA_FIELD(bool, m_bDisabled)
SCHEMA_FIELD(float, m_flUseLockedTime)
SCHEMA_FIELD(bool, m_bSolidBsp)
SCHEMA_FIELD(CEntityIOOutput, m_OnDamaged)
SCHEMA_FIELD(CEntityIOOutput, m_OnPressed)
SCHEMA_FIELD(CEntityIOOutput, m_OnUseLocked)
SCHEMA_FIELD(CEntityIOOutput, m_OnIn)
SCHEMA_FIELD(CEntityIOOutput, m_OnOut)
SCHEMA_FIELD(int32, m_nState)
SCHEMA_FIELD(CHandle<CEntityInstance>, m_hConstraint)
SCHEMA_FIELD(CHandle<CEntityInstance>, m_hConstraintParent)
SCHEMA_FIELD(bool, m_bForceNpcExclude)
SCHEMA_FIELD_POINTER(char, m_sGlowEntity)
SCHEMA_FIELD(CHandle<CBaseModelEntity>, m_glowEntity)
SCHEMA_FIELD(bool, m_usable)
SCHEMA_FIELD_POINTER(char, m_szDisplayText)
};
}

485
src/schema/CBaseEntity.h Normal file
View file

@ -0,0 +1,485 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <entitysystem.h>
#include <entity2/entityidentity.h>
#include "mathlib/vector.h"
#include "schemasystem.h"
#include "ccollisionproperty.h"
#include "globaltypes.h"
#include "ctakedamageinfo.h"
#include "virtual.h"
#include "dynlibutils/module.h"
#include "Shared.h"
namespace
TemplatePlugin
{
using UTIL_CreateEntityByName_t = CEntityInstance* (*)(const char* /*name*/, int /*forceEdictIndex*/);
inline UTIL_CreateEntityByName_t g_UTIL_CreateEntityByName = nullptr;
template <typename T>
inline T* UTIL_CreateEntityByName(const char* name)
{
if (!g_UTIL_CreateEntityByName)
{
UTIL_CreateEntityByName_t addr = DynLibUtils::CModule(shared::g_pServer).FindPattern(
shared::g_pGameConfig->GetSignature("UTIL_CreateEntityByName")).RCast<UTIL_CreateEntityByName_t>();
if (!addr)
return nullptr;
g_UTIL_CreateEntityByName = addr;
}
return reinterpret_cast<T*>(g_UTIL_CreateEntityByName(name, -1));
}
template <typename T>
inline std::vector<T*> UTIL_FindAllEntitiesByDesignerName(const char* designerName)
{
std::vector<T*> results;
if (!designerName || !shared::g_pEntitySystem)
return results;
auto* it = shared::g_pEntitySystem->m_EntityList.m_pFirstActiveEntity;
for (; it; it = it->m_pNext)
{
if (!it->m_pInstance) continue;
const char* dn = it->m_designerName.String();
if (!dn) continue;
if (std::strcmp(dn, designerName) == 0)
results.push_back((T*)it->m_pInstance);
}
return results;
}
inline CEntityInstance* UTIL_GetEntityByIndex(int index)
{
if (!shared::g_pEntitySystem) return nullptr;
CEntityIdentity* pEntity = shared::g_pEntitySystem->m_EntityList.m_pFirstActiveEntity;
for (; pEntity; pEntity = pEntity->m_pNext)
{
if (pEntity->m_EHandle.GetEntryIndex() == index)
return pEntity->m_pInstance;
};
return nullptr;
}
inline CEntityInstance* UTIL_FindEntityByClassname(const char* name)
{
if (!shared::g_pEntitySystem) return nullptr;
CEntityIdentity* pEntity = shared::g_pEntitySystem->m_EntityList.m_pFirstActiveEntity;
for (; pEntity; pEntity = pEntity->m_pNext)
{
if (!strcmp(pEntity->m_designerName.String(), name))
return pEntity->m_pInstance;
};
return nullptr;
}
inline CEntityInstance* UTIL_FindEntityByEHandle(CEntityInstance* pFind)
{
if (!shared::g_pEntitySystem) return nullptr;
CEntityIdentity* pEntity = shared::g_pEntitySystem->m_EntityList.m_pFirstActiveEntity;
for (; pEntity; pEntity = pEntity->m_pNext)
{
if (pEntity->GetRefEHandle() == pFind)
return pEntity->m_pInstance;
};
return nullptr;
}
typedef void (*CEntityInstance_AcceptInput_t)(CEntityInstance* pThis, const char* pInputName,
CEntityInstance* pActivator, CEntityInstance* pCaller,
const variant_t& pValue, int nOutputID, void* pUnk1);
inline CEntityInstance_AcceptInput_t g_CEntityInstance_AcceptInput = nullptr;
typedef void (*CEntitySystem_AddEntityIOEvent_t)(CEntitySystem* pEntitySystem, CEntityInstance* pThis,
const char* pInputName, CEntityInstance* pActivator,
CEntityInstance* pCaller, const variant_t& pValue, float delay,
int nOutputID, void* pUnk1, void* pUnk2);
inline CEntitySystem_AddEntityIOEvent_t g_CEntitySystem_AddEntityIOEvent = nullptr;
class CGameSceneNode
{
public:
DECLARE_SCHEMA_CLASS(CGameSceneNode)
SCHEMA_FIELD(CEntityInstance*, m_pOwner);
SCHEMA_FIELD(CGameSceneNode*, m_pParent);
SCHEMA_FIELD(CGameSceneNode*, m_pChild);
SCHEMA_FIELD(CNetworkOriginCellCoordQuantizedVector, m_vecOrigin);
SCHEMA_FIELD(QAngle, m_angRotation);
SCHEMA_FIELD(float, m_flScale);
SCHEMA_FIELD(float, m_flAbsScale);
SCHEMA_FIELD(Vector, m_vecAbsOrigin);
SCHEMA_FIELD(QAngle, m_angAbsRotation);
SCHEMA_FIELD(Vector, m_vRenderOrigin);
matrix3x4_t EntityToWorldTransform()
{
matrix3x4_t mat;
QAngle angles = this->m_angAbsRotation();
float sr, sp, sy, cr, cp, cy;
SinCos(DEG2RAD(angles[YAW]), &sy, &cy);
SinCos(DEG2RAD(angles[PITCH]), &sp, &cp);
SinCos(DEG2RAD(angles[ROLL]), &sr, &cr);
mat[0][0] = cp * cy;
mat[1][0] = cp * sy;
mat[2][0] = -sp;
float crcy = cr * cy;
float crsy = cr * sy;
float srcy = sr * cy;
float srsy = sr * sy;
mat[0][1] = sp * srcy - crsy;
mat[1][1] = sp * srsy + crcy;
mat[2][1] = sr * cp;
mat[0][2] = (sp * crcy + srsy);
mat[1][2] = (sp * crsy - srcy);
mat[2][2] = cr * cp;
Vector pos = this->m_vecAbsOrigin();
mat[0][3] = pos.x;
mat[1][3] = pos.y;
mat[2][3] = pos.z;
return mat;
}
};
class CBodyComponent
{
public:
DECLARE_SCHEMA_CLASS(CBodyComponent)
SCHEMA_FIELD(CGameSceneNode *, m_pSceneNode);
};
class CModelState
{
public:
DECLARE_SCHEMA_CLASS(CModelState)
SCHEMA_FIELD(CUtlSymbolLarge, m_ModelName)
SCHEMA_FIELD(uint64, m_MeshGroupMask)
};
class CSkeletonInstance : public CGameSceneNode
{
public:
DECLARE_SCHEMA_CLASS(CSkeletonInstance)
SCHEMA_FIELD(CModelState, m_modelState)
};
class CEntitySubclassVDataBase
{
public:
DECLARE_SCHEMA_CLASS(CEntitySubclassVDataBase)
};
class CBaseEntity : public CEntityInstance
{
public:
DECLARE_SCHEMA_CLASS(CBaseEntity)
SCHEMA_FIELD(float32, m_flSimulationTime)
SCHEMA_FIELD(float, m_flCreateTime);
SCHEMA_FIELD(CBodyComponent *, m_CBodyComponent)
SCHEMA_FIELD(CBitVec<64>, m_isSteadyState)
SCHEMA_FIELD(float, m_lastNetworkChange)
SCHEMA_FIELD_POINTER(CNetworkTransmitComponent, m_NetworkTransmitComponent)
SCHEMA_FIELD_POINTER(char, m_iszDamageFilterName);
SCHEMA_FIELD(int, m_iHealth)
SCHEMA_FIELD(int, m_iMaxHealth)
SCHEMA_FIELD(int, m_iTeamNum)
SCHEMA_FIELD(bool, m_bLagCompensate)
SCHEMA_FIELD(Vector, m_vecAbsVelocity)
SCHEMA_FIELD(Vector, m_vecBaseVelocity)
SCHEMA_FIELD(CCollisionProperty*, m_pCollision)
SCHEMA_FIELD(MoveCollide_t, m_MoveCollide)
SCHEMA_FIELD(MoveType_t, m_MoveType)
SCHEMA_FIELD(MoveType_t, m_nActualMoveType)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hEffectEntity)
SCHEMA_FIELD(uint32, m_spawnflags)
SCHEMA_FIELD(uint32, m_fFlags)
SCHEMA_FIELD(LifeState_t, m_lifeState)
SCHEMA_FIELD(float, m_flDamageAccumulator)
SCHEMA_FIELD(bool, m_bTakesDamage)
SCHEMA_FIELD(TakeDamageFlags_t, m_nTakeDamageFlags)
SCHEMA_FIELD_POINTER(CUtlStringToken, m_nSubclassID)
SCHEMA_FIELD(float, m_flFriction)
SCHEMA_FIELD(float, m_flActualGravityScale)
SCHEMA_FIELD(float, m_flTimeScale)
SCHEMA_FIELD(float, m_flSpeed)
SCHEMA_FIELD(CUtlString, m_sUniqueHammerID)
SCHEMA_FIELD(CUtlSymbolLarge, m_target)
SCHEMA_FIELD(CUtlSymbolLarge, m_iGlobalname)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hOwnerEntity)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hGroundEntity);
SCHEMA_FIELD(uint32, m_fEffects)
SCHEMA_FIELD(QAngle, m_vecAngVelocity)
// ---------------------------
// Flag helpers
// ---------------------------
void SetFlags(uint32_t mask)
{
auto& flags = m_fFlags();
flags = mask;
}
void AddFlags(uint32_t mask)
{
auto& flags = m_fFlags();
flags |= mask;
}
void ClearFlags(uint32_t mask)
{
auto& flags = m_fFlags();
flags &= ~mask;
}
bool HasFlags(uint32_t mask) const
{
return (const_cast<CBaseEntity*>(this)->m_fFlags() & mask) == mask;
}
uint32_t GetFlags() const
{
return const_cast<CBaseEntity*>(this)->m_fFlags();
}
// ---------------------------
// Basic entity info
// ---------------------------
Vector GetAbsOrigin()
{
if (!m_CBodyComponent) return Vector{};
if (!m_CBodyComponent->m_pSceneNode) return Vector{};
return m_CBodyComponent->m_pSceneNode->m_vecAbsOrigin();
}
QAngle GetAngRotation()
{
if (!m_CBodyComponent) return QAngle{};
if (!m_CBodyComponent->m_pSceneNode) return QAngle{};
return m_CBodyComponent->m_pSceneNode->m_angRotation();
}
QAngle GetAbsRotation()
{
if (!m_CBodyComponent) return QAngle{};
if (!m_CBodyComponent->m_pSceneNode) return QAngle{};
return m_CBodyComponent->m_pSceneNode->m_angAbsRotation();
}
Vector GetAbsVelocity() { return m_vecAbsVelocity; }
void SetAbsOrigin(const Vector& vecOrigin)
{
if (!m_CBodyComponent) return;
if (!m_CBodyComponent->m_pSceneNode) return;
m_CBodyComponent->m_pSceneNode->m_vecAbsOrigin(vecOrigin);
}
void SetAbsRotation(const QAngle& angAbsRotation)
{
if (!m_CBodyComponent) return;
if (!m_CBodyComponent->m_pSceneNode) return;
m_CBodyComponent->m_pSceneNode->m_angAbsRotation(angAbsRotation);
}
void SetAngRotation(const QAngle& angRotation)
{
if (!m_CBodyComponent) return;
if (!m_CBodyComponent->m_pSceneNode) return;
m_CBodyComponent->m_pSceneNode->m_angRotation(angRotation);
}
void SetAbsVelocity(const Vector& vecVelocity) { m_vecAbsVelocity = vecVelocity; }
void SetBaseVelocity(const Vector& vecVelocity) { m_vecBaseVelocity = vecVelocity; }
CEntitySubclassVDataBase* GetVData()
{
return *(CEntitySubclassVDataBase**)((uint8*)(m_nSubclassID()) + 4);
}
// ---------------------------
// Engine calls
// ---------------------------
void Teleport(const Vector* position, const QAngle* angles, const Vector* velocity)
{
static int offset = shared::g_pGameConfig->GetOffset("CBaseEntity_Teleport");
CALL_VIRTUAL(void, offset, this, position, angles, velocity);
}
void SetMoveType(MoveType_t nMoveType)
{
m_MoveType() = nMoveType;
m_nActualMoveType() = nMoveType;
}
void CollisionRulesChanged()
{
static int offset = shared::g_pGameConfig->GetOffset("CBaseEntity_CollisionRulesChanged");
CALL_VIRTUAL(void, offset, this);
}
int GetTeam() { return m_iTeamNum(); }
bool IsAlive() { return m_lifeState() == LifeState_t::LIFE_ALIVE; }
CHandle<CBaseEntity> GetHandle() const { return m_pEntity->m_EHandle; }
const char* GetName() const { return m_pEntity->m_name.String(); }
const char* GetDesignerName() const { return m_pEntity ? m_pEntity->m_designerName.String() : ""; }
// ---------------------------
// Detours
// ---------------------------
void DispatchSpawn()
{
using DispatchSpawn_t = void (*)(CBaseEntity* /*self*/, void* /*pMapData*/);
static DispatchSpawn_t s_DispatchSpawn = nullptr;
if (!s_DispatchSpawn)
{
DispatchSpawn_t addr = DynLibUtils::CModule(shared::g_pServer).FindPattern(
shared::g_pGameConfig->GetSignature("CBaseEntity_DispatchSpawn")).RCast<DispatchSpawn_t>();
if (!addr) return;
s_DispatchSpawn = addr;
}
s_DispatchSpawn(this, nullptr);
}
void AcceptInput(const char* pInputName, CEntityInstance* pActivator = nullptr,
CEntityInstance* pCaller = nullptr, const char* value = "")
{
if (!g_CEntityInstance_AcceptInput)
{
g_CEntityInstance_AcceptInput =
DynLibUtils::CModule(shared::g_pServer)
.FindPattern(shared::g_pGameConfig->GetSignature("CEntityInstance_AcceptInput"))
.RCast<CEntityInstance_AcceptInput_t>();
if (!g_CEntityInstance_AcceptInput)
return;
}
g_CEntityInstance_AcceptInput(this, pInputName, pActivator, pCaller, variant_t(value), 0, 0LL);
}
void AddEntityIOEvent(const char* pInputName, CEntityInstance* pActivator = nullptr,
CEntityInstance* pCaller = nullptr, const char* value = "", float flDelay = 0.0)
{
if (!g_CEntitySystem_AddEntityIOEvent)
{
g_CEntitySystem_AddEntityIOEvent =
DynLibUtils::CModule(shared::g_pServer)
.FindPattern(shared::g_pGameConfig->GetSignature("CEntitySystem_AddEntityIOEvent"))
.RCast<CEntitySystem_AddEntityIOEvent_t>();
if (!g_CEntitySystem_AddEntityIOEvent)
return;
}
g_CEntitySystem_AddEntityIOEvent(shared::g_pEntitySystem, this, pInputName, pActivator, pCaller,
variant_t(value), flDelay, 0,
0LL, 0LL);
}
};
class CBodyComponentSkeletonInstance : public CBodyComponent
{
public:
DECLARE_SCHEMA_CLASS(CBodyComponentSkeletonInstance);
};
class CBaseAnimGraphController
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CBaseAnimGraphController);
SCHEMA_FIELD(float, m_flPlaybackRate);
};
class CBodyComponentBaseAnimGraph : public CBodyComponentSkeletonInstance
{
public:
DECLARE_SCHEMA_CLASS(CBodyComponentBaseAnimGraph);
SCHEMA_FIELD(CBaseAnimGraphController, m_animationController);
};
class SpawnPoint : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(SpawnPoint);
SCHEMA_FIELD(bool, m_bEnabled);
};
}

21
src/schema/CBaseFilter.h Normal file
View file

@ -0,0 +1,21 @@
//
// Created by Michal Přikryl on 07.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseTrigger.h"
#include "globaltypes.h"
namespace
TemplatePlugin
{
class CBaseFilter : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CBaseFilter);
SCHEMA_FIELD(bool, m_bNegated)
SCHEMA_FIELD(CEntityIOOutput, m_OnPass)
SCHEMA_FIELD(CEntityIOOutput, m_OnFail)
};
}

33
src/schema/CBaseGrenade.h Normal file
View file

@ -0,0 +1,33 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseModelEntity.h"
#include "CBasePlayerPawn.h"
#include "globaltypes.h"
namespace TemplatePlugin {
class CBaseGrenade : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CBaseGrenade);
SCHEMA_FIELD(CHandle<CCSPlayerPawn>, m_hThrower);
SCHEMA_FIELD(float, m_flDamage);
SCHEMA_FIELD(float, m_DmgRadius);
};
class CBaseCSGrenadeProjectile : public CBaseGrenade
{
public:
DECLARE_SCHEMA_CLASS(CBaseCSGrenadeProjectile);
};
class CSmokeGrenadeProjectile : public CBaseCSGrenadeProjectile
{
public:
DECLARE_SCHEMA_CLASS(CSmokeGrenadeProjectile);
SCHEMA_FIELD(Vector, m_vSmokeColor);
SCHEMA_FIELD(int, m_nSmokeEffectTickBegin);
};
}

View file

@ -0,0 +1,89 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
namespace
TemplatePlugin
{
class CBaseModelEntity : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CBaseModelEntity);
SCHEMA_FIELD(CCollisionProperty, m_Collision)
SCHEMA_FIELD(CGlowProperty, m_Glow)
SCHEMA_FIELD(Color, m_clrRender)
SCHEMA_FIELD(RenderMode_t, m_nRenderMode)
SCHEMA_FIELD(RenderFx_t, m_nRenderFX)
SCHEMA_FIELD(float, m_flDissolveStartTime)
SCHEMA_FIELD(Vector, m_vecViewOffset)
SCHEMA_FIELD(float32, m_flShadowStrength)
void SetModel(const char* model)
{
using SetModel_t = void (*)(CBaseModelEntity*, const char*);
static SetModel_t s_SetModel = nullptr;
if (!s_SetModel)
{
SetModel_t addr =
DynLibUtils::CModule(shared::g_pServer)
.FindPattern(shared::g_pGameConfig->GetSignature("CBaseModelEntity_SetModel"))
.RCast<SetModel_t>();
if (!addr) return;
s_SetModel = addr;
}
s_SetModel(this, model);
}
void SetBodyGroup(std::string name, int value)
{
char bodygroupStr[64];
g_SMAPI->Format(bodygroupStr, sizeof(bodygroupStr), "%s,%i", name.c_str(), value);
this->AcceptInput(
"SetBodyGroup",
this,
this,
bodygroupStr
);
}
CUtlSymbolLarge GetModelName()
{
if (m_CBodyComponent == nullptr) return CUtlSymbolLarge();
if (m_CBodyComponent->m_pSceneNode == nullptr) return CUtlSymbolLarge();
if (((CSkeletonInstance*)m_CBodyComponent->m_pSceneNode.Get()) == nullptr) return CUtlSymbolLarge();
return ((CSkeletonInstance*)m_CBodyComponent->m_pSceneNode.Get())->m_modelState().m_ModelName.Get();
}
Vector GetEyePosition()
{
const auto x = m_vecViewOffset();
return x + GetAbsOrigin();
}
};
class CBeam : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CBeam);
SCHEMA_FIELD(Vector, m_vecEndPos);
SCHEMA_FIELD(float, m_fWidth);
SCHEMA_FIELD(float, m_fEndWidth);
};
}

View file

@ -0,0 +1,83 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "ehandle.h"
#include "CCSPlayerPawn.h"
namespace TemplatePlugin
{
enum class PlayerConnectedState : uint32_t
{
PlayerNeverConnected = 0xFFFFFFFF,
PlayerConnected = 0x0,
PlayerConnecting = 0x1,
PlayerReconnecting = 0x2,
PlayerDisconnecting = 0x3,
PlayerDisconnected = 0x4,
PlayerReserved = 0x5,
};
class CBasePlayerController : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CBasePlayerController);
SCHEMA_FIELD(CHandle<CBasePlayerPawn>, m_hPawn)
SCHEMA_FIELD_POINTER(char, m_iszPlayerName)
SCHEMA_FIELD(PlayerConnectedState, m_iConnected)
SCHEMA_FIELD(uint32_t, m_iDesiredFOV);
SCHEMA_FIELD_POINTER(uint64_t, m_steamID)
SCHEMA_FIELD_POINTER(bool, m_bIsHLTV)
CBasePlayerPawn* GetPawn() { return m_hPawn.Get(); }
const char* GetPlayerName() { return m_iszPlayerName(); }
void SetPlayerName(const std::string& name)
{
std::strncpy(m_iszPlayerName(), name.c_str(), 128);
m_iszPlayerName()[127] = '\0';
}
int GetPlayerSlot() { return GetEntityIndex().Get() - 1; }
bool IsConnected() { return m_iConnected() == PlayerConnectedState::PlayerConnected; }
uint64_t GetSteamID() { return *m_steamID(); }
void SetSteamID(uint64_t id) { *m_steamID() = id; }
bool GetIsHLTV() { return *m_bIsHLTV(); }
void SetIsHLTV(bool value) { *m_bIsHLTV() = value; }
uint64_t& SteamID() { return *m_steamID(); }
bool& IsHLTV() { return *m_bIsHLTV(); }
using CBasePlayerController_SetPawn_t = void(*)(CBasePlayerController* pController, CCSPlayerPawn* pPawn,
bool a3, bool a4, bool a5, bool a6);
void SetPawn(CCSPlayerPawn* pawn)
{
if (!pawn) return;
CBasePlayerController_SetPawn_t CBasePlayerController_SetPawn = nullptr;
if (!CBasePlayerController_SetPawn)
{
CBasePlayerController_SetPawn_t addr = addr =
DynLibUtils::CModule(shared::g_pServer)
.FindPattern(shared::g_pGameConfig->GetSignature("CBasePlayerController_SetPawn"))
.RCast<CBasePlayerController_SetPawn_t>();
if (!addr)
return;
CBasePlayerController_SetPawn = addr;
}
CBasePlayerController_SetPawn(this, pawn, true, false, false, false);
}
};
}

View file

@ -0,0 +1,76 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "CBaseModelEntity.h"
#include "services.h"
namespace TemplatePlugin {
enum class Hull_t : uint32
{
HULL_HUMAN = 0,
HULL_SMALL_CENTERED = 1,
HULL_WIDE_HUMAN = 2,
HULL_TINY = 3,
HULL_MEDIUM = 4,
HULL_TINY_CENTERED = 5,
HULL_LARGE = 6,
HULL_LARGE_CENTERED = 7,
HULL_MEDIUM_TALL = 8,
HULL_SMALL = 9,
NUM_HULLS = 10,
HULL_NONE = 11,
};
class CMovementStatsProperty
{
public:
DECLARE_SCHEMA_CLASS(CMovementStatsProperty);
SCHEMA_FIELD(uint32, m_nUseCounter);
SCHEMA_FIELD(void*, m_emaMovementDirection);
};
class CBaseCombatCharacter : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CBaseCombatCharacter);
SCHEMA_FIELD(bool, m_bForceServerRagdoll);
SCHEMA_FIELD_POINTER(CUtlVector<CHandle<CEconWearable>>, m_hMyWearables)
SCHEMA_FIELD(float, m_impactEnergyScale);
SCHEMA_FIELD(bool, m_bApplyStressDamage);
SCHEMA_FIELD(bool, m_bDeathEventsDispatched);
SCHEMA_FIELD(CUtlString, m_strRelationships);
SCHEMA_FIELD(Hull_t, m_eHull);
SCHEMA_FIELD(uint32, m_nNavHullIdx);
SCHEMA_FIELD(CMovementStatsProperty, m_movementStats);
};
class CBasePlayerPawn : public CBaseCombatCharacter
{
public:
DECLARE_SCHEMA_CLASS(CBasePlayerPawn);
SCHEMA_FIELD(CCSPlayer_MovementServices*, m_pMovementServices)
SCHEMA_FIELD(CCSPlayer_WeaponServices*, m_pWeaponServices)
SCHEMA_FIELD(CCSPlayer_ItemServices*, m_pItemServices)
SCHEMA_FIELD(CPlayer_ObserverServices*, m_pObserverServices)
SCHEMA_FIELD(CPlayer_CameraServices*, m_pCameraServices)
SCHEMA_FIELD(CHandle<CBasePlayerController>, m_hController)
SCHEMA_FIELD(QAngle, v_angle)
SCHEMA_FIELD(QAngle, v_anglePrevious)
SCHEMA_FIELD(uint32, m_iHideHUD)
SCHEMA_FIELD(bool, m_fInitHUD)
void CommitSuicide(bool bExplode, bool bForce)
{
static int offset = shared::g_pGameConfig->GetOffset("CBasePlayerPawn_CommitSuicide");
CALL_VIRTUAL(void, offset, this, bExplode, bForce);
}
CBasePlayerController *GetController() { return m_hController.Get(); }
};
}

23
src/schema/CBaseProp.h Normal file
View file

@ -0,0 +1,23 @@
//
// Created by Michal Přikryl on 31.10.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
#include "CBaseAnimGraph.h"
namespace
TemplatePlugin
{
class CBaseProp : public CBaseAnimGraph
{
public:
DECLARE_SCHEMA_CLASS(CBaseProp)
SCHEMA_FIELD(bool, m_bModelOverrodeBlockLOS)
SCHEMA_FIELD(int, m_iShapeType)
SCHEMA_FIELD(bool, m_bConformToCollisionBounds)
SCHEMA_FIELD(CTransform, m_mPreferredCatchTransform)
};
}

79
src/schema/CBaseTrigger.h Normal file
View file

@ -0,0 +1,79 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseModelEntity.h"
#include "schemasystem.h"
#define SF_TRIG_PUSH_ONCE 0x80
namespace TemplatePlugin {
enum TOGGLE_STATE : uint32_t
{
TS_AT_TOP = 0,
TS_AT_BOTTOM = 1,
TS_GOING_UP = 2,
TS_GOING_DOWN = 3,
DOOR_OPEN = 0,
DOOR_CLOSED = 1,
DOOR_OPENING = 2,
DOOR_CLOSING = 3,
};
class CBaseToggle : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CBaseToggle);
SCHEMA_FIELD(TOGGLE_STATE, m_toggle_state)
SCHEMA_FIELD(float32, m_flMoveDistance)
SCHEMA_FIELD(float32, m_flWait)
SCHEMA_FIELD(float32, m_flLip)
SCHEMA_FIELD(bool, m_bAlwaysFireBlockedOutputs)
SCHEMA_FIELD(Vector, m_vecPosition1)
SCHEMA_FIELD(Vector, m_vecPosition2)
SCHEMA_FIELD(QAngle, m_vecMoveAng)
SCHEMA_FIELD(QAngle, m_vecAngle1)
SCHEMA_FIELD(QAngle, m_vecAngle2)
SCHEMA_FIELD(float32, m_flHeight)
SCHEMA_FIELD(CEntityHandle, m_hActivator)
SCHEMA_FIELD(Vector, m_vecFinalDest)
SCHEMA_FIELD(QAngle, m_vecFinalAngle)
SCHEMA_FIELD(int32, m_movementType)
SCHEMA_FIELD(CUtlSymbolLarge, m_sMaster)
};
class CBaseTrigger : public CBaseToggle
{
public:
DECLARE_SCHEMA_CLASS(CBaseTrigger);
SCHEMA_FIELD(CEntityIOOutput, m_OnStartTouch);
SCHEMA_FIELD(CEntityIOOutput, m_OnStartTouchAll);
SCHEMA_FIELD(CEntityIOOutput, m_OnEndTouch);
SCHEMA_FIELD(CEntityIOOutput, m_OnEndTouchAll);
SCHEMA_FIELD(CEntityIOOutput, m_OnTouching);
SCHEMA_FIELD(CEntityIOOutput, m_OnTouchingEachEntity);
SCHEMA_FIELD(CEntityIOOutput, m_OnNotTouching);
SCHEMA_FIELD_POINTER(CUtlVector<CHandle<CBaseEntity>>, m_hTouchingEntities)
SCHEMA_FIELD(CUtlSymbolLarge, m_iFilterName)
SCHEMA_FIELD(CEntityHandle, m_hFilter)
SCHEMA_FIELD(bool, m_bDisabled);
SCHEMA_FIELD(bool, m_bUseAsyncQueries);
};
class CTriggerMultiple : public CBaseTrigger
{
public:
DECLARE_SCHEMA_CLASS(CTriggerMultiple);
SCHEMA_FIELD(CEntityIOOutput, m_OnTrigger);
};
class CBombTarget : public CBaseTrigger
{
public:
DECLARE_SCHEMA_CLASS(CBombTarget);
SCHEMA_FIELD(bool, m_bIsBombSiteB);
};
}

View file

@ -0,0 +1,81 @@
//
// Created by Michal Přikryl on 01.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
#include "CBaseProp.h"
namespace
TemplatePlugin
{
class CPropDataComponent : public CEntityComponent
{
public:
DECLARE_SCHEMA_CLASS(CPropDataComponent)
SCHEMA_FIELD(float, m_flDmgModBullet)
SCHEMA_FIELD(float, m_flDmgModClub)
SCHEMA_FIELD(float, m_flDmgModExplosive)
SCHEMA_FIELD(float, m_flDmgModFire)
SCHEMA_FIELD_POINTER(char, m_iszPhysicsDamageTableName)
SCHEMA_FIELD_POINTER(char, m_iszBasePropData)
SCHEMA_FIELD(int32, m_nInteractions)
SCHEMA_FIELD(bool, m_bSpawnMotionDisabled)
SCHEMA_FIELD(int32, m_nDisableTakePhysicsDamageSpawnFlag)
SCHEMA_FIELD(int32, m_nMotionDisabledSpawnFlag)
};
enum BreakableContentsType_t : uint
{
BC_DEFAULT = 0x0,
BC_EMPTY = 0x1,
BC_PROP_GROUP_OVERRIDE = 0x2,
BC_PARTICLE_SYSTEM_OVERRIDE = 0x3,
};
enum PerformanceMode_t : uint
{
PM_NORMAL = 0x0,
PM_NO_GIBS = 0x1,
};
class CBreakableProp : public CBaseProp
{
public:
DECLARE_SCHEMA_CLASS(CBreakableProp);
SCHEMA_FIELD(CPropDataComponent, m_CPropDataComponent)
SCHEMA_FIELD(CEntityIOOutput, m_OnStartDeath)
SCHEMA_FIELD(CEntityIOOutput, m_OnBreak)
SCHEMA_FIELD(CEntityIOOutput, m_OnTakeDamage)
SCHEMA_FIELD(float, m_impactEnergyScale)
SCHEMA_FIELD(int32, m_iMinHealthDmg)
SCHEMA_FIELD(QAngle, m_preferredCarryAngles)
SCHEMA_FIELD(float, m_flPressureDelay)
SCHEMA_FIELD(float, m_flDefBurstScale)
SCHEMA_FIELD(Vector, m_vDefBurstOffset)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hBreaker)
SCHEMA_FIELD(PerformanceMode_t, m_PerformanceMode)
SCHEMA_FIELD(float, m_flPreventDamageBeforeTime)
SCHEMA_FIELD(BreakableContentsType_t, m_BreakableContentsType)
SCHEMA_FIELD_POINTER(char, m_strBreakableContentsPropGroupOverride)
SCHEMA_FIELD_POINTER(char, m_strBreakableContentsParticleOverride)
SCHEMA_FIELD(bool, m_bHasBreakPiecesOrCommands)
SCHEMA_FIELD(float, m_explodeDamage)
SCHEMA_FIELD(float, m_explodeRadius)
SCHEMA_FIELD(float, m_explosionDelay)
SCHEMA_FIELD_POINTER(char, m_explosionBuildupSound)
SCHEMA_FIELD_POINTER(char, m_explosionCustomEffect)
SCHEMA_FIELD_POINTER(char, m_explosionCustomSound)
SCHEMA_FIELD_POINTER(char, m_explosionModifier)
SCHEMA_FIELD(CHandle<CBasePlayerPawn>, m_hPhysicsAttacker)
SCHEMA_FIELD(float, m_flLastPhysicsInfluenceTime)
SCHEMA_FIELD(float, m_flDefaultFadeScale)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hLastAttacker)
SCHEMA_FIELD_POINTER(char, m_iszPuntSound)
SCHEMA_FIELD(bool, m_bUsePuntSound)
SCHEMA_FIELD(bool, m_bOriginalBlockLOS)
};
}

18
src/schema/CC4.h Normal file
View file

@ -0,0 +1,18 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <entity2/entityidentity.h>
#include "CGameRules.h"
#include "schemasystem.h"
namespace TemplatePlugin {
class CC4
{
public:
DECLARE_SCHEMA_CLASS(CC4);
SCHEMA_FIELD(GameTime_t, m_fArmedTime);
};
}

View file

@ -0,0 +1,727 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <colors.h>
#include <detourtypes.h>
#include <tasks.h>
#include <vectorextends.h>
#include "CBasePlayerController.h"
#include "serversideclient.h"
#include "services.h"
#include "Shared.h"
#include "RayTrace.h"
namespace TemplatePlugin {
extern CServerSideClient* GetClientBySlot(CPlayerSlot slot);
class CCSPlayerController : public CBasePlayerController
{
public:
DECLARE_SCHEMA_CLASS(CCSPlayerController);
SCHEMA_FIELD(CCSPlayerController_InGameMoneyServices*, m_pInGameMoneyServices);
SCHEMA_FIELD(CCSPlayerController_ActionTrackingServices*, m_pActionTrackingServices);
SCHEMA_FIELD(CCSPlayerController_InventoryServices*, m_pInventoryServices);
SCHEMA_FIELD(uint32_t, m_iPing);
SCHEMA_FIELD(CUtlSymbolLarge, m_szClan);
SCHEMA_FIELD_POINTER(char, m_szClanName) // char m_szClanName[32]
SCHEMA_FIELD(bool, m_bEverFullyConnected);
SCHEMA_FIELD(bool, m_bPawnIsAlive);
SCHEMA_FIELD(int32_t, m_nDisconnectionTick);
SCHEMA_FIELD(CHandle<CCSPlayerPawn>, m_hPlayerPawn);
SCHEMA_FIELD(int32, m_DesiredObserverMode);
SCHEMA_FIELD(int16_t, m_nPawnCharacterDefIndex);
SCHEMA_FIELD(CHandle<CCSPlayerPawnBase>, m_hObserverPawn);
SCHEMA_FIELD(CHandle<CCSPlayerController>, m_hOriginalControllerOfCurrentPawn);
SCHEMA_FIELD(uint32_t, m_iPawnHealth);
SCHEMA_FIELD(int32_t, m_iPawnArmor);
SCHEMA_FIELD(int32_t, m_iScore);
SCHEMA_FIELD(int32_t, m_iRoundScore);
SCHEMA_FIELD(int32_t, m_iRoundsWon);
SCHEMA_FIELD(int32_t, m_iMVPs);
SCHEMA_FIELD(float, m_flSmoothedPing);
SCHEMA_FIELD(GameTime_t, m_flForceTeamTime);
SCHEMA_FIELD(int32_t, m_iCompetitiveRanking);
SCHEMA_FIELD(int8_t, m_iCompetitiveRankType);
SCHEMA_FIELD(int32_t, m_iCompetitiveWins);
SCHEMA_FIELD(byte, m_iPendingTeamNum);
SCHEMA_FIELD(byte, m_bTeamChanged);
SCHEMA_FIELD(byte, m_bInSwitchTeam);
static CCSPlayerController* FromUserId(int userid)
{
for (int i = 0; i < shared::getGlobalVars()->maxClients; ++i)
{
CCSPlayerController* controller = FromSlot(i);
if (!controller)
continue;
int iUserId = shared::g_pEngine->GetPlayerUserId(i).Get();
if (userid == iUserId) return controller;
}
return nullptr;
}
static CCSPlayerController* FromPawn(CCSPlayerPawn* pawn)
{
return reinterpret_cast<CCSPlayerController*>(pawn->m_hController().Get());
}
static CCSPlayerController* FromIndex(int iIndex)
{
return static_cast<CCSPlayerController*>(shared::g_pEntitySystem->
GetEntityInstance(CEntityIndex(iIndex)));
}
static CCSPlayerController* FromSlot(int iSlot)
{
return static_cast<CCSPlayerController*>(shared::g_pEntitySystem->
GetEntityInstance(CEntityIndex(iSlot + 1)));
}
static CCSPlayerController* FromSteamId(uint64 steamid)
{
for (int i = 0; i < shared::getGlobalVars()->maxClients; ++i)
{
CCSPlayerController* controller = FromSlot(i);
if (!controller)
continue;
if (steamid == controller->GetSteamID()) return controller;
}
return nullptr;
}
CServerSideClient* GetServerSideClient()
{
return GetClientBySlot(GetPlayerSlot());
}
CCSPlayerPawn* GetPlayerPawn()
{
if (auto handle = m_hPlayerPawn(); handle.IsValid())
return handle.Get();
return nullptr;
}
CCSPlayerPawn* GetObserverPawn()
{
if (auto handle = m_hObserverPawn(); handle.IsValid())
return static_cast<CCSPlayerPawn*>(handle.Get());
return nullptr;
}
bool IsBot() { return GetSteamID() == 0; }
void ChangeTeam(CsTeam iTeam)
{
CALL_VIRTUAL(void, shared::g_pGameConfig->GetOffset("CCSPlayerController_ChangeTeam"), this,
static_cast<byte>(iTeam));
}
static std::string CsTeamToString(CsTeam team)
{
switch (team)
{
case CsTeam::None: return "None";
case CsTeam::Spectator: return "Spectator";
case CsTeam::Terrorist: return "Terrorist";
case CsTeam::CounterTerrorist: return "CounterTerrorist";
default: return "Unknown";
}
}
CsTeam Team()
{
byte teamNum = GetTeam();
if (teamNum < static_cast<byte>(CsTeam::None) || teamNum > static_cast<byte>(CsTeam::CounterTerrorist))
return CsTeam::None;
return static_cast<CsTeam>(teamNum);
}
void Respawn()
{
CCSPlayerPawn* pPawn = GetPlayerPawn();
if (!pPawn || m_bPawnIsAlive)
return;
SetPawn(pPawn);
CALL_VIRTUAL(void, shared::g_pGameConfig->GetOffset("CCSPlayerController_Respawn"), this);
}
bool CheckValid(bool alive = false)
{
CCSPlayerPawn* pawn = GetPlayerPawn();
if (IsBot() || GetIsHLTV() || GetSteamID() <= 0 || !IsConnected() || !pawn)
return false;
if (alive && !m_bPawnIsAlive())
{
return false;
}
return true;
}
void TakeFakeDamage(CCSPlayerController* attacker, int damage)
{
static CBaseEntity_TakeDamageOld_t s_TakeDamageOld = nullptr;
if (!s_TakeDamageOld)
{
CBaseEntity_TakeDamageOld_t addr =
DynLibUtils::CModule(shared::g_pServer)
.FindPattern(shared::g_pGameConfig->GetSignature("CBaseEntity_TakeDamageOld"))
.RCast<CBaseEntity_TakeDamageOld_t>();
if (!addr) return;
s_TakeDamageOld = addr;
}
if (!CheckValid(true) || !attacker || !attacker->CheckValid())
return;
auto* victimPawn = GetPlayerPawn();
if (!victimPawn) return;
auto* attackerPawn = attacker->GetPlayerPawn();
if (!attackerPawn) return;
const size_t infoSize = schema::GetClassSize("CTakeDamageInfo");
const size_t resultSize = schema::GetClassSize("CTakeDamageResult");
auto* infoMem = std::malloc(infoSize);
auto* resultMem = std::malloc(resultSize);
if (!infoMem || !resultMem)
{
std::free(infoMem);
std::free(resultMem);
return;
}
std::memset(infoMem, 0, infoSize);
std::memset(resultMem, 0, resultSize);
CAttackerInfo attackerInfo(attacker);
std::memcpy(reinterpret_cast<std::uint8_t*>(infoMem) + 0x98, &attackerInfo, sizeof(CAttackerInfo));
auto* info = reinterpret_cast<CTakeDamageInfo*>(infoMem);
auto* result = reinterpret_cast<CTakeDamageResult*>(resultMem);
info->m_hInflictor() = attackerPawn->GetHandle();
info->m_hAttacker() = attackerPawn->GetHandle();
info->m_flDamage() = static_cast<float>(damage);
info->m_flFriendlyFireDamageReductionRatio() = static_cast<float>(0);
info->m_bInTakeDamageFlow() = true;
info->m_bitsDamageType() = DMG_GENERIC;
result->m_pOriginatingInfo() = info;
result->m_nDamageDealt() = damage;
result->m_nHealthLost() = damage;
result->m_flPreModifiedDamage() = static_cast<float>(damage);
result->m_nTotalledDamageDealt() = damage;
result->m_nTotalledHealthLost() = damage;
result->m_bWasDamageSuppressed() = false;
s_TakeDamageOld(victimPawn, info, result);
std::free(infoMem);
std::free(resultMem);
}
void Kick(ENetworkDisconnectionReason reason)
{
if (!CheckValid()) return;
CPlayerSlot slot = CPlayerSlot(GetPlayerSlot());
Tasks::NextFrame([slot, reason]
{
shared::g_pEngine->DisconnectClient(
slot, reason);
});
}
PlayerConnectedState GetConnectedState() { return m_iConnected(); }
CSPlayerState GetPawnState()
{
CCSPlayerPawnBase* pPawn = static_cast<CCSPlayerPawnBase*>(GetPawn());
if (!pPawn)
return STATE_WELCOME;
return pPawn->m_iPlayerState();
}
CSPlayerState GetPlayerPawnState()
{
CCSPlayerPawn* pPawn = GetPlayerPawn();
if (!pPawn)
return STATE_WELCOME;
return pPawn->m_iPlayerState();
}
CBaseEntity* GetObserverTarget()
{
auto pPawn = GetPawn();
if (!pPawn)
return nullptr;
return pPawn->m_pObserverServices->m_hObserverTarget().Get();
}
// --- HEALTH & ARMOR ---
int GetHealth()
{
if (!CheckValid(true)) return 0;
auto pawn = GetPlayerPawn();
return pawn->m_iHealth;
}
void SetHealth(int health)
{
if (!CheckValid(true) || health <= 0) return;
auto pawn = GetPlayerPawn();
pawn->m_iHealth = health;
m_iPawnHealth = health;
if (health > pawn->m_iMaxHealth)
pawn->m_iMaxHealth = health;
}
void AddHealth(int delta)
{
if (!CheckValid(true) || delta <= 0) return;
auto pawn = GetPlayerPawn();
pawn->m_iHealth += delta;
m_iPawnHealth += delta;
}
void SetMaxHealth(int maxHp)
{
if (!CheckValid(true) || maxHp <= 0) return;
auto pawn = GetPlayerPawn();
pawn->m_iMaxHealth = maxHp;
}
void SetArmor(int armor, bool helmet = false)
{
if (!CheckValid(true) || armor < 0) return;
auto pawn = GetPlayerPawn();
pawn->m_ArmorValue = armor;
m_iPawnArmor = armor;
if (helmet && pawn->m_pItemServices)
pawn->m_pItemServices->m_bHasHelmet = true;
}
void AddArmor(int delta)
{
if (!CheckValid(true) || delta <= 0) return;
auto pawn = GetPlayerPawn();
pawn->m_ArmorValue() += delta;
m_iPawnArmor() += delta;
}
// --- WEAPONS ---
std::vector<CCSWeaponBase*> GetWeapons()
{
std::vector<CCSWeaponBase*> weapons;
auto pawn = GetPlayerPawn();
if (!pawn || !pawn->m_pWeaponServices() || !pawn->m_pWeaponServices()->m_hMyWeapons())
return weapons;
const auto& myWeapons = *pawn->m_pWeaponServices()->m_hMyWeapons();
for (int i = 0; i < myWeapons.Count(); ++i)
{
const auto& handle = myWeapons[i];
if (!handle.IsValid()) continue;
if (auto* weapon = static_cast<CCSWeaponBase*>(handle.Get()))
{
weapons.push_back(weapon);
}
}
return weapons;
}
CCSWeaponBase* GetWeaponByDefIndex(int defIndex)
{
for (auto* base : GetWeapons())
{
if (base->m_AttributeManager().m_Item().m_iItemDefinitionIndex() == defIndex)
return base;
}
return nullptr;
}
CCSWeaponBase* GetWeaponBySlot(gear_slot_t slot)
{
for (auto* base : GetWeapons())
{
if (base->GetWeaponVData() && base->GetWeaponVData()->m_GearSlot() == slot)
return base;
}
return nullptr;
}
CCSWeaponBase* GetWeaponByName(const std::string& weaponName)
{
for (auto* base : GetWeapons())
{
if (base->GetDesignerName() == weaponName)
return base;
}
return nullptr;
}
bool HasWeaponByName(const std::string& weaponName)
{
return GetWeaponByName(weaponName) != nullptr;
}
bool HasWeaponInSlot(gear_slot_t slot)
{
return GetWeaponBySlot(slot) != nullptr;
}
std::string GetWeaponNameBySlot(gear_slot_t slot)
{
if (auto* w = GetWeaponBySlot(slot))
{
return w->GetDesignerName();
}
return {};
}
void DropWeaponByName(const std::string& weaponName)
{
auto pawn = GetPlayerPawn();
if (!pawn || !pawn->m_pWeaponServices()) return;
if (auto* weapon = GetWeaponByName(weaponName))
{
pawn->m_pWeaponServices()->m_hActiveWeapon() = CHandle<CBaseEntity>(weapon);
pawn->m_pItemServices()->DropActiveWeapon(weapon);
}
}
// --- POSITION / AIM ---
Vector GetEyePosition()
{
auto pawn = GetPlayerPawn();
if (!pawn) return {0, 0, 0};
Vector origin = pawn->GetAbsOrigin();
Vector offset = pawn->m_vecViewOffset();
return {origin.x, origin.y, origin.z + offset.z};
}
CBaseEntity* GetAimEntity(bool includePlayers = false)
{
if (!CheckValid(true))
return nullptr;
auto* pawn = GetPlayerPawn();
if (!pawn)
return nullptr;
QAngle eyeAngles = pawn->GetEyeAngles();
Vector eyePos = GetEyePosition();
RayTrace::TraceOptions opts{};
if (!includePlayers)
opts.InteractsExclude = RayTrace::InteractionLayers::Player;
auto result = RayTrace::TraceShape(
eyePos,
eyeAngles,
pawn,
&opts
);
return result.has_value()
? static_cast<CBaseEntity*>(result->HitEntity)
: nullptr;
}
struct AimPositionResult
{
bool Hit{false};
Vector Origin{};
Vector Normal{};
};
AimPositionResult GetAimPositionEx(bool includePlayers = true)
{
AimPositionResult result{};
if (!CheckValid(true))
return result;
auto* pawn = GetPlayerPawn();
if (!pawn)
return result;
QAngle eyeAngles = pawn->GetEyeAngles();
Vector eyePos = GetEyePosition();
Vector forward{};
AngleVectors(eyeAngles, &forward, nullptr, nullptr);
Vector origin = eyePos + forward * 15.0f;
RayTrace::TraceOptions opts{};
if (!includePlayers)
opts.InteractsExclude = RayTrace::InteractionLayers::Player;
auto traceResult = RayTrace::TraceShape(
eyePos,
eyeAngles,
pawn,
&opts
);
if (traceResult.has_value())
{
result.Hit = true;
result.Origin = traceResult->EndPos;
result.Normal = traceResult->Normal;
return result;
}
result.Hit = false;
result.Origin = origin + forward * 8192.0f;
return result;
}
Vector GetAimPosition(bool includePlayers = true)
{
if (!CheckValid(true))
return {0, 0, 0};
auto* pawn = GetPlayerPawn();
if (!pawn)
return {0, 0, 0};
QAngle eyeAngles = pawn->GetEyeAngles();
Vector eyePos = GetEyePosition();
Vector forward{};
AngleVectors(eyeAngles, &forward, nullptr, nullptr);
Vector origin = eyePos + forward * 15.0f;
RayTrace::TraceOptions opts{};
if (!includePlayers)
opts.InteractsExclude = RayTrace::InteractionLayers::Player;
auto traceResult = RayTrace::TraceShape(
eyePos,
eyeAngles,
pawn,
&opts
);
if (traceResult.has_value())
{
return traceResult->EndPos;
}
return origin + forward * 8192.0f;
}
bool IsPlayerInSightRange(CCSPlayerController* target,
float distance = 0.0f, float angle = 90.0f, bool heightCheck = true,
bool negativeAngle = false)
{
if (!CheckValid(true) || !target->CheckValid(true) || GetPlayerPawn()->GetAbsOrigin().IsZero() ||
target->GetPlayerPawn()->GetAbsOrigin().IsZero())
return false;
float resultDistance = 0;
auto playerEyeAngles = GetPlayerPawn()->GetEyeAngles();
playerEyeAngles.x = 0.0f;
playerEyeAngles.z = 0.0f;
Vector angleVector;
AngleVectors(playerEyeAngles, &angleVector, nullptr, nullptr);
angleVector = VectorExtends::Normalize(angleVector);
if (negativeAngle)
angleVector = VectorExtends::NegateVector(angleVector);
auto playerOrigin = GetPlayerPawn()->GetAbsOrigin();
auto targetOrigin = target->GetPlayerPawn()->GetAbsOrigin();
if (heightCheck && distance > 0.0f)
resultDistance = VectorExtends::Distance(playerOrigin, targetOrigin);
if (distance > 0.0f)
{
if (resultDistance > distance)
return false;
}
playerOrigin.z = 0.0f;
targetOrigin.z = 0.0f;
auto targetVector = VectorExtends::MakeVectorFromPoints(playerOrigin, targetOrigin);
targetVector = VectorExtends::Normalize(targetVector);
auto resultAngle = VectorExtends::RadToDeg(
VectorExtends::ArcCosine(VectorExtends::GetVectorDotProduct(targetVector, angleVector)));
if (resultAngle > angle / 2)
return false;
if (distance > 0.0f)
{
if (!heightCheck)
resultDistance = VectorExtends::Distance(playerOrigin, targetOrigin);
return resultDistance <= distance;
}
return true;
}
void TeleportTo(CCSPlayerController* target)
{
if (!CheckValid(true) || !target || !target->CheckValid(true)) return;
auto pawn = GetPlayerPawn();
auto tpawn = target->GetPlayerPawn();
if (!pawn || !tpawn) return;
Vector origin = tpawn->GetAbsOrigin();
QAngle rot = tpawn->GetAbsRotation();
Vector vel = tpawn->GetAbsVelocity();
pawn->Teleport(&origin, &rot, &vel);
}
void TeleportPlayer(CCSPlayerController* target)
{
CCSPlayerPawn* myPawn = GetPlayerPawn();
CCSPlayerPawn* targetPawn = target ? target->GetPlayerPawn() : nullptr;
if (!myPawn || !targetPawn)
return;
const Vector& origin = targetPawn->GetAbsOrigin();
const QAngle& rotation = targetPawn->GetAbsRotation();
const Vector& velocity = targetPawn->GetAbsVelocity();
myPawn->Teleport(&origin, &rotation, &velocity);
}
void SetPlayerRenderColor(int a = 255, int r = 255, int g = 255, int b = 255)
{
CCSPlayerPawn* pawn = GetPlayerPawn();
if (!pawn) return;
pawn->m_clrRender() = Color(r, g, b, a);
}
void SetPlayerInvisible(int alpha = 0)
{
if (alpha < 0) alpha = 0;
if (alpha > 255) alpha = 255;
CCSPlayerPawn* pawn = GetPlayerPawn();
if (!pawn)
return;
SetPlayerRenderColor(alpha);
if (auto weaponServices = pawn->m_pWeaponServices())
{
CHandle<CBasePlayerWeapon> activeWeapon = weaponServices->m_hActiveWeapon();
if (activeWeapon && activeWeapon.IsValid())
{
auto* weapon = activeWeapon.Get();
weapon->m_clrRender() = colors(255, 255, 255, alpha).ToValveColor();
weapon->m_flShadowStrength() = 0.0f;
}
if (auto myWeapons = weaponServices->m_hMyWeapons())
{
FOR_EACH_VEC(*myWeapons, i)
{
const CHandle<CBasePlayerWeapon>& handle = (*myWeapons)[i];
if (!handle.IsValid() || !handle.Get())
continue;
auto* weapon = handle.Get();
weapon->m_clrRender() = colors(255, 255, 255, 0).ToValveColor();
weapon->m_flShadowStrength() = 0.0f;
}
}
}
}
void SetPlayerVisible()
{
CCSPlayerPawn* pawn = GetPlayerPawn();
if (!pawn)
return;
SetPlayerRenderColor();
if (auto weaponServices = pawn->m_pWeaponServices())
{
CHandle<CBasePlayerWeapon> activeWeapon = weaponServices->m_hActiveWeapon();
if (activeWeapon && activeWeapon.IsValid())
{
auto* weapon = activeWeapon.Get();
weapon->m_clrRender() = colors(255, 255, 255, 255).ToValveColor();
weapon->m_flShadowStrength() = 1.0f;
}
if (auto myWeapons = weaponServices->m_hMyWeapons())
{
FOR_EACH_VEC(*myWeapons, i)
{
const CHandle<CBasePlayerWeapon>& handle = (*myWeapons)[i];
if (!handle.IsValid() || !handle.Get())
continue;
auto* weapon = handle.Get();
weapon->m_clrRender() = colors(255, 255, 255, 255).ToValveColor();
weapon->m_flShadowStrength() = 1.0f;
}
}
}
}
};
}

141
src/schema/CCSPlayerPawn.h Normal file
View file

@ -0,0 +1,141 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "ehandle.h"
#include "CBasePlayerPawn.h"
#include "services.h"
#include "CCSWeaponBase.h"
namespace TemplatePlugin {
enum CSPlayerState
{
STATE_ACTIVE = 0x0,
STATE_WELCOME = 0x1,
STATE_PICKINGTEAM = 0x2,
STATE_PICKINGCLASS = 0x3,
STATE_DEATH_ANIM = 0x4,
STATE_DEATH_WAIT_FOR_KEY = 0x5,
STATE_OBSERVER_MODE = 0x6,
STATE_GUNGAME_RESPAWN = 0x7,
STATE_DORMANT = 0x8,
NUM_PLAYER_STATES = 0x9,
};
class CCSPlayerController;
struct EntitySpottedState_t
{
private:
[[maybe_unused]] std::uint8_t __pad0000[ 0x8 ]; // 0x0
public:
// MNetworkEnable
// MNetworkChangeCallback "OnIsSpottedChanged"
bool m_bSpotted; // 0x8
private:
[[maybe_unused]] std::uint8_t __pad0009[ 0x3 ]; // 0x9
public:
// MNetworkEnable
// MNetworkChangeCallback "OnIsSpottedChanged"
std::uint32_t m_bSpottedByMask[2]; // 0xc
};
class CTouchExpansionComponent : public CEntityComponent
{
DECLARE_SCHEMA_CLASS(CTouchExpansionComponent)
};
class CCSPlayerPawnBase : public CBasePlayerPawn {
public:
DECLARE_SCHEMA_CLASS(CCSPlayerPawnBase);
SCHEMA_FIELD(CTouchExpansionComponent, m_CTouchExpansionComponent)
SCHEMA_FIELD(GameTime_t, m_blindUntilTime)
SCHEMA_FIELD(GameTime_t, m_blindStartTime)
SCHEMA_FIELD(CSPlayerState, m_iPlayerState)
SCHEMA_FIELD(bool, m_bRespawning)
SCHEMA_FIELD(GameTime_t, m_fImmuneToGunGameDamageTime)
SCHEMA_FIELD(bool, m_bGunGameImmunity)
SCHEMA_FIELD(float, m_fMolotovDamageTime)
SCHEMA_FIELD(bool, m_bHasMovedSinceSpawn)
SCHEMA_FIELD(int32, m_iNumSpawns)
SCHEMA_FIELD(float, m_flIdleTimeSinceLastAction)
SCHEMA_FIELD(float, m_fNextRadarUpdateTime)
SCHEMA_FIELD(float, m_flFlashDuration)
SCHEMA_FIELD(float, m_flFlashMaxAlpha)
SCHEMA_FIELD(float, m_flProgressBarStartTime)
SCHEMA_FIELD(int32, m_iProgressBarDuration)
SCHEMA_FIELD(bool, m_wasNotKilledNaturally)
SCHEMA_FIELD(bool, m_bCommittingSuicideOnTeamChange)
SCHEMA_FIELD(CHandle<CCSPlayerController>, m_hOriginalController)
};
enum CSPlayerBlockingUseAction_t : uint
{
k_CSPlayerBlockingUseAction_None = 0x0,
k_CSPlayerBlockingUseAction_DefusingDefault = 0x1,
k_CSPlayerBlockingUseAction_DefusingWithKit = 0x2,
k_CSPlayerBlockingUseAction_HostageGrabbing = 0x3,
k_CSPlayerBlockingUseAction_HostageDropping = 0x4,
k_CSPlayerBlockingUseAction_MapLongUseEntity_Pickup = 0x5,
k_CSPlayerBlockingUseAction_MapLongUseEntity_Place = 0x6,
k_CSPlayerBlockingUseAction_MaxCount = 0x7,
};
class CCSPlayerPawn : public CCSPlayerPawnBase
{
public:
DECLARE_SCHEMA_CLASS(CCSPlayerPawn);
SCHEMA_FIELD(CEconItemView, m_EconGloves)
SCHEMA_FIELD(uint8, m_nEconGlovesChanged)
SCHEMA_FIELD(uint16, m_nCharacterDefIndex)
SCHEMA_FIELD(CUtlString, m_strVOPrefix)
SCHEMA_FIELD(float, m_flVelocityModifier)
SCHEMA_FIELD(CCSPlayer_ActionTrackingServices*, m_pActionTrackingServices)
SCHEMA_FIELD(QAngle, m_angEyeAngles)
SCHEMA_FIELD(GameTime_t, m_flHealthShotBoostExpirationTime)
SCHEMA_FIELD(int32, m_ArmorValue)
SCHEMA_FIELD(bool, m_bInBuyZone)
SCHEMA_FIELD(bool, m_bInBombZone);
SCHEMA_FIELD(EntitySpottedState_t, m_entitySpottedState)
SCHEMA_FIELD(bool, m_bIsScoped)
SCHEMA_FIELD(int, m_aimPunchTickBase)
SCHEMA_FIELD(float, m_aimPunchTickFraction)
SCHEMA_FIELD(QAngle, m_aimPunchAngle)
SCHEMA_FIELD(QAngle, m_aimPunchAngleVel)
SCHEMA_FIELD(CSPlayerBlockingUseAction_t, m_iBlockingUseActionInProgress);
uint8 GetCollisionGroup()
{
return m_Collision().m_collisionAttribute().m_nCollisionGroup();
}
void SetCollisionGroup(uint8 nCollisionGroup = COLLISION_GROUP_DEBRIS)
{
m_Collision().m_CollisionGroup() = nCollisionGroup;
m_Collision().m_collisionAttribute().m_nCollisionGroup() = nCollisionGroup;
CollisionRulesChanged();
}
QAngle GetEyeAngles() {
return m_angEyeAngles.Get();
}
};
class CCSGO_TeamPreviewCharacterPosition: public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CCSGO_TeamPreviewCharacterPosition);
SCHEMA_FIELD(int32, m_nVariant);
SCHEMA_FIELD(int32, m_nRandom);
SCHEMA_FIELD(int32, m_nOrdinal);
SCHEMA_FIELD(CUtlString, m_sWeaponName);
SCHEMA_FIELD(uint64, m_xuid);
SCHEMA_FIELD_POINTER(CEconItemView, m_agentItem);
SCHEMA_FIELD_POINTER(CEconItemView, m_glovesItem);
SCHEMA_FIELD_POINTER(CEconItemView, m_weaponItem);
};
}

232
src/schema/CCSWeaponBase.h Normal file
View file

@ -0,0 +1,232 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "virtual.h"
namespace TemplatePlugin {
enum gear_slot_t : uint32_t
{
GEAR_SLOT_INVALID = 0xffffffff,
GEAR_SLOT_RIFLE = 0x0,
GEAR_SLOT_PISTOL = 0x1,
GEAR_SLOT_KNIFE = 0x2,
GEAR_SLOT_GRENADES = 0x3,
GEAR_SLOT_C4 = 0x4,
GEAR_SLOT_RESERVED_SLOT6 = 0x5,
GEAR_SLOT_RESERVED_SLOT7 = 0x6,
GEAR_SLOT_RESERVED_SLOT8 = 0x7,
GEAR_SLOT_RESERVED_SLOT9 = 0x8,
GEAR_SLOT_RESERVED_SLOT10 = 0x9,
GEAR_SLOT_RESERVED_SLOT11 = 0xa,
GEAR_SLOT_BOOSTS = 0xb,
GEAR_SLOT_UTILITY = 0xc,
GEAR_SLOT_COUNT = 0xd,
GEAR_SLOT_FIRST = 0x0,
GEAR_SLOT_LAST = 0xc,
};
enum CSWeaponType : uint32_t
{
WEAPONTYPE_KNIFE = 0,
WEAPONTYPE_PISTOL = 1,
WEAPONTYPE_SUBMACHINEGUN = 2,
WEAPONTYPE_RIFLE = 3,
WEAPONTYPE_SHOTGUN = 4,
WEAPONTYPE_SNIPER_RIFLE = 5,
WEAPONTYPE_MACHINEGUN = 6,
WEAPONTYPE_C4 = 7,
WEAPONTYPE_TASER = 8,
WEAPONTYPE_GRENADE = 9,
WEAPONTYPE_EQUIPMENT = 10,
WEAPONTYPE_STACKABLEITEM = 11,
WEAPONTYPE_UNKNOWN = 12,
};
enum CSWeaponCategory : uint32_t
{
WEAPONCATEGORY_OTHER = 0,
WEAPONCATEGORY_MELEE = 1,
WEAPONCATEGORY_SECONDARY = 2,
WEAPONCATEGORY_SMG = 3,
WEAPONCATEGORY_RIFLE = 4,
WEAPONCATEGORY_HEAVY = 5,
WEAPONCATEGORY_COUNT = 6,
};
class CAttributeManager
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CAttributeManager);
SCHEMA_FIELD_POINTER(CUtlVector<CAttributeManager>, m_CachedResults);
};
class CEconItemAttribute
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CEconItemAttribute);
SCHEMA_FIELD(uint16_t, m_iAttributeDefinitionIndex);
SCHEMA_FIELD(float32, m_flValue);
SCHEMA_FIELD(float32, m_flInitialValue);
SCHEMA_FIELD(int32, m_nRefundableCurrency);
SCHEMA_FIELD(bool, m_bSetBonus);
};
class CAttributeList
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CAttributeList);
SCHEMA_FIELD_POINTER(CUtlVector<CEconItemAttribute>, m_Attributes)
SCHEMA_FIELD(CAttributeManager*, m_pManager);
};
class CEconItemView
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CEconItemView);
SCHEMA_FIELD(uint16, m_iItemDefinitionIndex)
SCHEMA_FIELD(int32, m_iEntityQuality)
SCHEMA_FIELD(uint32, m_iEntityLevel)
SCHEMA_FIELD(uint64_t, m_iItemID)
SCHEMA_FIELD(uint32, m_iItemIDHigh)
SCHEMA_FIELD(uint32, m_iItemIDLow)
SCHEMA_FIELD(uint32, m_iAccountID)
SCHEMA_FIELD(uint32, m_iInventoryPosition)
SCHEMA_FIELD(bool, m_bInitialized)
SCHEMA_FIELD(CAttributeList, m_AttributeList)
SCHEMA_FIELD(CAttributeList, m_NetworkedDynamicAttributes)
SCHEMA_FIELD_POINTER(char, m_szCustomName)
SCHEMA_FIELD_POINTER(char, m_szCustomNameOverride)
};
class CAttributeContainer
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CAttributeContainer);
SCHEMA_FIELD(CEconItemView, m_Item)
};
class CEconEntity : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CEconEntity)
SCHEMA_FIELD(CAttributeContainer, m_AttributeManager)
SCHEMA_FIELD(uint32, m_OriginalOwnerXuidLow)
SCHEMA_FIELD(uint32, m_OriginalOwnerXuidHigh)
SCHEMA_FIELD(int32, m_nFallbackPaintKit)
SCHEMA_FIELD(int32, m_nFallbackSeed)
SCHEMA_FIELD(float, m_flFallbackWear)
SCHEMA_FIELD(int32, m_nFallbackStatTrak)
};
class CEconWearable : public CEconEntity
{
public:
DECLARE_SCHEMA_CLASS(CEconWearable)
SCHEMA_FIELD(int32, m_nForceSkin)
SCHEMA_FIELD(bool, m_bAlwaysAllow)
};
class CBasePlayerWeaponVData : public CEntitySubclassVDataBase
{
public:
DECLARE_SCHEMA_CLASS(CBasePlayerWeaponVData)
SCHEMA_FIELD(int, m_iMaxClip1)
SCHEMA_FIELD(int, m_iMaxClip2)
SCHEMA_FIELD(int, m_iDefaultClip1)
};
class CCSWeaponBaseVData : public CBasePlayerWeaponVData
{
public:
DECLARE_SCHEMA_CLASS(CCSWeaponBaseVData)
SCHEMA_FIELD(CSWeaponType, m_WeaponType);
SCHEMA_FIELD(CSWeaponCategory, m_WeaponCategory);
SCHEMA_FIELD(gear_slot_t, m_GearSlot)
SCHEMA_FIELD(int, m_nPrice)
SCHEMA_FIELD(CUtlString, m_szName)
SCHEMA_FIELD(int, m_nPrimaryReserveAmmoMax)
SCHEMA_FIELD(int, m_nSecondaryReserveAmmoMax)
SCHEMA_FIELD(int, m_nDamage)
};
class CBasePlayerWeapon : public CEconEntity
{
public:
DECLARE_SCHEMA_CLASS(CBasePlayerWeapon)
SCHEMA_FIELD(int, m_nNextPrimaryAttackTick);
SCHEMA_FIELD(float, m_flNextPrimaryAttackTickRatio);
SCHEMA_FIELD(int, m_nNextSecondaryAttackTick);
SCHEMA_FIELD(float, m_flNextSecondaryAttackTickRatio);
SCHEMA_FIELD(int32_t, m_iClip1);
SCHEMA_FIELD(int32_t, m_iClip2);
SCHEMA_FIELD_POINTER(int, m_pReserveAmmo);
CCSWeaponBaseVData* GetWeaponVData() { return (CCSWeaponBaseVData*)GetVData(); }
const char* GetWeaponClassname() noexcept
{
const char* pszClassname = GetClassname();
if (V_StringHasPrefixCaseSensitive(pszClassname, "item_"))
return pszClassname;
switch (m_AttributeManager().m_Item().m_iItemDefinitionIndex)
{
case 23:
return "weapon_mp5sd";
case 41:
return "weapon_knifegg";
case 42:
return "weapon_knife";
case 59:
return "weapon_knife_t";
case 60:
return "weapon_m4a1_silencer";
case 61:
return "weapon_usp_silencer";
case 63:
return "weapon_cz75a";
case 64:
return "weapon_revolver";
default:
return pszClassname;
}
}
};
class CCSWeaponBase : public CBasePlayerWeapon
{
public:
DECLARE_SCHEMA_CLASS(CCSWeaponBase)
SCHEMA_FIELD(bool, m_bInReload);
SCHEMA_FIELD(bool, m_bReloadVisuallyComplete);
SCHEMA_FIELD(bool, m_bRequireUseToTouch);
SCHEMA_FIELD(float, m_flDroppedAtTime);
SCHEMA_FIELD(float, m_fAccuracyPenalty);
SCHEMA_FIELD(float, m_flRecoilIndex);
SCHEMA_FIELD(int, m_iRecoilIndex);
};
class CWeaponBaseItem : public CCSWeaponBase
{
public:
DECLARE_SCHEMA_CLASS(CWeaponBaseItem)
};
class CCSWeaponBaseGun : public CCSWeaponBase
{
public:
DECLARE_SCHEMA_CLASS(CCSWeaponBaseGun)
};
}

26
src/schema/CChicken.h Normal file
View file

@ -0,0 +1,26 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
namespace TemplatePlugin {
enum class ChickenActivity : uint32_t {
IDLE = 0x0,
SQUAT = 0x1,
WALK = 0x2,
RUN = 0x3,
GLIDE = 0x4,
LAND = 0x5,
PANIC = 0x6
};
class CChicken : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CChicken)
SCHEMA_FIELD(ChickenActivity, m_currentActivity)
};
}

50
src/schema/CDynamicProp.h Normal file
View file

@ -0,0 +1,50 @@
//
// Created by Michal Přikryl on 01.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
#include "CBreakableProp.h"
namespace
TemplatePlugin
{
enum AnimLoopMode_t : uint
{
ANIM_LOOP_MODE_INVALID = 0xFFFFFFFF,
ANIM_LOOP_MODE_NOT_LOOPING = 0x0,
ANIM_LOOP_MODE_LOOPING = 0x1,
ANIM_LOOP_MODE_USE_SEQUENCE_SETTINGS = 0x2,
ANIM_LOOP_MODE_COUNT = 0x3,
};
class CDynamicProp : public CBreakableProp
{
public:
DECLARE_SCHEMA_CLASS(CDynamicProp);
SCHEMA_FIELD(bool, m_bCreateNavObstacle)
SCHEMA_FIELD(bool, m_bNavObstacleUpdatesOverridden)
SCHEMA_FIELD(bool, m_bUseHitboxesForRenderBox)
SCHEMA_FIELD(bool, m_bUseAnimGraph)
SCHEMA_FIELD(CEntityIOOutput, m_pOutputAnimBegun)
SCHEMA_FIELD(CEntityIOOutput, m_pOutputAnimOver)
SCHEMA_FIELD(CEntityIOOutput, m_pOutputAnimLoopCycleOver)
SCHEMA_FIELD(CEntityIOOutput, m_OnAnimReachedStart)
SCHEMA_FIELD(CEntityIOOutput, m_OnAnimReachedEnd)
SCHEMA_FIELD_POINTER(char, m_iszIdleAnim)
SCHEMA_FIELD(AnimLoopMode_t, m_nIdleAnimLoopMode)
SCHEMA_FIELD(bool, m_bRandomizeCycle)
SCHEMA_FIELD(bool, m_bStartDisabled)
SCHEMA_FIELD(bool, m_bFiredStartEndOutput)
SCHEMA_FIELD(bool, m_bForceNpcExclude)
SCHEMA_FIELD(bool, m_bCreateNonSolid)
SCHEMA_FIELD(bool, m_bIsOverrideProp)
SCHEMA_FIELD(int32, m_iInitialGlowState)
SCHEMA_FIELD(int32, m_nGlowRange)
SCHEMA_FIELD(int32, m_nGlowRangeMin)
SCHEMA_FIELD(Color, m_glowColor)
SCHEMA_FIELD(int32, m_nGlowTeam)
};
}

View file

@ -0,0 +1,20 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include"schemasystem.h"
#define SF_TRIG_PUSH_ONCE 0x80
namespace TemplatePlugin {
class CEnvEntityMaker : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CEnvEntityMaker);
SCHEMA_FIELD(CUtlSymbolLarge, m_iszTemplate)
};
}

29
src/schema/CFuncBrush.h Normal file
View file

@ -0,0 +1,29 @@
//
// Created by Michal Přikryl on 07.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
#pragma once
#include "CBaseModelEntity.h"
#include "globaltypes.h"
namespace
TemplatePlugin
{
enum BrushSolidities_e : uint
{
BRUSHSOLID_TOGGLE,
BRUSHSOLID_NEVER,
BRUSHSOLID_ALWAYS,
};
class CFuncBrush : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CFuncBrush);
SCHEMA_FIELD(BrushSolidities_e, m_iSolidity)
SCHEMA_FIELD(int, m_iDisabled)
SCHEMA_FIELD(bool, m_bSolidBsp)
SCHEMA_FIELD_POINTER(char, m_iszExcludedClass)
SCHEMA_FIELD(bool, m_bInvertExclusion)
SCHEMA_FIELD(bool, m_bScriptedMovement)
};
}

View file

@ -0,0 +1,17 @@
//
// Created by Michal Přikryl on 07.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
#pragma once
#include "CBaseModelEntity.h"
#include "globaltypes.h"
namespace
TemplatePlugin
{
class CFuncVPhysicsClip : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CFuncVPhysicsClip);
SCHEMA_FIELD(int, m_bDisabled)
};
}

125
src/schema/CGameRules.h Normal file
View file

@ -0,0 +1,125 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <platform.h>
#include "CBaseEntity.h"
namespace TemplatePlugin {
enum class RoundEndReason : uint32_t
{
Unknown = 0x0u,
TargetBombed = 0x1u,
TerroristsEscaped = 0x4u,
CTsPreventEscape = 0x5u,
EscapingTerroristsNeutralized = 0x6u,
BombDefused = 0x7u,
CTsWin = 0x8u,
TerroristsWin = 0x9u,
RoundDraw = 0xAu,
AllHostageRescued = 0xBu,
TargetSaved = 0xCu,
HostagesNotRescued = 0xDu,
TerroristsNotEscaped = 0xEu,
GameCommencing = 0x10u,
TerroristsSurrender = 0x11u,
CTsSurrender = 0x12u,
TerroristsPlanted = 0x13u,
CTsReachedHostage = 0x14u,
SurvivalWin = 0x15u,
SurvivalDraw = 0x16u,
TerroristsPlanned = 0x13u
};
class CGameRules
{
public:
DECLARE_SCHEMA_CLASS(CGameRules)
};
class CCSGameModeRules_Deathmatch : public CGameRules
{
public:
DECLARE_SCHEMA_CLASS(CCSGameModeRules_Deathmatch)
SCHEMA_FIELD(GameTime_t, m_flDMBonusStartTime);
SCHEMA_FIELD(float32, m_flDMBonusTimeLength);
SCHEMA_FIELD(CUtlString, m_sDMBonusWeapon);
};
class CCSGameRules;
class CCSGameRulesProxy : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CCSGameRulesProxy)
SCHEMA_FIELD(CCSGameRules *, m_pGameRules);
};
class CCSGameRules : public CGameRules
{
public:
DECLARE_SCHEMA_CLASS(CCSGameRules)
SCHEMA_FIELD(float, m_fMatchStartTime);
SCHEMA_FIELD(float, m_flGameStartTime);
SCHEMA_FIELD(int, m_totalRoundsPlayed);
SCHEMA_FIELD(float, m_fRoundStartTime);
SCHEMA_FIELD(float, m_flRestartRoundTime);
SCHEMA_FIELD_POINTER(int, m_nEndMatchMapGroupVoteOptions)
SCHEMA_FIELD(int, m_nEndMatchMapVoteWinner);
SCHEMA_FIELD(int, m_iRoundTime);
SCHEMA_FIELD(bool, m_bWarmupPeriod);
SCHEMA_FIELD(float, m_fWarmupPeriodEnd);
SCHEMA_FIELD(float, m_fWarmupPeriodStart);
SCHEMA_FIELD(GamePhase, m_gamePhase);
SCHEMA_FIELD(bool, m_bGameRestart);
SCHEMA_FIELD(bool, m_bBombPlanted);
SCHEMA_FIELD(int32_t, m_totaArenaoundsPlayed);
SCHEMA_FIELD(int32_t, m_nOvertimePlaying);
SCHEMA_FIELD(bool, m_bBuyTimeEnded);
SCHEMA_FIELD(bool, m_bTCantBuy);
SCHEMA_FIELD(bool, m_bCTCantBuy);
SCHEMA_FIELD(bool, m_bSwitchingTeamsAtRoundReset);
SCHEMA_FIELD(int, m_iRoundEndWinnerTeam);
SCHEMA_FIELD(CUtlString, m_sRoundEndMessage);
SCHEMA_FIELD(CUtlString, m_sRoundEndFunFactToken);
SCHEMA_FIELD(bool, m_bIsValveDS);
SCHEMA_FIELD(bool, m_bIsQuestEligible);
SCHEMA_FIELD(int32, m_iSpectatorSlotCount);
SCHEMA_FIELD(float, m_fWarmupNextChatNoticeTime);
SCHEMA_FIELD_POINTER(CUtlVector<SpawnPoint*>, m_CTSpawnPoints);
SCHEMA_FIELD_POINTER(CUtlVector<SpawnPoint*>, m_TerroristSpawnPoints);
using TerminateRoundFn = void(*)(CCSGameRules*, RoundEndReason, float, void*, uint8_t);
TerminateRoundFn s_pTerminateRound = nullptr;
void TerminateRound(float delay, RoundEndReason roundEndReason) {
if (!s_pTerminateRound) {
TerminateRoundFn addr = DynLibUtils::CModule(shared::g_pServer).FindPattern(
shared::g_pGameConfig->GetSignature("CCSGameRules_TerminateRound")).RCast<TerminateRoundFn>();
if (!addr)
return;
s_pTerminateRound = addr;
}
s_pTerminateRound(this, roundEndReason, delay, nullptr, 0);
}
static CCSGameRules* FindGameRules()
{
auto entities = UTIL_FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules");
if (entities.empty())
return nullptr;
auto* proxy = entities.front();
return proxy ? proxy->m_pGameRules() : nullptr;
}
};
}

View file

@ -0,0 +1,38 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseModelEntity.h"
namespace TemplatePlugin {
class CParticleSystem : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CParticleSystem);
SCHEMA_FIELD(bool, m_bActive)
SCHEMA_FIELD(bool, m_bStartActive)
SCHEMA_FIELD(bool, m_bFrozen)
SCHEMA_FIELD(float32, m_flFreezeTransitionDuration)
SCHEMA_FIELD(int32, m_nStopType)
SCHEMA_FIELD(bool, m_bAnimateDuringGameplayPause)
SCHEMA_FIELD(CUtlSymbolLarge, m_iszEffectName)
SCHEMA_FIELD(int, m_nTintCP)
SCHEMA_FIELD(GameTime_t, m_flStartTime)
SCHEMA_FIELD_POINTER(Color, m_clrTint)
SCHEMA_FIELD_POINTER(CHandle<CBaseEntity>, m_hControlPointEnts)
};
class CEnvParticleGlow : public CParticleSystem
{
public:
DECLARE_SCHEMA_CLASS(CEnvParticleGlow);
SCHEMA_FIELD(float, m_flAlphaScale)
SCHEMA_FIELD(float, m_flRadiusScale)
SCHEMA_FIELD(float, m_flSelfIllumScale)
SCHEMA_FIELD_POINTER(Color, m_ColorTint)
};
}

View file

@ -0,0 +1,27 @@
//
// Created by Michal Přikryl on 09.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
namespace TemplatePlugin
{
class CPhysExplosion : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CPhysExplosion);
SCHEMA_FIELD(bool, m_bExplodeOnSpawn)
SCHEMA_FIELD(float, m_flMagnitude)
SCHEMA_FIELD(float, m_flDamage)
SCHEMA_FIELD(float, m_radius)
SCHEMA_FIELD_POINTER(char, m_targetEntityName)
SCHEMA_FIELD(float, m_flInnerRadius)
SCHEMA_FIELD(float, m_flPushScale)
SCHEMA_FIELD(bool, m_bConvertToDebrisWhenPossible)
SCHEMA_FIELD(bool, m_bAffectInvulnerableEnts)
SCHEMA_FIELD(CEntityIOOutput, m_OnPushedPlayer)
};
}

70
src/schema/CPhysicsProp.h Normal file
View file

@ -0,0 +1,70 @@
//
// Created by Michal Přikryl on 01.11.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
#include "CBreakableProp.h"
namespace
TemplatePlugin
{
enum DynamicContinuousContactBehavior_t : byte
{
DYNAMIC_CONTINUOUS_ALLOW_IF_REQUESTED_BY_OTHER_BODY = 0x0,
DYNAMIC_CONTINUOUS_ALWAYS = 0x1,
DYNAMIC_CONTINUOUS_NEVER = 0x2,
};
enum CPhysicsPropCrateType_t : uint
{
CRATE_SPECIFIC_ITEM = 0x0,
CRATE_TYPE_COUNT = 0x1,
};
class CPhysicsProp : public CBreakableProp
{
public:
DECLARE_SCHEMA_CLASS(CPhysicsProp);
SCHEMA_FIELD(CEntityIOOutput, m_MotionEnabled)
SCHEMA_FIELD(CEntityIOOutput, m_OnAwakened)
SCHEMA_FIELD(CEntityIOOutput, m_OnAwake)
SCHEMA_FIELD(CEntityIOOutput, m_OnAsleep)
SCHEMA_FIELD(CEntityIOOutput, m_OnPlayerUse)
SCHEMA_FIELD(CEntityIOOutput, m_OnOutOfWorld)
SCHEMA_FIELD(CEntityIOOutput, m_OnPlayerPickup)
SCHEMA_FIELD(bool, m_bForceNavIgnore)
SCHEMA_FIELD(bool, m_bNoNavmeshBlocker)
SCHEMA_FIELD(bool, m_bForceNpcExclude)
SCHEMA_FIELD(float, m_massScale)
SCHEMA_FIELD(float, m_buoyancyScale)
SCHEMA_FIELD(int32, m_damageType)
SCHEMA_FIELD(int32, m_damageToEnableMotion)
SCHEMA_FIELD(float, m_flForceToEnableMotion)
SCHEMA_FIELD(bool, m_bThrownByPlayer)
SCHEMA_FIELD(bool, m_bDroppedByPlayer)
SCHEMA_FIELD(bool, m_bTouchedByPlayer)
SCHEMA_FIELD(bool, m_bFirstCollisionAfterLaunch)
SCHEMA_FIELD(bool, m_bHasBeenAwakened)
SCHEMA_FIELD(bool, m_bIsOverrideProp)
SCHEMA_FIELD(float, m_flLastBurn)
SCHEMA_FIELD(DynamicContinuousContactBehavior_t, m_nDynamicContinuousContactBehavior)
SCHEMA_FIELD(float, m_fNextCheckDisableMotionContactsTime)
SCHEMA_FIELD(int32, m_iInitialGlowState)
SCHEMA_FIELD(int32, m_nGlowRange)
SCHEMA_FIELD(int32, m_nGlowRangeMin)
SCHEMA_FIELD(Color, m_glowColor)
SCHEMA_FIELD(bool, m_bShouldAutoConvertBackFromDebris)
SCHEMA_FIELD(bool, m_bMuteImpactEffects)
SCHEMA_FIELD(bool, m_bAcceptDamageFromHeldObjects)
SCHEMA_FIELD(bool, m_bEnableUseOutput)
SCHEMA_FIELD(CPhysicsPropCrateType_t, m_CrateType)
SCHEMA_FIELD_POINTER(char, m_strItemClass)
SCHEMA_FIELD_POINTER(int32, m_nItemCount)
SCHEMA_FIELD(bool, m_bRemovableForAmmoBalancing)
SCHEMA_FIELD(bool, m_bAwake)
SCHEMA_FIELD(bool, m_bAttachedToReferenceFrame)
};
}

18
src/schema/CPlantedC4.h Normal file
View file

@ -0,0 +1,18 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseModelEntity.h"
#include "globaltypes.h"
namespace TemplatePlugin {
class CPlantedC4 : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CPlantedC4)
SCHEMA_FIELD(GameTime_t, m_flC4Blow);
SCHEMA_FIELD(GameTime_t, m_flDefuseCountDown);
};
}

View file

@ -0,0 +1,74 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2025 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "CBaseModelEntity.h"
namespace TemplatePlugin {
enum class PointWorldTextJustifyHorizontal_t : uint32_t
{
POINT_WORLD_TEXT_JUSTIFY_HORIZONTAL_LEFT = 0x0,
POINT_WORLD_TEXT_JUSTIFY_HORIZONTAL_CENTER = 0x1,
POINT_WORLD_TEXT_JUSTIFY_HORIZONTAL_RIGHT = 0x2,
};
enum class PointWorldTextJustifyVertical_t : uint32_t
{
POINT_WORLD_TEXT_JUSTIFY_VERTICAL_BOTTOM = 0x0,
POINT_WORLD_TEXT_JUSTIFY_VERTICAL_CENTER = 0x1,
POINT_WORLD_TEXT_JUSTIFY_VERTICAL_TOP = 0x2,
};
enum class PointWorldTextReorientMode_t : uint32_t
{
POINT_WORLD_TEXT_REORIENT_NONE = 0x0,
POINT_WORLD_TEXT_REORIENT_AROUND_UP = 0x1,
};
class CPointWorldText : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CPointWorldText)
SCHEMA_FIELD_POINTER(char, m_messageText) // char m_messageText[512]
SCHEMA_FIELD_POINTER(char, m_FontName) // char m_FontName[64]
SCHEMA_FIELD_POINTER(char, m_BackgroundMaterialName) // char m_BackgroundMaterialName[64]
SCHEMA_FIELD(bool, m_bEnabled)
SCHEMA_FIELD(bool, m_bFullbright)
SCHEMA_FIELD(float, m_flWorldUnitsPerPx)
SCHEMA_FIELD(float, m_flFontSize)
SCHEMA_FIELD(float, m_flDepthOffset)
SCHEMA_FIELD(bool, m_bDrawBackground)
SCHEMA_FIELD(float, m_flBackgroundBorderWidth)
SCHEMA_FIELD(float, m_flBackgroundBorderHeight)
SCHEMA_FIELD(float, m_flBackgroundWorldToUV)
SCHEMA_FIELD(Color, m_Color)
SCHEMA_FIELD(PointWorldTextJustifyHorizontal_t, m_nJustifyHorizontal)
SCHEMA_FIELD(PointWorldTextJustifyVertical_t, m_nJustifyVertical)
SCHEMA_FIELD(PointWorldTextReorientMode_t, m_nReorientMode)
void SetMessage(const char* sMessage)
{
V_strncpy(m_messageText, sMessage, 512);
}
};
}

View file

@ -0,0 +1,80 @@
//
// Created by Michal Přikryl on 17.08.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "irecipientfilter.h"
class CRecipientFilter : public IRecipientFilter
{
public:
CRecipientFilter(NetChannelBufType_t nBufType = BUF_RELIABLE, bool bInitMessage = false)
: m_nBufType(nBufType), m_bInitMessage(bInitMessage)
{
}
CRecipientFilter(const IRecipientFilter* source, CPlayerSlot exceptSlot = -1)
{
m_Recipients = source->GetRecipients();
m_nBufType = source->GetNetworkBufType();
m_bInitMessage = source->IsInitMessage();
if (exceptSlot != -1)
m_Recipients.Clear(exceptSlot.Get());
}
~CRecipientFilter() override = default;
NetChannelBufType_t GetNetworkBufType() const override { return m_nBufType; }
bool IsInitMessage() const override { return m_bInitMessage; }
const CPlayerBitVec& GetRecipients() const override { return m_Recipients; }
CPlayerSlot GetPredictedPlayerSlot() const override { return m_slotPlayerExcludedDueToPrediction; }
virtual CPlayerSlot GetExcludedPlayerDueToPrediction() const { return m_slotPlayerExcludedDueToPrediction; }
void AddRecipient(CPlayerSlot slot)
{
if (slot.Get() >= 0 && slot.Get() < ABSOLUTE_PLAYER_LIMIT)
m_Recipients.Set(slot.Get());
}
void AddRecipientsFromMask(uint64 mask)
{
for (int i = 0; i < 64; ++i)
{
if (mask & (uint64{1} << i))
AddRecipient(CPlayerSlot(i));
}
}
int GetRecipientCount() const
{
const auto& vec = GetRecipients();
int count = 0;
for (int i = 0; i < ABSOLUTE_PLAYER_LIMIT; ++i)
{
if (vec.Get(i))
++count;
}
return count;
}
protected:
CPlayerBitVec m_Recipients;
CPlayerSlot m_slotPlayerExcludedDueToPrediction = -1;
NetChannelBufType_t m_nBufType = BUF_DEFAULT;
bool m_bInitMessage = false;
bool m_bDoNotSuppressPrediction = false; // unused
};
class CSingleRecipientFilter : public CRecipientFilter
{
public:
CSingleRecipientFilter(CPlayerSlot nRecipientSlot,
NetChannelBufType_t nBufType = BUF_RELIABLE,
bool bInitMessage = false)
: CRecipientFilter(nBufType, bInitMessage)
{
if (nRecipientSlot.Get() >= 0 && nRecipientSlot.Get() < ABSOLUTE_PLAYER_LIMIT)
m_Recipients.Set(nRecipientSlot.Get());
}
};

28
src/schema/CSkyCamera.h Normal file
View file

@ -0,0 +1,28 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
#include "globaltypes.h"
#include "CBaseModelEntity.h"
namespace TemplatePlugin {
class CSkyCamera : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CSkyCamera)
SCHEMA_FIELD(CUtlStringToken, m_skyboxSlotToken);
};
class CEnvSky : public CBaseModelEntity
{
public:
DECLARE_SCHEMA_CLASS(CEnvSky)
SCHEMA_FIELD(int, m_hSkyMaterial)
SCHEMA_FIELD(int, m_hSkyMaterialLightingOnly)
SCHEMA_FIELD(Color, m_vTintColor)
SCHEMA_FIELD(float32, m_flBrightnessScale)
};
}

View file

@ -0,0 +1,40 @@
//
// Created by Michal Přikryl on 23.08.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
namespace TemplatePlugin {
class CSoundEventEntity : public CBaseEntity {
public:
DECLARE_SCHEMA_CLASS(CSoundEventEntity)
SCHEMA_FIELD(bool, m_bStartOnSpawn);
SCHEMA_FIELD(bool, m_bToLocalPlayer);
SCHEMA_FIELD(bool, m_bStopOnNew);
SCHEMA_FIELD(bool, m_bSaveRestore);
SCHEMA_FIELD(bool, m_bSavedIsPlaying);
SCHEMA_FIELD(float32, m_flSavedElapsedTime);
SCHEMA_FIELD(CUtlSymbolLarge, m_iszSourceEntityName);
SCHEMA_FIELD(CUtlSymbolLarge, m_iszAttachmentName);
SCHEMA_FIELD(char, m_onGUIDChanged);
SCHEMA_FIELD(CEntityIOOutput, m_onSoundFinished);
SCHEMA_FIELD(float32, m_flClientCullRadius);
SCHEMA_FIELD(CUtlSymbolLarge, m_iszSoundName);
SCHEMA_FIELD(CEntityHandle, m_hSource);
SCHEMA_FIELD(int32, m_nEntityIndexSelection);
};
}

16
src/schema/CTeam.h Normal file
View file

@ -0,0 +1,16 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
namespace TemplatePlugin {
class CTeam : public CBaseEntity
{
public:
DECLARE_SCHEMA_CLASS(CTeam)
SCHEMA_FIELD(int32_t, m_iScore);
};
}

84
src/schema/CTimer.cpp Normal file
View file

@ -0,0 +1,84 @@
//
// Created by Michal Přikryl on 31.10.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "CTimer.h"
#include <Shared.h>
namespace
TemplatePlugin
{
std::list<std::shared_ptr<CTimerBase>> g_timers;
void RunTimers()
{
auto iterator = g_timers.begin();
while (iterator != g_timers.end())
{
auto pTimer = *iterator;
pTimer->Initialize();
// Timer execute
if (pTimer->GetLastExecute() + pTimer->GetInterval() <= universal_time && !pTimer->Execute(true))
iterator = g_timers.erase(iterator);
else
iterator++;
}
}
void RemoveAllTimers()
{
g_timers.clear();
}
void RemoveTimers(uint64 iTimerFlag)
{
auto iterator = g_timers.begin();
while (iterator != g_timers.end())
if ((*iterator)->IsTimerFlagSet(iTimerFlag))
iterator = g_timers.erase(iterator);
else
iterator++;
}
std::weak_ptr<CTimer> CTimer::Create(float flInitialInterval, uint64 nTimerFlags, std::function<float()> func)
{
auto pTimer = std::make_shared<CTimer>(flInitialInterval, nTimerFlags, func, _timer_constructor_tag{});
g_timers.push_back(pTimer);
return pTimer;
}
bool CTimer::Execute(bool bAutomaticExecute)
{
SetInterval(m_func());
SetLastExecute(universal_time);
bool bContinue = GetInterval() >= 0;
// Only scan the timer list if this isn't an automatic execute (RunTimers() already has the iterator to erase)
if (!bAutomaticExecute && !bContinue)
Cancel();
return bContinue;
}
void CTimer::Cancel()
{
auto iterator = g_timers.begin();
while (iterator != g_timers.end())
{
if (*iterator == shared_from_this())
{
g_timers.erase(iterator);
break;
}
iterator++;
}
}
}

76
src/schema/CTimer.h Normal file
View file

@ -0,0 +1,76 @@
//
// Created by Michal Přikryl on 31.10.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <functional>
#include <list>
#include <memory>
#include <platform.h>
#include <Shared.h>
#include "tasks.h"
// clang-format off
#define TIMERFLAG_NONE (0)
#define TIMERFLAG_MAP (1 << 0) // Only valid for this map, cancels on map change
#define TIMERFLAG_ROUND (1 << 1) // Only valid for this round, cancels on new round
namespace TemplatePlugin {
class CTimerBase
{
protected:
CTimerBase(float flInitialInterval, uint64 nTimerFlags) :
m_flInterval(flInitialInterval), m_nTimerFlags(nTimerFlags)
{}
void SetInterval(float flInterval) { m_flInterval = flInterval; }
void SetLastExecute(float flLastExecute) { m_flLastExecute = flLastExecute; }
public:
virtual bool Execute(bool bAutomaticExecute = false) = 0;
virtual void Cancel() = 0;
float GetInterval() { return m_flInterval; }
float GetLastExecute() { return m_flLastExecute; }
bool IsTimerFlagSet(uint64 iTimerFlag) { return !iTimerFlag || (m_nTimerFlags & iTimerFlag); }
void Initialize()
{
if (m_flLastExecute == -1)
m_flLastExecute = universal_time;
}
private:
float m_flInterval;
float m_flLastExecute = -1;
uint64 m_nTimerFlags;
};
// Timer functions should return the time until next execution, or a negative value like -1.0f to stop
// Having an interval of 0 is fine, in this case it will run on every game frame
class CTimer : public CTimerBase, public std::enable_shared_from_this<CTimer>
{
private:
// Silly workaround to achieve a "private constructor" only Create() can call
struct _timer_constructor_tag
{
explicit _timer_constructor_tag() = default;
};
public:
CTimer(float flInitialInterval, uint64 nTimerFlags, std::function<float()> func, _timer_constructor_tag) :
CTimerBase(flInitialInterval, nTimerFlags), m_func(func)
{}
static std::weak_ptr<CTimer> Create(float flInitialInterval, uint64 nTimerFlags, std::function<float()> func);
bool Execute(bool bAutomaticExecute) override;
void Cancel() override;
private:
std::function<float()> m_func;
};
void RunTimers();
void RemoveAllTimers();
void RemoveTimers(uint64 iTimerFlag);
}

21
src/schema/CTriggerPush.h Normal file
View file

@ -0,0 +1,21 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseTrigger.h"
#include "schemasystem.h"
#define SF_TRIG_PUSH_ONCE 0x80
namespace TemplatePlugin {
class CTriggerPush : public CBaseTrigger
{
public:
DECLARE_SCHEMA_CLASS(CTriggerPush);
SCHEMA_FIELD(Vector, m_vecPushDirEntitySpace)
SCHEMA_FIELD(bool, m_bTriggerOnStartTouch)
};
}

View file

@ -0,0 +1,33 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "CBaseEntity.h"
namespace TemplatePlugin {
struct VPhysicsCollisionAttribute_t
{
DECLARE_SCHEMA_CLASS_INLINE(VPhysicsCollisionAttribute_t)
SCHEMA_FIELD(uint8, m_nCollisionGroup)
SCHEMA_FIELD(uint64_t, m_nInteractsAs)
SCHEMA_FIELD(uint64_t, m_nInteractsWith)
SCHEMA_FIELD(uint64_t, m_nInteractsExclude)
SCHEMA_FIELD(uint16, m_nHierarchyId)
};
class CCollisionProperty
{
public:
DECLARE_SCHEMA_CLASS_INLINE(CCollisionProperty)
SCHEMA_FIELD(VPhysicsCollisionAttribute_t, m_collisionAttribute)
SCHEMA_FIELD(SolidType_t, m_nSolidType)
SCHEMA_FIELD(uint8, m_usSolidFlags)
SCHEMA_FIELD(uint8, m_CollisionGroup)
SCHEMA_FIELD(Vector, m_vecMins)
SCHEMA_FIELD(Vector, m_vecMaxs)
};
}

View file

@ -0,0 +1,38 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include "TemplatePlugin.h"
class CGameEntitySystem;
class CGameResourceService
{
public:
CGameEntitySystem* GetGameEntitySystem()
{
#ifdef WIN32
static int offset = 88;
#else
static int offset = 80;
#endif
return *reinterpret_cast<CGameEntitySystem**>(reinterpret_cast<uintptr_t>(g_pGameResourceServiceServer) + offset);
}
};

View file

@ -0,0 +1,53 @@
//
// Created by Michal Přikryl on 23.09.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "ctakedamageinfo.h"
#include "dynlibutils/module.h"
#include "CCSPlayerController.h"
#include <Shared.h>
namespace
TemplatePlugin
{
CAttackerInfo::CAttackerInfo(CEntityInstance* attacker)
{
NeedInit = false;
IsWorld = true;
IsPawn = false;
Attacker = attacker ? attacker->m_pEntity->GetRefEHandle().ToInt() : 0;
if (!attacker || (attacker->m_pEntity && attacker->m_pEntity->m_designerName.String() !=
"cs_player_controller"))
return;
if (auto* controller = static_cast<CCSPlayerController*>(attacker))
{
IsWorld = false;
IsPawn = true;
AttackerUserId = static_cast<unsigned short>(shared::g_pEngine->GetPlayerUserId(controller->GetPlayerSlot()).Get());
TeamNum = controller->m_iTeamNum();
TeamChecked = controller->m_iTeamNum();
}
}
HitGroup_t CTakeDamageInfo::GetHitGroup() const
{
const int off = shared::g_pGameConfig->GetOffset("CTakeDamageInfo_HitGroup");
if (off <= 0)
return HitGroup_t::HITGROUP_INVALID;
const uintptr_t base = reinterpret_cast<uintptr_t>(this);
const uintptr_t v4 = *reinterpret_cast<const uintptr_t*>(base + off);
if (!v4)
return HitGroup_t::HITGROUP_INVALID;
const uintptr_t v1 = *reinterpret_cast<const uintptr_t*>(v4 + 16);
if (!v1)
return HitGroup_t::HITGROUP_GENERIC;
const int32_t group = *reinterpret_cast<const int32_t*>(v1 + 56);
return static_cast<HitGroup_t>(group);
}
}

View file

@ -0,0 +1,84 @@
//
// Created by Michal Přikryl on 23.09.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <public/mathlib/vector.h>
#include "ehandle.h"
#include "schemasystem.h"
#include "globaltypes.h"
namespace
TemplatePlugin
{
class CBaseEntity;
class CTakeDamageInfo;
#pragma pack(push, 1)
struct CAttackerInfo
{
bool NeedInit = true; // 0x0
bool IsPawn = false; // 0x1
bool IsWorld = false; // 0x2
std::uint8_t pad_3[1] = {}; // vyrovnání na 4 bajty
std::uint32_t Attacker = 0; // 0x4
std::uint16_t AttackerUserId = 0; // 0x8
std::uint8_t pad_A[2] = {}; // vyrovnání na 0xC
int TeamChecked = -1; // 0x0C
int TeamNum = -1; // 0x10
CAttackerInfo() = default;
explicit CAttackerInfo(CEntityInstance* attacker);
};
#pragma pack(pop)
struct CTakeDamageInfoContainer
{
CTakeDamageInfo* pInfo;
};
class CTakeDamageInfo
{
public:
DECLARE_SCHEMA_CLASS(CTakeDamageInfo)
SCHEMA_FIELD(Vector, m_vecDamageForce);
SCHEMA_FIELD(Vector, m_vecDamagePosition);
SCHEMA_FIELD(Vector, m_vecReportedPosition);
SCHEMA_FIELD(Vector, m_vecDamageDirection);
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hInflictor);
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hAttacker);
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hAbility);
SCHEMA_FIELD(float, m_flDamage);
SCHEMA_FIELD(float, m_flTotalledDamage);
SCHEMA_FIELD(int32_t, m_bitsDamageType);
SCHEMA_FIELD(int32_t, m_iDamageCustom);
SCHEMA_FIELD(int8_t, m_iAmmoType);
SCHEMA_FIELD(float, m_flOriginalDamage);
SCHEMA_FIELD(bool, m_bShouldBleed);
SCHEMA_FIELD(bool, m_bShouldSpark);
SCHEMA_FIELD(TakeDamageFlags_t, m_nDamageFlags);
SCHEMA_FIELD_POINTER(char, m_sDamageSourceName);
SCHEMA_FIELD(HitGroup_t, m_iHitGroupId);
SCHEMA_FIELD(int32_t, m_nNumObjectsPenetrated);
SCHEMA_FIELD(float, m_flFriendlyFireDamageReductionRatio);
SCHEMA_FIELD(bool, m_bInTakeDamageFlow);
HitGroup_t GetHitGroup() const;
};
class CTakeDamageResult
{
public:
DECLARE_SCHEMA_CLASS(CTakeDamageResult)
SCHEMA_FIELD(CTakeDamageInfo*, m_pOriginatingInfo)
SCHEMA_FIELD(int32, m_nHealthLost)
SCHEMA_FIELD(int32, m_nHealthBefore)
SCHEMA_FIELD(int32, m_nDamageDealt)
SCHEMA_FIELD(float32, m_flPreModifiedDamage)
SCHEMA_FIELD(int32, m_nTotalledHealthLost)
SCHEMA_FIELD(int32, m_nTotalledDamageDealt)
SCHEMA_FIELD(bool, m_bWasDamageSuppressed)
};
}

482
src/schema/globaltypes.h Normal file
View file

@ -0,0 +1,482 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <platform.h>
#include "soundflags.h"
// struct TransmitInfo
// {
// CBitVec<16384> *m_pTransmitEdict;
// };
namespace TemplatePlugin {
enum CSRoundEndReason {
TargetBombed = 1, /**< Target Successfully Bombed! */
VIPEscaped, /**< The VIP has escaped! - Doesn't exist on CS:GO */
VIPKilled, /**< VIP has been assassinated! - Doesn't exist on CS:GO */
TerroristsEscaped, /**< The terrorists have escaped! */
CTStoppedEscape, /**< The CTs have prevented most of the terrorists from escaping! */
TerroristsStopped, /**< Escaping terrorists have all been neutralized! */
BombDefused, /**< The bomb has been defused! */
CTWin, /**< Counter-Terrorists Win! */
TerroristWin, /**< Terrorists Win! */
Draw, /**< Round Draw! */
HostagesRescued, /**< All Hostages have been rescued! */
TargetSaved, /**< Target has been saved! */
HostagesNotRescued, /**< Hostages have not been rescued! */
TerroristsNotEscaped, /**< Terrorists have not escaped! */
VIPNotEscaped, /**< VIP has not escaped! - Doesn't exist on CS:GO */
GameStart, /**< Game Commencing! */
TerroristsSurrender, /**< Terrorists Surrender */
CTSurrender, /**< CTs Surrender */
TerroristsPlanted, /**< Terrorists Planted the bomb */
CTsReachedHostage, /**< CTs Reached the hostage */
SurvivalWin,
SurvivalDraw
};
enum class CsTeam : uint8_t {
None = 0,
Spectator = 1,
Terrorist = 2,
CounterTerrorist = 3
};
enum GamePhase : int32_t {
GAMEPHASE_WARMUP_ROUND,
GAMEPHASE_PLAYING_STANDARD,
GAMEPHASE_PLAYING_FIArenaT_HALF,
GAMEPHASE_PLAYING_SECOND_HALF,
GAMEPHASE_HALFTIME,
GAMEPHASE_MATCH_ENDED,
GAMEPHASE_MAX
};
enum InputBitMask_t : uint64_t {
// MEnumeratorIsNotAFlag
IN_NONE = 0x0,
// MEnumeratorIsNotAFlag
IN_ALL = 0xffffffffffffffff,
IN_ATTACK = 0x1,
IN_JUMP = 0x2,
IN_DUCK = 0x4,
IN_FORWARD = 0x8,
IN_BACK = 0x10,
IN_USE = 0x20,
IN_TURNLEFT = 0x80,
IN_TURNRIGHT = 0x100,
IN_MOVELEFT = 0x200,
IN_MOVERIGHT = 0x400,
IN_ATTACK2 = 0x800,
IN_RELOAD = 0x2000,
IN_SPEED = 0x10000,
IN_JOYAUTOSPRINT = 0x20000,
// MEnumeratorIsNotAFlag
IN_FIRST_MOD_SPECIFIC_BIT = 0x100000000,
IN_USEORRELOAD = 0x100000000,
IN_SCORE = 0x200000000,
IN_ZOOM = 0x400000000,
IN_LOOK_AT_WEAPON = 0x800000000,
};
enum EInButtonState : uint32_t {
IN_BUTTON_UP = 0x0,
IN_BUTTON_DOWN = 0x1,
IN_BUTTON_DOWN_UP = 0x2,
IN_BUTTON_UP_DOWN = 0x3,
IN_BUTTON_UP_DOWN_UP = 0x4,
IN_BUTTON_DOWN_UP_DOWN = 0x5,
IN_BUTTON_DOWN_UP_DOWN_UP = 0x6,
IN_BUTTON_UP_DOWN_UP_DOWN = 0x7,
IN_BUTTON_STATE_COUNT = 0x8,
};
enum ParticleAttachment_t : uint32_t {
PATTACH_INVALID = 0xffffffff,
PATTACH_ABSORIGIN = 0x0, // Spawn at entity origin
PATTACH_ABSORIGIN_FOLLOW = 0x1, // Spawn at and follow entity origin
PATTACH_CUSTOMORIGIN = 0x2,
PATTACH_CUSTOMORIGIN_FOLLOW = 0x3,
PATTACH_POINT = 0x4, // Spawn at attachment point
PATTACH_POINT_FOLLOW = 0x5, // Spawn at and follow attachment point
PATTACH_EYES_FOLLOW = 0x6,
PATTACH_OVERHEAD_FOLLOW = 0x7,
PATTACH_WORLDORIGIN = 0x8,
PATTACH_ROOTBONE_FOLLOW = 0x9,
PATTACH_RENDERORIGIN_FOLLOW = 0xa,
PATTACH_MAIN_VIEW = 0xb,
PATTACH_WATERWAKE = 0xc,
PATTACH_CENTER_FOLLOW = 0xd,
PATTACH_CUSTOM_GAME_STATE_1 = 0xe,
PATTACH_HEALTHBAR = 0xf,
MAX_PATTACH_TYPES = 0x10,
};
enum ObserverMode_t : uint8_t {
OBS_MODE_NONE = 0x0,
OBS_MODE_FIXED = 0x1,
OBS_MODE_IN_EYE = 0x2,
OBS_MODE_CHASE = 0x3,
OBS_MODE_ROAMING = 0x4,
OBS_MODE_DIRECTED = 0x5,
NUM_OBSERVER_MODES = 0x6,
};
typedef uint32 SoundEventGuid_t;
struct SndOpEventGuid_t {
SoundEventGuid_t m_nGuid;
uint64 m_hStackHash;
};
// used with EmitSound_t
enum gender_t : uint8 {
GENDER_NONE = 0x0,
GENDER_MALE = 0x1,
GENDER_FEMALE = 0x2,
GENDER_NAMVET = 0x3,
GENDER_TEENGIRL = 0x4,
GENDER_BIKER = 0x5,
GENDER_MANAGER = 0x6,
GENDER_GAMBLER = 0x7,
GENDER_PRODUCER = 0x8,
GENDER_COACH = 0x9,
GENDER_MECHANIC = 0xA,
GENDER_CEDA = 0xB,
GENDER_CRAWLER = 0xC,
GENDER_UNDISTRACTABLE = 0xD,
GENDER_FALLEN = 0xE,
GENDER_RIOT_CONTROL = 0xF,
GENDER_CLOWN = 0x10,
GENDER_JIMMY = 0x11,
GENDER_HOSPITAL_PATIENT = 0x12,
GENDER_BRIDE = 0x13,
GENDER_LAST = 0x14,
};
enum DamageTypes_t : uint32_t {
DMG_GENERIC = 0x0,
DMG_CRUSH = 0x1,
DMG_BULLET = 0x2,
DMG_SLASH = 0x4,
DMG_BURN = 0x8,
DMG_VEHICLE = 0x10,
DMG_FALL = 0x20,
DMG_BLAST = 0x40,
DMG_CLUB = 0x80,
DMG_SHOCK = 0x100,
DMG_SONIC = 0x200,
DMG_ENERGYBEAM = 0x400,
DMG_DROWN = 0x4000,
DMG_POISON = 0x8000,
DMG_RADIATION = 0x10000,
DMG_DROWNRECOVER = 0x20000,
DMG_ACID = 0x40000,
DMG_PHYSGUN = 0x100000,
DMG_DISSOLVE = 0x200000,
DMG_BLAST_SURFACE = 0x400000,
DMG_BUCKSHOT = 0x1000000,
DMG_LASTGENERICFLAG = 0x1000000,
DMG_HEADSHOT = 0x2000000,
DMG_DANGERZONE = 0x4000000,
};
enum TakeDamageFlags_t : uint64_t
{
DFLAG_NONE = 0x0,
DFLAG_SUPPRESS_HEALTH_CHANGES = 0x1,
DFLAG_SUPPRESS_PHYSICS_FORCE = 0x2,
DFLAG_SUPPRESS_EFFECTS = 0x4,
DFLAG_PREVENT_DEATH = 0x8,
DFLAG_FORCE_DEATH = 0x10,
DFLAG_ALWAYS_GIB = 0x20,
DFLAG_NEVER_GIB = 0x40,
DFLAG_REMOVE_NO_RAGDOLL = 0x80,
DFLAG_SUPPRESS_DAMAGE_MODIFICATION = 0x100,
DFLAG_ALWAYS_FIRE_DAMAGE_EVENTS = 0x200,
DFLAG_RADIUS_DMG = 0x400,
DFLAG_FORCEREDUCEARMOR_DMG = 0x800,
DFLAG_SUPPRESS_INTERRUPT_FLINCH = 0x1000,
DFLAG_IGNORE_DESTRUCTIBLE_PARTS = 0x2000,
DMG_LASTDFLAG = 0x2000,
DFLAG_IGNORE_ARMOR = 0x4000,
DFLAG_SUPPRESS_UTILREMOVE = 0x8000,
};
struct EmitSound_t {
EmitSound_t() : m_nChannel(0),
m_pSoundName(0),
m_flVolume(VOL_NORM),
m_SoundLevel(SNDLVL_NONE),
m_nFlags(0),
m_nPitch(PITCH_NORM),
m_pOrigin(0),
m_flSoundTime(0.0f),
m_pflSoundDuration(0),
m_bEmitCloseCaption(true),
m_bWarnOnMissingCloseCaption(false),
m_bWarnOnDirectWaveReference(false),
m_nSpeakerEntity(-1),
m_nForceGuid(0),
m_SpeakerGender(GENDER_NONE) {
}
int m_nChannel;
const char *m_pSoundName;
float m_flVolume;
soundlevel_t m_SoundLevel;
int m_nFlags;
int m_nPitch;
const Vector *m_pOrigin;
float m_flSoundTime;
float *m_pflSoundDuration;
bool m_bEmitCloseCaption;
bool m_bWarnOnMissingCloseCaption;
bool m_bWarnOnDirectWaveReference;
CEntityIndex m_nSpeakerEntity;
// CUtlVector<Vector, CUtlMemory<Vector, int> > m_UtlVecSoundOrigin;
void *unk01;
SoundEventGuid_t m_nForceGuid;
gender_t m_SpeakerGender;
};
struct GameTick_t {
public:
DECLARE_SCHEMA_CLASS_INLINE(GameTick_t)
SCHEMA_FIELD(int32_t, m_Value);
};
struct AmmoIndex_t {
public:
DECLARE_SCHEMA_CLASS_INLINE(AmmoIndex_t)
SCHEMA_FIELD(int8_t, m_Value);
};
class CNetworkTransmitComponent {
public:
DECLARE_SCHEMA_CLASS_INLINE(CNetworkTransmitComponent)
};
class CNetworkVelocityVector {
public:
DECLARE_SCHEMA_CLASS_INLINE(CNetworkVelocityVector)
SCHEMA_FIELD(float, m_vecX)
SCHEMA_FIELD(float, m_vecY)
SCHEMA_FIELD(float, m_vecZ)
};
class CNetworkOriginCellCoordQuantizedVector {
public:
DECLARE_SCHEMA_CLASS_INLINE(CNetworkOriginCellCoordQuantizedVector)
SCHEMA_FIELD(uint16, m_cellX)
SCHEMA_FIELD(uint16, m_cellY)
SCHEMA_FIELD(uint16, m_cellZ)
SCHEMA_FIELD(uint16, m_nOutsideWorld)
// These are actually CNetworkedQuantizedFloat but we don't have the definition for it...
SCHEMA_FIELD(float, m_vecX)
SCHEMA_FIELD(float, m_vecY)
SCHEMA_FIELD(float, m_vecZ)
};
class CInButtonState {
public:
DECLARE_SCHEMA_CLASS_INLINE(CInButtonState)
// m_pButtonStates[3]
// m_pButtonStates[0] is the mask of currently pressed buttons
// m_pButtonStates[1] is the mask of buttons that changed in the current frame
SCHEMA_FIELD_POINTER(uint64, m_pButtonStates)
};
enum class PlayerButtons : uint64_t {
Attack = (1ull << 0),
Jump = (1ull << 1),
Duck = (1ull << 2),
Forward = (1ull << 3),
Back = (1ull << 4),
Use = (1ull << 5),
Cancel = (1ull << 6),
Left = (1ull << 7),
Right = (1ull << 8),
Moveleft = (1ull << 9),
Moveright = (1ull << 10),
Attack2 = (1ull << 11),
Run = (1ull << 12),
Reload = (1ull << 13),
Alt1 = (1ull << 14),
Alt2 = (1ull << 15),
Speed = (1ull << 16),
Walk = (1ull << 17),
Zoom = (1ull << 18),
Weapon1 = (1ull << 19),
Weapon2 = (1ull << 20),
Bullrush = (1ull << 21),
Grenade1 = (1ull << 22),
Grenade2 = (1ull << 23),
Attack3 = (1ull << 24),
Scoreboard = (1ull << 33),
Inspect = (1ull << 35),
};
inline std::string PlayerButtonsToString(uint64* buttons) {
struct BtnName {
uint64 flag;
const char *name;
};
static constexpr BtnName names[] = {
{(uint64) PlayerButtons::Attack, "Attack"},
{(uint64) PlayerButtons::Jump, "Jump"},
{(uint64) PlayerButtons::Duck, "Duck"},
{(uint64) PlayerButtons::Forward, "Forward"},
{(uint64) PlayerButtons::Back, "Back"},
{(uint64) PlayerButtons::Use, "Use"},
{(uint64) PlayerButtons::Cancel, "Cancel"},
{(uint64) PlayerButtons::Left, "Left"},
{(uint64) PlayerButtons::Right, "Right"},
{(uint64) PlayerButtons::Moveleft, "Moveleft"},
{(uint64) PlayerButtons::Moveright, "Moveright"},
{(uint64) PlayerButtons::Attack2, "Attack2"},
{(uint64) PlayerButtons::Run, "Run"},
{(uint64) PlayerButtons::Reload, "Reload"},
{(uint64) PlayerButtons::Alt1, "Alt1"},
{(uint64) PlayerButtons::Alt2, "Alt2"},
{(uint64) PlayerButtons::Speed, "Speed"},
{(uint64) PlayerButtons::Walk, "Walk"},
{(uint64) PlayerButtons::Zoom, "Zoom"},
{(uint64) PlayerButtons::Weapon1, "Weapon1"},
{(uint64) PlayerButtons::Weapon2, "Weapon2"},
{(uint64) PlayerButtons::Bullrush, "Bullrush"},
{(uint64) PlayerButtons::Grenade1, "Grenade1"},
{(uint64) PlayerButtons::Grenade2, "Grenade2"},
{(uint64) PlayerButtons::Attack3, "Attack3"},
{(uint64) PlayerButtons::Scoreboard, "Scoreboard"},
{(uint64) PlayerButtons::Inspect, "Inspect"},
};
std::string out;
for (const auto &btn: names) {
if (*buttons & btn.flag) {
if (!out.empty())
out += " | ";
out += btn.name;
}
}
if (out.empty())
out = "NONE";
return out;
}
enum class PlayerFlags : uint32_t {
OnGround = (1u << 0), // At rest / on the ground
Ducking = (1u << 1), // Player is fully crouched
WaterJump = (1u << 2), // Jumping out of water
OnTrain = (1u << 3), // Controlling a train
InRain = (1u << 4), // Standing in rain
Frozen = (1u << 5), // Frozen for 3rd person cam
AtControls = (1u << 6), // Can't move, but key inputs go to other entity
Client = (1u << 7), // Is a player
FakeClient = (1u << 8), // Simulated server-side
InWater = (1u << 9), // In water
};
inline std::string PlayerFlagsToString(uint32_t flags) {
struct FlagName {
uint32_t flag;
const char* name;
};
static constexpr FlagName names[] = {
{ (uint32_t)PlayerFlags::OnGround, "OnGround" },
{ (uint32_t)PlayerFlags::Ducking, "Ducking" },
{ (uint32_t)PlayerFlags::WaterJump, "WaterJump" },
{ (uint32_t)PlayerFlags::OnTrain, "OnTrain" },
{ (uint32_t)PlayerFlags::InRain, "InRain" },
{ (uint32_t)PlayerFlags::Frozen, "Frozen" },
{ (uint32_t)PlayerFlags::AtControls, "AtControls" },
{ (uint32_t)PlayerFlags::Client, "Client" },
{ (uint32_t)PlayerFlags::FakeClient, "FakeClient" },
{ (uint32_t)PlayerFlags::InWater, "InWater" },
};
std::string out;
for (const auto& f : names) {
if (flags & f.flag) {
if (!out.empty())
out += " | ";
out += f.name;
}
}
if (out.empty())
out = "NONE";
return out;
}
class CGlowProperty {
public:
DECLARE_SCHEMA_CLASS_INLINE(CGlowProperty)
SCHEMA_FIELD(Vector, m_fGlowColor)
SCHEMA_FIELD(int, m_iGlowType)
SCHEMA_FIELD(int, m_iGlowTeam)
SCHEMA_FIELD(int, m_nGlowRange)
SCHEMA_FIELD(int, m_nGlowRangeMin)
SCHEMA_FIELD(Color, m_glowColorOverride)
SCHEMA_FIELD(bool, m_bFlashing)
SCHEMA_FIELD(bool, m_bGlowing)
};
struct TraceHistory {
Vector start;
Vector end;
Ray_t ray;
bool didHit;
Vector m_vStartPos; // start position
Vector m_vEndPos; // final position
Vector m_vHitNormal; // surface normal at impact
Vector m_vHitPoint; // exact hit point if m_bExactHitPoint is true, otherwise equal to m_vEndPos
float m_flHitOffset; // surface normal hit offset
float m_flFraction; // time completed, 1.0 = didn't hit anything
float32 error;
Vector velocity;
};
class CCheckTransmitInfoHack {
public:
CBitVec<16384> *m_pTransmitEntity;
private:
[[maybe_unused]] int8_t m_pad8[568];
public:
int32_t m_nPlayerSlot;
bool m_bFullUpdate;
};
}

45
src/schema/plat.h Normal file
View file

@ -0,0 +1,45 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include "metamod_oslink.h"
struct Module
{
#ifndef _WIN32
void* pHandle;
#endif
uint8_t* pBase;
unsigned int nSize;
};
#ifndef _WIN32
int GetModuleInformation(HINSTANCE module, void** base, size_t* length);
#endif
#ifdef _WIN32
#define MODULE_PREFIX ""
#define MODULE_EXT ".dll"
#else
#define MODULE_PREFIX "lib"
#define MODULE_EXT ".so"
#endif
void Plat_WriteMemory(void* pPatchAddress, uint8_t *pPatch, int iPatchSize);

230
src/schema/schemasystem.cpp Normal file
View file

@ -0,0 +1,230 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "schemasystem.h"
#include "platform.h"
#include "schemasystem/schemasystem.h"
#include "tier1/utlmap.h"
#include <entity2/entityidentity.h>
#include <entity2/entityinstance.h>
#include <edict.h>
#include "CBaseEntity.h"
#include "tier0/memdbgon.h"
#ifdef _WIN32
#define MODULE_PREFIX ""
#define MODULE_EXT ".dll"
#else
#define MODULE_PREFIX "lib"
#define MODULE_EXT ".so"
#endif
namespace
TemplatePlugin
{
using SchemaKeyValueMap_t = std::map<uint32_t, SchemaKey>;
using SchemaTableMap_t = std::map<uint32_t, SchemaKeyValueMap_t>;
static constexpr uint32_t g_ChainKey = hash_32_fnv1a_const("__m_pChainEntity");
static bool IsFieldNetworked(SchemaClassFieldData_t& field)
{
for (int i = 0; i < field.m_nStaticMetadataCount; i++)
{
static auto networkEnabled = hash_32_fnv1a_const("MNetworkEnable");
if (networkEnabled == hash_32_fnv1a_const(field.m_pStaticMetadata[i].m_pszName))
return true;
}
return false;
}
// Try to recursively find __m_pChainEntity in base classes
// (e.g. CCSGameRules -> CTeamplayRules -> CMultiplayRules -> CGameRules, in this case it's in CGameRules)
static void InitChainOffset(SchemaClassInfoData_t* pClassInfo, SchemaKeyValueMap_t& keyValueMap)
{
short fieldsSize = pClassInfo->m_nFieldCount;
SchemaClassFieldData_t* pFields = pClassInfo->m_pFields;
for (int i = 0; i < fieldsSize; ++i)
{
SchemaClassFieldData_t& field = pFields[i];
if (hash_32_fnv1a_const(field.m_pszName) != g_ChainKey)
continue;
std::pair<uint32_t, SchemaKey> keyValuePair;
keyValuePair.first = g_ChainKey;
keyValuePair.second.offset = field.m_nSingleInheritanceOffset;
keyValuePair.second.networked = IsFieldNetworked(field);
keyValueMap.insert(keyValuePair);
return;
}
// Not the base class yet, keep looking
if (pClassInfo->m_nBaseClassCount)
return InitChainOffset(pClassInfo->m_pBaseClasses[0].m_pClass, keyValueMap);
}
static void InitSchemaKeyValueMap(SchemaClassInfoData_t* pClassInfo, SchemaKeyValueMap_t& keyValueMap)
{
short fieldsSize = pClassInfo->m_nFieldCount;
SchemaClassFieldData_t* pFields = pClassInfo->m_pFields;
for (int i = 0; i < fieldsSize; ++i)
{
SchemaClassFieldData_t& field = pFields[i];
std::pair<uint32_t, SchemaKey> keyValuePair;
keyValuePair.first = hash_32_fnv1a_const(field.m_pszName);
keyValuePair.second.offset = field.m_nSingleInheritanceOffset;
keyValuePair.second.networked = IsFieldNetworked(field);
keyValueMap.insert(keyValuePair);
}
// If this is a child class there might be a parent class with __m_pChainEntity
if (keyValueMap.find(g_ChainKey) == keyValueMap.end() && pClassInfo->m_nBaseClassCount)
InitChainOffset(pClassInfo->m_pBaseClasses[0].m_pClass, keyValueMap);
}
static bool InitSchemaFieldsForClass(SchemaTableMap_t& tableMap, const char* className, uint32_t classKey)
{
CSchemaSystemTypeScope* pType = shared::g_pSchemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT);
if (!pType)
return false;
SchemaClassInfoData_t* pClassInfo = pType->FindDeclaredClass(className).Get();
if (!pClassInfo)
{
SchemaKeyValueMap_t map;
tableMap.insert(std::make_pair(classKey, map));
Warning("InitSchemaFieldsForClass(): '%s' was not found!\n", className);
return false;
}
SchemaKeyValueMap_t& keyValueMap = tableMap.insert(std::make_pair(classKey, SchemaKeyValueMap_t())).first->
second;
InitSchemaKeyValueMap(pClassInfo, keyValueMap);
return true;
}
int16_t schema::FindChainOffset(const char* className, uint32_t classNameHash)
{
return schema::GetOffset(className, classNameHash, "__m_pChainEntity", g_ChainKey).offset;
}
int16_t schema::FindChainOffset(const char* className)
{
CSchemaSystemTypeScope* pType = shared::g_pSchemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT);
if (!pType)
return false;
SchemaClassInfoData_t* pClassInfo = pType->FindDeclaredClass(className).Get();
do
{
SchemaClassFieldData_t* pFields = pClassInfo->m_pFields;
short fieldsSize = pClassInfo->m_nFieldCount;
for (int i = 0; i < fieldsSize; ++i)
{
SchemaClassFieldData_t& field = pFields[i];
if (V_strcmp(field.m_pszName, "__m_pChainEntity") == 0)
{
return field.m_nSingleInheritanceOffset;
}
}
}
while ((pClassInfo = pClassInfo->m_pBaseClasses ? pClassInfo->m_pBaseClasses->m_pClass : nullptr) != nullptr);
return 0;
}
SchemaKey schema::GetOffset(const char* className, uint32_t classKey, const char* memberName, uint32_t memberKey)
{
static SchemaTableMap_t schemaTableMap;
if (schemaTableMap.find(classKey) == schemaTableMap.end())
{
if (InitSchemaFieldsForClass(schemaTableMap, className, classKey))
return GetOffset(className, classKey, memberName, memberKey);
return {0, 0};
}
SchemaKeyValueMap_t tableMap = schemaTableMap[classKey];
if (tableMap.find(memberKey) == tableMap.end())
{
if (memberKey != g_ChainKey)
Warning("schema::GetOffset(): '%s' was not found in '%s'!\n", memberName, className);
return {0, 0};
}
return tableMap[memberKey];
}
int32_t schema::GetServerOffset(const char* pszClassName, const char* pszPropName)
{
SchemaClassInfoData_t* pClassInfo = shared::g_pSchemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT)->
FindDeclaredClass(pszClassName).Get();
if (pClassInfo)
{
for (int i = 0; i < pClassInfo->m_nFieldCount; i++)
{
auto& pFieldData = pClassInfo->m_pFields[i];
if (std::strcmp(pFieldData.m_pszName, pszPropName) == 0)
{
return pFieldData.m_nSingleInheritanceOffset;
}
}
}
return -1;
}
int32 schema::GetClassSize(const char* className) {
CSchemaSystemTypeScope *pType = shared::g_pSchemaSystem->FindTypeScopeForModule(
MODULE_PREFIX "server" MODULE_EXT);
SchemaClassInfoData_t *pClassInfo = pType->FindDeclaredClass(className).Get();
if (!pClassInfo) return -1;
return pClassInfo->m_nSize;
}
void NetworkVarStateChanged(uintptr_t pNetworkVar, uint32_t nOffset, uint32 nNetworkStateChangedOffset)
{
NetworkStateChangedData data(nOffset);
CALL_VIRTUAL(void, nNetworkStateChangedOffset, (void*)pNetworkVar, &data);
}
void EntityNetworkStateChanged(uintptr_t pEntity, uint nOffset)
{
NetworkStateChangedData data(nOffset);
reinterpret_cast<CEntityInstance*>(pEntity)->NetworkStateChanged(data);
}
void ChainNetworkStateChanged(uintptr_t pNetworkVarChainer, uint nLocalOffset)
{
CEntityInstance* pEntity = reinterpret_cast<CNetworkVarChainer2*>(pNetworkVarChainer)->m_pEntity;
if (pEntity)
// NetworkStateChanged_t WENDER SDK
// NetworkStateChangedData HL2SDK-CS@
pEntity->NetworkStateChanged(NetworkStateChangedData(nLocalOffset, -1,
reinterpret_cast<CNetworkVarChainer2*>(
pNetworkVarChainer)->m_PathIndex));
}
}

289
src/schema/schemasystem.h Normal file
View file

@ -0,0 +1,289 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2025 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable : 4005)
#endif
#include <type_traits>
#ifdef _WIN32
#pragma warning(pop)
#endif
#include <entity2/entityidentity.h>
#include <entity2/entityinstance.h>
#include "const.h"
#include "stdint.h"
#include "tier0/dbg.h"
#include "virtual.h"
#undef schema
namespace
TemplatePlugin
{
struct SchemaKey
{
int32 offset;
bool networked;
};
class CNetworkVarChainer2
{
public:
CEntityInstance* m_pEntity;
private:
uint8 pad_0000[24];
public:
ChangeAccessorFieldPathIndex_t m_PathIndex;
private:
uint8 pad_0024[4];
};
void EntityNetworkStateChanged(uintptr_t pEntity, uint nOffset);
void ChainNetworkStateChanged(uintptr_t pNetworkVarChainer, uint nOffset);
void NetworkVarStateChanged(uintptr_t pNetworkVar, uint32_t nOffset, uint32 nNetworkStateChangedOffset);
namespace schema
{
int16_t FindChainOffset(const char* className, uint32_t classNameHash);
int16_t FindChainOffset(const char* className);
SchemaKey GetOffset(const char* className, uint32_t classKey, const char* memberName, uint32_t memberKey);
int32_t GetServerOffset(const char* pszClassName, const char* pszPropName);
int32_t GetClassSize(const char* className);
} // namespace schema
constexpr uint32_t val_32_const = 0x811c9dc5;
constexpr uint32_t prime_32_const = 0x1000193;
constexpr uint64_t val_64_const = 0xcbf29ce484222325;
constexpr uint64_t prime_64_const = 0x100000001b3;
inline constexpr uint32_t hash_32_fnv1a_const(const char* const str, const uint32_t value = val_32_const) noexcept
{
return (str[0] == '\0') ? value : hash_32_fnv1a_const(&str[1], (value ^ uint32_t(str[0])) * prime_32_const);
}
inline constexpr uint64_t hash_64_fnv1a_const(const char* const str, const uint64_t value = val_64_const) noexcept
{
return (str[0] == '\0') ? value : hash_64_fnv1a_const(&str[1], (value ^ uint64_t(str[0])) * prime_64_const);
}
template <typename T>
inline constexpr bool schema_writable_v =
std::is_pointer_v<T> ||
std::is_trivially_copyable_v<T> ||
std::is_same_v<T, Vector> ||
std::is_same_v<T, QAngle> ||
std::is_same_v<T, Color>;
#define SCHEMA_FIELD_OFFSET(type, varName, extra_offset) \
class varName##_prop \
{ \
public: \
std::add_lvalue_reference_t<type> Get() \
{ \
static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \
static const auto m_offset = offsetof(ThisClass, varName); \
\
uintptr_t pThisClass = ((uintptr_t)this - m_offset); \
\
return *reinterpret_cast<std::add_pointer_t<type>>(pThisClass + m_key.offset + extra_offset); \
} \
template <typename T = type> \
std::enable_if_t<std::is_pointer_v<T>, void> \
Set(T val) \
{ \
static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \
static const auto m_offset = offsetof(ThisClass, varName); \
\
uintptr_t pThisClass = ((uintptr_t)this - m_offset); \
\
NetworkStateChanged(); \
*reinterpret_cast<T*>(pThisClass + m_key.offset + extra_offset) = val; \
} \
template <typename T = type> \
std::enable_if_t<!std::is_pointer_v<T> && std::is_trivially_copyable_v<T>, void> \
Set(T val) \
{ \
static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \
static const auto m_offset = offsetof(ThisClass, varName); \
\
uintptr_t pThisClass = ((uintptr_t)this - m_offset); \
\
NetworkStateChanged(); \
*reinterpret_cast<T*>(pThisClass + m_key.offset + extra_offset) = val; \
} \
template <typename T = type> \
std::enable_if_t<!std::is_pointer_v<T> && !std::is_trivially_copyable_v<T>, void> \
Set(const T& val) \
{ \
static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \
static const auto m_offset = offsetof(ThisClass, varName); \
\
uintptr_t pThisClass = ((uintptr_t)this - m_offset); \
\
NetworkStateChanged(); \
std::memcpy( \
reinterpret_cast<void*>(pThisClass + m_key.offset + extra_offset), \
&val, \
sizeof(type) \
); \
} \
void NetworkStateChanged() \
{ \
static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \
static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \
static const auto m_offset = offsetof(ThisClass, varName); \
\
uintptr_t pThisClass = ((uintptr_t)this - m_offset); \
\
if (m_chain != 0 && m_key.networked) \
{ \
ChainNetworkStateChanged(pThisClass + m_chain, m_key.offset + extra_offset); \
} \
else if (m_key.networked) \
{ \
if (!m_networkStateChangedOffset) \
EntityNetworkStateChanged(pThisClass, m_key.offset + extra_offset); \
else \
NetworkVarStateChanged(pThisClass, m_key.offset + extra_offset, m_networkStateChangedOffset); \
} \
} \
operator std::add_lvalue_reference_t<type>() \
{ \
return Get(); \
} \
std::add_lvalue_reference_t<type> operator()() \
{ \
return Get(); \
} \
std::add_lvalue_reference_t<type> operator->() \
{ \
return Get(); \
} \
template <typename T = type> \
std::enable_if_t<schema_writable_v<T>, void> \
operator()(T val) \
{ \
Set(val); \
} \
template <typename T = type> \
std::enable_if_t<schema_writable_v<T>, varName##_prop&> \
operator=(T val) \
{ \
Set(val); \
return *this; \
} \
private: \
/*Prevent accidentally copying this wrapper class instead of the underlying field*/ \
varName##_prop(const varName##_prop&) = delete; \
static constexpr auto m_varNameHash = hash_32_fnv1a_const(#varName); \
} varName;
#define SCHEMA_FIELD_POINTER_OFFSET(type, varName, extra_offset) \
class varName##_prop \
{ \
public: \
type* Get() \
{ \
static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \
static const auto m_offset = offsetof(ThisClass, varName); \
\
uintptr_t pThisClass = ((uintptr_t)this - m_offset); \
\
return reinterpret_cast<std::add_pointer_t<type>>(pThisClass + m_key.offset + extra_offset); \
} \
void NetworkStateChanged() /*Call this after editing the field*/ \
{ \
static const auto m_key = schema::GetOffset(m_className, m_classNameHash, #varName, m_varNameHash); \
static const auto m_chain = schema::FindChainOffset(m_className, m_classNameHash); \
static const auto m_offset = offsetof(ThisClass, varName); \
\
uintptr_t pThisClass = ((uintptr_t)this - m_offset); \
\
if (m_chain != 0 && m_key.networked) \
{ \
ChainNetworkStateChanged(pThisClass + m_chain, m_key.offset + extra_offset); \
} \
else if (m_key.networked) \
{ \
if (!m_networkStateChangedOffset) \
EntityNetworkStateChanged(pThisClass, m_key.offset + extra_offset); \
else \
NetworkVarStateChanged(pThisClass, m_key.offset + extra_offset, m_networkStateChangedOffset); \
} \
} \
operator type*() \
{ \
return Get(); \
} \
type* operator()() \
{ \
return Get(); \
} \
type* operator->() \
{ \
return Get(); \
} \
private: \
/*Prevent accidentally copying this wrapper class instead of the underlying field*/ \
varName##_prop(const varName##_prop&) = delete; \
static constexpr auto m_varNameHash = hash_32_fnv1a_const(#varName); \
} varName;
// Use this when you want the member's value itself
#define SCHEMA_FIELD(type, varName) \
SCHEMA_FIELD_OFFSET(type, varName, 0)
// Use this when you want a pointer to a member
#define SCHEMA_FIELD_POINTER(type, varName) \
SCHEMA_FIELD_POINTER_OFFSET(type, varName, 0)
// If the class needs a specific offset for its NetworkStateChanged (like CEconItemView), use this and provide the offset
#define DECLARE_SCHEMA_CLASS_BASE(ClassName, offset) \
private: \
typedef ClassName ThisClass; \
static constexpr const char* m_className = #ClassName; \
static constexpr uint32_t m_classNameHash = hash_32_fnv1a_const(#ClassName);\
static constexpr int m_networkStateChangedOffset = offset; \
public:
#define SCHEMA_FIELD_OLD(type, className, propName) \
std::add_lvalue_reference_t<type> propName() \
{ \
static const int32_t offset = schema::GetServerOffset(#className, #propName); \
if(offset == -1) \
std::runtime_error("Failed to find " #propName " in " #className); \
return *reinterpret_cast<std::add_pointer_t<type>>(reinterpret_cast<intptr_t>(this) + offset); \
}
#define DECLARE_SCHEMA_CLASS(className) DECLARE_SCHEMA_CLASS_BASE(className, 0)
// Use this for non-entity classes such as CCollisionProperty or CGlowProperty
// The only difference is that their NetworkStateChanged function is index 1 on their vtable rather than being CEntityInstance::NetworkStateChanged
// Though some classes like CGameRules will instead use their CNetworkVarChainer as a link back to the parent entity
#define DECLARE_SCHEMA_CLASS_INLINE(className) DECLARE_SCHEMA_CLASS_BASE(className, 1)
}

View file

@ -0,0 +1,445 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "playerslot.h"
#include "steam/steamclientpublic.h"
#include "utlstring.h"
#include "inetchannel.h"
#include "networkbasetypes.pb.h"
#include <inetchannel.h>
#include <playerslot.h>
#include "circularbuffer.h"
#include "networksystem/inetworksystem.h"
#include "threadtools.h"
#include "tier1/netadr.h"
#include <entity2/entityidentity.h>
#include <networksystem/inetworksystem.h>
#include <steam/steamclientpublic.h>
#include <tier1/utlstring.h>
#include <network_connection.pb.h>
#include <netmessages.pb.h>
namespace
TemplatePlugin
{
class CHLTVServer;
class INetMessage;
class CNetworkGameServerBase;
class CNetworkGameServer;
class CFrameSnapshot;
class CMsg_CVars;
class CNETMsg_StringCmd_t;
class CNETMsg_Tick_t;
class CNETMsg_SpawnGroup_LoadCompleted_t;
class CCLCMsg_ClientInfo_t;
class CCLCMsg_BaselineAck_t;
class CCLCMsg_LoadingProgress_t;
class CCLCMsg_SplitPlayerConnect_t;
class CCLCMsg_SplitPlayerDisconnect_t;
class CCLCMsg_CmdKeyValues_t;
class CCLCMsg_Move_t;
class CCLCMsg_VoiceData_t;
class CCLCMsg_FileCRCCheck_t;
class CCLCMsg_RespondCvarValue_t;
class NetMessagePacketStart_t;
class NetMessagePacketEnd_t;
class NetMessageConnectionClosed_t;
class NetMessageConnectionCrashed_t;
class NetMessageSplitscreenUserChanged_t;
struct HltvReplayStats_t
{
enum FailEnum_t
{
FAILURE_ALREADY_IN_REPLAY,
FAILURE_TOO_FREQUENT,
FAILURE_NO_FRAME,
FAILURE_NO_FRAME2,
FAILURE_CANNOT_MATCH_DELAY,
FAILURE_FRAME_NOT_READY,
NUM_FAILURES
};
uint nClients;
uint nStartRequests;
uint nSuccessfulStarts;
uint nStopRequests;
uint nAbortStopRequests;
uint nUserCancels;
uint nFullReplays;
uint nNetAbortReplays;
uint nFailedReplays[NUM_FAILURES];
}; // sizeof 56
COMPILE_TIME_ASSERT(sizeof(HltvReplayStats_t) == 56);
struct Spike_t
{
public:
CUtlString m_szDesc;
int m_nBits;
};
COMPILE_TIME_ASSERT(sizeof(Spike_t) == 16);
class CNetworkStatTrace
{
public:
CUtlVector<Spike_t> m_Records;
int m_nMinWarningBytes;
int m_nStartBit;
int m_nCurBit;
};
COMPILE_TIME_ASSERT(sizeof(CNetworkStatTrace) == 40);
enum CopiedLockState_t : int32
{
CLS_NOCOPY = 0,
CLS_UNLOCKED = 1,
CLS_LOCKED_BY_COPYING_THREAD = 2,
};
template <class MUTEX, CopiedLockState_t L = CLS_UNLOCKED>
class CCopyableLock : public MUTEX
{
typedef MUTEX BaseClass;
public:
// ...
};
class CUtlSignaller_Base
{
public:
using Delegate_t = CUtlDelegate<void(CUtlSlot*)>;
CUtlSignaller_Base(const Delegate_t& other) :
m_SlotDeletionDelegate(other)
{
}
CUtlSignaller_Base(Delegate_t&& other) :
m_SlotDeletionDelegate(Move(other))
{
}
private:
Delegate_t m_SlotDeletionDelegate;
};
class CUtlSlot
{
public:
using MTElement_t = CUtlSignaller_Base*;
CUtlSlot() :
m_ConnectedSignallers(0, 1)
{
}
private:
CCopyableLock<CThreadFastMutex> m_Mutex;
CUtlVector<MTElement_t> m_ConnectedSignallers;
};
class CServerSideClientBase : public CUtlSlot, public INetworkChannelNotify,
public INetworkMessageProcessingPreFilter
{
public:
virtual ~CServerSideClientBase() = 0;
public:
CPlayerSlot GetPlayerSlot() const { return m_nClientSlot; }
CPlayerUserId GetUserID() const { return m_UserID; }
CEntityIndex GetEntityIndex() const { return m_nEntityIndex; }
CSteamID GetClientSteamID() const { return m_SteamID; }
const char* GetClientName() const { return m_Name; }
INetChannel* GetNetChannel() const { return m_NetChannel; }
const netadr_t* GetRemoteAddress() const { return &m_nAddr.GetAddress(); }
CNetworkGameServerBase* GetServer() const { return m_Server; }
virtual void Connect(int socket, const char* pszName, int nUserID, INetChannel* pNetChannel,
uint8 nConnectionTypeFlags,
uint32 uChallengeNumber) = 0;
// bool bFakePlayer = !nConnectionTypeFlags || (nConnectionTypeFlags & 8) != 0;
virtual void Inactivate(const char* pszAddons) = 0;
virtual void Reactivate(CPlayerSlot nSlot) = 0;
virtual void SetServer(CNetworkGameServer* pNetServer) = 0;
virtual void Reconnect() = 0;
virtual void Disconnect(ENetworkDisconnectionReason reason, const char* pszInternalReason) = 0;
virtual bool CheckConnect() = 0;
virtual void Create(CPlayerSlot& nSlot, CSteamID nSteamID, const char* pszName) = 0;
virtual void SetRate(int nRate) = 0;
virtual void SetUpdateRate(float fUpdateRate) = 0;
virtual int GetRate() = 0;
virtual void Clear() = 0;
virtual bool ExecuteStringCommand(const CNETMsg_StringCmd_t& msg) = 0;
// "false" trigger an anti spam counter to kick a client.
virtual bool SendNetMessage(const CNetMessage* pData, NetChannelBufType_t bufType = BUF_DEFAULT) = 0;
// "Client %d(%s) tried to send a RebroadcastSourceId msg.\n"
virtual bool FilterMessage(const CNetMessage* pData, INetChannel* pChannel) = 0;
// On Windows, this function is in a separate virtual table
public:
virtual void ClientPrintf(PRINTF_FORMAT_STRING const char*, ...) = 0;
bool IsConnected() const { return m_nSignonState >= SIGNONSTATE_CONNECTED; }
bool IsInGame() const { return m_nSignonState == SIGNONSTATE_FULL; }
bool IsSpawned() const { return m_nSignonState >= SIGNONSTATE_NEW; }
bool IsActive() const { return m_nSignonState == SIGNONSTATE_FULL; }
virtual bool IsFakeClient() const { return m_bFakePlayer; }
bool IsHLTV() const { return m_bIsHLTV; }
virtual bool IsHumanPlayer() const { return false; }
// Is an actual human player or splitscreen player (not a bot and not a HLTV slot)
virtual bool IsHearingClient(CPlayerSlot nSlot) const { return false; }
virtual bool IsProximityHearingClient() const = 0;
virtual bool IsLowViolenceClient() const { return m_bLowViolence; }
virtual bool IsSplitScreenUser() const { return m_bSplitScreenUser; }
public: // Message Handlers
virtual bool ProcessTick(const CNETMsg_Tick_t& msg) = 0;
virtual bool ProcessStringCmd(const CNETMsg_StringCmd_t& msg) = 0;
public:
virtual bool ApplyConVars(const CMsg_CVars& list) = 0;
private:
virtual bool unk_28() = 0;
public:
virtual bool ProcessSpawnGroup_LoadCompleted(const CNETMsg_SpawnGroup_LoadCompleted_t& msg) = 0;
virtual bool ProcessClientInfo(const CCLCMsg_ClientInfo_t& msg) = 0;
virtual bool ProcessBaselineAck(const CCLCMsg_BaselineAck_t& msg) = 0;
virtual bool ProcessLoadingProgress(const CCLCMsg_LoadingProgress_t& msg) = 0;
virtual bool ProcessSplitPlayerConnect(const CCLCMsg_SplitPlayerConnect_t& msg) = 0;
virtual bool ProcessSplitPlayerDisconnect(const CCLCMsg_SplitPlayerDisconnect_t& msg) = 0;
virtual bool ProcessCmdKeyValues(const CCLCMsg_CmdKeyValues_t& msg) = 0;
private:
virtual bool unk_36() = 0;
virtual bool unk_37() = 0;
public:
virtual bool ProcessMove(const CCLCMsg_Move_t& msg) = 0;
virtual bool ProcessVoiceData(const CCLCMsg_VoiceData_t& msg) = 0;
virtual bool ProcessRespondCvarValue(const CCLCMsg_RespondCvarValue_t& msg) = 0;
virtual bool ProcessPacketStart(const NetMessagePacketStart_t& msg) = 0;
virtual bool ProcessPacketEnd(const NetMessagePacketEnd_t& msg) = 0;
virtual bool ProcessConnectionClosed(const NetMessageConnectionClosed_t& msg) = 0;
virtual bool ProcessConnectionCrashed(const NetMessageConnectionCrashed_t& msg) = 0;
public:
virtual bool ProcessChangeSplitscreenUser(const NetMessageSplitscreenUserChanged_t& msg) = 0;
private:
virtual bool unk_47() = 0;
virtual bool unk_48() = 0;
virtual bool unk_49() = 0;
public:
virtual void ConnectionStart(INetChannel* pNetChannel) = 0;
private: // SpawnGroup something.
virtual void unk_51() = 0;
virtual void unk_52() = 0;
public:
virtual void ExecuteDelayedCall(void*) = 0;
virtual bool UpdateAcknowledgedFramecount(int tick) = 0;
void ForceFullUpdate()
{
// For some reason, it doesn't work.
// UpdateAcknowledgedFramecount(-1);
m_nDeltaTick = -1;
}
virtual bool ShouldSendMessages() = 0;
virtual void UpdateSendState() = 0;
virtual const CMsgPlayerInfo& GetPlayerInfo() const { return m_playerInfo; }
virtual void UpdateUserSettings() = 0;
virtual void ResetUserSettings() = 0;
private:
virtual void unk_60() = 0;
public:
virtual void SendSignonData() = 0;
virtual void SpawnPlayer() = 0;
virtual void ActivatePlayer() = 0;
virtual void SetName(const char* name) = 0;
virtual void SetUserCVar(const char* cvar, const char* value) = 0;
SignonState_t GetSignonState() const { return m_nSignonState; }
virtual void FreeBaselines() = 0;
bool IsFullyAuthenticated(void) { return m_bFullyAuthenticated; }
void SetFullyAuthenticated(void) { m_bFullyAuthenticated = true; }
virtual CServerSideClientBase* GetSplitScreenOwner() { return m_pAttachedTo; }
virtual int GetNumPlayers() = 0;
virtual void ShouldReceiveStringTableUserData() = 0;
private:
virtual void unk_70(CPlayerSlot nSlot) = 0;
virtual void unk_71() = 0;
virtual void unk_72() = 0;
public:
virtual int GetHltvLastSendTick() = 0;
private:
virtual void unk_74() = 0;
virtual void unk_75() = 0;
virtual void unk_76() = 0;
public:
virtual void Await() = 0;
virtual void MarkToKick() = 0;
virtual void UnmarkToKick() = 0;
virtual bool ProcessSignonStateMsg(int state) = 0;
virtual void PerformDisconnection(ENetworkDisconnectionReason reason) = 0;
public:
CUtlString m_UserIDString;
CUtlString m_Name;
CPlayerSlot m_nClientSlot;
CEntityIndex m_nEntityIndex;
CNetworkGameServerBase* m_Server;
INetChannel* m_NetChannel;
// CServerSideClientBase::Connect( name='%s', userid=%d, fake=%d, connectiontypeflags=%d, chan->addr=%s )
uint8 m_nConnectionTypeFlags;
uint8 m_nAsyncDisconnectFlags; // check in Disconnect function, 1 add to queue, 2 disconnect now
bool m_bMarkedToKick;
SignonState_t m_nSignonState;
bool m_bSplitScreenUser;
bool m_bSplitAllowFastDisconnect;
int m_nSplitScreenPlayerSlot;
CServerSideClientBase* m_SplitScreenUsers[4];
CServerSideClientBase* m_pAttachedTo;
bool m_bSplitPlayerDisconnecting;
int m_nDisconnectionTypeFlags;
bool m_bFakePlayer;
bool m_bSendingSnapshot;
private:
[[maybe_unused]] char pad162[0x6];
public:
CPlayerUserId m_UserID = -1;
bool m_bReceivedPacket; // true, if client received a packet after the last send packet
CSteamID m_SteamID;
CSteamID m_DisconnectedSteamID;
CSteamID m_AuthTicketSteamID; // Auth ticket
CSteamID m_nFriendsID;
ns_address m_nAddr;
ns_address m_nAddr2;
KeyValues* m_ConVars;
bool m_bUnk0;
private:
[[maybe_unused]] char pad281[0x28];
public:
bool m_bConVarsChanged;
bool m_bIsHLTV;
private:
[[maybe_unused]] char pad323[0xD];
public:
uint32 m_nSendtableCRC;
uint32 m_uChallengeNumber;
int m_nSignonTick;
int m_nDeltaTick;
int m_UnkVariable3;
int m_nStringTableAckTick;
int m_UnkVariable4;
CFrameSnapshot* m_pLastSnapshot; // last send snapshot
CUtlVector<void*> m_vecLoadedSpawnGroups;
CMsgPlayerInfo m_playerInfo;
CFrameSnapshot* m_pBaseline;
int m_nBaselineUpdateTick;
CBitVec<MAX_EDICTS> m_BaselinesSent;
int m_nBaselineUsed; // 0/1 toggling flag, singaling client what baseline to use
int m_nLoadingProgress; // 0..100 progress, only valid during loading
// This is used when we send out a nodelta packet to put the client in a state where we wait
// until we get an ack from them on this packet.
// This is for 3 reasons:
// 1. A client requesting a nodelta packet means they're screwed so no point in deluging them with data.
// Better to send the uncompressed data at a slow rate until we hear back from them (if at all).
// 2. Since the nodelta packet deletes all client entities, we can't ever delta from a packet previous to it.
// 3. It can eat up a lot of CPU on the server to keep building nodelta packets while waiting for
// a client to get back on its feet.
int m_nForceWaitForTick = -1;
CCircularBuffer m_UnkBuffer = {1024};
bool m_bLowViolence = false; // true if client is in low-violence mode (L4D server needs to know)
bool m_bSomethingWithAddressType = true;
bool m_bFullyAuthenticated = false;
bool m_bUnk1 = false;
int m_nUnk;
// The datagram is written to after every frame, but only cleared
// when it is sent out to the client. overflow is tolerated.
// Time when we should send next world state update ( datagram )
float m_fNextMessageTime = 0.0f;
float m_fAuthenticatedTime = -1.0f;
// Default time to wait for next message
float m_fSnapshotInterval = 0.0f;
private:
[[maybe_unused]] char pad2572[0x8];
[[maybe_unused]] char m_packetmsg[0x15C]; // CSVCMsg_PacketEntities_t
#ifdef __linux__
[[maybe_unused]] char pad2928[0x8];
#endif
public:
CNetworkStatTrace m_Trace;
private:
[[maybe_unused]] char pad2976[0x8];
public:
// SV: Player %s kicked for too many failed console commands
int m_spamCommandsCount = 0; // if the value is greater than 16, the player will be kicked with reason 39
int m_unknown = 0;
double m_lastExecutedCommand = 0.0; // if command executed more than once per second, ++m_spamCommandCount
private:
[[maybe_unused]] char pad3000[0x8];
public:
CCommand* m_pCommand;
};
#ifdef __linux__
COMPILE_TIME_ASSERT(sizeof(CServerSideClientBase) == 3016);
#endif
class CServerSideClient : public CServerSideClientBase
{
};
}

410
src/schema/services.h Normal file
View file

@ -0,0 +1,410 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2025 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "globaltypes.h"
#include "CCSWeaponBase.h"
#include <platform.h>
#include <unordered_map>
#define AMMO_OFFSET_HEGRENADE 13
#define AMMO_OFFSET_FLASHBANG 14
#define AMMO_OFFSET_SMOKEGRENADE 15
#define AMMO_OFFSET_MOLOTOV 16
#define AMMO_OFFSET_DECOY 17
namespace TemplatePlugin {
class CCSPlayerController;
class CCSPlayerPawn;
extern bool g_bAwsChangingTeam;
struct CSPerRoundStats_t {
DECLARE_SCHEMA_CLASS_INLINE(CSPerRoundStats_t)
SCHEMA_FIELD(int32_t, m_iKills);
SCHEMA_FIELD(int32_t, m_iDeaths);
SCHEMA_FIELD(int32_t, m_iAssists);
SCHEMA_FIELD(int32_t, m_iDamage);
SCHEMA_FIELD(int32_t, m_iHeadShotKills);
SCHEMA_FIELD(int32_t, m_iUtilityDamage);
SCHEMA_FIELD(int32_t, m_iEnemiesFlashed);
SCHEMA_FIELD(int32_t, m_iObjective);
SCHEMA_FIELD(int32_t, m_iCashEarned);
SCHEMA_FIELD(int32_t, m_iEquipmentValue);
SCHEMA_FIELD(int32_t, m_iKillReward);
SCHEMA_FIELD(int32_t, m_iMoneySaved);
};
struct CSMatchStats_t : public CSPerRoundStats_t {
public:
DECLARE_SCHEMA_CLASS_INLINE(CSMatchStats_t)
SCHEMA_FIELD(int32_t, m_iEntryWins);
};
class CPlayerControllerComponent {
virtual ~CPlayerControllerComponent() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayerControllerComponent)
CCSPlayerController* __m_pChainEntity;
CCSPlayerController* GetController() { return __m_pChainEntity; }
};
class CPlayerPawnComponent {
virtual ~CPlayerPawnComponent() = 0;
virtual void unk_01() = 0;
virtual void unk_02() = 0;
virtual void unk_03() = 0;
virtual void unk_04() = 0;
virtual void unk_05() = 0;
virtual void unk_06() = 0;
virtual void unk_07() = 0;
virtual void unk_08() = 0;
virtual void unk_09() = 0;
virtual void unk_10() = 0;
virtual void unk_11() = 0;
virtual void unk_12() = 0;
virtual void unk_13() = 0;
virtual void unk_14() = 0;
virtual void unk_15() = 0;
virtual void unk_16() = 0;
virtual void unk_17() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayerPawnComponent);
SCHEMA_FIELD(CCSPlayerPawn*, __m_pChainEntity)
CCSPlayerPawn *GetPawn() { return __m_pChainEntity; }
};
class CPlayer_MovementServices : public CPlayerPawnComponent {
virtual ~CPlayer_MovementServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayer_MovementServices);
SCHEMA_FIELD(CInButtonState, m_nButtons)
SCHEMA_FIELD(uint64_t, m_nQueuedButtonDownMask)
SCHEMA_FIELD(uint64_t, m_nQueuedButtonChangeMask)
SCHEMA_FIELD(uint64_t, m_nButtonDoublePressed)
// m_pButtonPressedCmdNumber[64]
SCHEMA_FIELD_POINTER(uint32_t, m_pButtonPressedCmdNumber)
SCHEMA_FIELD(uint32_t, m_nLastCommandNumberProcessed)
SCHEMA_FIELD(uint64_t, m_nToggleButtonDownMask)
SCHEMA_FIELD(float, m_flMaxspeed)
};
class CPlayer_MovementServices_Humanoid : public CPlayer_MovementServices {
virtual ~CPlayer_MovementServices_Humanoid() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayer_MovementServices_Humanoid);
SCHEMA_FIELD(float, m_flFallVelocity)
SCHEMA_FIELD(float, m_bInCrouch)
SCHEMA_FIELD(uint32_t, m_nCrouchState)
SCHEMA_FIELD(bool, m_bInDuckJump)
SCHEMA_FIELD(float, m_flSurfaceFriction)
};
class CCSPlayer_MovementServices : public CPlayer_MovementServices_Humanoid {
virtual ~CCSPlayer_MovementServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayer_MovementServices);
SCHEMA_FIELD(float, m_flMaxFallVelocity)
SCHEMA_FIELD(float, m_flJumpVel)
SCHEMA_FIELD(float, m_flJumpPressedTime)
SCHEMA_FIELD(float, m_flStamina)
SCHEMA_FIELD(float, m_flDuckSpeed)
SCHEMA_FIELD(bool, m_bDuckOverride)
};
class CPlayer_WeaponServices : public CPlayerPawnComponent {
virtual ~CPlayer_WeaponServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayer_WeaponServices);
SCHEMA_FIELD_POINTER(CUtlVector<CHandle<CBasePlayerWeapon>>, m_hMyWeapons)
SCHEMA_FIELD(CHandle<CBasePlayerWeapon>, m_hActiveWeapon)
SCHEMA_FIELD_POINTER(uint16_t, m_iAmmo)
};
class CCSPlayer_WeaponServices : public CPlayer_WeaponServices {
virtual ~CCSPlayer_WeaponServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayer_WeaponServices);
SCHEMA_FIELD(GameTime_t, m_flNextAttack)
SCHEMA_FIELD(bool, m_bIsLookingAtWeapon)
SCHEMA_FIELD(bool, m_bIsHoldingLookAtWeapon)
SCHEMA_FIELD(CHandle<CBasePlayerWeapon>, m_hSavedWeapon)
SCHEMA_FIELD(int32_t, m_nTimeToMelee)
SCHEMA_FIELD(int32_t, m_nTimeToSecondary)
SCHEMA_FIELD(int32_t, m_nTimeToPrimary)
SCHEMA_FIELD(int32_t, m_nTimeToSniperRifle)
SCHEMA_FIELD(bool, m_bIsBeingGivenItem)
SCHEMA_FIELD(bool, m_bIsPickingUpItemWithUse)
SCHEMA_FIELD(bool, m_bPickedUpWeapon)
void DropWeapon(CBasePlayerWeapon *pWeapon, Vector *pVecTarget = nullptr, Vector *pVelocity = nullptr) {
static int offset = shared::g_pGameConfig->GetOffset("CCSPlayer_WeaponServices_DropWeapon");
CALL_VIRTUAL(void, offset, this, pWeapon, pVecTarget, pVelocity);
}
void SelectItem(CBasePlayerWeapon *pWeapon, int unk1 = 0) {
static int offset = shared::g_pGameConfig->GetOffset("CCSPlayer_WeaponServices_SelectItem");
CALL_VIRTUAL(void, offset, this, pWeapon, unk1);
}
};
class CCSPlayerController_ActionTrackingServices : public CPlayerControllerComponent {
virtual ~CCSPlayerController_ActionTrackingServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayerController_ActionTrackingServices)
SCHEMA_FIELD(CSMatchStats_t, m_matchStats)
SCHEMA_FIELD(int, m_iNumRoundKills);
};
class CCSPlayerController_InGameMoneyServices : public CPlayerControllerComponent {
virtual ~CCSPlayerController_InGameMoneyServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayerController_InGameMoneyServices);
SCHEMA_FIELD(bool, m_bReceivesMoneyNextRound); // ReceivesMoneyNextRound
SCHEMA_FIELD(int, m_iMoneyEarnedForNextRound); // MoneyEarnedForNextRound
SCHEMA_FIELD(int, m_iAccount); // Account
SCHEMA_FIELD(int, m_iStartAccount); // StartAccount
SCHEMA_FIELD(int, m_iTotalCashSpent); // TotalCashSpent
SCHEMA_FIELD(int, m_iCashSpentThisRound); // CashSpentThisRound
};
class CCSPlayerController_InventoryServices : public CPlayerControllerComponent {
virtual ~CCSPlayerController_InventoryServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayerController_InventoryServices);
SCHEMA_FIELD(int32_t, m_nPersonaDataXpTrailLevel)
SCHEMA_FIELD(int32_t, m_nPersonaDataPublicLevel)
SCHEMA_FIELD(uint16_t, m_unMusicID)
SCHEMA_FIELD_POINTER(int, m_rank)
};
class CPlayer_ItemServices : public CPlayerPawnComponent {
virtual ~CPlayer_ItemServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayer_ItemServices);
};
class CCSPlayer_ItemServices : public CPlayer_ItemServices {
virtual ~CCSPlayer_ItemServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayer_ItemServices);
SCHEMA_FIELD(bool, m_bHasDefuser)
SCHEMA_FIELD(bool, m_bHasHelmet)
private:
virtual CBasePlayerWeapon *_GiveNamedItem(const char *pchName) = 0;
public:
virtual bool GiveNamedItemBool(const char *pchName) = 0;
virtual CBasePlayerWeapon *GiveNamedItem(const char *pchName) = 0;
// Recommended to use CCSPlayer_WeaponServices::DropWeapon instead (parameter is ignored here)
virtual void DropActiveWeapon(CBasePlayerWeapon *pWeapon) = 0;
virtual void StripPlayerWeapons(bool removeSuit) = 0;
// Custom functions
[[nodiscard]] static bool IsAwsProcessing() noexcept { return g_bAwsChangingTeam; }
static void ResetAwsProcessing() { g_bAwsChangingTeam = false; }
[[nodiscard]] static gear_slot_t GetItemGearSlot(const char *item) noexcept;
CBasePlayerWeapon *GiveNamedItemAws(const char *item) noexcept;
void RemoveWeapons() {
CALL_VIRTUAL(void, shared::g_pGameConfig->GetOffset("CCSPlayer_ItemServices_RemoveWeapons"), this);
}
};
// We need an exactly sized class to be able to iterate the vector, our schema system implementation can't do this
class WeaponPurchaseCount_t {
private:
virtual void unk00() {
};
virtual void unk01() {
};
virtual void unk02() {
};
virtual void unk03() {
};
virtual void unk04() {
};
CCSPlayerPawn *m_pPawn;
uint64_t unk2 = 0; // 0x10
uint64_t unk3 = 0; // 0x18
uint64_t unk4 = 0; // 0x20
uint64_t unk5 = -1; // 0x28
public:
WeaponPurchaseCount_t(CCSPlayerPawn *pPawn, uint16 nItemDefIndex, uint16 nCount) : m_pPawn(pPawn),
m_nItemDefIndex(nItemDefIndex), m_nCount(nCount) {
// Since we're constructing a new object, the vtable pointer will be incorrect so fix it
static const auto pVTable = DynLibUtils::CModule(shared::g_pEntitySystem).GetVirtualTableByName("WeaponPurchaseCount_t");
((void **) this)[0] = pVTable;
}
uint16_t m_nItemDefIndex; // 0x30
uint16_t m_nCount; // 0x32
private:
uint32_t unk6 = 0;
};
struct WeaponPurchaseTracker_t {
public:
DECLARE_SCHEMA_CLASS_INLINE(WeaponPurchaseTracker_t)
SCHEMA_FIELD_POINTER(CUtlVector<WeaponPurchaseCount_t>, m_weaponPurchases)
};
class CCSPlayer_ActionTrackingServices : CPlayerPawnComponent {
virtual ~CCSPlayer_ActionTrackingServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayer_ActionTrackingServices)
SCHEMA_FIELD(WeaponPurchaseTracker_t, m_weaponPurchasesThisRound)
};
class CPlayer_ObserverServices : public CPlayerPawnComponent {
virtual ~CPlayer_ObserverServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayer_ObserverServices)
SCHEMA_FIELD(ObserverMode_t, m_iObserverMode)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hObserverTarget)
SCHEMA_FIELD(ObserverMode_t, m_iObserverLastMode)
SCHEMA_FIELD(bool, m_bForcedObserverMode)
};
class CPlayer_CameraServices : public CPlayerPawnComponent {
virtual ~CPlayer_CameraServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CPlayer_CameraServices)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hViewEntity)
};
class CCSPlayerBase_CameraServices : public CPlayer_CameraServices {
public:
virtual ~CCSPlayerBase_CameraServices() = 0;
DECLARE_SCHEMA_CLASS(CCSPlayerBase_CameraServices)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hZoomOwner)
SCHEMA_FIELD(uint, m_iFOV)
};
class CCSPlayer_CameraServices : public CCSPlayerBase_CameraServices {
virtual ~CCSPlayer_CameraServices() = 0;
};
class CCSPlayer_PingServices : public CPlayerPawnComponent {
virtual ~CCSPlayer_PingServices() = 0;
public:
DECLARE_SCHEMA_CLASS(CCSPlayer_PingServices);
SCHEMA_FIELD_POINTER(GameTime_t, m_flPlayerPingTokens)
SCHEMA_FIELD(CHandle<CBaseEntity>, m_hPlayerPing)
};
}

18
src/schema/vfunc.h Normal file
View file

@ -0,0 +1,18 @@
//
// Created by Michal Přikryl on 26.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <cstdint>
namespace TemplatePlugin
{
template <typename T, typename... Args>
inline T CallVFunc(void* base, int index, Args... args)
{
using Fn = T(*)(void*, Args...);
void** vtable = *reinterpret_cast<void***>(base);
return reinterpret_cast<Fn>(vtable[index])(base, args...);
}
}

64
src/schema/virtual.h Normal file
View file

@ -0,0 +1,64 @@
//
// Created by Michal Přikryl on 20.06.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include "platform.h"
#define CALL_VIRTUAL(retType, idx, ...) vmt::CallVirtual<retType>(idx, __VA_ARGS__)
#define CALL_VIRTUAL_OVERRIDE_VTBL(retType, idx, vtable, classPtr, ...) vmt::CallVirtualOverrideVTable<retType>(idx, vtable, classPtr, __VA_ARGS__)
namespace vmt {
template <typename T = void*> inline T GetVMethod(uint32 uIndex, void* pClass)
{
if (!pClass)
{
return T();
}
void** pVTable = *static_cast<void***>(pClass);
if (!pVTable)
{
return T();
}
return reinterpret_cast<T>(pVTable[uIndex]);
}
template <typename T, typename... Args> inline T CallVirtual(uint32 uIndex, void* pClass, Args... args)
{
#ifdef _WIN32
auto pFunc = GetVMethod<T(__thiscall*)(void*, Args...)>(uIndex, pClass);
#else
auto pFunc = GetVMethod<T (*)(void*, Args...)>(uIndex, pClass);
#endif
if (!pFunc)
{
return T();
}
return pFunc(pClass, args...);
}
template<typename T, typename... Args>
inline T CallVirtualOverrideVTable(uint32 uIndex, void **pVTable, void *pClass, Args... args)
{
if (!pVTable)
{
Warning("Tried getting virtual function from a null vtable.\n");
return T();
}
#ifdef _WIN32
auto pFunc = reinterpret_cast<T(__thiscall *)(void *, Args...)>(pVTable[uIndex]);
#else
auto pFunc = reinterpret_cast<T(__cdecl *)(void *, Args...)>(pVTable[uIndex]);
#endif
if (!pFunc)
{
Warning("Tried calling a null virtual function.\n");
return T();
}
return pFunc(pClass, args...);
}
} // namespace vmt

160
src/tasks.cpp Normal file
View file

@ -0,0 +1,160 @@
//
// Created by Michal Přikryl on 10.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#include "tasks.h"
#include <algorithm>
#include <chrono>
#include <mutex>
#include <queue>
namespace TemplatePlugin {
double universal_time = 0.0;
double last_tick_time = 0.0;
double timer_next_think = 0.0;
namespace {
std::vector<Timer *> once_off_timers;
std::vector<Timer *> repeat_timers;
std::mutex nextFrameMutex;
std::queue<std::function<void()> > nextFrameQueue;
}
void Tasks::NextFrame(std::function<void()> &&task) {
std::lock_guard lock(nextFrameMutex);
nextFrameQueue.emplace(std::move(task));
}
Timer::Timer(float interval, double execTime, TimerCallback callback, int flags)
: Interval(interval), ExecTime(execTime), Callback(std::move(callback)), Flags(flags) {
}
void Tasks::Init() {
universal_time = 0.0;
last_tick_time = 0.0;
timer_next_think = 0.0;
}
void Tasks::Shutdown() {
for (auto *timer: once_off_timers)
delete timer;
for (auto *timer: repeat_timers)
delete timer;
once_off_timers.clear();
repeat_timers.clear();
std::lock_guard lock(nextFrameMutex);
std::queue<std::function<void()> > empty;
std::swap(nextFrameQueue, empty);
}
void Tasks::Tick(bool simulating) {
std::queue<std::function<void()>> localQueue;
{
std::lock_guard lock(nextFrameMutex);
std::swap(localQueue, nextFrameQueue);
}
while (!localQueue.empty()) {
try {
localQueue.front()();
} catch (...) {
}
localQueue.pop();
}
double now = std::chrono::duration_cast<std::chrono::duration<float> >(
std::chrono::steady_clock::now().time_since_epoch()).count();
if (simulating)
universal_time += now - last_tick_time;
else
universal_time += 0.015;
last_tick_time = now;
if (universal_time < timer_next_think)
return;
for (int i = static_cast<int>(once_off_timers.size()) - 1; i >= 0; --i) {
Timer *timer = once_off_timers[i];
if (universal_time >= timer->ExecTime) {
timer->InExec = true;
try {
timer->Callback();
} catch (...) {
}
delete timer;
once_off_timers.erase(once_off_timers.begin() + i);
}
}
for (int i = static_cast<int>(repeat_timers.size()) - 1; i >= 0; --i) {
Timer *timer = repeat_timers[i];
if (universal_time >= timer->ExecTime) {
timer->InExec = true;
try {
timer->Callback();
} catch (...) {
}
if (timer->KillMe) {
delete timer;
repeat_timers.erase(repeat_timers.begin() + i);
continue;
}
timer->InExec = false;
timer->ExecTime = universal_time + timer->Interval;
}
}
timer_next_think = universal_time + 0.1;
}
Timer *Tasks::AddTimer(float interval, TimerCallback callback, int flags) {
Timer *timer = new Timer(interval, universal_time + interval, std::move(callback), flags);
if (flags & TIMER_FLAG_REPEAT)
repeat_timers.push_back(timer);
else
once_off_timers.push_back(timer);
return timer;
}
void Tasks::KillTimer(Timer *timer) {
if (!timer) return;
auto killFrom = [](std::vector<Timer *> &list, Timer *target) {
auto it = std::remove_if(list.begin(), list.end(), [=](Timer *t) { return t == target; });
if (it != list.end()) {
delete target;
list.erase(it, list.end());
}
};
if (timer->InExec) {
timer->KillMe = true;
return;
}
if (timer->Flags & TIMER_FLAG_REPEAT)
killFrom(repeat_timers, timer);
else
killFrom(once_off_timers, timer);
}
void Tasks::RemoveMapChangeTimers() {
auto removeFrom = [](std::vector<Timer*>& list) {
for (int i = static_cast<int>(list.size()) - 1; i >= 0; --i) {
Timer* t = list[i];
if (t->Flags & TIMER_FLAG_NO_MAPCHANGE) {
delete t;
list.erase(list.begin() + i);
}
}
};
removeFrom(once_off_timers);
removeFrom(repeat_timers);
}
}

43
src/tasks.h Normal file
View file

@ -0,0 +1,43 @@
//
// Created by Michal Přikryl on 10.07.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <functional>
namespace TemplatePlugin {
using TimerCallback = std::function<void()>;
enum TimerFlags {
TIMER_FLAG_REPEAT = 1 << 0,
TIMER_FLAG_NO_MAPCHANGE = 1 << 1,
};
extern double universal_time;
extern double last_tick_time;
extern double timer_next_think;
class Timer {
public:
Timer(float interval, double execTime, TimerCallback callback, int flags);
~Timer() = default;
float Interval;
double ExecTime;
TimerCallback Callback;
int Flags;
bool InExec = false;
bool KillMe = false;
};
namespace Tasks {
void Init();
void Shutdown();
void Tick(bool simulating = true);
void NextFrame(std::function<void()> &&task);
Timer* AddTimer(float interval, TimerCallback callback, int flags = 0);
void KillTimer(Timer* timer);
void RemoveMapChangeTimers();
}
}

126
src/vectorextends.h Normal file
View file

@ -0,0 +1,126 @@
//
// Created by Michal Přikryl on 07.08.2025.
// Copyright (c) 2025 slynxcz. All rights reserved.
//
#pragma once
#include <cmath>
#include <algorithm>
#include <vector.h>
namespace TemplatePlugin::VectorExtends
{
inline const Vector VectorZero{0.0f, 0.0f, 0.0f};
inline const QAngle RotationZero{0.0f, 0.0f, 0.0f};
inline const Vector StaticTeleportVector{9999999.0f, 8888888.0f, 777777.0f};
inline Vector Clone(const Vector& vector)
{
return Vector{vector.x, vector.y, vector.z};
}
inline QAngle QClone(const QAngle& angle)
{
return QAngle{angle.x, angle.y, angle.z};
}
inline Vector Adds(const Vector& vector, const Vector& other)
{
return Vector{vector.x + other.x, vector.y + other.y, vector.z + other.z};
}
inline Vector Scale(const Vector& vector, float scale)
{
return Vector{vector.x * scale, vector.y * scale, vector.z * scale};
}
inline Vector Normalize(const Vector& vector)
{
float length = vector.Length();
return Vector{vector.x / length, vector.y / length, vector.z / length};
}
inline float Distance(const Vector& vector, const Vector& other)
{
return std::sqrt(
std::pow(vector.x - other.x, 2.0f) +
std::pow(vector.y - other.y, 2.0f) +
std::pow(vector.z - other.z, 2.0f)
);
}
inline float DistanceSquared(const Vector& vector, const Vector& other)
{
return
std::pow(vector.x - other.x, 2.0f) +
std::pow(vector.y - other.y, 2.0f) +
std::pow(vector.z - other.z, 2.0f);
}
inline Vector MakeVectorFromPoints(const Vector& pt1, const Vector& pt2)
{
return Vector{pt2.x - pt1.x, pt2.y - pt1.y, pt2.z - pt1.z};
}
inline Vector SubtractVectors(const Vector& vec1, const Vector& vec2)
{
return Vector{vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z};
}
inline Vector NegateVector(const Vector& vec)
{
return Vector{-vec.x, -vec.y, -vec.z};
}
inline float DegToRad(float degrees)
{
return degrees * (static_cast<float>(M_PI) / 180.0f);
}
inline float RadToDeg(float radians)
{
return radians * (180.0f / static_cast<float>(M_PI));
}
inline bool IsInsideBox(const Vector& playerVector, const Vector& corner1, const Vector& corner2, float height = 0.0f)
{
float minX = std::min(corner1.x, corner2.x);
float minY = std::min(corner1.y, corner2.y);
float minZ = std::min(corner1.z, corner2.z);
float maxX = std::max(corner1.x, corner2.x);
float maxY = std::max(corner1.y, corner2.y);
float maxZ = std::max(corner1.z, corner2.z);
return playerVector.x >= minX && playerVector.x <= maxX &&
playerVector.y >= minY && playerVector.y <= maxY &&
playerVector.z >= minZ && playerVector.z <= maxZ + height;
}
inline bool IsInsideField(const Vector& playerVector, const Vector& corner1, const Vector& corner2)
{
float minX = std::min(corner1.x, corner2.x);
float minY = std::min(corner1.y, corner2.y);
float maxX = std::max(corner1.x, corner2.x);
float maxY = std::max(corner1.y, corner2.y);
return playerVector.x >= minX && playerVector.x <= maxX &&
playerVector.y >= minY && playerVector.y <= maxY;
}
inline float ArcCosine(float value)
{
return std::acos(value);
}
inline float GetVectorDotProduct(const Vector& vec1, const Vector& vec2)
{
return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
}
inline float Dot(const Vector& vec1, const Vector& vec2)
{
return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
}
}