From c33465611ff2202adf7a90adb795bf0c3cffe44e Mon Sep 17 00:00:00 2001 From: RT Date: Fri, 24 Jul 2026 08:44:57 -0500 Subject: [PATCH] Add [automaticmode] Load File button to console lobby options.ini [automaticmode] section: automaticmode=1 automaticfile=c:\path\to\config.ini Right-click was considered then dropped in favor of a dedicated button at 467,510 (below Pick Cond., left of Reprint). C++ (MW4Shell.cpp): - SAutoFileSlot struct + g_aAutoSlots[16], g_bAutomaticMode, g_szAutomaticFile globals - [automaticmode] ini read at StartUp - CTCL_LoadAutoFile: checks file exists, reads [mission] page into existing g_nRookieXxx globals + [slot0]..[slot15] pages into g_aAutoSlots[]; returns 1 if loaded - CTCL_GetAutoSlotName(out_str, k): pilot name for slot k - CTCL_GetAutoSlotMech(out_str, k): mech display name for slot k - CTCL_GetAutoSlotInt(k, field): Type/Team/Skin/Decal for slot k (fields 0-3) - Register/unregister all 4 callbacks in StartUp/ShutDown Script (ConLobby.script): - o_load_file button at 467, 510 - Handler: CTCL_LoadAutoFile -> if loaded, sends MAIL_SET_ROOKIE_MISSION to ConLobbyMission (game options), clears all slots, then applies per-slot data in a loop (pilot names, mech by display-name lookup in allowed_mechs[], team, skin, decal). USE_ALLOWED_MECHS/non-ALLOWED_MECHS both handled via #if. File not consumed (stays on disk); external app overwrites for next load. Rebuild required: MW4.exe (Release + Profile). --- Gameleap/code/mw4/Code/MW4/MW4Shell.cpp | 135 ++++++++++++++++++ .../mw4/Content/ShellScripts/ConLobby.script | 98 +++++++++++++ 2 files changed, 233 insertions(+) diff --git a/Gameleap/code/mw4/Code/MW4/MW4Shell.cpp b/Gameleap/code/mw4/Code/MW4/MW4Shell.cpp index ac6f28d8..d261984c 100644 --- a/Gameleap/code/mw4/Code/MW4/MW4Shell.cpp +++ b/Gameleap/code/mw4/Code/MW4/MW4Shell.cpp @@ -132,6 +132,10 @@ extern SCRIPTCALLBACK(CTCL_GetMissionState); extern SCRIPTCALLBACK(CTCL_DoBreak); extern SCRIPTCALLBACK(CTCL_IsGameLoaded); extern SCRIPTCALLBACK(CTCL_DoReprint); +extern SCRIPTCALLBACK(CTCL_LoadAutoFile); +extern SCRIPTCALLBACK(CTCL_GetAutoSlotName); +extern SCRIPTCALLBACK(CTCL_GetAutoSlotMech); +extern SCRIPTCALLBACK(CTCL_GetAutoSlotInt); extern SCRIPTCALLBACK(CTCL_CheckPlayMovie); extern SCRIPTCALLBACK(CTCL_CheckCoinCounts); extern SCRIPTCALLBACK(CTCL_CheckUseJPD); @@ -174,6 +178,19 @@ int g_nRookieUnlimitedAmmo = 1; // 1=on (be cautious) int g_nRookieWeaponJam = 0; int g_nRookieAdvanceMode = 0; int g_nRookieArmorMode = 0; +// [automaticmode] options.ini ? Load File button +struct SAutoFileSlot { + char szName[64]; // pilot name + char szMech[64]; // mech display name (matched against mech[] array) + int nType; // 0=empty, 1=player, 2-9=bot difficulty level + int nTeam; + int nSkin; + int nDecal; +}; +static SAutoFileSlot g_aAutoSlots[16]; +static int g_nAutoSlotsCount = 0; +static int g_bAutomaticMode = 0; +static char g_szAutomaticFile[MAX_PATH] = ""; // MSL 5.06 int g_nMechVariant = 0; int g_nMechLabOp = 0; @@ -758,6 +775,21 @@ void MW4Shell::StartUp() gosScript_RegisterVariable("g_nRookieWeaponJam", &g_nRookieWeaponJam, GOSVAR_INT, 0, NULL); gosScript_RegisterVariable("g_nRookieAdvanceMode", &g_nRookieAdvanceMode, GOSVAR_INT, 0, NULL); gosScript_RegisterVariable("g_nRookieArmorMode", &g_nRookieArmorMode, GOSVAR_INT, 0, NULL); + // [automaticmode] callbacks + gosScript_RegisterCallback("CTCL_LoadAutoFile", &CTCL_LoadAutoFile, GOSVAR_INT, 0, NULL); + gosScript_RegisterCallback("CTCL_GetAutoSlotName", &CTCL_GetAutoSlotName, GOSVAR_INT, 0, NULL); + gosScript_RegisterCallback("CTCL_GetAutoSlotMech", &CTCL_GetAutoSlotMech, GOSVAR_INT, 0, NULL); + gosScript_RegisterCallback("CTCL_GetAutoSlotInt", &CTCL_GetAutoSlotInt, GOSVAR_INT, 0, NULL); + { + NotationFile opts("options.ini", NotationFile::Standard, true); + Page *pAuto = opts.FindPage("automaticmode"); + if (pAuto) { + pAuto->GetEntry("automaticmode", &g_bAutomaticMode); + const char *sz = NULL; + if (pAuto->GetEntry("automaticfile", &sz) && sz) + strncpy(g_szAutomaticFile, sz, sizeof(g_szAutomaticFile)-1); + } + } // MSL 5.06 gosScript_RegisterVariable("g_nMechVariant", &g_nMechVariant, GOSVAR_INT, 0, NULL); gosScript_RegisterVariable("g_nMechLabOp", &g_nMechLabOp, GOSVAR_INT, 0, NULL); @@ -1154,6 +1186,11 @@ void MW4Shell::ShutDown() gosScript_UnregisterVariable("g_nRookieWeaponJam"); gosScript_UnregisterVariable("g_nRookieAdvanceMode"); gosScript_UnregisterVariable("g_nRookieArmorMode"); + // [automaticmode] callbacks + gosScript_UnregisterCallback("CTCL_GetAutoSlotInt"); + gosScript_UnregisterCallback("CTCL_GetAutoSlotMech"); + gosScript_UnregisterCallback("CTCL_GetAutoSlotName"); + gosScript_UnregisterCallback("CTCL_LoadAutoFile"); // MSL 5.06 gosScript_UnregisterVariable("g_nBlackMech"); gosScript_UnregisterVariable("g_nMechVariant"); @@ -12809,6 +12846,104 @@ int __stdcall CTCL_SetCDSP(void* instance, int args, void* data[]) return 0; } +// [automaticmode] Load File button ? reads automatic file into Rookie Mission globals + per-slot data. +// Returns 1 if the file was found and loaded, 0 otherwise. File is NOT consumed (stays on disk). +int __stdcall CTCL_LoadAutoFile(void* instance, int args, void* data[]) +{ + if (!g_bAutomaticMode || !g_szAutomaticFile[0]) + return 0; + if (GetFileAttributes(g_szAutomaticFile) == 0xFFFFFFFF) + return 0; + + NotationFile autofile(g_szAutomaticFile, NotationFile::Standard, true); + + // Game options ? overwrite Rookie Mission globals so the existing + // MAIL_SET_ROOKIE_MISSION script flow picks them up automatically. + Page *pMission = autofile.FindPage("mission"); + if (pMission) { + const char *sz = NULL; + if (pMission->GetEntry("MissionName", &sz) && sz) { + strncpy(g_szRookieMission, sz, sizeof(g_szRookieMission)-1); + g_szRookieMission[sizeof(g_szRookieMission)-1] = '\0'; + } + pMission->GetEntry("GameType", &g_nRookieGameType); + pMission->GetEntry("Visibility", &g_nRookieVisibility); + pMission->GetEntry("Weather", &g_nRookieWeather); + pMission->GetEntry("TimeOfDay", &g_nRookieTimeOfDay); + pMission->GetEntry("TimeLimit", &g_nRookieTimeLimit); + pMission->GetEntry("Radar", &g_nRookieRadar); + pMission->GetEntry("HeatOn", &g_nRookieHeat); + pMission->GetEntry("FriendlyFire", &g_nRookieFriendlyFire); + pMission->GetEntry("SplashDamage", &g_nRookieSplash); + pMission->GetEntry("UnlimitedAmmo", &g_nRookieUnlimitedAmmo); + pMission->GetEntry("WeaponJam", &g_nRookieWeaponJam); + pMission->GetEntry("AdvanceMode", &g_nRookieAdvanceMode); + pMission->GetEntry("ArmorMode", &g_nRookieArmorMode); + } + + // Per-slot data (up to 16 slots via [slot0]..[slot15] pages) + g_nAutoSlotsCount = 0; + memset(g_aAutoSlots, 0, sizeof(g_aAutoSlots)); + char szPageName[16]; + for (int i = 0; i < 16; i++) { + sprintf(szPageName, "slot%d", i); + Page *pSlot = autofile.FindPage(szPageName); + if (!pSlot) break; + g_nAutoSlotsCount = i + 1; + const char *sz = NULL; + if (pSlot->GetEntry("PilotName", &sz) && sz) + strncpy(g_aAutoSlots[i].szName, sz, sizeof(g_aAutoSlots[i].szName)-1); + sz = NULL; + if (pSlot->GetEntry("Mech", &sz) && sz) + strncpy(g_aAutoSlots[i].szMech, sz, sizeof(g_aAutoSlots[i].szMech)-1); + pSlot->GetEntry("Type", &g_aAutoSlots[i].nType); + pSlot->GetEntry("Team", &g_aAutoSlots[i].nTeam); + pSlot->GetEntry("Skin", &g_aAutoSlots[i].nSkin); + pSlot->GetEntry("Decal", &g_aAutoSlots[i].nDecal); + } + + return 1; +} + +// Returns pilot name string for slot k ? data[0]=string output buffer, data[1]=int k +int __stdcall CTCL_GetAutoSlotName(void* instance, int args, void* data[]) +{ + int k = INTPARM(1); + const char *sz = (k >= 0 && k < 16) ? g_aAutoSlots[k].szName : ""; + if (STRPARM(0)) gos_Free(STRPARM(0)); + STRPARM(0) = (char *)gos_Malloc(64); + strncpy(STRPARM(0), sz, 63); + STRPARM(0)[63] = '\0'; + return 1; +} + +// Returns mech display name for slot k ? data[0]=string output buffer, data[1]=int k +int __stdcall CTCL_GetAutoSlotMech(void* instance, int args, void* data[]) +{ + int k = INTPARM(1); + const char *sz = (k >= 0 && k < 16) ? g_aAutoSlots[k].szMech : ""; + if (STRPARM(0)) gos_Free(STRPARM(0)); + STRPARM(0) = (char *)gos_Malloc(64); + strncpy(STRPARM(0), sz, 63); + STRPARM(0)[63] = '\0'; + return 1; +} + +// Returns int field for slot k ? data[0]=int k, data[1]=int field (0=Type 1=Team 2=Skin 3=Decal) +int __stdcall CTCL_GetAutoSlotInt(void* instance, int args, void* data[]) +{ + int k = INTPARM(0); + int field = INTPARM(1); + if (k < 0 || k >= 16) return 0; + switch (field) { + case 0: return g_aAutoSlots[k].nType; + case 1: return g_aAutoSlots[k].nTeam; + case 2: return g_aAutoSlots[k].nSkin; + case 3: return g_aAutoSlots[k].nDecal; + } + return 0; +} + void CTCL_API CTCL_InitMechDatas() { //CTCL_ClearMechDatas(); diff --git a/Gameleap/mw4/Content/ShellScripts/ConLobby.script b/Gameleap/mw4/Content/ShellScripts/ConLobby.script index 2dd68f7f..2ece1c52 100644 --- a/Gameleap/mw4/Content/ShellScripts/ConLobby.script +++ b/Gameleap/mw4/Content/ShellScripts/ConLobby.script @@ -1229,6 +1229,16 @@ main o_all_random_mech.state = 0 // initially enabled initialize(o_all_random_mech) +// [automaticmode] Load File button -- below Pick Cond., left of Reprint + object o_load_file = s_multistatepane + o_load_file.total_states = 3 + o_load_file.textsize = 1 + o_load_file.text = "Load File" + o_load_file.file = WPATH "button_reg_138x23m_3state.tga" + o_load_file.location = 467, 510, o_frame.location.z + 1 + o_load_file.state = 0 // initially enabled + initialize(o_load_file) + // MSL Added default settings button object o_default = s_multistatepane o_default.total_states = 3 @@ -1664,6 +1674,94 @@ main mail(MAIL_RESET_AFTER_LAUNCH, this) // reset return } +// [automaticmode] Load File button handler + if (sender == o_load_file) + { + play press, 1 + if callback($$CTCL_LoadAutoFile$$) + { + // Apply mission + game options via existing Rookie Mission flow + mail(MAIL_SET_ROOKIE_MISSION, @ConLobbyMission@) + // Clear all slots + o_cam_list.nselected = 0 + for (k = 0; k < nRosterCount; k++) + { + if (1 <= o_BotList[k].nselected) + { + if (o_BotList[k].nselected == 1) && (k < MAXTESLA_P) + { + o_Editbox[k].boxValue = "" + initialize(o_Editbox[k]) + deactivate(o_Editbox[k]) + } + o_BotList[k].nselected = 0 + m_naLastSelected[k] = 0 + if (ROSTER_top_of_list <= k) && (k < ROSTER_top_of_list + ROSTER_DISPLAY_COUNT) + mail(MAIL_PLAYER_TYPE_CHANGED, k, this) + } + } + // Apply per-slot data from file + string szAutoName + string szAutoMech + int nAutoType + int j + for (k = 0; k < nRosterCount; k++) + { + nAutoType = callback($$CTCL_GetAutoSlotInt$$, k, 0) + if nAutoType > 0 + { + o_BotList[k].nselected = nAutoType + m_naLastSelected[k] = nAutoType + if (o_BotList[k].nselected > 0) + activate(o_BotList[k]) + if (nAutoType == 1) && (k < MAXTESLA_P) + { + callback($$CTCL_GetAutoSlotName$$, szAutoName, k) + o_Editbox[k].boxValue = szAutoName + initialize(o_Editbox[k]) + activate(o_Editbox[k]) + } + callback($$CTCL_GetAutoSlotMech$$, szAutoMech, k) + if (!equal$(szAutoMech, "")) + { +#if USE_ALLOWED_MECHS + for (j = 0; j < MAX_ALLOWED_MECHS; j++) + { + if equal$(mech[allowed_mechs[j]], szAutoMech) + { + o_mech_variant[k].nselected = j + break + } + } +#else + for (j = 0; j < $$m_mechCount$$; j++) + { + if equal$(mech[j], szAutoMech) + { + o_mech_variant[k].nselected = j + break + } + } +#endif + } + if cur_team_val > 0 + o_team[k].nselected = callback($$CTCL_GetAutoSlotInt$$, k, 1) + else + o_skins[k].nselected = callback($$CTCL_GetAutoSlotInt$$, k, 2) + o_decal[k].nselected = callback($$CTCL_GetAutoSlotInt$$, k, 3) + initialize(o_mech_variant[k]) + if (ROSTER_top_of_list <= k) && (k < ROSTER_top_of_list + ROSTER_DISPLAY_COUNT) + { + activate(o_mech_variant[k]) + mail(MAIL_PLAYER_TYPE_CHANGED, k, this) + } + else + deactivate(o_mech_variant[k]) + } + } + } + return + } if (sender == o_reprint) { play press,1