Files
perfect-dark/src/game/mplayer/mplayer.c
T
2023-05-28 00:48:58 +10:00

3944 lines
114 KiB
C

#include <ultra64.h>
#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
/*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 },
/*0x25*/ { WEAPON_MPSHIELD, 0, 0, 0, 0, 1, MPFEATURE_WEAPON_SHIELD, MODEL_CHRSHIELD, 256 },
/*0x26*/ { WEAPON_DISABLED },
};
/**
* 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;
g_PlayerConfigsArray[playernum].options = OPTION_LOOKAHEAD
| 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"
};
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);
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);
savebufferOr(buffer, g_PlayerConfigsArray[playernum].controlmode, 2);
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];
}