#include #include "constants.h" #include "game/camdraw.h" #include "game/title.h" #include "game/pdmode.h" #include "game/bondgun.h" #include "game/game_0b0fd0.h" #include "game/player.h" #include "game/savebuffer.h" #include "game/menu.h" #include "game/playermgr.h" #include "game/game_1531a0.h" #include "game/lv.h" #include "game/music.h" #include "game/mplayer/setup.h" #include "game/mplayer/scenarios.h" #include "game/mpstats.h" #include "game/challenge.h" #include "game/lang.h" #include "game/mplayer/mplayer.h" #include "game/pak.h" #include "bss.h" #include "lib/args.h" #include "lib/vi.h" #include "lib/main.h" #include "lib/rng.h" #include "lib/str.h" #include "lib/lib_317f0.h" #include "data.h" #include "types.h" // bss struct chrdata *g_MpAllChrPtrs[MAX_MPCHRS]; struct mpchrconfig *g_MpAllChrConfigPtrs[MAX_MPCHRS]; s32 g_MpNumChrs; u32 var800ac534; struct mpbotconfig g_BotConfigsArray[MAX_BOTS]; u8 g_MpSimulantDifficultiesPerNumPlayers[MAX_BOTS][MAX_PLAYERS]; struct mpplayerconfig g_PlayerConfigsArray[MAX_MPPLAYERCONFIGS]; u8 g_AmBotCommands[9]; struct mpsetup g_MpSetup; struct bossfile g_BossFile; u32 var800acc1c; struct mplockinfo g_MpLockInfo; struct modeldef *var800acc28[18]; // Forward declaractions struct mpweaponset g_MpWeaponSets[12]; s32 g_MpWeaponSetNum; #if VERSION >= VERSION_NTSC_1_0 const char var7f1b8a00[] = "||||||||||||| Starting game... players %d\n"; #endif s32 var80087260 = 0x00000000; bool g_MpEnableMusicSwitching = false; struct mpweapon g_MpWeapons[NUM_MPWEAPONS] = { /*0x00*/ { WEAPON_NONE, 0, 0, 0, 0, 1, 0, MODEL_CHRTT33, 256 }, /*0x01*/ { WEAPON_FALCON2, AMMOTYPE_PISTOL, 80, 0, 0, 1, 0, MODEL_CHRFALCON2, 256 }, /*0x02*/ { WEAPON_FALCON2_SILENCER, AMMOTYPE_PISTOL, 80, 0, 0, 1, MPFEATURE_WEAPON_FALCON2SILENCED, MODEL_CHRFALCON2SIL, 256 }, /*0x03*/ { WEAPON_FALCON2_SCOPE, AMMOTYPE_PISTOL, 80, 0, 0, 1, MPFEATURE_WEAPON_FALCON2SCOPE, MODEL_CHRFALCON2SCOPE, 256 }, /*0x04*/ { WEAPON_MAGSEC4, AMMOTYPE_PISTOL, 80, 0, 0, 1, 0, MODEL_CHRLEEGUN1, 256 }, /*0x05*/ { WEAPON_MAULER, AMMOTYPE_PISTOL, 92, 0, 0, 1, MPFEATURE_WEAPON_MAULER, MODEL_CHRMAULER, 256 }, /*0x06*/ { WEAPON_PHOENIX, AMMOTYPE_PISTOL, 64, 0, 0, 1, MPFEATURE_WEAPON_PHOENIX, MODEL_CHRMAIANPISTOL, 256 }, /*0x07*/ { WEAPON_DY357MAGNUM, AMMOTYPE_MAGNUM, 50, 0, 0, 1, 0, MODEL_CHRDY357, 256 }, /*0x08*/ { WEAPON_DY357LX, AMMOTYPE_MAGNUM, 50, 0, 0, 1, MPFEATURE_WEAPON_DY357LX, MODEL_CHRDY357TRENT, 256 }, /*0x09*/ { WEAPON_CMP150, AMMOTYPE_SMG, 100, 0, 0, 1, 0, MODEL_CHRCMP150, 256 }, /*0x0a*/ { WEAPON_CYCLONE, AMMOTYPE_SMG, 150, 0, 0, 1, 0, MODEL_CHRCYCLONE, 256 }, /*0x0b*/ { WEAPON_CALLISTO, AMMOTYPE_SMG, 150, 0, 0, 1, MPFEATURE_WEAPON_CALLISTO, MODEL_CHRMAIANSMG, 256 }, /*0x0c*/ { WEAPON_RCP120, AMMOTYPE_SMG, 150, 0, 0, 1, MPFEATURE_WEAPON_RCP120, MODEL_CHRRCP120, 256 }, /*0x0d*/ { WEAPON_LAPTOPGUN, AMMOTYPE_SMG, 150, 0, 0, 1, MPFEATURE_WEAPON_LAPTOPGUN, MODEL_CHRPCGUN, 256 }, /*0x0e*/ { WEAPON_DRAGON, AMMOTYPE_RIFLE, 150, 0, 0, 1, 0, MODEL_CHRDRAGON, 256 }, /*0x0f*/ { WEAPON_K7AVENGER, AMMOTYPE_RIFLE, 150, 0, 0, 1, MPFEATURE_WEAPON_K7AVENGER, MODEL_CHRAVENGER, 256 }, /*0x10*/ { WEAPON_AR34, AMMOTYPE_RIFLE, 100, 0, 0, 1, 0, MODEL_CHRAR34, 256 }, /*0x11*/ { WEAPON_SUPERDRAGON, AMMOTYPE_RIFLE, 150, AMMOTYPE_DEVASTATOR, 16, 1, MPFEATURE_WEAPON_SUPERDRAGON, MODEL_CHRSUPERDRAGON, 256 }, /*0x12*/ { WEAPON_SHOTGUN, AMMOTYPE_SHOTGUN, 16, 0, 0, 1, MPFEATURE_WEAPON_SHOTGUN, MODEL_CHRSHOTGUN, 256 }, /*0x13*/ { WEAPON_REAPER, AMMOTYPE_REAPER, 200, 0, 0, 1, MPFEATURE_WEAPON_REAPER, MODEL_CHRSKMINIGUN, 256 }, /*0x14*/ { WEAPON_SNIPERRIFLE, AMMOTYPE_RIFLE, 50, 0, 0, 1, 0, MODEL_CHRSNIPERRIFLE, 256 }, /*0x15*/ { WEAPON_FARSIGHT, AMMOTYPE_FARSIGHT, 10, 0, 0, 1, MPFEATURE_WEAPON_FARSIGHT, MODEL_CHRZ2020, 256 }, /*0x16*/ { WEAPON_DEVASTATOR, AMMOTYPE_DEVASTATOR, 16, 0, 0, 1, MPFEATURE_WEAPON_DEVASTATOR, MODEL_CHRDEVASTATOR, 256 }, /*0x17*/ { WEAPON_ROCKETLAUNCHER, AMMOTYPE_ROCKET, 3, 0, 0, 1, 0, MODEL_CHRDYROCKET, 256 }, /*0x18*/ { WEAPON_SLAYER, AMMOTYPE_ROCKET, 3, 0, 0, 1, MPFEATURE_WEAPON_SLAYER, MODEL_CHRSKROCKET, 256 }, #if !((VERSION == VERSION_JPN_FINAL) && defined(PLATFORM_N64)) /*0x19*/ { WEAPON_COMBATKNIFE, AMMOTYPE_KNIFE, 5, 0, 0, 1, 0, MODEL_CHRKNIFE, 256 }, #endif /*0x1a*/ { WEAPON_CROSSBOW, AMMOTYPE_CROSSBOW, 10, 0, 0, 1, MPFEATURE_WEAPON_CROSSBOW, MODEL_CHRCROSSBOW, 256 }, /*0x1b*/ { WEAPON_TRANQUILIZER, AMMOTYPE_SEDATIVE, 50, 0, 0, 1, MPFEATURE_WEAPON_TRANQUILIZER, MODEL_CHRDRUGGUN, 256 }, /*0x1c*/ { WEAPON_GRENADE, AMMOTYPE_GRENADE, 5, 0, 0, 0, 0, MODEL_CHRGRENADE, 256 }, /*0x1d*/ { WEAPON_NBOMB, AMMOTYPE_NBOMB, 3, 0, 0, 0, MPFEATURE_WEAPON_NBOMB, MODEL_CHRNBOMB, 256 }, /*0x1e*/ { WEAPON_TIMEDMINE, AMMOTYPE_TIMED_MINE, 5, 0, 0, 0, 0, MODEL_CHRTIMEDMINE, 384 }, /*0x1f*/ { WEAPON_PROXIMITYMINE, AMMOTYPE_PROXY_MINE, 5, 0, 0, 0, MPFEATURE_WEAPON_PROXIMITYMINE, MODEL_CHRPROXIMITYMINE, 384 }, /*0x20*/ { WEAPON_REMOTEMINE, AMMOTYPE_REMOTE_MINE, 5, 0, 0, 0, MPFEATURE_WEAPON_REMOTEMINE, MODEL_CHRREMOTEMINE, 384 }, /*0x21*/ { WEAPON_LASER, 0, 0, 0, 0, 1, MPFEATURE_WEAPON_LASER, MODEL_CHRLASER, 512 }, /*0x22*/ { WEAPON_XRAYSCANNER, 0, 0, 0, 0, 1, MPFEATURE_WEAPON_XRAYSCANNER, MODEL_CHRNIGHTSIGHT, 256 }, /*0x23*/ { WEAPON_CLOAKINGDEVICE, 0, 0, 0, 0, 1, MPFEATURE_WEAPON_CLOAKINGDEVICE, MODEL_CHRCLOAKER, 256 }, /*0x24*/ { WEAPON_COMBATBOOST, 0, 0, 0, 0, 1, MPFEATURE_WEAPON_COMBATBOOST, MODEL_CHRSPEEDPILL, 256 }, #ifndef PLATFORM_N64 /*0x25*/ { WEAPON_PP9I, AMMOTYPE_PISTOL, 80, 0, 0, 1, 0, MODEL_CHRWPPK, 256 }, /*0x26*/ { WEAPON_CC13, AMMOTYPE_PISTOL, 80, 0, 0, 1, 0, MODEL_CHRTT33, 256 }, /*0x27*/ { WEAPON_KL01313, AMMOTYPE_SMG, 100, 0, 0, 1, 0, MODEL_CHRSKORPION, 256 }, /*0x28*/ { WEAPON_KF7SPECIAL, AMMOTYPE_RIFLE, 100, 0, 0, 1, 0, MODEL_CHRKALASH, 256 }, /*0x29*/ { WEAPON_ZZT, AMMOTYPE_SMG, 100, 0, 0, 1, 0, MODEL_CHRUZI, 256 }, /*0x2a*/ { WEAPON_DMC, AMMOTYPE_SMG, 100, 0, 0, 1, 0, MODEL_CHRMP5K, 256 }, /*0x2b*/ { WEAPON_AR53, AMMOTYPE_RIFLE, 150, 0, 0, 1, 0, MODEL_CHRM16, 256 }, /*0x2c*/ { WEAPON_RCP45, AMMOTYPE_SMG, 150, 0, 0, 1, 0, MODEL_CHRFNP90, 256 }, #endif /*0x2d*/ { WEAPON_MPSHIELD, 0, 0, 0, 0, 1, MPFEATURE_WEAPON_SHIELD, MODEL_CHRSHIELD, 256 }, /*0x2e*/ { WEAPON_DISABLED }, // 0x25 on N64 }; #ifndef PLATFORM_N64 #define PLAYER_EXT_CFG_DEFAULT { \ .fovy = 60.f, \ .fovzoommult = 1.f, \ .fovzoom = true, \ .mouseaimmode = MOUSEAIM_CLASSIC, \ .mouseaimspeedx = 0.7f, \ .mouseaimspeedy = 0.7f, \ .radialmenuspeed = 4.f, \ .crosshairsway = 1.f, \ .crouchmode = CROUCHMODE_TOGGLE_ANALOG, \ .extcontrols = true, \ } struct extplayerconfig g_PlayerExtCfg[MAX_PLAYERS] = { PLAYER_EXT_CFG_DEFAULT, PLAYER_EXT_CFG_DEFAULT, PLAYER_EXT_CFG_DEFAULT, PLAYER_EXT_CFG_DEFAULT, }; #endif /** * Converts the given value into a float on a curved scale from 0.1 to 10. * * value 0 will return 0.1 * value 127 will return 1 * value 255 will return 10 */ f32 mpHandicapToDamageScale(u8 value) { f32 tmp; if (value < 127) { return (value / 127.0f) * (value / 127.0f) * 0.9f + 0.1f; } if (value == 127) { return 1; } tmp = (value - 128) / 127.0f + 1; return tmp * tmp * 3 - 2; } void func0f187838(struct mpchrconfig *mpchr) { s32 i = 0; while (i < ARRAYCOUNT(mpchr->killcounts)) { mpchr->killcounts[i++] = 0; } mpchr->numdeaths = 0; mpchr->numpoints = 0; mpchr->unk40 = 0; } void mpStartMatch(void) { s32 i; s32 numplayers = 0; s32 stagenum; mpConfigureQuickTeamSimulants(); if (!challengeIsFeatureUnlocked(MPFEATURE_ONEHITKILLS)) { g_MpSetup.options &= ~MPOPTION_ONEHITKILLS; } if (!challengeIsFeatureUnlocked(MPFEATURE_SLOWMOTION)) { g_MpSetup.options &= ~(MPOPTION_SLOWMOTION_ON | MPOPTION_SLOWMOTION_SMART); } for (i = 0; i < MAX_PLAYERS; i++) { if (g_MpSetup.chrslots & (1 << i)) { numplayers++; } } stagenum = g_MpSetup.stagenum; if (g_MpSetup.stagenum == STAGE_MP_RANDOM) { stagenum = mpChooseRandomStage(); } titleSetNextStage(stagenum); mainChangeToStage(stagenum); setNumPlayers(numplayers); titleSetNextMode(TITLEMODE_SKIP); g_Vars.perfectbuddynum = 1; } void mpReset(void) { s32 i; s32 mpindex = 0; g_MpNumChrs = 0; g_Vars.mplayerisrunning = true; if (g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) { g_Vars.normmplayerisrunning = false; } else { g_Vars.normmplayerisrunning = true; } g_Vars.perfectbuddynum = 0; if (mpHasSimulants()) { g_Vars.lvmpbotlevel = true; } if (g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) { struct mpplayerconfig tmp; tmp = g_PlayerConfigsArray[MAX_PLAYERS]; g_PlayerConfigsArray[MAX_PLAYERS] = g_PlayerConfigsArray[0]; g_PlayerConfigsArray[0] = tmp; tmp = g_PlayerConfigsArray[MAX_PLAYERS + 1]; g_PlayerConfigsArray[MAX_PLAYERS + 1] = g_PlayerConfigsArray[1]; g_PlayerConfigsArray[1] = tmp; // Player index 0 g_Vars.playerstats[0].mpindex = 0; g_PlayerConfigsArray[0].contpad1 = 0; g_PlayerConfigsArray[0].contpad2 = 2; if ((g_Vars.coopplayernum >= 0 && g_Vars.coopradaron) || (g_Vars.antiplayernum >= 0 && g_Vars.antiradaron)) { g_PlayerConfigsArray[0].base.displayoptions |= MPDISPLAYOPTION_RADAR; } else { g_PlayerConfigsArray[0].base.displayoptions &= ~MPDISPLAYOPTION_RADAR; } // Player index 1 g_Vars.playerstats[1].mpindexu32 = 1; g_PlayerConfigsArray[1].contpad1 = 1; g_PlayerConfigsArray[1].contpad2 = 3; if ((g_Vars.coopplayernum >= 0 && g_Vars.coopradaron) || (g_Vars.antiplayernum >= 0 && g_Vars.antiradaron)) { g_PlayerConfigsArray[1].base.displayoptions |= MPDISPLAYOPTION_RADAR; } else { g_PlayerConfigsArray[1].base.displayoptions &= ~MPDISPLAYOPTION_RADAR; } g_MpNumChrs = 2; } else { for (i = 0; i < MAX_PLAYERS; i++) { if (g_MpSetup.chrslots & (1 << i)) { g_Vars.playerstats[mpindex].mpindex = i; g_PlayerConfigsArray[i].contpad1 = i; g_PlayerConfigsArray[i].contpad2 = 0; mpCalculatePlayerTitle(&g_PlayerConfigsArray[i]); g_PlayerConfigsArray[i].newtitle = g_PlayerConfigsArray[i].title; g_MpNumChrs++; mpindex++; } } } for (i = 0; i < MAX_MPCHRS; i++) { struct mpchrconfig *mpchr = MPCHR(i); func0f187838(mpchr); #if VERSION >= VERSION_NTSC_1_0 g_MpAllChrPtrs[i] = NULL; #endif } g_MpSetup.paused = false; g_MpEnableMusicSwitching = false; if (g_Vars.normmplayerisrunning) { if (mpGetUsingMultipleTunes()) { s32 i = 0; s32 count = 0; s32 numtracks = mpGetNumUnlockedTracks(); for (i; i < numtracks && count < 2; i++) { if (mpIsMultiTrackSlotEnabled(i)) { count++; } } if (count >= 2) { g_MpEnableMusicSwitching = true; } } g_MpLockInfo.unk04 = -1; } // Assign aibot commands to active menu slots for (i = 0; i < ARRAYCOUNT(g_AmBotCommands); i++) { g_AmBotCommands[i] = AIBOTCMD_NORMAL; } g_AmBotCommands[1] = AIBOTCMD_ATTACK; g_AmBotCommands[7] = AIBOTCMD_NORMAL; g_AmBotCommands[0] = AIBOTCMD_FOLLOW; g_AmBotCommands[2] = AIBOTCMD_PROTECT; g_AmBotCommands[3] = AIBOTCMD_DEFEND; g_AmBotCommands[5] = AIBOTCMD_HOLD; switch (g_MpSetup.scenario) { case MPSCENARIO_CAPTURETHECASE: g_AmBotCommands[3] = AIBOTCMD_GETCASE; g_AmBotCommands[5] = AIBOTCMD_SAVECASE; break; case MPSCENARIO_KINGOFTHEHILL: g_AmBotCommands[6] = AIBOTCMD_DEFHILL; g_AmBotCommands[8] = AIBOTCMD_HOLDHILL; break; case MPSCENARIO_HACKERCENTRAL: g_AmBotCommands[6] = AIBOTCMD_DOWNLOAD; g_AmBotCommands[8] = AIBOTCMD_DOWNLOAD; break; case MPSCENARIO_POPACAP: g_AmBotCommands[6] = AIBOTCMD_POPCAP; g_AmBotCommands[8] = AIBOTCMD_POPCAP; break; case MPSCENARIO_HOLDTHEBRIEFCASE: g_AmBotCommands[6] = AIBOTCMD_GETCASE2; g_AmBotCommands[8] = AIBOTCMD_GETCASE2; break; default: g_AmBotCommands[0] = AIBOTCMD_FOLLOW; g_AmBotCommands[1] = AIBOTCMD_ATTACK; g_AmBotCommands[2] = AIBOTCMD_PROTECT; g_AmBotCommands[3] = AIBOTCMD_DEFEND; g_AmBotCommands[5] = AIBOTCMD_HOLD; g_AmBotCommands[7] = AIBOTCMD_NORMAL; break; } } void mpCalculateTeamIsOnlyAi(void) { s32 playercount = PLAYERCOUNT(); s32 i; s32 j; // Iterate simulants, which go after players in the g_MpAllChrPtrs array for (i = playercount; i < g_MpNumChrs; i++) { if (!g_MpAllChrPtrs[i]) { continue; } g_MpAllChrPtrs[i]->aibot->teamisonlyai = true; if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { // Iterate human players for (j = 0; j < playercount; j++) { if (g_MpAllChrPtrs[i]->team == g_MpAllChrPtrs[j]->team) { g_MpAllChrPtrs[i]->aibot->teamisonlyai = false; break; } } } } } void func0f187fbc(s32 playernum) { g_PlayerConfigsArray[playernum].base.unk18 = 80; g_PlayerConfigsArray[playernum].base.unk1a = 80; g_PlayerConfigsArray[playernum].base.unk1c = 75; } void func0f187fec(void) { g_MpSetup.timelimit = 9; g_MpSetup.scorelimit = 9; g_MpSetup.teamscorelimit = 19; } void mpPlayerSetDefaults(s32 playernum, bool autonames) { s32 i; s32 j; func0f187fbc(playernum); g_PlayerConfigsArray[playernum].controlmode = CONTROLMODE_11; #ifndef PLATFORM_N64 if (g_PlayerExtCfg[playernum % MAX_PLAYERS].extcontrols) { g_PlayerConfigsArray[playernum].controlmode = CONTROLMODE_PC; } #endif g_PlayerConfigsArray[playernum].options = #ifdef PLATFORM_N64 OPTION_LOOKAHEAD #else OPTION_FORWARDPITCH #endif | OPTION_SIGHTONSCREEN | OPTION_AUTOAIM | OPTION_AMMOONSCREEN | OPTION_SHOWGUNFUNCTION | OPTION_HEADROLL | OPTION_0100 | OPTION_ALWAYSSHOWTARGET | OPTION_SHOWZOOMRANGE; g_PlayerConfigsArray[playernum].handicap = 128; switch (playernum) { case 0: default: g_PlayerConfigsArray[playernum].base.mpbodynum = MPBODY_DARK_COMBAT; break; case 1: g_PlayerConfigsArray[playernum].base.mpbodynum = MPBODY_CASSANDRA; break; case 2: g_PlayerConfigsArray[playernum].base.mpbodynum = MPBODY_CARRINGTON; break; case 3: g_PlayerConfigsArray[playernum].base.mpbodynum = MPBODY_CILABTECH; break; } g_PlayerConfigsArray[playernum].base.mpheadnum = mpGetMpheadnumByMpbodynum(g_PlayerConfigsArray[playernum].base.mpbodynum); g_PlayerConfigsArray[playernum].base.displayoptions = MPDISPLAYOPTION_RADAR | MPDISPLAYOPTION_HIGHLIGHTTEAMS; g_PlayerConfigsArray[playernum].fileguid.fileid = 0; g_PlayerConfigsArray[playernum].fileguid.deviceserial = 0; if (autonames) { // "Player 1" etc sprintf(g_PlayerConfigsArray[playernum].base.name, "%s %d\n", langGet(L_MISC_437), playernum + 1); } else { g_PlayerConfigsArray[playernum].base.name[0] = '\0'; } g_PlayerConfigsArray[playernum].kills = 0; g_PlayerConfigsArray[playernum].deaths = 0; g_PlayerConfigsArray[playernum].gamesplayed = 0; g_PlayerConfigsArray[playernum].gameswon = 0; g_PlayerConfigsArray[playernum].gameslost = 0; g_PlayerConfigsArray[playernum].time = 0; g_PlayerConfigsArray[playernum].distance = 0; g_PlayerConfigsArray[playernum].accuracy = 1000; g_PlayerConfigsArray[playernum].damagedealt = 0; g_PlayerConfigsArray[playernum].painreceived = 0; g_PlayerConfigsArray[playernum].headshots = 0; g_PlayerConfigsArray[playernum].ammoused = 0; g_PlayerConfigsArray[playernum].accuracymedals = 0; g_PlayerConfigsArray[playernum].headshotmedals = 0; g_PlayerConfigsArray[playernum].killmastermedals = 0; g_PlayerConfigsArray[playernum].survivormedals = 0; g_PlayerConfigsArray[playernum].title = MPPLAYERTITLE_BEGINNER; if (playernum < MAX_PLAYERS) { for (i = 0; i < ARRAYCOUNT(g_MpChallenges); i++) { for (j = 1; j <= MAX_PLAYERS; j++) { challengeSetCompletedByPlayerWithNumPlayers(playernum, i, j, false); } } challengeDetermineUnlockedFeatures(); } for (i = 0; i < ARRAYCOUNT(g_PlayerConfigsArray); i++) { g_PlayerConfigsArray[playernum].gunfuncs[i] = 0; } } void func0f1881d4(s32 index) { g_BotConfigsArray[index].base.name[0] = '\0'; g_BotConfigsArray[index].base.mpheadnum = MPHEAD_DARK_COMBAT; g_BotConfigsArray[index].base.mpbodynum = MPBODY_DARK_COMBAT; g_BotConfigsArray[index].type = BOTTYPE_GENERAL; g_BotConfigsArray[index].difficulty = BOTDIFF_DISABLED; } void mpInit(void) { s32 i; s32 j; g_MpSetup.scenario = MPSCENARIO_COMBAT; g_MpSetup.stagenum = STAGE_MP_SKEDAR; g_MpSetup.options = MPOPTION_DISPLAYTEAM | MPOPTION_KILLSSCORE | MPOPTION_HTB_HIGHLIGHTBRIEFCASE | MPOPTION_HTB_SHOWONRADAR | MPOPTION_CTC_SHOWONRADAR | MPOPTION_KOH_HILLONRADAR | MPOPTION_KOH_MOBILEHILL | MPOPTION_00010000 | MPOPTION_HTM_HIGHLIGHTTERMINAL | MPOPTION_HTM_SHOWONRADAR | MPOPTION_PAC_HIGHLIGHTTARGET | MPOPTION_PAC_SHOWONRADAR; g_Vars.mphilltime = 10; func0f187fec(); g_MpSetup.fileguid.fileid = 0; g_MpSetup.fileguid.deviceserial = 0; strcpy(g_MpSetup.name, ""); for (i = 0; i < ARRAYCOUNT(g_PlayerConfigsArray); i++) { mpPlayerSetDefaults(i, false); } for (i = 0; i < MAX_BOTS; i++) { func0f1881d4(i); } if (argFindByPrefix(1, "-mpwpnset")) { char *value = argFindByPrefix(1, "-mpwpnset"); mpSetWeaponSet(*value - '0'); } else { mpSetWeaponSet(0); } g_Vars.mplayerisrunning = false; g_Vars.normmplayerisrunning = false; g_Vars.lvmpbotlevel = 0; g_MpLockInfo.lockedplayernum = 0; g_MpLockInfo.lastwinner = -1; g_MpLockInfo.lastloser = -1; g_MpLockInfo.unk03 = -1; g_MpLockInfo.unk04 = -1; challengeForceUnlockBotFeatures(); for (i = 0; i < ARRAYCOUNT(g_PlayerConfigsArray); i++) { for (j = 0; j < ARRAYCOUNT(g_PlayerConfigsArray[i].gunfuncs); j++) { g_PlayerConfigsArray[i].gunfuncs[j] = 0; } } g_MpSetup.chrslots = 0; } #if VERSION >= VERSION_PAL_BETA void mpGetTeamsWithDefaultName(u8 *mask) { s32 i; *mask = 0; for (i = 0; i < ARRAYCOUNT(g_BossFile.teamnames); i++) { if (strcmp(g_BossFile.teamnames[i], langGet(L_OPTIONS_008 + i)) == 0) { *mask |= 1 << i; } } } #endif #if VERSION >= VERSION_PAL_BETA void mpSetTeamNamesToDefault(u8 mask) { s32 i; for (i = 0; i < ARRAYCOUNT(g_BossFile.teamnames); i++) { if (mask & (1 << i)) { strcpy(g_BossFile.teamnames[i], langGet(L_OPTIONS_008 + i)); } } } #endif void mpSetDefaultNamesIfEmpty(void) { s32 i; // Setup file name if (g_MpSetup.name[0] == '\0') { strcpy(g_MpSetup.name, langGet(L_MISC_438)); // empty string } // Team names for (i = 0; i < ARRAYCOUNT(g_BossFile.teamnames); i++) { if (g_BossFile.teamnames[i][0] == '\0') { strcpy(g_BossFile.teamnames[i], langGet(L_OPTIONS_008 + i)); // "Red", "Yellow" etc } } // Player names for (i = 0; i < MAX_PLAYERS; i++) { if (g_PlayerConfigsArray[i].base.name[0] == '\0') { sprintf(g_PlayerConfigsArray[i].base.name, "%s %d\n", langGet(L_MISC_437), i + 1); // "Player 1" etc } } } s32 mpCalculateTeamScoreLimit(void) { s32 limit = g_MpSetup.teamscorelimit; s32 i; if (g_BossFile.locktype == MPLOCKTYPE_CHALLENGE && g_MpSetup.teamscorelimit != 400 && (g_MpSetup.scenario == MPSCENARIO_COMBAT || g_MpSetup.scenario == MPSCENARIO_KINGOFTHEHILL)) { s32 numchrs = 0; for (i = 0; i < MAX_PLAYERS; i++) { if (g_MpSetup.chrslots & (1 << i)) { numchrs++; } } switch (numchrs) { case 1: break; case 2: limit = limit * 2 + 1; break; case 3: limit = (limit * 5 + 5) / 2 - 1; break; case 4: limit = limit * 3 + 2; break; } } return limit; } void mpApplyLimits(void) { if (g_MpSetup.timelimit >= 60) { lvSetMpTimeLimit60(0); } else { lvSetMpTimeLimit60(SECSTOTIME60((g_MpSetup.timelimit + 1) * 60)); } if (g_MpSetup.scorelimit >= 100) { lvSetMpScoreLimit(0); } else { lvSetMpScoreLimit(g_MpSetup.scorelimit + 1); } if (g_MpSetup.teamscorelimit >= 400) { lvSetMpTeamScoreLimit(0); } else { lvSetMpTeamScoreLimit(mpCalculateTeamScoreLimit() + 1); } } /** * Given a pointer to an array of 12 ranking structs, populate those rankings * with the winner first, loser last. Return the number of rows (players). * * The function also populates the placement an rankablescore values in the * mpchr structs and calculates the last winner and loser for the lock settings. */ s32 mpGetPlayerRankings(struct ranking *rankings) { s32 i; s32 scores[MAX_MPCHRS]; u32 rankablescores[MAX_MPCHRS]; struct mpchrconfig *mpchrs[MAX_MPCHRS]; s32 chrnums[MAX_MPCHRS]; s32 count = 0; s32 numteams; struct ranking teamrankings[MAX_MPCHRS]; s32 winner; s32 loser; s32 score; s32 deaths; struct mpchrconfig *mpchr; s32 dstindex; bool found; u32 rankablescore; s32 j; if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { numteams = mpGetTeamRankings(teamrankings); } // Populate 4 arrays with player info, sorted by highest score descending for (i = 0; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { mpchr = MPCHR(i); scenarioCalculatePlayerScore(mpchr, i, &score, &deaths); rankablescore = (score + 0x8000) << 16 | (0xffff - deaths); dstindex = 0; found = false; // Find where this player should be placed in the sorted arrays for (j = 0; j < count && !found; j++) { if (rankablescore > rankablescores[j]) { dstindex = j; found = true; } } if (!found) { dstindex = count; } // Shuffle everything forward after dstindex to create a gap for (j = count; j > dstindex; j--) { rankablescores[j] = rankablescores[j - 1]; scores[j] = scores[j - 1]; mpchrs[j] = mpchrs[j - 1]; chrnums[j] = chrnums[j - 1]; } count++; // Write the new figures rankablescores[dstindex] = (score + 0x8000) << 16 | (0xffff - deaths); scores[dstindex] = score; mpchrs[dstindex] = mpchr; chrnums[dstindex] = i; } } // Populate the rankings array, copy some values into the mpchr structs // and calculate the winner and loser winner = -1; loser = -1; for (j = 0; j < count; j++) { rankings[j].mpchr = mpchrs[j]; rankings[j].teamnum = chrnums[j]; rankings[j].positionindex = j; rankings[j].unk0c = 0; rankings[j].score = scores[j]; // For a team game, the mpchr's placement has to be the team's placement // and not the placement of the individual player. if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { s32 placement = numteams - 1; s32 i; for (i = 0; i < numteams; i++) { if (teamrankings[i].teamnum == mpchrs[j]->team) { placement = i; } } mpchrs[j]->placement = placement; #if VERSION >= VERSION_NTSC_1_0 mpchrs[j]->rankablescore = 255 - placement; #endif } else { mpchrs[j]->placement = j; #if VERSION >= VERSION_NTSC_1_0 mpchrs[j]->rankablescore = rankablescores[j]; #endif } #if VERSION < VERSION_NTSC_1_0 mpchrs[j]->rankablescore = rankablescores[j]; #endif if (chrnums[j] < 4) { loser = chrnums[j]; if (winner == -1) { winner = chrnums[j]; if (1); } } } g_MpLockInfo.lastwinner = winner; g_MpLockInfo.lastloser = loser; if (g_BossFile.locktype == MPLOCKTYPE_RANDOM) { g_MpLockInfo.lockedplayernum = mpChooseRandomLockPlayer(); } return count; } /** * Iterate players on the given team and add their scores. * * There are two score values: * - The team score, which is written to the result pointer and eventually * displayed on screen * - The rankable score, which is the returned value. The rankable score is * similar to the team score but uses the number of deaths as a tie breaker. */ s32 mpCalculateTeamScore(s32 teamnum, s32 *result) { struct mpchrconfig *mpchr; s32 teamscore = 0; s32 teamdeaths = 0; bool teamexists = false; s32 i; u32 stack; s32 score; s32 deaths; for (i = 0; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { mpchr = MPCHR(i); if (mpchr->team == teamnum) { scenarioCalculatePlayerScore(mpchr, i, &score, &deaths); teamexists = true; teamscore += score; teamdeaths += deaths; } } } if (teamexists) { *result = teamscore; return (teamscore + 0x8000) << 16 | (0xffff - teamdeaths); } return 0; } /** * Given a pointer to an array of 8 ranking structs, populate those rankings * with the winning team first, losing team last. Return the number of rows * (teams). */ s32 mpGetTeamRankings(struct ranking *rankings) { s32 i; s32 count; s32 apparentscores[MAX_TEAMS]; u32 rankablescores[MAX_TEAMS]; u32 bestrankablescore; s32 thisteamnum; count = 0; // Calculate scores for each team for (i = 0; i < MAX_TEAMS; i++) { apparentscores[i] = -8000; rankablescores[i] = mpCalculateTeamScore(i, &apparentscores[i]); } // Iterate each slot and determine which team belongs in it. // As each team is found, set their apparent score to -8000 so they're not // found again. do { bestrankablescore = 0; thisteamnum = -8000; // Check which team has the best remaining score for (i = 0; i < MAX_TEAMS; i++) { if (apparentscores[7 - i] > -8000 && rankablescores[7 - i] >= bestrankablescore) { thisteamnum = 7 - i; bestrankablescore = rankablescores[thisteamnum]; } } if (thisteamnum > -8000) { // Append to the rankings array rankings[count].mpchr = NULL; rankings[count].teamnum = thisteamnum; rankings[count].positionindex = count + 1; rankings[count].unk0c = 0; rankings[count].score = apparentscores[thisteamnum]; apparentscores[thisteamnum] = -8000; count++; } } while (thisteamnum != -8000); return count; } s32 func0f188bcc(void) { return NUM_MPWEAPONS; } s32 mpGetNumWeaponOptions(void) { s32 count = 0; s32 i; for (i = 0; i < ARRAYCOUNT(g_MpWeapons); i++) { if (challengeIsFeatureUnlocked(g_MpWeapons[i].unlockfeature)) { count++; } } return count; } char *mpGetWeaponLabel(s32 weaponnum) { s32 i; for (i = 0; i < ARRAYCOUNT(g_MpWeapons); i++) { if (challengeIsFeatureUnlocked(g_MpWeapons[i].unlockfeature)) { if (weaponnum == 0) { if (g_MpWeapons[i].weaponnum == WEAPON_NONE) { return langGet(L_MPWEAPONS_058); // "Nothing" } if (g_MpWeapons[i].weaponnum == WEAPON_MPSHIELD) { return langGet(L_MPWEAPONS_059); // "Shield" } if (g_MpWeapons[i].weaponnum == WEAPON_DISABLED) { return langGet(L_MPWEAPONS_060); // "Disabled" } return bgunGetName(g_MpWeapons[i].weaponnum); } weaponnum--; } } return ""; } #if VERSION >= VERSION_NTSC_1_0 const char var7f1b8a5c[] = "Gun index %d -> slot %d = gun %d\n\n"; const char var7f1b8a80[] = "HOLDER: selecting weapon set %d\n"; #endif void mpSetWeaponSlot(s32 slot, s32 mpweaponnum) { s32 optionindex = mpweaponnum; s32 i; for (i = 0; i <= mpweaponnum; i++) { if (challengeIsFeatureUnlocked(g_MpWeapons[i].unlockfeature) == 0) { mpweaponnum++; } optionindex = mpweaponnum; } g_MpSetup.weapons[slot] = optionindex; } s32 mpGetWeaponSlot(s32 slot) { s32 count = 0; s32 i; for (i = 0; i < g_MpSetup.weapons[slot]; i++) { if (challengeIsFeatureUnlocked(g_MpWeapons[i].unlockfeature)) { count++; } } return count; } struct mpweapon *mpGetMpWeaponByLocation(s32 locationindex) { s32 v0 = locationindex + 1; s32 slot = 0; s32 a2 = v0; u8 mpweaponnum; while (v0 > 0) { mpweaponnum = g_MpSetup.weapons[slot]; if (g_MpWeapons[mpweaponnum].weaponnum != WEAPON_DISABLED) { v0--; } if (v0 > 0) { slot++; if (slot >= ARRAYCOUNT(g_MpSetup.weapons)) { slot = 0; if (a2 == v0) { mpweaponnum = 0; v0 = 0; } a2 = v0; } } } return &g_MpWeapons[mpweaponnum]; } s32 mpCountWeaponSetThing(s32 weaponsetindex) { s32 i; s32 count = 0; if (weaponsetindex >= ARRAYCOUNT(g_MpWeaponSets)) { count = weaponsetindex - ARRAYCOUNT(g_MpWeaponSets); weaponsetindex = ARRAYCOUNT(g_MpWeaponSets); } for (i = 0; i < weaponsetindex; i++) { if ((challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[0]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[1]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[2]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[3])) || g_MpWeaponSets[i].unk0c != WEAPON_DISABLED) { count++; } } return count; } s32 func0f188f9c(s32 arg0) { s32 i; for (i = 0; i < ARRAYCOUNT(g_MpWeaponSets); i++) { // @bug? Shouldn't the disabled check be == WEAPON_DISABLED? if ((challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[0]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[1]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[2]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[3])) || g_MpWeaponSets[i].unk0c != WEAPON_DISABLED) { if (arg0 == 0) { break; } arg0--; } } return i + arg0; } s32 func0f189058(bool full) { return mpCountWeaponSetThing(full ? ARRAYCOUNT(g_MpWeaponSets) + 3 : ARRAYCOUNT(g_MpWeaponSets)); } s32 func0f189088(void) { return mpCountWeaponSetThing(ARRAYCOUNT(g_MpWeaponSets) + 2); } char *mpGetWeaponSetName(s32 index) { index = func0f188f9c(index); if (index < 0 || index >= ARRAYCOUNT(g_MpWeaponSets) + 2) { return langGet(L_MPWEAPONS_041); // "Custom" } if (index == ARRAYCOUNT(g_MpWeaponSets) + 1) { return langGet(L_MPWEAPONS_042); // "Random" } if (index == ARRAYCOUNT(g_MpWeaponSets)) { return langGet(L_MPWEAPONS_043); // "Random Five" } return langGet(g_MpWeaponSets[index].name); } void func0f18913c(void) { s32 i; bool done = false; u8 *ptr; s32 j; for (i = 0; !done && i < ARRAYCOUNT(g_MpWeaponSets); i++) { if (challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[0]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[1]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[2]) && challengeIsFeatureUnlocked(g_MpWeaponSets[i].requirefeatures[3])) { ptr = &g_MpWeaponSets[i].slots[0]; } else if (g_MpWeaponSets[i].unk0c != WEAPON_DISABLED) { ptr = &g_MpWeaponSets[i].unk0c; } else { ptr = NULL; } if (ptr != NULL) { bool ok = true; for (j = 0; j < ARRAYCOUNT(g_MpWeaponSets[j].slots); j++) { s32 weaponnum = ptr[j]; if (weaponnum == WEAPON_MPSHIELD) { if (!challengeIsFeatureUnlocked(MPFEATURE_WEAPON_SHIELD)) { weaponnum = 0; } } if (weaponnum != g_MpWeapons[g_MpSetup.weapons[j]].weaponnum) { ok = false; } } if (ok) { done = true; g_MpWeaponSetNum = i; } } } if (!done) { g_MpWeaponSetNum = WEAPONSET_CUSTOM; } } void mpApplyWeaponSet(void) { s32 i; u8 *ptr; if (g_MpWeaponSetNum >= 0 && g_MpWeaponSetNum < ARRAYCOUNT(g_MpWeaponSets)) { if (challengeIsFeatureUnlocked(g_MpWeaponSets[g_MpWeaponSetNum].requirefeatures[0]) && challengeIsFeatureUnlocked(g_MpWeaponSets[g_MpWeaponSetNum].requirefeatures[1]) && challengeIsFeatureUnlocked(g_MpWeaponSets[g_MpWeaponSetNum].requirefeatures[2]) && challengeIsFeatureUnlocked(g_MpWeaponSets[g_MpWeaponSetNum].requirefeatures[3])) { ptr = &g_MpWeaponSets[g_MpWeaponSetNum].slots[0]; } else if (g_MpWeaponSets[g_MpWeaponSetNum].unk0c != WEAPON_DISABLED) { ptr = &g_MpWeaponSets[g_MpWeaponSetNum].unk0c; } else { ptr = NULL; } if (ptr != NULL) { for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) { u32 j; bool done = false; s32 mpweaponnum = MPWEAPON_NONE; s32 weaponnum = ptr[i]; if (weaponnum == WEAPON_MPSHIELD && !challengeIsFeatureUnlocked(MPFEATURE_WEAPON_SHIELD)) { weaponnum = 0; } for (j = 0; !done; j++) { if (j > MPWEAPON_DISABLED) { done = true; } else if (weaponnum == g_MpWeapons[j].weaponnum) { mpweaponnum = j; done = true; } } g_MpSetup.weapons[i] = mpweaponnum; } } } else if (g_MpWeaponSetNum == WEAPONSET_RANDOM) { s32 numoptions = mpGetNumWeaponOptions(); for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) { mpSetWeaponSlot(i, random() % numoptions); } } else if (g_MpWeaponSetNum == WEAPONSET_RANDOMFIVE) { s32 numoptions = mpGetNumWeaponOptions() - 2; for (i = 0; i < 5; i++) { mpSetWeaponSlot(i, random() % numoptions + 1); } mpSetWeaponSlot(i, mpGetNumWeaponOptions() - 1); } } void mpSetWeaponSet(s32 weaponsetnum) { g_MpWeaponSetNum = func0f188f9c(weaponsetnum); mpApplyWeaponSet(); } void func0f1895e8(void) { if (g_MpWeaponSetNum < ARRAYCOUNT(g_MpWeaponSets)) { mpApplyWeaponSet(); } } s32 mpGetWeaponSet(void) { return mpCountWeaponSetThing(g_MpWeaponSetNum); } bool mpIsPaused(void) { if (PLAYERCOUNT() == 1 && g_Vars.mplayerisrunning && g_Menus[g_Vars.currentplayerstats->mpindex].curdialog) { return true; } if (g_MpSetup.paused == PAUSEMODE_UNPAUSED) { return false; } return true; } void mpSetPaused(u8 mode) { g_MpSetup.paused = mode; } /** * Render "Paused" in the middle of the viewport if paused, * or "Press START" if player has finished their death animation. * * @bug: When calling textRender(), viGetWidth() is used twice but it should be * viGetViewWidth() and viGetViewHeight(). These arguments to textRender() set * the crop box for the text, but the text doesn't extend past the box anyway * so it has no effect. */ Gfx *mpRenderModalText(Gfx *gdl) { s32 textwidth; s32 textheight; s32 x; s32 y; char text[50]; s32 stack1; #if VERSION >= VERSION_JPN_FINAL g_ScaleX = g_ViRes == VIRES_HI ? 2 : 1; #endif if (g_MpSetup.paused == MPPAUSEMODE_PAUSED) { s32 red = (s32) ((1.0f - g_20SecIntervalFrac) * 20.0f * 255.0f) % 255; s32 stack2; gdl = text0f153628(gdl); strcpy(text, langGet(L_MPWEAPONS_040)); // "Paused" x = viGetViewLeft() + viGetViewWidth() / 2; #if VERSION >= VERSION_JPN_FINAL x = x / g_ScaleX; #endif #if VERSION >= VERSION_NTSC_1_0 if (g_Menus[g_Vars.currentplayerstats->mpindex].curdialog) #else if (g_Menus[g_Vars.currentplayernum].curdialog) #endif { y = viGetViewTop() + 10; } else { y = viGetViewTop() + viGetViewHeight() / 2; } #if VERSION >= VERSION_JPN_FINAL textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0); #elif PAL // Use smaller fonts textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0); #else textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicMd, g_FontHandelGothicMd, 0); #endif x -= textwidth / 2; #if VERSION >= VERSION_JPN_FINAL gdl = func0f1574d0jf(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, (red << 24) | 0x00ff00ff, 0x000000ff, viGetWidth(), viGetWidth(), 0, 0); #elif PAL gdl = textRender(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, (red << 24) | 0x00ff00ff, 0x000000ff, viGetWidth(), viGetWidth(), 0, 0); #else gdl = textRender(gdl, &x, &y, text, g_CharsHandelGothicMd, g_FontHandelGothicMd, (red << 24) | 0x00ff00ff, 0x000000ff, viGetWidth(), viGetWidth(), 0, 0); #endif gdl = text0f153780(gdl); } else if (!g_MainIsEndscreen && g_MpSetup.paused == MPPAUSEMODE_UNPAUSED && g_Vars.currentplayer->isdead && g_Vars.currentplayer->redbloodfinished && g_Vars.currentplayer->deathanimfinished && !(g_Vars.coopplayernum >= 0 && ((g_Vars.bond->isdead && g_Vars.coop->isdead) || !g_Vars.currentplayer->coopcanrestart || g_InCutscene)) && !(g_Vars.antiplayernum >= 0 && ((g_Vars.currentplayer != g_Vars.anti || g_InCutscene))) && g_NumReasonsToEndMpMatch == 0) { // Render "Press START" text gdl = text0f153628(gdl); strcpy(text, langGet(L_MPWEAPONS_039)); x = viGetViewLeft() + viGetViewWidth() / 2; #if VERSION >= VERSION_JPN_FINAL x = x / g_ScaleX; #endif y = viGetViewTop() + viGetViewHeight() / 2; textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0); x -= textwidth / 2; #if VERSION >= VERSION_JPN_FINAL gdl = func0f1574d0jf(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0xff0000ff, 0x000000ff, viGetWidth(), viGetWidth(), 0, 0); #else gdl = textRender(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0xff0000ff, 0x000000ff, viGetWidth(), viGetWidth(), 0, 0); #endif if (g_Vars.currentplayer->deadtimer > 0) { // Render countdown timer s32 countdowny = viGetViewTop() + viGetViewHeight() / 2 + textheight + 2; s32 countdownx = viGetViewLeft() + viGetViewWidth() / 2; #if VERSION >= VERSION_JPN_FINAL countdownx = countdownx / g_ScaleX; #endif sprintf(text, "%d\n", (g_Vars.currentplayer->deadtimer + TICKS(60) - 1) / TICKS(60)); textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0); x = countdownx - textwidth / 2; y = countdowny; #if VERSION >= VERSION_JPN_FINAL gdl = func0f1574d0jf(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0xff0000ff, 0x000000ff, viGetWidth(), viGetWidth(), 0, 0); #else gdl = textRender(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0xff0000ff, 0x000000ff, viGetWidth(), viGetWidth(), 0, 0); #endif } gdl = text0f153780(gdl); g_Menus[g_Vars.currentplayerstats->mpindex].openinhibit = 10; } #if VERSION >= VERSION_JPN_FINAL g_ScaleX = 1; #endif return gdl; } s32 mpFindMaxInt(s32 numplayers, s32 val0, s32 val1, s32 val2, s32 val3) { s32 bestvalue = val0; s32 bestplayer = 0; if (numplayers >= 2) { if (val1 > bestvalue || (val1 == bestvalue && (random() % 2))) { bestplayer = 1; bestvalue = val1; } else { bestvalue = val0; bestplayer = 0; } if (numplayers >= 3) { if (val2 > bestvalue || (val2 == bestvalue && (random() % 2))) { bestplayer = 2; bestvalue = val2; } if (numplayers >= 4) { if (val3 > bestvalue || (val3 == bestvalue && (random() % 2))) { bestplayer = 3; } } } } return bestplayer; } s32 mpFindMinInt(s32 numplayers, s32 val0, s32 val1, s32 val2, s32 val3) { s32 bestvalue = val0; s32 bestplayer = 0; if (numplayers >= 2) { if (val1 < bestvalue || (val1 == bestvalue && (random() % 2))) { bestplayer = 1; bestvalue = val1; } else { bestvalue = val0; bestplayer = 0; } if (numplayers >= 3) { if (val2 < bestvalue || (val2 == bestvalue && (random() % 2))) { bestplayer = 2; bestvalue = val2; } if (numplayers >= 4) { if (val3 < bestvalue || (val3 == bestvalue && (random() % 2))) { bestplayer = 3; } } } } return bestplayer; } s32 mpFindMaxFloat(s32 numplayers, f32 val0, f32 val1, f32 val2, f32 val3) { // @bug: bestvalue should be an f32. Any value saved into here will be // rounded down which may cause the function to return an incorrect result. s32 bestvalue; s32 bestplayer = 0; if (numplayers >= 2) { if (val1 > val0 || (val1 == val0 && (random() % 2))) { bestplayer = 1; bestvalue = val1; } else { bestvalue = val0; bestplayer = 0; } if (numplayers >= 3) { if (val2 > bestvalue || (val2 == bestvalue && (random() % 2))) { bestplayer = 2; bestvalue = val2; } if (numplayers >= 4) { if (val3 > bestvalue || (val3 == bestvalue && (random() % 2))) { bestplayer = 3; } } } } return bestplayer; } s32 mpFindMinFloat(s32 numplayers, f32 val0, f32 val1, f32 val2, f32 val3) { // @bug: bestvalue should be an f32. Any value saved into here will be // rounded down which may cause the function to return an incorrect result. s32 bestvalue; s32 bestplayer = 0; if (numplayers >= 2) { if (val0); if (val1 < val0 || (val1 == val0 && (random() % 2))) { bestplayer = 1; bestvalue = val1; } else { bestplayer = 0; bestvalue = val0; } if (numplayers >= 3) { if (val2 < bestvalue || (val2 == bestvalue && (random() % 2))) { bestplayer = 2; bestvalue = val2; } if (numplayers >= 4) { if (val3 < bestvalue || (val3 == bestvalue && (random() % 2))) { bestplayer = 3; } } } } return bestplayer; } struct mpweaponset g_MpWeaponSets[12] = { { /*0x00*/ L_MPWEAPONS_055, { WEAPON_FALCON2, WEAPON_MAGSEC4, WEAPON_PHOENIX, WEAPON_MAULER, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_PHOENIX, MPFEATURE_WEAPON_MAULER, 0, 0 }, WEAPON_FALCON2, WEAPON_MAGSEC4, WEAPON_FALCON2, WEAPON_DY357MAGNUM, WEAPON_MPSHIELD, WEAPON_DISABLED }, // Pistols { /*0x01*/ L_MPWEAPONS_054, { WEAPON_FALCON2, WEAPON_CMP150, WEAPON_LAPTOPGUN, WEAPON_AR34, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_LAPTOPGUN, 0, 0, 0 }, WEAPON_FALCON2, WEAPON_CMP150, WEAPON_DRAGON, WEAPON_AR34, WEAPON_MPSHIELD, WEAPON_DISABLED }, // Automatics { /*0x02*/ L_MPWEAPONS_053, { WEAPON_MAGSEC4, WEAPON_DY357MAGNUM, WEAPON_SHOTGUN, WEAPON_RCP120, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_SHOTGUN, MPFEATURE_WEAPON_RCP120, 0, 0 }, WEAPON_MAGSEC4, WEAPON_DY357MAGNUM, WEAPON_DRAGON, WEAPON_AR34, WEAPON_MPSHIELD, WEAPON_DISABLED }, // Power { /*0x03*/ L_MPWEAPONS_052, { WEAPON_PHOENIX, WEAPON_CYCLONE, WEAPON_CALLISTO, WEAPON_FARSIGHT, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_FARSIGHT, MPFEATURE_WEAPON_CALLISTO, 0, 0 }, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED }, // FarSight { /*0x04*/ L_MPWEAPONS_051, { WEAPON_FALCON2, WEAPON_CMP150, WEAPON_DRAGON, WEAPON_TRANQUILIZER, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_TRANQUILIZER, 0, 0, 0 }, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED }, // Tranquilizer { /*0x05*/ L_MPWEAPONS_050, { WEAPON_MAULER, WEAPON_K7AVENGER, WEAPON_REAPER, WEAPON_SUPERDRAGON, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_SUPERDRAGON, MPFEATURE_WEAPON_MAULER, MPFEATURE_WEAPON_K7AVENGER, MPFEATURE_WEAPON_REAPER }, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED }, // Heavy { /*0x06*/ L_MPWEAPONS_049, { WEAPON_FALCON2_SILENCER, WEAPON_GRENADE, WEAPON_CMP150, WEAPON_DY357LX, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_FALCON2SILENCED, MPFEATURE_WEAPON_DY357LX, 0, 0 }, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED }, // Golden Magnum { /*0x07*/ L_MPWEAPONS_048, { WEAPON_DEVASTATOR, WEAPON_DEVASTATOR, WEAPON_SUPERDRAGON, WEAPON_SUPERDRAGON, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_SUPERDRAGON, MPFEATURE_WEAPON_DEVASTATOR, 0, 0 }, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED }, // Explosive { /*0x08*/ L_MPWEAPONS_047, { WEAPON_MAGSEC4, WEAPON_CMP150, WEAPON_AR34, WEAPON_DEVASTATOR, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_DEVASTATOR, 0, 0, 0 }, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED }, // Grenade Launcher { /*0x09*/ L_MPWEAPONS_046, { WEAPON_MAULER, WEAPON_CYCLONE, WEAPON_DRAGON, WEAPON_ROCKETLAUNCHER, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_MAULER, 0, 0, 0 }, WEAPON_FALCON2, WEAPON_CYCLONE, WEAPON_DRAGON, WEAPON_ROCKETLAUNCHER, WEAPON_MPSHIELD, WEAPON_DISABLED }, // Rocket Launcher { /*0x0a*/ L_MPWEAPONS_045, { WEAPON_MAGSEC4, WEAPON_LAPTOPGUN, WEAPON_K7AVENGER, WEAPON_PROXIMITYMINE, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_LAPTOPGUN, MPFEATURE_WEAPON_K7AVENGER, MPFEATURE_WEAPON_PROXIMITYMINE, 0 }, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED, WEAPON_DISABLED }, // Proximity Mine #if VERSION == VERSION_JPN_FINAL { /*0x0b*/ L_MPWEAPONS_044, { WEAPON_TIMEDMINE, WEAPON_CROSSBOW, WEAPON_TIMEDMINE, WEAPON_CROSSBOW, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_CROSSBOW, 0, 0, 0 }, WEAPON_TIMEDMINE, WEAPON_TIMEDMINE, WEAPON_TIMEDMINE, WEAPON_TIMEDMINE, WEAPON_MPSHIELD, WEAPON_DISABLED }, // Close Combat #else { /*0x0b*/ L_MPWEAPONS_044, { WEAPON_COMBATKNIFE, WEAPON_COMBATKNIFE, WEAPON_TIMEDMINE, WEAPON_CROSSBOW, WEAPON_MPSHIELD, WEAPON_DISABLED }, { MPFEATURE_WEAPON_CROSSBOW, 0, 0, 0 }, WEAPON_COMBATKNIFE, WEAPON_COMBATKNIFE, WEAPON_TIMEDMINE, WEAPON_TIMEDMINE, WEAPON_MPSHIELD, WEAPON_DISABLED }, // Close Combat #endif }; s32 g_MpWeaponSetNum = 0x00000000; u16 g_AwardNames[] = { L_MPMENU_000, // "Most Suicidal" L_MPMENU_001, // "Who Needs Ammo?" L_MPMENU_002, // "Least Shielded" L_MPMENU_003, // "Best Protected" L_MPMENU_004, // "Marksmanship" L_MPMENU_005, // "Most Professional" L_MPMENU_006, // "Most Deadly" L_MPMENU_007, // "Most Harmless" L_MPMENU_008, // "Most Cowardly" L_MPMENU_009, // "Most Frantic" L_MPMENU_010, // "Most Honorable" L_MPMENU_011, // "Most Dishonourable" L_MPMENU_012, // "Shortest Life" L_MPMENU_013, // "Longest Life" L_MPMENU_014, // "Double Kill" L_MPMENU_015, // "Triple Kill" L_MPMENU_016, // "Quad Kill" }; void mpCalculatePlayerTitle(struct mpplayerconfig *mpplayer) { #if VERSION >= VERSION_NTSC_1_0 const u32 tiers[] = { 2, 4, 8, 16, 28, 60, 100, 150, 210, 300 }; #else const u32 tiers[] = { 2, 4, 8, 16, 28, 48, 78, 138, 198, 300 }; #endif s32 tallies[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; s32 sum; s32 max; s32 i; #if VERSION >= VERSION_NTSC_1_0 #define MULT(val) (val * 3) #else #define MULT(val) (val) #endif for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->kills >= tiers[i] * MULT(20)) { tallies[0]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->gameswon >= tiers[i] * MULT(1)) { tallies[1]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->accuracymedals >= tiers[i] * MULT(1)) { tallies[2]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->headshotmedals >= tiers[i] * MULT(1)) { tallies[3]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->killmastermedals >= tiers[i] * MULT(1)) { tallies[4]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->time >= tiers[i] * MULT(1200)) { tallies[5]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->distance >= tiers[i] * MULT(100)) { tallies[6]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->damagedealt >= tiers[i] * MULT(1)) { tallies[7]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->ammoused >= tiers[i] * MULT(500)) { tallies[8]++; } else { break; } } for (i = 0; i < ARRAYCOUNT(tiers); i++) { if (mpplayer->survivormedals >= tiers[i] * MULT(1)) { tallies[9]++; } else { break; } } sum = 0; for (i = 0; i < ARRAYCOUNT(tallies); i++) { sum = sum + tallies[i]; } if (sum > 100) { sum = 100; } mpplayer->title = sum / 5; max = MPPLAYERTITLE_PERFECT; if (mpplayer->title > max) { mpplayer->title = max; } } #if VERSION >= VERSION_NTSC_1_0 const char var7f1b8ad0[] = "%s%sAccuracy Peak! real value: %f (*100)\n"; const char var7f1b8afc[] = ""; const char var7f1b8b00[] = ""; const char var7f1b8b04[] = "%splayer %d Accuracy :%f\n"; const char var7f1b8b20[] = ""; const char var7f1b8b24[] = "%splayer %d dist:%f -> %f = %d\n"; const char var7f1b8b44[] = ""; const char var7f1b8b48[] = "Player %d TitleCalc ============\n"; #endif struct mphead g_MpBeauHeads[] = { // head, require feature { HEAD_BEAU2, 0 }, { HEAD_BEAU3, 0 }, { HEAD_BEAU4, 0 }, { HEAD_BEAU5, 0 }, { HEAD_BEAU6, 0 }, }; struct mphead g_MpHeads[] = { // head, require feature { /*0x00*/ HEAD_DARK_COMBAT, 0 }, { /*0x01*/ HEAD_DARK_FROCK, MPFEATURE_CHR_CI }, { /*0x02*/ HEAD_DARKAQUA, MPFEATURE_CHR_PELAGIC }, { /*0x03*/ HEAD_DARK_SNOW, MPFEATURE_CHR_DARKSNOW }, { /*0x04*/ HEAD_ELVIS, MPFEATURE_CHR_ELVIS }, { /*0x05*/ HEAD_ELVIS_GOGS, MPFEATURE_CHR_ELVIS }, { /*0x06*/ HEAD_CARRINGTON, 0 }, { /*0x07*/ HEAD_MRBLONDE, MPFEATURE_CHR_MRBLONDE }, { /*0x08*/ HEAD_CASSANDRA, 0 }, { /*0x09*/ HEAD_TRENT, MPFEATURE_CHR_TRENT }, { /*0x0a*/ HEAD_JONATHAN, MPFEATURE_CHR_INFILTRATION }, { /*0x0b*/ HEAD_VD, 0 }, { /*0x0c*/ HEAD_PRESIDENT, MPFEATURE_CHR_CI }, { /*0x0d*/ HEAD_DDSHOCK, 0 }, { /*0x0e*/ HEAD_BIOTECH, MPFEATURE_CHR_BIOTECH }, { /*0x0f*/ HEAD_DDSNIPER, MPFEATURE_CHR_VILLACHRS }, { /*0x10*/ HEAD_A51FACEPLATE, MPFEATURE_CHR_INFILTRATION }, { /*0x11*/ HEAD_SECRETARY, 0 }, { /*0x12*/ HEAD_FEM_GUARD, MPFEATURE_CHR_FEMGUARD }, { /*0x13*/ HEAD_FEM_GUARD2, MPFEATURE_CHR_FEMGUARD }, { /*0x14*/ HEAD_MAIAN_S, MPFEATURE_CHR_ELVIS }, { /*0x15*/ HEAD_JON, 0 }, { /*0x16*/ HEAD_BEAU1, 0 }, { /*0x17*/ HEAD_ROSS, 0 }, { /*0x18*/ HEAD_MARK2, 0 }, { /*0x19*/ HEAD_CHRIST, 0 }, { /*0x1a*/ HEAD_RUSS, 0 }, { /*0x1b*/ HEAD_DARLING, 0 }, { /*0x1c*/ HEAD_BRIAN, 0 }, { /*0x1d*/ HEAD_JAMIE, 0 }, { /*0x1e*/ HEAD_DUNCAN2, 0 }, { /*0x1f*/ HEAD_KEITH, 0 }, { /*0x20*/ HEAD_STEVEM, 0 }, { /*0x21*/ HEAD_GRANT, 0 }, { /*0x22*/ HEAD_PENNY, 0 }, { /*0x23*/ HEAD_DAVEC, 0 }, { /*0x24*/ HEAD_JONES, 0 }, { /*0x25*/ HEAD_GRAHAM, 0 }, { /*0x26*/ HEAD_ROBERT, 0 }, { /*0x27*/ HEAD_NEIL2, 0 }, { /*0x28*/ HEAD_SHAUN, 0 }, { /*0x29*/ HEAD_ROBIN, 0 }, { /*0x2a*/ HEAD_COOK, 0 }, { /*0x2b*/ HEAD_PRYCE, 0 }, { /*0x2c*/ HEAD_SILKE, 0 }, { /*0x2d*/ HEAD_SMITH, 0 }, { /*0x2e*/ HEAD_GARETH, 0 }, { /*0x2f*/ HEAD_MURCHIE, 0 }, { /*0x30*/ HEAD_WONG, 0 }, { /*0x31*/ HEAD_CARTER, 0 }, { /*0x32*/ HEAD_TINTIN, 0 }, { /*0x33*/ HEAD_MUNTON, 0 }, { /*0x34*/ HEAD_STAMPER, 0 }, { /*0x35*/ HEAD_PHELPS, 0 }, { /*0x36*/ HEAD_ALEX, 0 }, { /*0x37*/ HEAD_JULIANNE, 0 }, { /*0x38*/ HEAD_LAURA, 0 }, { /*0x39*/ HEAD_EDMCG, 0 }, { /*0x3a*/ HEAD_ANKA, 0 }, { /*0x3b*/ HEAD_LESLIE_S, 0 }, { /*0x3c*/ HEAD_MATT_C, 0 }, { /*0x3d*/ HEAD_PEER_S, 0 }, { /*0x3e*/ HEAD_EILEEN_T, 0 }, { /*0x3f*/ HEAD_ANDY_R, 0 }, { /*0x40*/ HEAD_BEN_R, 0 }, { /*0x41*/ HEAD_STEVE_K, 0 }, { /*0x42*/ HEAD_SANCHEZ, 0 }, { /*0x43*/ HEAD_TIM, 0 }, { /*0x44*/ HEAD_KEN, 0 }, { /*0x45*/ HEAD_EILEEN_H, 0 }, { /*0x46*/ HEAD_SCOTT_H, 0 }, { /*0x47*/ HEAD_JOEL, 0 }, { /*0x48*/ HEAD_GRIFFEY, 0 }, #if VERSION != VERSION_JPN_FINAL { /*0x49*/ HEAD_MOTO, 0 }, #endif { /*0x4a*/ HEAD_WINNER, 0 }, }; u32 g_BotHeads[] = { MPHEAD_JON, MPHEAD_BEAU1, MPHEAD_ROSS, MPHEAD_MARK2, MPHEAD_CHRIST, MPHEAD_RUSS, MPHEAD_DARLING, MPHEAD_BRIAN, MPHEAD_JAMIE, MPHEAD_DUNCAN2, MPHEAD_KEITH, MPHEAD_STEVEM, MPHEAD_GRANT, MPHEAD_PENNY, MPHEAD_DAVEC, MPHEAD_JONES, MPHEAD_GRAHAM, MPHEAD_ROBERT, MPHEAD_NEIL2, MPHEAD_SHAUN, MPHEAD_ROBIN, MPHEAD_COOK, MPHEAD_PRYCE, MPHEAD_SILKE, MPHEAD_SMITH, MPHEAD_GARETH, MPHEAD_MURCHIE, MPHEAD_WONG, MPHEAD_CARTER, MPHEAD_TINTIN, MPHEAD_MUNTON, MPHEAD_STAMPER, MPHEAD_PHELPS, MPHEAD_ALEX, MPHEAD_JULIANNE, MPHEAD_LAURA, MPHEAD_EDMCG, MPHEAD_ANKA, MPHEAD_LESLIE_S, MPHEAD_MATT_C, MPHEAD_PEER_S, MPHEAD_EILEEN_T, MPHEAD_ANDY_R, MPHEAD_BEN_R, MPHEAD_STEVE_K, MPHEAD_SANCHEZ, MPHEAD_TIM, MPHEAD_KEN, MPHEAD_EILEEN_H, MPHEAD_SCOTT_H, MPHEAD_JOEL, MPHEAD_GRIFFEY, #if VERSION != VERSION_JPN_FINAL MPHEAD_MOTO, #endif }; struct botprofile g_BotProfiles[] = { // type, difficulty, name, body, require feature { BOTTYPE_GENERAL, BOTDIFF_MEAT, L_MISC_088, MPBODY_DD_GUARD, 0 }, { BOTTYPE_GENERAL, BOTDIFF_EASY, L_MISC_089, MPBODY_DD_SECGUARD, 0 }, { BOTTYPE_GENERAL, BOTDIFF_NORMAL, L_MISC_090, MPBODY_DD_SHOCK_INF, 0 }, { BOTTYPE_GENERAL, BOTDIFF_HARD, L_MISC_091, MPBODY_DDSHOCK, MPFEATURE_BOTDIFF_HARD }, { BOTTYPE_GENERAL, BOTDIFF_PERFECT, L_MISC_092, MPBODY_STRIPES, MPFEATURE_BOTDIFF_PERFECT }, { BOTTYPE_GENERAL, BOTDIFF_DARK, L_MISC_093, MPBODY_MOORE, MPFEATURE_BOTDIFF_DARK }, { BOTTYPE_PEACE, BOTDIFF_NORMAL, L_MISC_094, MPBODY_DD_LABTECH, 0 }, { BOTTYPE_SHIELD, BOTDIFF_NORMAL, L_MISC_095, MPBODY_G5_SWAT_GUARD, 0 }, { BOTTYPE_ROCKET, BOTDIFF_NORMAL, L_MISC_096, MPBODY_G5_GUARD, 0 }, { BOTTYPE_KAZE, BOTDIFF_NORMAL, L_MISC_097, MPBODY_PRES_SECURITY, 0 }, { BOTTYPE_FIST, BOTDIFF_NORMAL, L_MISC_098, MPBODY_PELAGIC_GUARD, 0 }, { BOTTYPE_PREY, BOTDIFF_NORMAL, L_MISC_099, MPBODY_DDSHOCK, 0 }, { BOTTYPE_COWARD, BOTDIFF_NORMAL, L_MISC_100, MPBODY_PRESIDENT, 0 }, { BOTTYPE_JUDGE, BOTDIFF_NORMAL, L_MISC_101, MPBODY_STEWARD, 0 }, { BOTTYPE_FEUD, BOTDIFF_NORMAL, L_MISC_102, MPBODY_NSA_LACKEY, 0 }, { BOTTYPE_SPEED, BOTDIFF_NORMAL, L_MISC_103, MPBODY_MRBLONDE, 0 }, { BOTTYPE_TURTLE, BOTDIFF_NORMAL, L_MISC_104, MPBODY_CARRINGTON, 0 }, { BOTTYPE_VENGE, BOTDIFF_NORMAL, L_MISC_105, MPBODY_ALASKAN_GUARD, 0 }, }; struct mpbody g_MpBodies[] = { // global body ID, name, head, require feature /*0x00*/ { BODY_DARK_COMBAT, L_OPTIONS_016, HEAD_DARK_COMBAT, 0 }, /*0x01*/ { BODY_DARK_TRENCH, L_OPTIONS_017, HEAD_DARK_COMBAT, MPFEATURE_CHR_JOTRENCH }, /*0x02*/ { BODY_DARK_FROCK, L_OPTIONS_018, HEAD_DARK_FROCK, MPFEATURE_CHR_CI }, /*0x03*/ { BODY_DARK_RIPPED, L_OPTIONS_019, HEAD_DARK_FROCK, MPFEATURE_CHR_CI }, /*0x04*/ { BODY_DARK_AF1, L_OPTIONS_020, HEAD_DARK_COMBAT, MPFEATURE_CHR_AF1 }, /*0x05*/ { BODY_DARK_LEATHER, L_MPWEAPONS_156, HEAD_DARK_COMBAT, MPFEATURE_CHR_G5 }, /*0x06*/ { BODY_DARK_NEGOTIATOR, L_MPWEAPONS_157, HEAD_DARK_COMBAT, MPFEATURE_CHR_VILLACHRS }, /*0x07*/ { BODY_DARKWET, L_OPTIONS_021, HEAD_DARKAQUA, MPFEATURE_CHR_PELAGIC }, /*0x08*/ { BODY_DARKAQUALUNG, L_OPTIONS_022, HEAD_DARKAQUA, MPFEATURE_CHR_PELAGIC }, /*0x09*/ { BODY_DARKSNOW, L_OPTIONS_023, HEAD_DARK_SNOW, MPFEATURE_CHR_DARKSNOW }, /*0x0a*/ { BODY_DARKLAB, L_OPTIONS_024, HEAD_DARK_COMBAT, MPFEATURE_CHR_INFILTRATION }, /*0x0b*/ { BODY_THEKING, L_OPTIONS_025, HEAD_ELVIS, MPFEATURE_CHR_ELVIS }, /*0x0c*/ { BODY_ELVIS1, L_OPTIONS_026, HEAD_ELVIS, MPFEATURE_CHR_ELVIS }, /*0x0d*/ { BODY_ELVISWAISTCOAT, L_MPWEAPONS_158, HEAD_ELVIS, MPFEATURE_CHR_ELVIS }, /*0x0e*/ { BODY_CARRINGTON, L_OPTIONS_027, HEAD_CARRINGTON, 0 }, /*0x0f*/ { BODY_CARREVENINGSUIT, L_OPTIONS_028, HEAD_CARRINGTON, MPFEATURE_CHR_CI }, /*0x10*/ { BODY_MRBLONDE, L_OPTIONS_029, HEAD_MRBLONDE, MPFEATURE_CHR_MRBLONDE }, /*0x11*/ { BODY_CASSANDRA, L_OPTIONS_030, HEAD_CASSANDRA, 0 }, /*0x12*/ { BODY_TRENT, L_OPTIONS_031, HEAD_TRENT, MPFEATURE_CHR_TRENT }, /*0x13*/ { BODY_JONATHAN, L_OPTIONS_032, HEAD_JONATHAN, MPFEATURE_CHR_JONATHAN }, /*0x14*/ { BODY_CILABTECH, L_OPTIONS_033, 1000, 0 }, /*0x15*/ { BODY_CIFEMTECH, L_OPTIONS_034, 1000, 0 }, /*0x16*/ { BODY_CISOLDIER, L_OPTIONS_035, 1000, 0 }, /*0x17*/ { BODY_DDSHOCK, L_OPTIONS_036, HEAD_DDSHOCK, 0 }, /*0x18*/ { BODY_FEM_GUARD, L_OPTIONS_037, 1000, MPFEATURE_CHR_FEMGUARD }, /*0x19*/ { BODY_DD_SECGUARD, L_OPTIONS_038, 1000, 0 }, /*0x1a*/ { BODY_DD_GUARD, L_OPTIONS_039, 1000, 0 }, /*0x1b*/ { BODY_DD_SHOCK_INF, L_OPTIONS_040, 1000, 0 }, /*0x1c*/ { BODY_SECRETARY, L_OPTIONS_041, 1000, 0 }, /*0x1d*/ { BODY_OFFICEWORKER, L_OPTIONS_042, 1000, MPFEATURE_CHR_OFFICEWORKER }, /*0x1e*/ { BODY_OFFICEWORKER2, L_OPTIONS_043, 1000, MPFEATURE_CHR_OFFICEWORKER }, /*0x1f*/ { BODY_NEGOTIATOR, L_OPTIONS_044, 1000, MPFEATURE_CHR_VILLACHRS }, /*0x20*/ { BODY_DDSNIPER, L_OPTIONS_045, HEAD_DDSNIPER, MPFEATURE_CHR_VILLACHRS }, /*0x21*/ { BODY_G5_GUARD, L_OPTIONS_046, 1000, MPFEATURE_CHR_G5 }, /*0x22*/ { BODY_G5_SWAT_GUARD, L_OPTIONS_047, 1000, MPFEATURE_CHR_G5 }, /*0x23*/ { BODY_CIAGUY, L_OPTIONS_048, 1000, MPFEATURE_CHR_CIAFBI }, /*0x24*/ { BODY_FBIGUY, L_OPTIONS_049, 1000, MPFEATURE_CHR_CIAFBI }, /*0x25*/ { BODY_AREA51GUARD, L_OPTIONS_050, 1000, MPFEATURE_CHR_INFILTRATION }, /*0x26*/ { BODY_A51TROOPER, L_OPTIONS_051, 1000, MPFEATURE_CHR_INFILTRATION }, /*0x27*/ { BODY_A51AIRMAN, L_OPTIONS_052, 1000, MPFEATURE_CHR_INFILTRATION }, /*0x28*/ { BODY_OVERALL, L_OPTIONS_053, 1000, MPFEATURE_CHR_INFILTRATION }, /*0x29*/ { BODY_STRIPES, L_OPTIONS_054, 1000, MPFEATURE_CHR_STRIPES }, /*0x2a*/ { BODY_LABTECH, L_OPTIONS_055, 1000, MPFEATURE_CHR_LABTECH }, /*0x2b*/ { BODY_FEMLABTECH, L_OPTIONS_056, 1000, MPFEATURE_CHR_LABTECH }, /*0x2c*/ { BODY_DD_LABTECH, L_OPTIONS_057, 1000, MPFEATURE_CHR_LABTECH }, /*0x2d*/ { BODY_BIOTECH, L_OPTIONS_058, HEAD_BIOTECH, MPFEATURE_CHR_BIOTECH }, /*0x2e*/ { BODY_ALASKAN_GUARD, L_OPTIONS_059, 1000, MPFEATURE_CHR_ALASKANGUARD }, /*0x2f*/ { BODY_PILOTAF1, L_OPTIONS_060, 1000, MPFEATURE_CHR_AF1 }, /*0x30*/ { BODY_STEWARD, L_OPTIONS_061, 1000, MPFEATURE_CHR_AF1 }, /*0x31*/ { BODY_STEWARDESS, L_OPTIONS_062, 1000, MPFEATURE_CHR_AF1 }, /*0x32*/ { BODY_STEWARDESS_COAT, L_OPTIONS_063, 1000, MPFEATURE_CHR_AF1 }, /*0x33*/ { BODY_PRESIDENT, L_OPTIONS_064, HEAD_PRESIDENT, MPFEATURE_CHR_CI }, /*0x34*/ { BODY_NSA_LACKEY, L_OPTIONS_065, 1000, MPFEATURE_CHR_NSALACKEY }, /*0x35*/ { BODY_PRES_SECURITY, L_OPTIONS_066, 1000, MPFEATURE_CHR_PRESSECURITY }, /*0x36*/ { BODY_PRESIDENT_CLONE2, L_OPTIONS_067, HEAD_PRESIDENT, MPFEATURE_CHR_PRESCLONE }, /*0x37*/ { BODY_PELAGIC_GUARD, L_OPTIONS_068, 1000, MPFEATURE_CHR_PELAGIC }, /*0x38*/ { BODY_MAIAN_SOLDIER, L_OPTIONS_069, HEAD_MAIAN_S, MPFEATURE_CHR_ELVIS }, /*0x39*/ { BODY_CONNERY, L_OPTIONS_070, 1000, MPFEATURE_8BOTS }, /*0x3a*/ { BODY_MOORE, L_OPTIONS_070, 1000, MPFEATURE_8BOTS }, /*0x3b*/ { BODY_DALTON, L_OPTIONS_070, 1000, MPFEATURE_8BOTS }, /*0x3c*/ { BODY_DJBOND, L_OPTIONS_070, 1000, MPFEATURE_8BOTS }, }; u32 g_MpMaleHeads[] = { HEAD_JON, HEAD_BEAU1, HEAD_ROSS, HEAD_MARK2, HEAD_CHRIST, HEAD_RUSS, HEAD_DARLING, HEAD_BRIAN, HEAD_JAMIE, HEAD_DUNCAN2, HEAD_KEITH, HEAD_STEVEM, HEAD_GRANT, HEAD_PENNY, HEAD_DAVEC, HEAD_JONES, HEAD_GRAHAM, HEAD_NEIL2, HEAD_SHAUN, HEAD_ROBIN, HEAD_COOK, HEAD_PRYCE, HEAD_SILKE, HEAD_SMITH, HEAD_GARETH, HEAD_MURCHIE, HEAD_WONG, HEAD_CARTER, HEAD_TINTIN, HEAD_MUNTON, HEAD_STAMPER, HEAD_PHELPS, HEAD_EDMCG, HEAD_MATT_C, HEAD_PEER_S, HEAD_ANDY_R, HEAD_BEN_R, HEAD_STEVE_K, HEAD_SANCHEZ, HEAD_TIM, HEAD_KEN, HEAD_SCOTT_H, HEAD_JOEL, #if VERSION != VERSION_JPN_FINAL HEAD_MOTO, #endif }; u32 g_MpFemaleHeads[] = { HEAD_ALEX, HEAD_JULIANNE, HEAD_LAURA, HEAD_ANKA, HEAD_LESLIE_S, HEAD_EILEEN_T, HEAD_EILEEN_H, }; /** * Calculate player awards, medals, and update character statistics. */ void mpCalculateAwards(void) { s32 playercount; s32 i; s32 j; s32 prevplayernum; s32 duration60; struct awardmetrics metrics[MAX_PLAYERS]; // @bug: playerrankings should have 12 elements. Because it's too small, // overflow occurs in mpGetPlayerRankings. The overflow writes into the // metrics array (above) which is yet to be initialised, so this bug has // no effect on IDO. #ifdef AVOID_UB struct ranking playerrankings[MAX_MPCHRS]; #else struct ranking playerrankings[1]; #endif s32 numchrs; #if VERSION >= VERSION_NTSC_1_0 s32 numteams; struct ranking teamrankings[MAX_MPCHRS]; u32 stack[4]; #else u32 stack[3]; #endif playercount = PLAYERCOUNT(); duration60 = playerGetMissionTime(); func00033dd8(); numchrs = mpGetPlayerRankings(playerrankings); #if VERSION >= VERSION_NTSC_1_0 numteams = (g_MpSetup.options & MPOPTION_TEAMSENABLED) ? mpGetTeamRankings(teamrankings) : 0; #endif prevplayernum = g_Vars.currentplayernum; // Iterate all human players and update their character stats. // At the same time, populate the metrics array // which is a temporary array for award calculation. for (i = 0; i < playercount; i++) { struct mpchrconfig *mpchr = mpGetChrConfigBySlotNum(i); struct mpplayerconfig *mpplayer = (struct mpplayerconfig *)mpchr; #if VERSION >= VERSION_NTSC_1_0 s32 chrnum = mpGetChrIndexBySlotNum(i); #else s32 chrnum = i; #endif s32 sum; setCurrentPlayerNum(i); g_Vars.currentplayer->award1 = NULL; g_Vars.currentplayer->award2 = NULL; metrics[i].numshots = mpstatsGetPlayerShotCountByRegion(SHOTREGION_TOTAL); metrics[i].numheadshots = mpstatsGetPlayerShotCountByRegion(SHOTREGION_HEAD); metrics[i].numkills = 0; metrics[i].numdeaths = 0; metrics[i].numsuicides = 0; for (j = 0; j < MAX_MPCHRS; j++) { #if VERSION >= VERSION_NTSC_1_0 if (chrnum == j) #else if (i == j) #endif { metrics[i].numsuicides += mpchr->killcounts[j]; } else { metrics[i].numkills += mpchr->killcounts[j]; } } for (j = 0; j < MAX_MPCHRS; j++) { struct mpchrconfig *othermpchr = MPCHR(j); #if VERSION >= VERSION_NTSC_1_0 metrics[i].numdeaths += othermpchr->killcounts[chrnum]; #else metrics[i].numdeaths += othermpchr->killcounts[i]; #endif } metrics[i].ksratio = metrics[i].numkills * 100.0f / (metrics[i].numshots + 1.0f); metrics[i].kdratio = metrics[i].numkills * 100.0f / (metrics[i].numdeaths + 1.0f); metrics[i].backshotcount = g_Vars.playerstats[i].backshotcount; metrics[i].drawplayercount = g_Vars.playerstats[i].drawplayercount; metrics[i].avgkmperhour = g_Vars.playerstats[i].distance / 100000.0f / ((duration60 + 1) / (3600.0f * 60.0f)); metrics[i].armourcount = g_Vars.playerstats[i].armourcount; metrics[i].awards = 0; metrics[i].longestlife = g_Vars.playerstats[i].longestlife; metrics[i].shortestlife = g_Vars.playerstats[i].shortestlife; sum = mpstatsGetPlayerShotCountByRegion(SHOTREGION_HEAD) + mpstatsGetPlayerShotCountByRegion(SHOTREGION_BODY) + mpstatsGetPlayerShotCountByRegion(SHOTREGION_LIMB) + mpstatsGetPlayerShotCountByRegion(SHOTREGION_GUN) + mpstatsGetPlayerShotCountByRegion(SHOTREGION_HAT) + mpstatsGetPlayerShotCountByRegion(SHOTREGION_OBJECT); if (metrics[i].numshots > 0) { metrics[i].accuracyfrac = sum / (f32)metrics[i].numshots; } else { metrics[i].accuracyfrac = 0.0f; } if (metrics[i].accuracyfrac > 1.0f) { metrics[i].accuracyfrac = 1.0f; } mpplayer->medals = 0; if (!g_CheatsActiveBank0 && !g_CheatsActiveBank1) { mpplayer->kills += metrics[i].numkills; mpplayer->deaths += metrics[i].numdeaths; mpplayer->gamesplayed++; mpplayer->time += duration60 / 60; mpplayer->distance += (u32)(g_Vars.playerstats[i].distance / 10000.0f); #if VERSION >= VERSION_NTSC_1_0 if (metrics[i].numshots > 0) { if (mpplayer->gamesplayed < 2) { mpplayer->accuracy = metrics[i].accuracyfrac * 1000.0f; } else { mpplayer->accuracy = ((metrics[i].accuracyfrac * 0.3f) + (mpplayer->accuracy / 1000.0f * 0.7f)) * 1000.0f; } } #else mpplayer->accuracy = ((metrics[i].accuracyfrac * 0.3f) + (mpplayer->accuracy / 1000.0f * 0.7f)) * 1000.0f; #endif mpplayer->damagedealt += (u32)(g_Vars.playerstats[i].damtransmitted / 0.1f); mpplayer->painreceived += (u32)(g_Vars.playerstats[i].damreceived / 0.1f); mpplayer->headshots += metrics[i].numheadshots; mpplayer->ammoused += metrics[i].numshots; #if VERSION >= VERSION_NTSC_1_0 if ((numchrs >= 2 && (g_MpSetup.options & MPOPTION_TEAMSENABLED) == 0) || numteams >= 2) #else if (numchrs >= 2) #endif { if (mpplayer->base.placement == 0) { bool lost = false; for (j = 0; j < MAX_MPCHRS; j++) { #if VERSION >= VERSION_NTSC_1_0 if (g_MpSetup.chrslots & (1 << j)) #endif { struct mpchrconfig *othermpchr = MPCHR(j); if (othermpchr->rankablescore == mpplayer->base.rankablescore && othermpchr != &mpplayer->base && !((g_MpSetup.options & MPOPTION_TEAMSENABLED) && othermpchr->team == mpplayer->base.team)) { lost = true; } } } if (!lost) { mpplayer->gameswon++; } } #if VERSION >= VERSION_NTSC_1_0 if (((g_MpSetup.options & MPOPTION_TEAMSENABLED) == 0 && numchrs == mpplayer->base.placement + 1) || ((g_MpSetup.options & MPOPTION_TEAMSENABLED) && numteams == mpplayer->base.placement + 1)) #else if (mpplayer->base.placement == numchrs - 1) #endif { bool won = false; for (j = 0; j < MAX_MPCHRS; j++) { #if VERSION >= VERSION_NTSC_1_0 if (g_MpSetup.chrslots & (1 << j)) #endif { struct mpchrconfig *othermpchr = MPCHR(j); if (othermpchr->rankablescore == mpplayer->base.rankablescore && &mpplayer->base != othermpchr && !((g_MpSetup.options & MPOPTION_TEAMSENABLED) && othermpchr->team == mpplayer->base.team)) { won = true; } } } if (!won) { mpplayer->gameslost++; } } } #if VERSION >= VERSION_NTSC_1_0 else { // empty } #endif } } setCurrentPlayerNum(prevplayernum); // Choose which players are eligible for which awards i = mpFindMaxInt(playercount, metrics[0].numsuicides, metrics[1].numsuicides, metrics[2].numsuicides, metrics[3].numsuicides); if (metrics[i].numsuicides > 0) { metrics[i].awards |= AWARD_MOSTSUICIDAL; } i = mpFindMinInt(playercount, metrics[0].numshots, metrics[1].numshots, metrics[2].numshots, metrics[3].numshots); if (metrics[i].numshots < 100) { metrics[i].awards |= AWARD_WHONEEDSAMMO; } i = mpFindMinFloat(playercount, metrics[0].armourcount, metrics[1].armourcount, metrics[2].armourcount, metrics[3].armourcount); if (metrics[i].armourcount <= 2.0f) { metrics[i].awards |= AWARD_LEASTSHIELDED; } i = mpFindMaxFloat(playercount, metrics[0].armourcount, metrics[1].armourcount, metrics[2].armourcount, metrics[3].armourcount); if (metrics[i].armourcount > 6.0f) { metrics[i].awards |= AWARD_BESTPROTECTED; } i = mpFindMaxInt(playercount, metrics[0].numheadshots, metrics[1].numheadshots, metrics[2].numheadshots, metrics[3].numheadshots); if (metrics[i].numheadshots > 0) { metrics[i].awards |= AWARD_MARKSMANSHIP; } i = mpFindMaxFloat(playercount, metrics[0].ksratio, metrics[1].ksratio, metrics[2].ksratio, metrics[3].ksratio); if (metrics[i].ksratio > 0.0f) { metrics[i].awards |= AWARD_MOSTPROFESSIONAL; } i = mpFindMaxFloat(playercount, metrics[0].kdratio, metrics[1].kdratio, metrics[2].kdratio, metrics[3].kdratio); if (metrics[i].kdratio > 0.0f) { metrics[i].awards |= AWARD_MOSTDEADLY; } i = mpFindMinFloat(playercount, metrics[0].kdratio, metrics[1].kdratio, metrics[2].kdratio, metrics[3].kdratio); if (PLAYERCOUNT() >= 2) { metrics[i].awards |= AWARD_MOSTHARMLESS; } i = mpFindMinInt(playercount, metrics[0].drawplayercount, metrics[1].drawplayercount, metrics[2].drawplayercount, metrics[3].drawplayercount); if (PLAYERCOUNT() >= 2) { metrics[i].awards |= AWARD_MOSTCOWARDLY; } i = mpFindMaxFloat(playercount, metrics[0].avgkmperhour, metrics[1].avgkmperhour, metrics[2].avgkmperhour, metrics[3].avgkmperhour); if (metrics[i].avgkmperhour > 10.0f) { metrics[i].awards |= AWARD_MOSTFRANTIC; } i = mpFindMinInt(playercount, metrics[0].backshotcount, metrics[1].backshotcount, metrics[2].backshotcount, metrics[3].backshotcount); metrics[i].awards |= AWARD_MOSTHONORABLE; i = mpFindMaxInt(playercount, metrics[0].backshotcount, metrics[1].backshotcount, metrics[2].backshotcount, metrics[3].backshotcount); if (metrics[i].backshotcount > 0 && (metrics[i].awards & AWARD_MOSTHONORABLE) == 0) { metrics[i].awards |= AWARD_MOSTDISHONORABLE; } i = mpFindMaxInt(playercount, metrics[0].longestlife, metrics[1].longestlife, metrics[2].longestlife, metrics[3].longestlife); if (metrics[i].longestlife > 0) { metrics[i].awards |= AWARD_LONGESTLIFE; } i = mpFindMinInt(playercount, metrics[0].shortestlife, metrics[1].shortestlife, metrics[2].shortestlife, metrics[3].shortestlife); if (metrics[i].shortestlife > 0 && metrics[i].numdeaths > 0) { metrics[i].awards |= AWARD_SHORTESTLIFE; } for (i = 0; i < playercount; i++) { if (g_Vars.playerstats[i].maxsimulkills == 4) { metrics[i].awards |= AWARD_QUADKILL; } else if (g_Vars.playerstats[i].maxsimulkills == 3) { metrics[i].awards |= AWARD_TRIPLEKILL; } else if (g_Vars.playerstats[i].maxsimulkills == 2) { metrics[i].awards |= AWARD_DOUBLEKILL; } } // These loops would have printed each player's awards for (i = 0; i < playercount; i++) { for (j = 0; j < 17; j++) { if (metrics[i].awards & (1 << j)); } } // For each player, choose which two awards to actually give them. // Note that the first award checked is quad kill, but after that the awards // are checked randomly. So if a player has quad kill they'll definitely see // it on the endscreen, but this is not the case for triple kill or any // other awards. for (i = 0; i < playercount; i++) { s32 numdone = 0; s32 awardindex = 16; if (playercount == 1) { // In a single player game, only allow the following awards metrics[i].awards &= 0 | AWARD_MARKSMANSHIP | AWARD_MOSTPROFESSIONAL | AWARD_MOSTDEADLY | AWARD_MOSTFRANTIC | AWARD_MOSTHONORABLE | AWARD_DOUBLEKILL | AWARD_TRIPLEKILL | AWARD_QUADKILL; } while (numdone == 0) { if (metrics[i].awards & (1 << awardindex)) { metrics[i].awards &= ~(1 << awardindex); g_Vars.players[i]->award1 = langGet(g_AwardNames[awardindex]); numdone = 1; } if (metrics[i].awards == 0) { numdone = 1; } awardindex = random() % 17; } while (numdone < 2) { awardindex = random() % 17; if (metrics[i].awards & (1 << awardindex)) { metrics[i].awards &= ~(1 << awardindex); g_Vars.players[i]->award2 = langGet(g_AwardNames[awardindex]); numdone = 2; } if (metrics[i].awards == 0) { numdone = 2; } } } // Calculate KillMaster and Survivor medals if (numchrs >= 2) { s32 mostkillsvalue = 0; s32 mostkillsplayer = -1; s32 leastdeathsvalue = 0xffffff; s32 leastdeathsplayer = -1; s32 k; for (k = 0; k < MAX_MPCHRS; k++) { if (g_MpSetup.chrslots & (1 << k)) { s32 totalkills = 0; struct mpchrconfig *mpchr = MPCHR(k); for (j = 0; j < MAX_MPCHRS; j++) { // @bug: i should be k. The value of i was incremented after // the last iteration of its loop above so it'll be between // 1 and 4 inclusively depending on the number of players. // The bot nums start from 4 regardless of how many players // there are. So in a game with 4 humans, the kills on the // first bot will not be considered for the killmaster medal // which means the medal could go to a player who got fewer // total kills. Additionally, suicides are counted as kills // while the intention here was to omit them. if (i != j) { totalkills += mpchr->killcounts[j]; } } if (totalkills == mostkillsvalue) { mostkillsplayer = -1; } if (totalkills > mostkillsvalue) { mostkillsplayer = k; mostkillsvalue = totalkills; } if (mpchr->numdeaths == leastdeathsvalue) { leastdeathsplayer = -1; } if (mpchr->numdeaths < leastdeathsvalue) { leastdeathsplayer = k; leastdeathsvalue = mpchr->numdeaths; } } } if (!g_CheatsActiveBank0 && !g_CheatsActiveBank1) { if (mostkillsplayer < 4 && mostkillsplayer >= 0) { struct mpplayerconfig *mpplayer = (struct mpplayerconfig *)MPCHR(mostkillsplayer); mpplayer->medals |= MEDAL_KILLMASTER; mpplayer->killmastermedals++; } if (leastdeathsplayer < 4 && leastdeathsplayer >= 0) { struct mpplayerconfig *mpplayer = (struct mpplayerconfig *)MPCHR(leastdeathsplayer); mpplayer->medals |= MEDAL_SURVIVOR; mpplayer->survivormedals++; } } } // Calculate Headshot and Accuracy medals if (!g_CheatsActiveBank0 && !g_CheatsActiveBank1 && playercount >= 2) { s32 mostheadshotvalue = 0; f32 mostaccuratevalue = 0.5f; s32 mostheadshotplayer = -1; s32 mostaccurateplayer = -1; for (i = 0; i < playercount; i++) { if (mostheadshotvalue == metrics[i].numheadshots) { mostheadshotplayer = -1; } if (metrics[i].numheadshots > mostheadshotvalue) { mostheadshotplayer = i; mostheadshotvalue = metrics[i].numheadshots; } if (metrics[i].accuracyfrac > mostaccuratevalue) { mostaccurateplayer = i; mostaccuratevalue = metrics[i].accuracyfrac; } } if (mostheadshotplayer >= 0) { struct mpplayerconfig *mpplayer = (struct mpplayerconfig *)mpGetChrConfigBySlotNum(mostheadshotplayer); mpplayer->medals |= MEDAL_HEADSHOT; mpplayer->headshotmedals++; } if (mostaccurateplayer >= 0) { struct mpplayerconfig *mpplayer = (struct mpplayerconfig *)mpGetChrConfigBySlotNum(mostaccurateplayer); mpplayer->medals |= MEDAL_ACCURACY; mpplayer->accuracymedals++; } } // Recalculate title for all players for (i = 0; i < playercount; i++) { struct mpplayerconfig *mpchr = (struct mpplayerconfig *)mpGetChrConfigBySlotNum(i); mpCalculatePlayerTitle(mpchr); } } void mpEndMatch(void) { s32 stack; s32 playercount = PLAYERCOUNT(); s32 prevplayernum; s32 i; musicStartMenu(); mpSetPaused(MPPAUSEMODE_GAMEOVER); prevplayernum = g_Vars.currentplayernum; for (i = 0; i < playercount; i++) { setCurrentPlayerNum(i); g_Vars.currentplayer->award1 = NULL; g_Vars.currentplayer->award2 = NULL; if (g_Vars.currentplayer->visionmode == VISIONMODE_SLAYERROCKET) { g_Vars.currentplayer->visionmode = VISIONMODE_NORMAL; } } setCurrentPlayerNum(prevplayernum); mpCalculateAwards(); if (g_BossFile.locktype == MPLOCKTYPE_CHALLENGE) { challengeConsiderMarkingComplete(); } func0f0f820c(NULL, -6); } s32 mpGetNumHeads2(void) { return ARRAYCOUNT(g_MpHeads); } s32 mpGetNumHeads(void) { return ARRAYCOUNT(g_MpHeads); } s32 mpGetHeadId(u8 headnum) { return g_MpHeads[headnum].headnum; } s32 mpGetHeadRequiredFeature(u8 headnum) { return g_MpHeads[headnum].requirefeature; } s32 mpGetBeauHeadId(u8 headnum) { return g_MpBeauHeads[headnum].headnum; } s32 mpGetNumBeauHeads(void) { return ARRAYCOUNT(g_MpBeauHeads); } u32 mpGetNumBodies(void) { return ARRAYCOUNT(g_MpBodies); } s32 mpGetBodyId(u8 bodynum) { /** * @bug: bodynum 61 (0x3d) would cause an array overflow. * ARRAYCOUNT(g_MpBodies) is 61. */ if (bodynum > ARRAYCOUNT(g_MpBodies)) { if (bodynum == ARRAYCOUNT(g_MpBodies) + 1) { return BODY_DRCAROLL; } return BODY_DARK_COMBAT; } return g_MpBodies[bodynum].bodynum; } s32 mpGetMpbodynumByBodynum(u16 bodynum) { s32 i; if (bodynum == BODY_DRCAROLL) { return ARRAYCOUNT(g_MpBodies) + 1; } for (i = 0; i < ARRAYCOUNT(g_MpBodies); i++) { if (g_MpBodies[i].bodynum == bodynum) { return i; } } return g_MpBodies[0].bodynum; } char *mpGetBodyName(u8 mpbodynum) { // @bug: This should be >= if (mpbodynum > ARRAYCOUNT(g_MpBodies)) { mpbodynum = 0; } return langGet(g_MpBodies[mpbodynum].name); } u8 mpGetBodyRequiredFeature(u8 mpbodynum) { // @bug: This should be >= if (mpbodynum > ARRAYCOUNT(g_MpBodies)) { mpbodynum = 0; } return g_MpBodies[mpbodynum].requirefeature; } s32 mpGetMpheadnumByMpbodynum(s32 mpbodynum) { s32 headnum; s32 index = 0; s32 i; if (mpbodynum >= HEAD_VD) { mpbodynum = 0; } headnum = g_MpBodies[mpbodynum].headnum; if (headnum == 1000) { if (g_HeadsAndBodies[g_MpBodies[mpbodynum].bodynum].ismale) { headnum = g_MpMaleHeads[random() % ARRAYCOUNT(g_MpMaleHeads)]; } else { headnum = g_MpFemaleHeads[random() % ARRAYCOUNT(g_MpFemaleHeads)]; } } for (i = 0; i != ARRAYCOUNT(g_MpHeads); i++) { if (g_MpHeads[i].headnum == headnum) { index = i; } } return index; } void mpFindUnusedHeadAndBody(u8 *mpheadnum, u8 *mpbodynum) { struct mpchrconfig *mpchr; bool available; u8 trympheadnum; u8 trympbodynum; s32 i; do { available = true; trympheadnum = random() % ARRAYCOUNT(g_MpHeads); trympbodynum = random() % ARRAYCOUNT(g_MpBodies); for (i = 0; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { mpchr = MPCHR(i); if (mpchr->mpheadnum == trympheadnum) { available = false; } if (mpchr->mpbodynum == trympbodynum) { available = false; } } } } while (!available); if (1); *mpheadnum = trympheadnum; *mpbodynum = trympbodynum; } s32 mpChooseRandomLockPlayer(void) { s32 start = random() % 4; s32 i; for (i = (start + 1) % 4;; i = (i + 1) % 4) { if ((g_MpSetup.chrslots & (1 << i)) || i == start) { break; } } return i; } bool mpSetLock(s32 locktype, s32 playernum) { g_BossFile.locktype = locktype; if (g_BossFile.locktype == MPLOCKTYPE_RANDOM) { g_MpLockInfo.lockedplayernum = mpChooseRandomLockPlayer(); } else { g_MpLockInfo.lockedplayernum = playernum; } return true; } s32 mpGetLockType(void) { return g_BossFile.locktype; } u32 mpGetLockPlayerNum(void) { return g_MpLockInfo.lockedplayernum; } bool mpIsPlayerLockedOut(s32 playernum) { if (g_BossFile.locktype == MPLOCKTYPE_NONE) { return false; } if (g_MpLockInfo.lockedplayernum == playernum) { return false; } return true; } void mpCalculateLockIfLastWinnerOrLoser(void) { if (g_BossFile.locktype == MPLOCKTYPE_LASTWINNER && g_MpLockInfo.lastwinner >= 0) { g_MpLockInfo.lockedplayernum = g_MpLockInfo.lastwinner; } if (g_BossFile.locktype == MPLOCKTYPE_LASTLOSER && g_MpLockInfo.lastloser >= 0) { g_MpLockInfo.lockedplayernum = g_MpLockInfo.lastloser; } if (g_MpLockInfo.lockedplayernum >= 0 && g_BossFile.locktype != MPLOCKTYPE_CHALLENGE && (g_MpSetup.chrslots & (1 << g_MpLockInfo.lockedplayernum)) == 0) { g_MpLockInfo.lastwinner = g_MpLockInfo.lastloser = -1; g_MpLockInfo.lockedplayernum = mpChooseRandomLockPlayer(); } } struct mptrack g_MpTracks[] = { // Audio ID, duration, name, unlock after stage /*0x00*/ { MUSIC_DARK_COMBAT, 160, L_MISC_124, -1 }, // "Dark Combat" /*0x01*/ { MUSIC_SKEDAR_MYSTERY, 170, L_MISC_125, -1 }, // "Skedar Mystery" /*0x02*/ { MUSIC_CI_OPERATIVE, 170, L_MISC_126, -1 }, // "CI Operative" /*0x03*/ { MUSIC_DATADYNE_ACTION, 180, L_MISC_127, -1 }, // "dataDyne Action" /*0x04*/ { MUSIC_MAIAN_TEARS, 200, L_MISC_128, -1 }, // "Maian Tears" /*0x05*/ { MUSIC_ALIEN_CONFLICT, 197, L_MISC_129, -1 }, // "Alien Conflict" /*0x06*/ { MUSIC_CI, 120, L_MISC_130, -1 }, // "Carrington Institute" /*0x07*/ { MUSIC_DEFECTION, 120, L_MISC_131, SOLOSTAGEINDEX_DEFECTION }, // "dD Central" /*0x08*/ { MUSIC_DEFECTION_X, 120, L_MISC_132, SOLOSTAGEINDEX_DEFECTION }, // "dD Central X" /*0x09*/ { MUSIC_INVESTIGATION, 120, L_MISC_133, SOLOSTAGEINDEX_INVESTIGATION }, // "dD Research" /*0x0a*/ { MUSIC_INVESTIGATION_X, 120, L_MISC_134, SOLOSTAGEINDEX_INVESTIGATION }, // "dD Research X" /*0x0b*/ { MUSIC_EXTRACTION, 120, L_MISC_135, SOLOSTAGEINDEX_EXTRACTION }, // "dD Extraction" /*0x0c*/ { MUSIC_EXTRACTION_X, 120, L_MISC_136, SOLOSTAGEINDEX_EXTRACTION }, // "dD Extraction X" /*0x0d*/ { MUSIC_VILLA, 120, L_MISC_137, SOLOSTAGEINDEX_VILLA }, // "Carrington Villa" /*0x0e*/ { MUSIC_VILLA_X, 120, L_MISC_138, SOLOSTAGEINDEX_VILLA }, // "Carrington Villa X" /*0x0f*/ { MUSIC_CHICAGO, 120, L_MISC_139, SOLOSTAGEINDEX_CHICAGO }, // "Chicago" /*0x10*/ { MUSIC_CHICAGO_X, 120, L_MISC_140, SOLOSTAGEINDEX_CHICAGO }, // "Chicago X" /*0x11*/ { MUSIC_G5, 120, L_MISC_141, SOLOSTAGEINDEX_G5BUILDING }, // "G5 Building" /*0x12*/ { MUSIC_G5_X, 120, L_MISC_142, SOLOSTAGEINDEX_G5BUILDING }, // "G5 Building X" /*0x13*/ { MUSIC_INFILTRATION, 120, L_MISC_143, SOLOSTAGEINDEX_INFILTRATION }, // "A51 Infiltration" /*0x14*/ { MUSIC_INFILTRATION_X, 120, L_MISC_144, SOLOSTAGEINDEX_INFILTRATION }, // "A51 Infiltration X" /*0x15*/ { MUSIC_RESCUE, 120, L_MISC_145, SOLOSTAGEINDEX_RESCUE }, // "A51 Rescue" /*0x16*/ { MUSIC_RESCUE_X, 120, L_MISC_146, SOLOSTAGEINDEX_RESCUE }, // "A51 Rescue X" /*0x17*/ { MUSIC_ESCAPE, 120, L_MISC_147, SOLOSTAGEINDEX_ESCAPE }, // "A51 Escape" /*0x18*/ { MUSIC_ESCAPE_X, 120, L_MISC_148, SOLOSTAGEINDEX_ESCAPE }, // "A51 Escape X" /*0x19*/ { MUSIC_AIRBASE, 120, L_MISC_149, SOLOSTAGEINDEX_AIRBASE }, // "Air Base" /*0x1a*/ { MUSIC_AIRBASE_X, 120, L_MISC_150, SOLOSTAGEINDEX_AIRBASE }, // "Air Base X" /*0x1b*/ { MUSIC_AIRFORCEONE, 120, L_MISC_151, SOLOSTAGEINDEX_AIRFORCEONE }, // "Air Force One" /*0x1c*/ { MUSIC_AIRFORCEONE_X, 120, L_MISC_152, SOLOSTAGEINDEX_AIRFORCEONE }, // "Air Force One X" /*0x1d*/ { MUSIC_CRASHSITE, 120, L_MISC_153, SOLOSTAGEINDEX_CRASHSITE }, // "Crash Site" /*0x1e*/ { MUSIC_CRASHSITE_X, 120, L_MISC_154, SOLOSTAGEINDEX_CRASHSITE }, // "Crash Site X" /*0x1f*/ { MUSIC_PELAGIC, 120, L_MISC_155, SOLOSTAGEINDEX_PELAGIC }, // "Pelagic II" /*0x20*/ { MUSIC_PELAGIC_X, 120, L_MISC_156, SOLOSTAGEINDEX_PELAGIC }, // "Pelagic II X" /*0x21*/ { MUSIC_DEEPSEA, 120, L_MISC_157, SOLOSTAGEINDEX_DEEPSEA }, // "Deep Sea" /*0x22*/ { MUSIC_DEEPSEA_X, 120, L_MISC_158, SOLOSTAGEINDEX_DEEPSEA }, // "Deep Sea X" /*0x23*/ { MUSIC_DEFENSE, 120, L_MISC_159, SOLOSTAGEINDEX_DEFENSE }, // "Institute Defense" /*0x24*/ { MUSIC_DEFENSE_X, 120, L_MISC_160, SOLOSTAGEINDEX_DEFENSE }, // "Institute Defense X" /*0x25*/ { MUSIC_ATTACKSHIP, 120, L_MISC_161, SOLOSTAGEINDEX_ATTACKSHIP }, // "Attack Ship" /*0x26*/ { MUSIC_ATTACKSHIP_X, 120, L_MISC_162, SOLOSTAGEINDEX_ATTACKSHIP }, // "Attack Ship X" /*0x27*/ { MUSIC_SKEDARRUINS, 120, L_MISC_163, SOLOSTAGEINDEX_SKEDARRUINS }, // "Skedar Ruins" /*0x28*/ { MUSIC_SKEDARRUINS_X, 120, L_MISC_164, SOLOSTAGEINDEX_SKEDARRUINS }, // "Skedar Ruins X" /*0x29*/ { MUSIC_CREDITS, 120, L_MISC_165, SOLOSTAGEINDEX_SKEDARRUINS }, // "End Credits" /*0x30*/ { MUSIC_SKEDARRUINS_KING,120, L_MISC_261, SOLOSTAGEINDEX_SKEDARRUINS }, // "Skedar Warrior" (Skedar Leader) }; bool mpIsTrackUnlocked(s32 tracknum) { s16 stageindex = g_MpTracks[tracknum].unlockstage; bool unlocked = false; u32 i; if (stageindex < 0 || stageindex > SOLOSTAGEINDEX_SKEDARRUINS) { unlocked = true; } else { for (i = 0; i != 3; i++) { if (g_GameFile.besttimes[stageindex][i]) { unlocked = true; break; } } } return unlocked; } s32 mpGetTrackSlotIndex(s32 tracknum) { s32 i; s32 slotindex = 0; for (i = 0; i < tracknum; i++) { if (mpIsTrackUnlocked(i)) { slotindex++; } } return slotindex; } s32 mpGetTrackNumAtSlotIndex(s32 slotindex) { s32 i; s32 numunlocked = 0; for (i = 0; i != ARRAYCOUNT(g_MpTracks); i++) { if (mpIsTrackUnlocked(i)) { if (numunlocked == slotindex) { break; } numunlocked++; } } return i; } s32 mpGetNumUnlockedTracks(void) { return mpGetTrackSlotIndex(ARRAYCOUNT(g_MpTracks)); } s32 mpGetTrackMusicNum(s32 slotindex) { s32 tracknum = mpGetTrackNumAtSlotIndex(slotindex); return g_MpTracks[tracknum].musicnum; } char *mpGetTrackName(s32 slotindex) { s32 tracknum = mpGetTrackNumAtSlotIndex(slotindex); return langGet(g_MpTracks[tracknum].name); } void mpSetUsingMultipleTunes(bool enable) { g_BossFile.usingmultipletunes = enable; } bool mpGetUsingMultipleTunes(void) { return g_BossFile.usingmultipletunes; } bool mpIsMultiTrackSlotEnabled(s32 slot) { s32 tracknum = mpGetTrackNumAtSlotIndex(slot); u8 index = tracknum >> 3; u8 value = 1 << (tracknum & 7); if ((g_BossFile.multipletracknums[index] & value) == 0) { return false; } return true; } void mpSetMultiTrackSlotEnabled(s32 slot, bool enable) { s32 tracknum = mpGetTrackNumAtSlotIndex(slot); u8 value = 1 << (tracknum & 7); u8 index = tracknum >> 3; if (enable) { g_BossFile.multipletracknums[index] |= value; } else { g_BossFile.multipletracknums[index] &= ~value; } } void mpSetTrackSlotEnabled(s32 slot) { if (mpGetUsingMultipleTunes()) { mpSetMultiTrackSlotEnabled(slot, 1 - mpIsMultiTrackSlotEnabled(slot)); } else { g_BossFile.tracknum = mpGetTrackNumAtSlotIndex(slot); } } void mpEnableAllMultiTracks(void) { s32 i; for (i = 0; i != ARRAYCOUNT(g_BossFile.multipletracknums); i++) { g_BossFile.multipletracknums[i] = 0xff; } } void mpDisableAllMultiTracks(void) { s32 i; for (i = 0; i != ARRAYCOUNT(g_BossFile.multipletracknums); i++) { g_BossFile.multipletracknums[i] = 0; } } void mpRandomiseMultiTracks(void) { s32 i; for (i = 0; i != ARRAYCOUNT(g_BossFile.multipletracknums); i++) { g_BossFile.multipletracknums[i] = random(); } } void mpSetTrackToRandom(void) { g_BossFile.tracknum = -1; } s32 mpGetCurrentTrackSlotNum(void) { if (g_BossFile.tracknum < 0) { return g_BossFile.tracknum; } return mpGetTrackSlotIndex(g_BossFile.tracknum); } s32 mpChooseTrack(void) { s32 i; s32 tracknum; if (mpGetUsingMultipleTunes()) { s32 numunlocked = mpGetNumUnlockedTracks(); s32 numselected = 0; for (i = 0; i < numunlocked; i++) { if (mpIsMultiTrackSlotEnabled(i)) { numselected++; } } if (numselected == 0) { do { tracknum = mpGetTrackNumAtSlotIndex(random() % numunlocked); } while (tracknum == g_MpLockInfo.unk04); g_MpLockInfo.unk04 = tracknum; g_MusicLife60 = g_MpTracks[tracknum].duration * TICKS(60); return g_MpTracks[tracknum].musicnum; } do { s32 selectionindex = random() % numselected; s32 selectioncount = 0; tracknum = -1; for (i = 0; i < numunlocked; i++) { if (mpIsMultiTrackSlotEnabled(i)) { if (selectionindex == selectioncount) { tracknum = i; } selectioncount++; } } if (tracknum == -1) { g_MusicLife60 = g_MpTracks[0].duration * TICKS(60); return g_MpTracks[0].musicnum; } tracknum = mpGetTrackNumAtSlotIndex(tracknum); } while (numselected > 1 && tracknum == g_MpLockInfo.unk04); g_MpLockInfo.unk04 = tracknum; g_MusicLife60 = g_MpTracks[tracknum].duration * TICKS(60); return g_MpTracks[tracknum].musicnum; } tracknum = mpGetCurrentTrackSlotNum(); if (tracknum < 0) { s32 numunlocked = mpGetNumUnlockedTracks(); do { tracknum = mpGetTrackNumAtSlotIndex(random() % numunlocked); } while (tracknum == g_MpLockInfo.unk04); g_MpLockInfo.unk04 = tracknum; g_MusicLife60 = g_MpTracks[tracknum].duration * TICKS(60); return g_MpTracks[tracknum].musicnum; } tracknum = mpGetTrackNumAtSlotIndex(tracknum); g_MpLockInfo.unk04 = tracknum; g_MusicLife60 = g_MpTracks[tracknum].duration * TICKS(60); return g_MpTracks[tracknum].musicnum; } struct mpchrconfig *mpGetChrConfigBySlotNum(s32 slot) { s32 count = 0; struct mpchrconfig *result = NULL; s32 i; for (i = 0; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { if (count == slot) { result = MPCHR(i); break; } count++; } } return result; } #if VERSION >= VERSION_NTSC_1_0 s32 mpGetChrIndexBySlotNum(s32 slot) { s32 count = 0; s32 result = 0; s32 i; for (i = 0; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { if (count == slot) { result = i; break; } count++; } } return result; } #endif s32 mpGetNumChrs(void) { s32 count = 0; s32 i; for (i = 0; i != MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { count++; } } return count; } u8 mpFindUnusedTeamNum(void) { u8 teamnum = 0; bool available = false; s32 i; while (teamnum < 7 && !available) { available = true; for (i = 0; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { struct mpchrconfig *mpchr = MPCHR(i); if (mpchr->team == teamnum) { available = false; } } } if (!available) { teamnum++; } } if (teamnum > 7) { teamnum = 7; } return teamnum; } void mpCreateBotFromProfile(s32 botnum, u8 profilenum) { s32 headnum = 0; u8 team = mpFindUnusedTeamNum(); bool available = false; s32 i; g_BotConfigsArray[botnum].type = g_BotProfiles[profilenum].type; g_BotConfigsArray[botnum].difficulty = g_BotProfiles[profilenum].difficulty; for (i = 0; i < MAX_PLAYERS; i++) { g_MpSimulantDifficultiesPerNumPlayers[botnum][i] = g_BotConfigsArray[botnum].difficulty; } g_MpSetup.chrslots |= 1 << (botnum + 4); strcpy(g_BotConfigsArray[botnum].base.name, "Sim\n"); g_BotConfigsArray[botnum].base.team = team; while (!available) { headnum = g_BotHeads[random() % ARRAYCOUNT(g_BotHeads)]; available = true; for (i = 0; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { struct mpchrconfig *mpchr = MPCHR(i); if (mpchr->mpheadnum == headnum) { available = false; } } } } g_BotConfigsArray[botnum].base.mpheadnum = headnum; g_BotConfigsArray[botnum].base.mpbodynum = g_BotProfiles[profilenum].body; } void mpSetBotDifficulty(s32 botnum, s32 difficulty) { s32 i; g_BotConfigsArray[botnum].difficulty = difficulty; for (i = 0; i < MAX_PLAYERS; i++) { g_MpSimulantDifficultiesPerNumPlayers[botnum][i] = g_BotConfigsArray[botnum].difficulty; } } /** * Return the first unused bot slot (0-7), or return 7 if all slots are full. * * This is used for the quick team feature. */ s32 mpGetSlotForNewBot(void) { s32 i = 0; while (i < MAX_BOTS - 1 && g_MpSetup.chrslots & (1 << (i + 4))) { i++; } return i; } void mpRemoveSimulant(s32 index) { g_MpSetup.chrslots &= ~(1 << (index + 4)); g_BotConfigsArray[index].base.name[0] = '\0'; func0f1881d4(index); mpGenerateBotNames(); } bool mpHasSimulants(void) { if ((g_MpSetup.chrslots & 0xfff0) != 0) { return true; } return false; } bool mpHasUnusedBotSlots(void) { s32 numvacant = challengeIsFeatureUnlocked(MPFEATURE_8BOTS) ? MAX_BOTS : 4; s32 i; for (i = 4; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { numvacant--; } } if (numvacant > 0) { return true; } return false; } bool mpIsSimSlotEnabled(s32 slot) { s32 numfree = MAX_BOTS; s32 i; if ((g_MpSetup.chrslots & (1 << (slot + 4))) == 0) { for (i = 0; i < MAX_BOTS; i++) { if (g_MpSetup.chrslots & (1 << (i + 4))) { numfree--; } } if (numfree > 0) { return true; } return false; } return true; } s32 mpFindBotProfile(s32 type, s32 difficulty) { s32 i; if (type == BOTTYPE_GENERAL) { for (i = 0; i < ARRAYCOUNT(g_BotProfiles); i++) { if (g_BotProfiles[i].difficulty == difficulty) { break; } } } else { for (i = 0; i < ARRAYCOUNT(g_BotProfiles); i++) { if (g_BotProfiles[i].type == type) { break; } } } if (i >= ARRAYCOUNT(g_BotProfiles)) { i = -1; } return i; } void mpGenerateBotNames(void) { s32 counts[ARRAYCOUNT(g_BotProfiles)]; s32 profilenum; s32 i; char name[16]; for (i = 0; i < ARRAYCOUNT(g_BotProfiles); i++) { counts[i] = 0; } // Count the number of bots using each profile (MeatSim, TurtleSim etc) for (i = 4; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { profilenum = mpFindBotProfile(g_BotConfigsArray[i - 4].type, g_BotConfigsArray[i - 4].difficulty); if (profilenum >= 0 && profilenum < ARRAYCOUNT(g_BotProfiles)) { counts[profilenum]++; } } } // Profiles with only one bot don't need to have to number appended to the // name, so mark those as -1. For profiles with multiple bots, reset them // to 0 because they'll be a counter for the final loop. for (i = 0; i < ARRAYCOUNT(g_BotProfiles); i++) { if (counts[i] <= 1) { counts[i] = -1; } else { counts[i] = 0; } } for (i = 4; i < MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { profilenum = mpFindBotProfile(g_BotConfigsArray[i - 4].type, g_BotConfigsArray[i - 4].difficulty); if (profilenum >= 0 && profilenum < ARRAYCOUNT(g_BotProfiles)) { if (counts[profilenum] >= 0) { // Multiple bots using this profile - append the number counts[profilenum]++; sprintf(name, "%s:%d\n", langGet(g_BotProfiles[profilenum].name), counts[profilenum]); strcpy(g_BotConfigsArray[i - 4].base.name, name); } else { // One bots using this profile - just use the profile name sprintf(name, "%s\n", langGet(g_BotProfiles[profilenum].name)); strcpy(g_BotConfigsArray[i - 4].base.name, name); } } } } } #if VERSION >= VERSION_NTSC_1_0 const char var7f1b8b80[] = "Adding GBCHead to load to slot %d: guid is %x-%x, player is %d\n"; const char var7f1b8bc0[] = "PakId for player %d: %d\n"; const char var7f1b8bdc[] = "Save Player Result: %d New GUID: %x\n"; const char var7f1b8c04[] = "PakId for player %d: %d\n"; const char var7f1b8c20[] = "Load Player - Result: %d\n"; #endif s32 mpPlayerGetIndex(struct chrdata *chr) { s32 i; for (i = 0; i < g_MpNumChrs; i++) { if (g_MpAllChrPtrs[i] == chr) { return i; } } return -1; } struct chrdata *mpGetChrFromPlayerIndex(s32 index) { if (index >= 0 && index < g_MpNumChrs) { return g_MpAllChrPtrs[index]; } return NULL; } s32 func0f18d074(s32 index) { s32 i; for (i = 0; i < MAX_PLAYERS; i++) { if (&g_PlayerConfigsArray[i].base == g_MpAllChrConfigPtrs[index]) { return i; } } for (i = 0; i < MAX_BOTS; i++) { if (&g_BotConfigsArray[i].base == g_MpAllChrConfigPtrs[index]) { return i + 4; } } return -1; } s32 func0f18d0e8(s32 arg0) { s32 i; if (arg0 < 4) { for (i = 0; i < g_MpNumChrs; i++) { if (g_MpAllChrConfigPtrs[i] == &g_PlayerConfigsArray[arg0].base) { return i; } } } else { for (i = 0; i < g_MpNumChrs; i++) { if (g_MpAllChrConfigPtrs[i] == &g_BotConfigsArray[arg0 - 4].base) { return i; } } } return -1; } void mpplayerfileLoadGunFuncs(struct savebuffer *buffer, s32 playernum) { s32 bitsremaining = 35; s32 i = 0; while (bitsremaining > 0) { s32 numbits = bitsremaining; if (numbits > 8) { numbits = 8; } g_PlayerConfigsArray[playernum].gunfuncs[i] = savebufferReadBits(buffer, numbits); bitsremaining -= 8; i++; } } void mpplayerfileSaveGunFuncs(struct savebuffer *buffer, s32 playernum) { s32 bitsremaining = 35; s32 i = 0; while (bitsremaining > 0) { s32 numbits = bitsremaining; if (numbits > 8) { numbits = 8; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].gunfuncs[i], numbits); bitsremaining -= 8; i++; } } void mpplayerfileLoadWad(s32 playernum, struct savebuffer *buffer, s32 arg2) { struct fileguid guid; u32 stack; s32 i; s32 j; savebufferReadString(buffer, g_PlayerConfigsArray[playernum].base.name, 1); g_PlayerConfigsArray[playernum].time = savebufferReadBits(buffer, 28); if (arg2 != 0) { g_PlayerConfigsArray[playernum].base.mpheadnum = savebufferReadBits(buffer, 7); g_PlayerConfigsArray[playernum].base.mpbodynum = savebufferReadBits(buffer, 7); savebufferReadGuid(buffer, &guid); if (g_PlayerConfigsArray[playernum].base.mpheadnum >= mpGetNumHeads2()) { if (guid.fileid != 0 && guid.deviceserial != 0) { if (g_MenuData.unk668 < 11) { g_MenuData.unk668++; g_MenuData.unk5d8[g_MenuData.unk668].fileguid.fileid = guid.fileid; g_MenuData.unk5d8[g_MenuData.unk668].fileguid.deviceserial = guid.deviceserial; g_MenuData.unk5d8[g_MenuData.unk668].unk08 = g_MpPlayerNum; g_MenuData.unk5d8[g_MenuData.unk668].unk09 = g_MpPlayerNum; } } else { g_PlayerConfigsArray[playernum].base.mpheadnum = MPHEAD_DARK_COMBAT; } } } else { savebufferReadBits(buffer, 7); savebufferReadBits(buffer, 7); savebufferReadGuid(buffer, &guid); } g_PlayerConfigsArray[playernum].base.displayoptions = savebufferReadBits(buffer, 8); g_PlayerConfigsArray[playernum].kills = savebufferReadBits(buffer, 20); g_PlayerConfigsArray[playernum].deaths = savebufferReadBits(buffer, 20); g_PlayerConfigsArray[playernum].gamesplayed = savebufferReadBits(buffer, 19); g_PlayerConfigsArray[playernum].gameswon = savebufferReadBits(buffer, 19); g_PlayerConfigsArray[playernum].gameslost = savebufferReadBits(buffer, 19); g_PlayerConfigsArray[playernum].distance = savebufferReadBits(buffer, 25); g_PlayerConfigsArray[playernum].accuracy = savebufferReadBits(buffer, 10); g_PlayerConfigsArray[playernum].damagedealt = savebufferReadBits(buffer, 26); g_PlayerConfigsArray[playernum].painreceived = savebufferReadBits(buffer, 26); g_PlayerConfigsArray[playernum].headshots = savebufferReadBits(buffer, 20); g_PlayerConfigsArray[playernum].ammoused = savebufferReadBits(buffer, 30); g_PlayerConfigsArray[playernum].accuracymedals = savebufferReadBits(buffer, 18); g_PlayerConfigsArray[playernum].headshotmedals = savebufferReadBits(buffer, 18); g_PlayerConfigsArray[playernum].killmastermedals = savebufferReadBits(buffer, 18); g_PlayerConfigsArray[playernum].survivormedals = savebufferReadBits(buffer, 16); g_PlayerConfigsArray[playernum].controlmode = savebufferReadBits(buffer, 2); g_PlayerConfigsArray[playernum].options = savebufferReadBits(buffer, 12); #ifndef PLATFORM_N64 // override with PC controls if enabled in the config if (g_PlayerExtCfg[playernum % MAX_PLAYERS].extcontrols) { g_PlayerConfigsArray[playernum].controlmode = CONTROLMODE_PC; } #endif for (i = 0; i < ARRAYCOUNT(g_MpChallenges); i++) { for (j = 1; j < MAX_PLAYERS + 1; j++) { challengeSetCompletedByPlayerWithNumPlayers(playernum, i, j, savebufferReadBits(buffer, 1)); } } challengeDetermineUnlockedFeatures(); mpCalculatePlayerTitle(&g_PlayerConfigsArray[playernum]); mpplayerfileLoadGunFuncs(buffer, playernum); } void mpplayerfileSaveWad(s32 playernum, struct savebuffer *buffer) { s32 i; s32 j; u32 stack; func0f0d55a4(buffer, g_PlayerConfigsArray[playernum].base.name); if (g_PlayerConfigsArray[playernum].time > 0x0fffffff) { // over 3106 days g_PlayerConfigsArray[playernum].time = 0x0fffffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].time, 28); savebufferOr(buffer, g_PlayerConfigsArray[playernum].base.mpheadnum, 7); savebufferOr(buffer, g_PlayerConfigsArray[playernum].base.mpbodynum, 7); if (g_PlayerConfigsArray[playernum].base.mpheadnum >= mpGetNumHeads2()) { struct fileguid guid; phGetGuid(g_PlayerConfigsArray[playernum].base.mpheadnum - mpGetNumHeads2(), &guid); savebufferWriteGuid(buffer, &guid); } else { struct fileguid guid; guid.deviceserial = 0; guid.fileid = 0; savebufferWriteGuid(buffer, &guid); } savebufferOr(buffer, g_PlayerConfigsArray[playernum].base.displayoptions, 8); if (g_PlayerConfigsArray[playernum].kills > 0xfffff) { // 1,048,575 g_PlayerConfigsArray[playernum].kills = 0xfffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].kills, 20); if (g_PlayerConfigsArray[playernum].deaths > 0xfffff) { // 1,048,575 g_PlayerConfigsArray[playernum].deaths = 0xfffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].deaths, 20); if (g_PlayerConfigsArray[playernum].gamesplayed > 0x7ffff) { // 524,287 g_PlayerConfigsArray[playernum].gamesplayed = 0x7ffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].gamesplayed, 19); if (g_PlayerConfigsArray[playernum].gameswon > 0x7ffff) { // 524,287 g_PlayerConfigsArray[playernum].gameswon = 0x7ffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].gameswon, 19); if (g_PlayerConfigsArray[playernum].gameslost > 0x7ffff) { // 524,287 g_PlayerConfigsArray[playernum].gameslost = 0x7ffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].gameslost, 19); if (g_PlayerConfigsArray[playernum].distance > 0x1ffffff) { // 33,554,431 g_PlayerConfigsArray[playernum].distance = 0x1ffffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].distance, 25); if (g_PlayerConfigsArray[playernum].accuracy > 0x3ff) { // 1023 g_PlayerConfigsArray[playernum].accuracy = 0x3ff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].accuracy, 10); if (g_PlayerConfigsArray[playernum].damagedealt > 0x3ffffff) { // 67,108,863 g_PlayerConfigsArray[playernum].damagedealt = 0x3ffffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].damagedealt, 26); if (g_PlayerConfigsArray[playernum].painreceived > 0x3ffffff) { // 67,108,863 g_PlayerConfigsArray[playernum].painreceived = 0x3ffffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].painreceived, 26); if (g_PlayerConfigsArray[playernum].headshots > 0xfffff) { // 1,048,575 g_PlayerConfigsArray[playernum].headshots = 0xfffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].headshots, 20); if (g_PlayerConfigsArray[playernum].ammoused > 0x3fffffff) { // 1,073,741,823 g_PlayerConfigsArray[playernum].ammoused = 0x3fffffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].ammoused, 30); if (g_PlayerConfigsArray[playernum].accuracymedals > 0x3ffff) { // 262,143 g_PlayerConfigsArray[playernum].accuracymedals = 0x3ffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].accuracymedals, 18); if (g_PlayerConfigsArray[playernum].headshotmedals > 0x3ffff) { // 262,143 g_PlayerConfigsArray[playernum].headshotmedals = 0x3ffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].headshotmedals, 18); if (g_PlayerConfigsArray[playernum].killmastermedals > 0x3ffff) { // 262,143 g_PlayerConfigsArray[playernum].killmastermedals = 0x3ffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].killmastermedals, 18); if (g_PlayerConfigsArray[playernum].survivormedals > 0xffff) { // 65,535 g_PlayerConfigsArray[playernum].survivormedals = 0xffff; } savebufferOr(buffer, g_PlayerConfigsArray[playernum].survivormedals, 16); #ifdef PLATFORM_N64 savebufferOr(buffer, g_PlayerConfigsArray[playernum].controlmode, 2); #else // PC control mode is enabled in the .ini to avoid changing the save structure const u32 controlmode = g_PlayerConfigsArray[playernum].controlmode; savebufferOr(buffer, ((controlmode == CONTROLMODE_PC) ? CONTROLMODE_11 : controlmode), 2); #endif savebufferOr(buffer, g_PlayerConfigsArray[playernum].options, 12); for (i = 0; i < ARRAYCOUNT(g_MpChallenges); i++) { for (j = 1; j < MAX_PLAYERS + 1; j++) { savebufferOr(buffer, challengeIsCompletedByPlayerWithNumPlayers(playernum, i, j), 1); } } mpplayerfileSaveGunFuncs(buffer, playernum); } void mpplayerfileGetOverview(char *arg0, char *name, u32 *playtime) { struct savebuffer buffer; func0f0d5484(&buffer, arg0, 15); savebufferReadString(&buffer, name, 0); *playtime = savebufferReadBits(&buffer, 28); } s32 mpplayerfileSave(s32 playernum, s32 device, s32 fileid, u16 deviceserial) { s32 ret; s32 newfileid; struct savebuffer buffer; if (device >= 0) { savebufferClear(&buffer); mpplayerfileSaveWad(playernum, &buffer); func0f0d54c4(&buffer); var80075bd0[2] = true; ret = pakSaveAtGuid(device, fileid, PAKFILETYPE_MPPLAYER, buffer.bytes, &newfileid, 0); if (ret == 0) { g_PlayerConfigsArray[playernum].fileguid.fileid = newfileid; g_PlayerConfigsArray[playernum].fileguid.deviceserial = deviceserial; return 0; } g_FilemgrLastPakError = ret; return -1; } return -1; } s32 mpplayerfileLoad(s32 playernum, s32 device, s32 fileid, u16 deviceserial) { s32 ret; struct savebuffer buffer; if (device >= 0) { savebufferClear(&buffer); ret = pakReadBodyAtGuid(device, fileid, buffer.bytes, 0); if (ret == 0) { g_PlayerConfigsArray[playernum].fileguid.fileid = fileid; g_PlayerConfigsArray[playernum].fileguid.deviceserial = deviceserial; mpplayerfileLoadWad(playernum, &buffer, 1); func0f0d54c4(&buffer); g_PlayerConfigsArray[playernum].handicap = 0x80; return 0; } g_FilemgrLastPakError = ret; return -1; } return -1; } struct mppreset g_MpPresets[] = { { L_MPWEAPONS_025, MPCONFIG_NOSHIELD }, // "No Shield" { L_MPWEAPONS_026, MPCONFIG_AUTOMATICS }, // "Automatics" { L_MPWEAPONS_027, MPCONFIG_ROCKETS }, // "Rocket Launcher" { L_MPWEAPONS_028, MPCONFIG_SIMULANTS }, // "Simulants" { L_MPWEAPONS_029, MPCONFIG_SKEDARKING }, // "King of the Hill" { L_MPWEAPONS_030, MPCONFIG_FARSIGHT }, // "Complex FarSight" { L_MPWEAPONS_031, MPCONFIG_BRIEFCASE }, // "Hold the Briefcase" { L_MPWEAPONS_032, MPCONFIG_PISTOLS }, // "Pistol One-Hit Kills" { L_MPWEAPONS_033, MPCONFIG_FLAGS }, // "Capture the Case" { L_MPWEAPONS_034, MPCONFIG_CLOAKING }, // "Cloaking" { L_MPWEAPONS_035, MPCONFIG_TEMPLE }, // "Temple Explosives" { L_MPWEAPONS_036, MPCONFIG_SLAYER }, // "Slayer" { L_MPWEAPONS_037, MPCONFIG_DRUGGUN }, // "Tranquilizr" { L_MPWEAPONS_038, MPCONFIG_SLOWMO }, // "Slow Motion" }; s32 mpGetNumPresets(void) { return ARRAYCOUNT(g_MpPresets); } bool mpIsPresetUnlocked(s32 presetnum) { s32 i; for (i = 0; i < ARRAYCOUNT(g_MpPresets[presetnum].requirefeatures); i++) { if (!challengeIsFeatureUnlocked(g_MpPresets[presetnum].requirefeatures[i]) && g_MpPresets[presetnum].requirefeatures[i] != MPFEATURE_WEAPON_SHIELD) { return false; } } return true; } s32 mpGetNumUnlockedPresets(void) { s32 numunlocked = 0; s32 i; for (i = 0; i < ARRAYCOUNT(g_MpPresets); i++) { if (mpIsPresetUnlocked(i)) { numunlocked++; } } return numunlocked; } char *mpGetPresetNameBySlot(s32 slot) { s32 i; for (i = 0; i < ARRAYCOUNT(g_MpPresets); i++) { if (mpIsPresetUnlocked(i)) { if (slot == 0) { return langGet(g_MpPresets[i].name); } slot--; } } return ""; } #if VERSION >= VERSION_NTSC_1_0 const char var7f1b8c40[] = "bot %d headId %d bodyId %d\n"; const char var7f1b8c5c[] = "team change %s %d\n"; #if VERSION >= VERSION_PAL_FINAL const char var7f1b8c70[] = "options/mplayer.c"; #else const char var7f1b8c70[] = "mplayer.c"; #endif const char var7f1b8c7c[] = "team change %s %d\n"; #if VERSION >= VERSION_PAL_FINAL const char var7f1b8c90[] = "options/mplayer.c"; #else const char var7f1b8c90[] = "mplayer.c"; #endif const char var7f1b8c9c[] = "SaveMultiGameFile : PakId=0x%x, FileId=0x%x\n"; const char var7f1b8ccc[] = "SaveGame Result: %d New GUID: %x\n"; const char var7f1b8cf0[] = "LoadMultiGameFile : PakId=0x%x, FileId=0x%x\n"; const char var7f1b8d20[] = "LoadGame Result: %d\n"; const char var7f1b8d38[] = "GBCHead: Call to create head for slot %d (gbcheadobjs[slotno]=%x)\n"; #endif // >= VERSION_NTSC_1_0 void mpApplyConfig(struct mpconfigfull *config) { s32 i; s32 j; u16 chrslots; g_MpSetup.scenario = config->config.setup.scenario; #if VERSION >= VERSION_PAL_FINAL chrslots = g_MpSetup.chrslots; #endif scenarioInit(); g_MpSetup = config->config.setup; #if VERSION >= VERSION_PAL_FINAL g_MpSetup.chrslots = chrslots; #endif for (i = 0; i < MAX_BOTS; i++) { g_BotConfigsArray[i].type = config->config.simulants[i].type; for (j = 0; j < MAX_PLAYERS; j++) { g_MpSimulantDifficultiesPerNumPlayers[i][j] = config->config.simulants[i].difficulties[j]; } g_BotConfigsArray[i].difficulty = g_MpSimulantDifficultiesPerNumPlayers[i][0]; #if VERSION >= VERSION_NTSC_1_0 if (IS4MB()) { // "ShockSim:%d" sprintf(g_BotConfigsArray[i].base.name, langGet(L_MPWEAPONS_241), i + 1); } else { strcpy(g_BotConfigsArray[i].base.name, config->strings.aibotnames[i]); } #else strcpy(g_BotConfigsArray[i].base.name, config->strings.aibotnames[i]); #endif g_BotConfigsArray[i].base.mpheadnum = config->config.simulants[i].mpheadnum; g_BotConfigsArray[i].base.mpbodynum = config->config.simulants[i].mpbodynum; g_BotConfigsArray[i].base.team = config->config.simulants[i].team; } if (!challengeIsFeatureUnlocked(MPFEATURE_WEAPON_SHIELD)) { for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) { if (g_MpSetup.weapons[i] == MPWEAPON_SHIELD) { g_MpSetup.weapons[i] = MPWEAPON_NONE; } } } func0f18913c(); challengeRemoveForceUnlocks(); } void mp0f18dec4(s32 slot) { struct mpconfigfull *config; u8 buffer[0x1ca]; s32 confignum = 0; u32 i; for (i = 0; i < ARRAYCOUNT(g_MpPresets); i++) { if (mpIsPresetUnlocked(i)) { if (slot == 0) { confignum = g_MpPresets[i].confignum; break; } slot--; } } config = challengeLoadConfig(confignum, buffer, sizeof(buffer)); mpApplyConfig(config); #if VERSION >= VERSION_JPN_FINAL g_MpSetup.chrslots &= 0x0f; for (i = 0; i < MAX_BOTS; i++) { if (g_BotConfigsArray[i].difficulty != BOTDIFF_DISABLED) { g_MpSetup.chrslots |= 1 << (i + 4); } } #endif } void mpsetupfileLoadWad(struct savebuffer *buffer) { s32 i; s32 j; savebufferReadString(buffer, g_MpSetup.name, false); savebufferReadBits(buffer, 4); g_MpSetup.stagenum = savebufferReadBits(buffer, 7); g_MpSetup.scenario = savebufferReadBits(buffer, 3); scenarioInit(); scenarioReadSave(buffer); g_MpSetup.options = savebufferReadBits(buffer, 21); g_MpSetup.chrslots &= 0x000f; for (i = 0; i < MAX_BOTS; i++) { g_BotConfigsArray[i].base.name[0] = '\0'; g_BotConfigsArray[i].type = savebufferReadBits(buffer, 5); g_BotConfigsArray[i].difficulty = savebufferReadBits(buffer, 3); for (j = 0; j < MAX_PLAYERS; j++) { g_MpSimulantDifficultiesPerNumPlayers[i][j] = g_BotConfigsArray[i].difficulty; } if (g_BotConfigsArray[i].difficulty != BOTDIFF_DISABLED) { g_MpSetup.chrslots |= 1 << (i + 4); } g_BotConfigsArray[i].base.mpheadnum = savebufferReadBits(buffer, 7); g_BotConfigsArray[i].base.mpbodynum = savebufferReadBits(buffer, 7); g_BotConfigsArray[i].base.team = savebufferReadBits(buffer, 3); } mpGenerateBotNames(); for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) { g_MpSetup.weapons[i] = savebufferReadBits(buffer, 7); } func0f18913c(); g_MpSetup.timelimit = savebufferReadBits(buffer, 6); g_MpSetup.scorelimit = savebufferReadBits(buffer, 7); g_MpSetup.teamscorelimit = savebufferReadBits(buffer, 9); for (i = 0; i < MAX_PLAYERS; i++) { g_PlayerConfigsArray[i].base.team = savebufferReadBits(buffer, 3); } challengeForceUnlockBotFeatures(); } void mpsetupfileSaveWad(struct savebuffer *buffer) { s32 numsims = 0; s32 mpbodynum; s32 i; func0f0d55a4(buffer, g_MpSetup.name); for (i = 0; i < MAX_BOTS; i++) { if (g_MpSetup.chrslots & (1 << (i + 4))) { numsims++; } } savebufferOr(buffer, numsims, 4); savebufferOr(buffer, g_MpSetup.stagenum, 7); savebufferOr(buffer, g_MpSetup.scenario, 3); scenarioWriteSave(buffer); savebufferOr(buffer, g_MpSetup.options, 21); for (i = 0; i < MAX_BOTS; i++) { savebufferOr(buffer, g_BotConfigsArray[i].type, 5); if (g_MpSetup.chrslots & (1 << (i + 4))) { savebufferOr(buffer, g_BotConfigsArray[i].difficulty, 3); } else { savebufferOr(buffer, BOTDIFF_DISABLED, 3); } savebufferOr(buffer, g_BotConfigsArray[i].base.mpheadnum, 7); if (g_BotConfigsArray[i].base.mpbodynum == 0xff) { s32 profilenum = mpFindBotProfile(g_BotConfigsArray[i].type, g_BotConfigsArray[i].difficulty); if (profilenum < 0 || profilenum >= ARRAYCOUNT(g_BotProfiles)) { profilenum = 0; } mpbodynum = g_BotProfiles[profilenum].body; } else { mpbodynum = g_BotConfigsArray[i].base.mpbodynum; } savebufferOr(buffer, mpbodynum, 7); savebufferOr(buffer, g_BotConfigsArray[i].base.team, 3); } for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) { savebufferOr(buffer, g_MpSetup.weapons[i], 7); } savebufferOr(buffer, g_MpSetup.timelimit, 6); savebufferOr(buffer, g_MpSetup.scorelimit, 7); savebufferOr(buffer, g_MpSetup.teamscorelimit, 9); for (i = 0; i < MAX_PLAYERS; i++) { savebufferOr(buffer, g_PlayerConfigsArray[i].base.team, 3); } } void mpsetupfileGetOverview(char *arg0, char *filename, u16 *numsims, u16 *stagenum, u16 *scenarionum) { struct savebuffer buffer; func0f0d5484(&buffer, arg0, 15); savebufferReadString(&buffer, filename, 0); *numsims = savebufferReadBits(&buffer, 4); *stagenum = savebufferReadBits(&buffer, 7); *scenarionum = savebufferReadBits(&buffer, 3); } s32 mpsetupfileSave(s32 device, s32 fileid, u16 deviceserial) { s32 ret; s32 newfileid; struct savebuffer buffer; if (device >= 0) { savebufferClear(&buffer); mpsetupfileSaveWad(&buffer); func0f0d54c4(&buffer); ret = pakSaveAtGuid(device, fileid, PAKFILETYPE_MPSETUP, buffer.bytes, &newfileid, 0); var80075bd0[1] = true; if (ret == 0) { g_MpSetup.fileguid.fileid = newfileid; g_MpSetup.fileguid.deviceserial = deviceserial; return 0; } g_FilemgrLastPakError = ret; return -1; } return -1; } s32 mpsetupfileLoad(s32 device, s32 fileid, u16 deviceserial) { s32 ret; struct savebuffer buffer; if (device >= 0) { savebufferClear(&buffer); ret = pakReadBodyAtGuid(device, fileid, buffer.bytes, 0); if (ret == 0) { g_MpSetup.fileguid.fileid = fileid; g_MpSetup.fileguid.deviceserial = deviceserial; mpsetupfileLoadWad(&buffer); func0f0d54c4(&buffer); return 0; } g_FilemgrLastPakError = ret; return -1; } return -1; } void func0f18e558(void) { s32 i; for (i = 0; i < ARRAYCOUNT(var800acc28); i++) { var800acc28[i] = NULL; } } struct modeldef *func0f18e57c(s32 index, s32 *headnum) { return var800acc28[index]; }