diff --git a/codxe.vcxproj b/codxe.vcxproj
index 59dc162..0c208ef 100644
--- a/codxe.vcxproj
+++ b/codxe.vcxproj
@@ -129,6 +129,7 @@
+
@@ -256,6 +257,7 @@
+
diff --git a/src/game/iw4/mp_tu6/components/cmds.cpp b/src/game/iw4/mp_tu6/components/cmds.cpp
new file mode 100644
index 0000000..67a8fd4
--- /dev/null
+++ b/src/game/iw4/mp_tu6/components/cmds.cpp
@@ -0,0 +1,90 @@
+#include "pch.h"
+#include "cmds.h"
+
+namespace iw4
+{
+namespace mp_tu6
+{
+namespace
+{
+
+Detour ClientCommand_Detour;
+
+void SendCommandMessage(int clientNum, const char *message)
+{
+ const char *commandString = va("%c \"%s\"", 101, message);
+
+ SV_GameSendServerCommand(clientNum, SV_CMD_CAN_IGNORE, commandString);
+}
+
+bool ToggleFlag(int &flags, int flag)
+{
+ const bool enableFlag = (flags & flag) == 0;
+
+ if (enableFlag)
+ flags |= flag;
+ else
+ flags &= ~flag;
+
+ return enableFlag;
+}
+
+void Cmd_Noclip_f(int clientNum, gentity_s *ent)
+{
+ const bool enabled = ToggleFlag(ent->client->flags, CF_NOCLIP);
+ SendCommandMessage(clientNum, enabled ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF");
+}
+
+void Cmd_UFO_f(int clientNum, gentity_s *ent)
+{
+ const bool enabled = ToggleFlag(ent->client->flags, CF_UFO);
+ SendCommandMessage(clientNum, enabled ? "GAME_UFOON" : "GAME_UFOOFF");
+}
+
+void Cmd_God_f(int clientNum, gentity_s *ent)
+{
+ const bool enabled = ToggleFlag(ent->flags, FL_GODMODE);
+ SendCommandMessage(clientNum, enabled ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF");
+}
+
+void Cmd_DemiGod_f(int clientNum, gentity_s *ent)
+{
+ const bool enabled = ToggleFlag(ent->flags, FL_DEMI_GODMODE);
+ SendCommandMessage(clientNum, enabled ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF");
+}
+
+void ClientCommand_Hook(int clientNum)
+{
+ assert(clientNum >= 0 && clientNum < IW4_MAX_CLIENTS);
+
+ gentity_s *ent = &g_entities[clientNum];
+ assert(ent->client);
+
+ char cmd[1024] = {};
+ SV_Cmd_ArgvBuffer(0, cmd, static_cast(sizeof(cmd)));
+
+ if (I_stricmp(cmd, "god") == 0)
+ Cmd_God_f(clientNum, ent);
+ else if (I_stricmp(cmd, "demigod") == 0)
+ Cmd_DemiGod_f(clientNum, ent);
+ else if (I_stricmp(cmd, "noclip") == 0)
+ Cmd_Noclip_f(clientNum, ent);
+ else if (I_stricmp(cmd, "ufo") == 0)
+ Cmd_UFO_f(clientNum, ent);
+ else
+ ClientCommand_Detour.GetOriginal()(clientNum);
+}
+} // namespace
+
+cmds::cmds()
+{
+ ClientCommand_Detour = Detour(ClientCommand, ClientCommand_Hook);
+ ClientCommand_Detour.Install();
+}
+
+cmds::~cmds()
+{
+ ClientCommand_Detour.Remove();
+}
+} // namespace mp_tu6
+} // namespace iw4
diff --git a/src/game/iw4/mp_tu6/components/cmds.h b/src/game/iw4/mp_tu6/components/cmds.h
new file mode 100644
index 0000000..2a8582d
--- /dev/null
+++ b/src/game/iw4/mp_tu6/components/cmds.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "pch.h"
+
+namespace iw4
+{
+namespace mp_tu6
+{
+class cmds : public Module
+{
+ public:
+ cmds();
+ ~cmds();
+
+ const char *get_name() override
+ {
+ return "cmds";
+ };
+};
+} // namespace mp_tu6
+} // namespace iw4
diff --git a/src/game/iw4/mp_tu6/main.cpp b/src/game/iw4/mp_tu6/main.cpp
index ff9f3f4..5cca696 100644
--- a/src/game/iw4/mp_tu6/main.cpp
+++ b/src/game/iw4/mp_tu6/main.cpp
@@ -1,6 +1,7 @@
#include "pch.h"
#include "components/cg.h"
#include "components/clipmap.h"
+#include "components/cmds.h"
#include "components/console.h"
#include "components/events.h"
#include "components/g_client_fields.h"
@@ -25,6 +26,7 @@ IW4_MP_TU6_Plugin::IW4_MP_TU6_Plugin()
RegisterModule(new Events()); // Must be registered first to ensure hooks are in place
RegisterModule(new cg());
RegisterModule(new clipmap());
+ RegisterModule(new cmds());
RegisterModule(new console());
RegisterModule(new g_client_fields());
RegisterModule(new g_scr_main());
diff --git a/src/game/iw4/mp_tu6/structs.h b/src/game/iw4/mp_tu6/structs.h
index 0957b31..244b66c 100644
--- a/src/game/iw4/mp_tu6/structs.h
+++ b/src/game/iw4/mp_tu6/structs.h
@@ -56,6 +56,12 @@ enum DvarFlags : std::uint16_t
DVAR_FLAG_SERVERINFO = 0x10,
};
+enum svscmd_type : __int32
+{
+ SV_CMD_CAN_IGNORE = 0x0,
+ SV_CMD_RELIABLE = 0x1,
+};
+
struct dvar_t
{
const char *name;
@@ -826,6 +832,37 @@ struct blend_ent_t
float totalTime;
};
+enum entityFlag_t : uint32_t
+{
+ FL_GODMODE = 0x1,
+ FL_DEMI_GODMODE = 0x2,
+ FL_NOTARGET = 0x4,
+ FL_NO_KNOCKBACK = 0x8,
+ FL_NO_RADIUS_DAMAGE = 0x10,
+ FL_SUPPORTS_LINKTO = 0x1000,
+ FL_NO_AUTO_ANIM_UPDATE = 0x2000,
+ FL_GRENADE_TOUCH_DAMAGE = 0x4000,
+ FL_STABLE_MISSILES = 0x20000,
+ FL_REPEAT_ANIM_UPDATE = 0x40000,
+ FL_VEHICLE_TARGET = 0x80000,
+ FL_GROUND_ENT = 0x100000,
+ FL_CURSOR_HINT = 0x200000,
+ FL_MISSILE_ATTRACTOR = 0x800000,
+ FL_WEAPON_BEING_GRABBED = 0x1000000,
+ FL_DELETE = 0x2000000,
+ FL_BOUNCE = 0x4000000,
+ FL_MOVER_SLIDE = 0x8000000,
+};
+
+enum clientFlag_t : uint32_t
+{
+ CF_NOCLIP = 1u << 0,
+ CF_UFO = 1u << 1,
+ CF_FROZEN = 1u << 2,
+ CF_DISABLE_USABILITY = 1u << 3,
+ CF_NO_KNOCKBACK = 1u << 4,
+};
+
struct gentity_s
{
entityState_s s;
@@ -1772,7 +1809,12 @@ enum XAssetType : __int32
struct cplane_s;
struct cStaticModel_s;
-struct ClipMaterial;
+struct ClipMaterial
+{
+ const char *name;
+ int surfaceFlags;
+ int contentFlags;
+};
struct cbrushside_t;
struct cNode_t;
struct cLeaf_t;
diff --git a/src/game/iw4/mp_tu6/symbols.h b/src/game/iw4/mp_tu6/symbols.h
index 233ebd9..ee6e7c7 100644
--- a/src/game/iw4/mp_tu6/symbols.h
+++ b/src/game/iw4/mp_tu6/symbols.h
@@ -21,6 +21,9 @@ static auto Hunk_AllocateTempMemoryHighInternal = reinterpret_cast(0x82275C60);
+typedef void (*ClientCommand_t)(int clientNum);
+static ClientCommand_t ClientCommand = reinterpret_cast(0x822266D0);
+
typedef void (*CL_CharEvent_t)(int localClientNum, int key);
static CL_CharEvent_t CL_CharEvent = reinterpret_cast(0x82182EC8);
@@ -53,6 +56,9 @@ static Com_Printf_t Com_Printf = reinterpret_cast(0x8227F448);
typedef void (*Com_PrintMessage_t)(int channel, const char *msg, int error);
static Com_PrintMessage_t Com_PrintMessage = reinterpret_cast(0x8227F370);
+typedef int (*I_stricmp_t)(const char *s0, const char *s1);
+static I_stricmp_t I_stricmp = reinterpret_cast(0x82315B70);
+
typedef void (*Info_SetValueForKey_t)(char *s, const char *key, const char *value);
static Info_SetValueForKey_t Info_SetValueForKey = reinterpret_cast(0x823167D8);
@@ -216,6 +222,12 @@ static SV_IsClientBot_t SV_IsClientBot = reinterpret_cast(0x82
typedef gentity_s *(*SV_AddTestClient_t)();
static SV_AddTestClient_t SV_AddTestClient = reinterpret_cast(0x822BDCB8);
+typedef void (*SV_Cmd_ArgvBuffer_t)(int arg, char *buffer, int bufferLength);
+static SV_Cmd_ArgvBuffer_t SV_Cmd_ArgvBuffer = reinterpret_cast(0x822760C8);
+
+typedef void (*SV_GameSendServerCommand_t)(int clientNum, svscmd_type type, const char *text);
+static SV_GameSendServerCommand_t SV_GameSendServerCommand = reinterpret_cast(0x822BDF00);
+
typedef void (*SV_UserinfoChanged_t)(client_t *cl);
static SV_UserinfoChanged_t SV_UserinfoChanged = reinterpret_cast(0x822BBED8);