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).
This commit is contained in:
2026-07-24 08:44:57 -05:00
parent fccdc2dee4
commit c33465611f
2 changed files with 233 additions and 0 deletions
+135
View File
@@ -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();