From 0563db8ef802f122dc153c2969ccf698a761a00b Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Tue, 23 Dec 2025 23:27:27 +0000 Subject: [PATCH] feat(IW3 MP): add GSC method `BotAction` from cod4x --- codxe.vcxproj | 1 + .../mods/codjumper/maps/mp/gametypes/cj.gsc | 30 +++- src/game/iw3/mp/components/sv_bots.cpp | 134 ++++++++++++++++++ src/game/iw3/mp/components/sv_bots.h | 21 +++ src/game/iw3/mp/main.cpp | 53 +------ 5 files changed, 187 insertions(+), 52 deletions(-) create mode 100644 src/game/iw3/mp/components/sv_bots.cpp create mode 100644 src/game/iw3/mp/components/sv_bots.h diff --git a/codxe.vcxproj b/codxe.vcxproj index c99d244..617d38a 100644 --- a/codxe.vcxproj +++ b/codxe.vcxproj @@ -97,6 +97,7 @@ + diff --git a/resources/iw3/_codxe/mods/codjumper/maps/mp/gametypes/cj.gsc b/resources/iw3/_codxe/mods/codjumper/maps/mp/gametypes/cj.gsc index 741ce92..b11c2c7 100644 --- a/resources/iw3/_codxe/mods/codjumper/maps/mp/gametypes/cj.gsc +++ b/resources/iw3/_codxe/mods/codjumper/maps/mp/gametypes/cj.gsc @@ -2753,6 +2753,7 @@ generateMenuOptions() self addMenuOption("bot_action_menu", "Auto Mantle ON/OFF", ::toggleAutoMantle); self addMenuOption("bot_action_menu", "Trigger Distance UP", ::modifyTriggerDistance, 10); self addMenuOption("bot_action_menu", "Trigger Distance DOWN", ::modifyTriggerDistance, -10); + self addMenuOption("bot_action_menu", "Toggle stance", ::CycleBotStance); } // Player model menu @@ -2777,6 +2778,31 @@ generateMenuOptions() self addMenuOption("graphics_menu", "Toggle Greenscreen", ::toggleGreenscreen); } +CycleBotStance() +{ + bot = self.cj["bots"][self.cj["botnumber"]]; + if(!isdefined(bot)) + { + self iprintln("^1bot not found."); + return; + } + + stance = bot GetStance(); + if (stance == "stand") + { + bot botaction("+gocrouch"); + } + else if (stance == "crouch") + { + bot botaction("-gocrouch"); + bot botaction("+goprone"); + } + else if (stance == "prone") + { + bot botaction("-goprone"); // returns to stand + } +} + toggleRpgLookdown() { if (!isdefined(self.cj["settings"]["tas_rpg_lookdown"])) @@ -3242,12 +3268,14 @@ startAutoMantle() { if (distance(botEye, self getorigin()) < self.triggerDistance) { - bot botjump(); + bot botaction("+gostand"); self waittill("position_loaded"); // wait for bot to finish mantling before loading position if (bot ismantling()) wait 0.5; + bot botstop(); + bot loadPos(0); } wait 0.05; diff --git a/src/game/iw3/mp/components/sv_bots.cpp b/src/game/iw3/mp/components/sv_bots.cpp new file mode 100644 index 0000000..9194c5d --- /dev/null +++ b/src/game/iw3/mp/components/sv_bots.cpp @@ -0,0 +1,134 @@ +#include "pch.h" +#include "g_scr_main.h" +#include "sv_bots.h" + +#define KEY_MASK_FIRE 1 +#define KEY_MASK_SPRINT 2 +#define KEY_MASK_MELEE 4 +#define KEY_MASK_RELOAD 16 +#define KEY_MASK_LEANLEFT 64 +#define KEY_MASK_LEANRIGHT 128 +#define KEY_MASK_PRONE 256 +#define KEY_MASK_CROUCH 512 +#define KEY_MASK_JUMP 1024 +#define KEY_MASK_ADS_MODE 2048 +#define KEY_MASK_TEMP_ACTION 4096 +#define KEY_MASK_HOLDBREATH 8192 +#define KEY_MASK_FRAG 16384 +#define KEY_MASK_SMOKE 32768 +#define KEY_MASK_NIGHTVISION 262144 +#define KEY_MASK_ADS 524288 +#define KEY_MASK_USE 8 +#define KEY_MASK_USERELOAD 32 +#define BUTTON_ATTACK KEY_MASK_FIRE +#define MAX_CLIENTS 18 + +namespace iw3 +{ +namespace mp +{ + +struct BotMovementInfo_t +{ + int buttons; +}; + +BotMovementInfo_t g_botai[MAX_CLIENTS]; + +Detour SV_BotUserMove_Detour; +void SV_BotUserMove_Stub(client_t *cl) +{ + if (!cl->gentity) + return; + + const int clientNum = cl - svsHeader->clients; + + usercmd_s cmd; + memset(&cmd, 0, sizeof(cmd)); + + cmd.serverTime = svsHeader->time; + + const playerState_s *ps = SV_GameClientNum(clientNum); + cmd.weapon = (unsigned char)ps->weapon; + + if (g_clients[clientNum].sess.archiveTime == 0) + { + cmd.buttons = g_botai[clientNum].buttons; + } + + cl->header.deltaMessage = cl->header.netchan.outgoingSequence - 1; + SV_ClientThink(cl, &cmd); +} + +struct BotAction_t +{ + const char *action; + int key; +}; + +const BotAction_t BotActions[] = {{"gostand", KEY_MASK_JUMP}, {"gocrouch", KEY_MASK_CROUCH}, + {"goprone", KEY_MASK_PRONE}, {"fire", KEY_MASK_FIRE}, + {"melee", KEY_MASK_MELEE}, {"frag", KEY_MASK_FRAG}, + {"smoke", KEY_MASK_SMOKE}, {"reload", KEY_MASK_RELOAD}, + {"sprint", KEY_MASK_SPRINT}, {"leanleft", KEY_MASK_LEANLEFT}, + {"leanright", KEY_MASK_LEANRIGHT}, {"ads", KEY_MASK_ADS_MODE | KEY_MASK_ADS}, + {"holdbreath", KEY_MASK_HOLDBREATH}, {"activate", KEY_MASK_USE}}; + +static void Scr_BotAction(scr_entref_t entref) +{ + GetPlayerEntity(entref); + + if (Scr_GetNumParam() != 1) + Scr_Error("Usage: botAction();"); + + const char *action = Scr_GetString(0); + + if (action[0] != '+' && action[0] != '-') + Scr_ParamError(0, "Sign for bot action must be '+' or '-'."); + + bool key_found = false; + for (size_t i = 0; i < ARRAYSIZE(g_botai); ++i) + { + if (!stricmp(&action[1], BotActions[i].action)) + { + key_found = true; + if (action[0] == '+') + g_botai[entref.entnum].buttons |= BotActions[i].key; + else + g_botai[entref.entnum].buttons &= ~(BotActions[i].key); + + return; + } + } + + if (!key_found) + { + Scr_ParamError(0, va("Unknown bot action.")); + } +} + +static void Scr_BotStop(scr_entref_t entref) +{ + GetPlayerEntity(entref); + + if (Scr_GetNumParam() != 0) + Scr_Error("Usage: botStop();"); + + g_botai[entref.entnum].buttons = 0; +} + +sv_bots::sv_bots() +{ + SV_BotUserMove_Detour = Detour(SV_BotUserMove, SV_BotUserMove_Stub); + SV_BotUserMove_Detour.Install(); + + Scr_AddMethod("botaction", Scr_BotAction, 0); + Scr_AddMethod("botstop", Scr_BotStop, 0); +} + +sv_bots::~sv_bots() +{ + SV_BotUserMove_Detour.Remove(); +} +} // namespace mp +} // namespace iw3 diff --git a/src/game/iw3/mp/components/sv_bots.h b/src/game/iw3/mp/components/sv_bots.h new file mode 100644 index 0000000..c1a554e --- /dev/null +++ b/src/game/iw3/mp/components/sv_bots.h @@ -0,0 +1,21 @@ +#pragma once + +#include "pch.h" + +namespace iw3 +{ +namespace mp +{ +class sv_bots : public Module +{ + public: + sv_bots(); + ~sv_bots(); + + const char *get_name() override + { + return "sv_bots"; + }; +}; +} // namespace mp +} // namespace iw3 diff --git a/src/game/iw3/mp/main.cpp b/src/game/iw3/mp/main.cpp index f5685d6..4874b94 100644 --- a/src/game/iw3/mp/main.cpp +++ b/src/game/iw3/mp/main.cpp @@ -11,6 +11,7 @@ #include "components/pm.h" #include "components/scr_parser.h" #include "components/scr_vm_functions.h" +#include "components/sv_bots.h" #include "common/config.h" // Structure to hold data for the active keyboard request @@ -1373,51 +1374,6 @@ void UI_Refresh_Hook(int localClientNum) CheckKeyboardCompletion(); } -struct BotAction -{ - bool jump; -}; - -// map of client index to bot action -std::map botActions; - -void GScr_BotJump(scr_entref_t entref) -{ - client_t *cl = &svsHeader->clients[entref.entnum]; - - if (cl->header.state && cl->header.netchan.remoteAddress.type == NA_BOT) - { - botActions[entref.entnum].jump = true; - } -} - -Detour SV_ClientThinkDetour; - -// TODO: maybe recreate the original and call it in the hook -void SV_ClientThinkHook(client_t *cl, usercmd_s *cmd) -{ - // Check if the client is a bot - if (cl->header.state && cl->header.netchan.remoteAddress.type == NA_BOT) - { - // Reset bot's movement and actions set in SV_BotUserMove - cmd->forwardmove = 0; - cmd->rightmove = 0; - cmd->buttons = 0; - - int clientIndex = cl - svsHeader->clients; - if (botActions.find(clientIndex) != botActions.end()) - { - if (botActions[clientIndex].jump) - { - cmd->buttons = 1024; // BUTTON_JUMP - botActions[clientIndex].jump = false; - } - } - } - - SV_ClientThinkDetour.GetOriginal()(cl, cmd); -} - Detour Pmove_Detour; // https://github.com/kejjjjj/iw3sptool/blob/17b669233a1ad086deed867469dc9530b84c20e6/iw3sptool/bg/bg_pmove.cpp#L11 @@ -1492,6 +1448,7 @@ IW3_MP_Plugin::IW3_MP_Plugin() RegisterModule(new mpsp()); RegisterModule(new scr_parser()); RegisterModule(new scr_vm_functions()); + RegisterModule(new sv_bots()); UI_Refresh_Detour = Detour(UI_Refresh, UI_Refresh_Hook); UI_Refresh_Detour.Install(); @@ -1517,11 +1474,6 @@ IW3_MP_Plugin::IW3_MP_Plugin() cmd_function_s *cmdinput_VAR = new cmd_function_s; Cmd_AddCommandInternal("cmdinput", Cmd_cmdinput_f, cmdinput_VAR); - Scr_AddMethod("botjump", GScr_BotJump, 0); - - SV_ClientThinkDetour = Detour(SV_ClientThink, SV_ClientThinkHook); - SV_ClientThinkDetour.Install(); - Dvar_RegisterBool("pm_fixed_fps_enable", false, 0, "Enable fixed FPS mode"); Dvar_RegisterInt("pm_fixed_fps", 250, 0, 1000, 0, "Fixed FPS value"); Pmove_Detour = Detour(Pmove, Pmove_Hook); @@ -1540,7 +1492,6 @@ IW3_MP_Plugin::~IW3_MP_Plugin() Load_MapEntsPtr_Detour.Remove(); R_StreamLoadFileSynchronously_Detour.Remove(); - SV_ClientThinkDetour.Remove(); Pmove_Detour.Remove(); }