diff --git a/src/game/bot.c b/src/game/bot.c index 4b60db754..83ee3b728 100644 --- a/src/game/bot.c +++ b/src/game/bot.c @@ -1037,7 +1037,7 @@ u32 botPickupProp(struct prop *prop, struct chrdata *chr) if (weapon->weaponnum == WEAPON_BRIEFCASE2) { result = scenarioPickUpBriefcase(chr, prop); } else if (weapon->weaponnum == WEAPON_DATAUPLINK) { - result = chrGiveUplink(chr, prop); + result = scenarioPickUpUplink(chr, prop); } else { propPlayPickupSound(prop, weapon->weaponnum); qty = weaponGetPickupAmmoQty(weapon); @@ -1361,7 +1361,7 @@ glabel var7f1b3480nb .NB0f18ab4c: /* f18ab4c: 14a10005 */ bne $a1,$at,.NB0f18ab64 /* f18ab50: 02402025 */ or $a0,$s2,$zero -/* f18ab54: 0fc60612 */ jal chrGiveUplink +/* f18ab54: 0fc60612 */ jal scenarioPickUpUplink /* f18ab58: 8fa50080 */ lw $a1,0x80($sp) /* f18ab5c: 1000005b */ beqz $zero,.NB0f18accc /* f18ab60: 8fbf0054 */ lw $ra,0x54($sp) @@ -3191,7 +3191,7 @@ glabel var7f1b8ef0 /* f191f54: 8fbf0024 */ lw $ra,0x24($sp) /* f191f58: 51c00004 */ beqzl $t6,.L0f191f6c /* f191f5c: 8faf0058 */ lw $t7,0x58($sp) -/* f191f60: 0fc615c8 */ jal scenarioCallback14 +/* f191f60: 0fc615c8 */ jal scenarioTickChr /* f191f64: 02202025 */ or $a0,$s1,$zero /* f191f68: 8faf0058 */ lw $t7,0x58($sp) .L0f191f6c: @@ -3703,7 +3703,7 @@ glabel var7f1b8ef0 /* f191f54: 8fbf0024 */ lw $ra,0x24($sp) /* f191f58: 51c00004 */ beqzl $t6,.L0f191f6c /* f191f5c: 8faf0058 */ lw $t7,0x58($sp) -/* f191f60: 0fc615c8 */ jal scenarioCallback14 +/* f191f60: 0fc615c8 */ jal scenarioTickChr /* f191f64: 02202025 */ or $a0,$s1,$zero /* f191f68: 8faf0058 */ lw $t7,0x58($sp) .L0f191f6c: @@ -3902,7 +3902,7 @@ glabel var7f1b8ef0 // // if (g_Vars.lvframe60 >= 145) { // if (updateable) { -// scenarioCallback14(chr); +// scenarioTickChr(chr); // } // // if (updateable && !chrIsDead(chr)) { @@ -7335,11 +7335,7 @@ s32 botGetNumOpponentsInHill(struct chrdata *chr) if (g_MpAllChrPtrs[i]->prop->rooms[0] == g_ScenarioData.koh.hillrooms[0]) { s32 mpindex = func0f18d074(i); - if (mpindex < 4) { - loopmpchr = &g_PlayerConfigsArray[mpindex].base; - } else { - loopmpchr = &g_BotConfigsArray[mpindex - 4].base; - } + loopmpchr = MPCHR(mpindex); if (loopmpchr->team != mpchr->team) { countsperteam[loopmpchr->team]++; diff --git a/src/game/botinv.c b/src/game/botinv.c index 4251861ec..8cd6d8caa 100644 --- a/src/game/botinv.c +++ b/src/game/botinv.c @@ -1143,7 +1143,7 @@ void botinvDrop(struct chrdata *chr, s32 weaponnum, u8 dropall) objDrop(prop, true); if (item->type_weap.weapon1 == WEAPON_BRIEFCASE2) { - scenarioReleaseToken(chr, prop); + scenarioHandleDroppedToken(chr, prop); } } } diff --git a/src/game/chr/chr.c b/src/game/chr/chr.c index fb6ba0670..ee643cafb 100644 --- a/src/game/chr/chr.c +++ b/src/game/chr/chr.c @@ -8347,7 +8347,7 @@ Gfx *chrRender(struct prop *prop, Gfx *gdl, bool withalpha) } if (g_Vars.normmplayerisrunning) { - speb = scenarioHighlight(prop, colour); + speb = scenarioHighlightProp(prop, colour); } if (!speb) { diff --git a/src/game/dlights.c b/src/game/dlights.c index 1e94e420d..d26df3ac7 100644 --- a/src/game/dlights.c +++ b/src/game/dlights.c @@ -3641,7 +3641,7 @@ glabel var7f1a7dd4 /* f004104: 27a50080 */ addiu $a1,$sp,0x80 /* f004108: 27a6007c */ addiu $a2,$sp,0x7c /* f00410c: 27a70078 */ addiu $a3,$sp,0x78 -/* f004110: 0fc6192e */ jal scenarioCallback38 +/* f004110: 0fc6192e */ jal scenarioHighlightRoom /* f004114: afa20080 */ sw $v0,0x80($sp) /* f004118: 8fb80080 */ lw $t8,0x80($sp) /* f00411c: 8e790000 */ lw $t9,0x0($s3) @@ -4471,7 +4471,7 @@ glabel var7f1a7dd4 /* f003e4c: 27a50080 */ addiu $a1,$sp,0x80 /* f003e50: 27a6007c */ addiu $a2,$sp,0x7c /* f003e54: 27a70078 */ addiu $a3,$sp,0x78 -/* f003e58: 0fc60248 */ jal scenarioCallback38 +/* f003e58: 0fc60248 */ jal scenarioHighlightRoom /* f003e5c: afa20080 */ sw $v0,0x80($sp) /* f003e60: 8fb90080 */ lw $t9,0x80($sp) /* f003e64: 8e680000 */ lw $t0,0x0($s3) @@ -5085,7 +5085,7 @@ glabel var7f1a7ddc /* f004b7c: 27a70078 */ addiu $a3,$sp,0x78 /* f004b80: afa9003c */ sw $t1,0x3c($sp) /* f004b84: afaa0040 */ sw $t2,0x40($sp) -/* f004b88: 0fc6192e */ jal scenarioCallback38 +/* f004b88: 0fc6192e */ jal scenarioHighlightRoom /* f004b8c: afac0074 */ sw $t4,0x74($sp) /* f004b90: 3c1f8007 */ lui $ra,%hi(g_InCutscene) /* f004b94: 27ff0764 */ addiu $ra,$ra,%lo(g_InCutscene) diff --git a/src/game/game_00c490.c b/src/game/game_00c490.c index 91c2e0df8..55da8c1c0 100644 --- a/src/game/game_00c490.c +++ b/src/game/game_00c490.c @@ -2838,7 +2838,7 @@ void setupLoadFiles(s32 stagenum) total += setupCountCommandType(OBJTYPE_ESCASTEP); if (g_Vars.normmplayerisrunning) { - total += scenarioCallback08(); + total += scenarioNumProps(); } func0f011130(total, numchrs); @@ -3480,7 +3480,7 @@ void setupParseObjects(s32 stagenum) } if (g_Vars.normmplayerisrunning) { - scenarioReset(); + scenarioInitProps(); } obj = (struct defaultobj *)g_StageSetup.props; diff --git a/src/game/game_19aa80.c b/src/game/game_19aa80.c index 8bf7e597f..9a1ebcdc9 100644 --- a/src/game/game_19aa80.c +++ b/src/game/game_19aa80.c @@ -11,6 +11,7 @@ #include "game/training/training.h" #include "game/lang.h" #include "game/mplayer/mplayer.h" +#include "game/mplayer/scenarios.h" #include "game/pad.h" #include "bss.h" #include "lib/dma.h" diff --git a/src/game/lv.c b/src/game/lv.c index bde7316c9..c656eace2 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -429,7 +429,7 @@ void lvInit(s32 stagenum) func0f011110(); func0f0108d0(); setupLoadFiles(stagenum); - mpPrepareScenario(); + scenarioReset(); gvarsInitProps(); setupInit(); func0f00b510(); @@ -815,7 +815,7 @@ glabel var7f1b1f44nc /* f162a94: 00000000 */ sll $zero,$zero,0x0 /* f162a98: 0fc03a25 */ jal setupLoadFiles /* f162a9c: 8fa40020 */ lw $a0,0x20($sp) -/* f162aa0: 0fc6019d */ jal mpPrepareScenario +/* f162aa0: 0fc6019d */ jal scenarioReset /* f162aa4: 00000000 */ sll $zero,$zero,0x0 /* f162aa8: 0fc02fbc */ jal gvarsInitProps /* f162aac: 00000000 */ sll $zero,$zero,0x0 @@ -2242,7 +2242,7 @@ glabel var7f1b8e7cpf /* f16ad40: 00000000 */ nop /* f16ad44: 0fc18ca3 */ jal propsTick /* f16ad48: 02202025 */ move $a0,$s1 -/* f16ad4c: 0fc619c9 */ jal scenarioCallback14 +/* f16ad4c: 0fc619c9 */ jal scenarioTickChr /* f16ad50: 00002025 */ move $a0,$zero /* f16ad54: 0fc18104 */ jal propsSort /* f16ad58: 00000000 */ nop @@ -3655,7 +3655,7 @@ Gfx *lvRender(Gfx *gdl) roomsTick(); func0f004314(); propsTick(islastplayer); - scenarioCallback14(NULL); + scenarioTickChr(NULL); propsSort(); autoaimTick(); handsTickAttack(); @@ -4830,7 +4830,7 @@ glabel var7f1b1fd4nb /* f164a48: 00000000 */ sll $zero,$zero,0x0 /* f164a4c: 0fc188ac */ jal propsTick /* f164a50: 02202025 */ or $a0,$s1,$zero -/* f164a54: 0fc5ff30 */ jal scenarioCallback14 +/* f164a54: 0fc5ff30 */ jal scenarioTickChr /* f164a58: 00002025 */ or $a0,$zero,$zero /* f164a5c: 0fc17d1c */ jal propsSort /* f164a60: 00000000 */ sll $zero,$zero,0x0 diff --git a/src/game/menu/items.c b/src/game/menu/items.c index fe76fa45a..9eee87506 100644 --- a/src/game/menu/items.c +++ b/src/game/menu/items.c @@ -10879,11 +10879,7 @@ Gfx *menuRenderItemPlayerStats(Gfx *gdl, struct menurendercontext *context) s32 gap; s32 ypos = 0; - if (playernum < 4) { - mpchr = &g_PlayerConfigsArray[playernum].base; - } else { - mpchr = &g_BotConfigsArray[playernum - 4].base; - } + mpchr = MPCHR(playernum); gdl = func0f153628(gdl); @@ -11035,13 +11031,7 @@ Gfx *menuRenderItemPlayerStats(Gfx *gdl, struct menurendercontext *context) for (i = 0; i < 12; i++) { if (g_MpSetup.chrslots & (1 << i)) { - struct mpchrconfig *loopmpchr; - - if (i < 4) { - loopmpchr = &g_PlayerConfigsArray[i].base; - } else { - loopmpchr = &g_BotConfigsArray[i - 4].base; - } + struct mpchrconfig *loopmpchr = MPCHR(i); if (i != playernum) { // Name diff --git a/src/game/mplayer/ingame.c b/src/game/mplayer/ingame.c index f788c0ec4..d9d994f0a 100644 --- a/src/game/mplayer/ingame.c +++ b/src/game/mplayer/ingame.c @@ -30,7 +30,7 @@ struct menudialog g_MpEndscreenSavePlayerMenuDialog; s32 mpStatsForPlayerDropdownHandler(s32 operation, struct menuitem *item, union handlerdata *data) { - char *name; + struct mpchrconfig *mpchr; s32 v0; s32 v1; s32 a1; @@ -50,14 +50,10 @@ s32 mpStatsForPlayerDropdownHandler(s32 operation, struct menuitem *item, union for (a1 = 0; a1 < 12; a1++) { if (g_MpSetup.chrslots & (1 << a1)) { - if (a1 < 4) { - name = g_PlayerConfigsArray[a1].base.name; - } else { - name = g_BotConfigsArray[a1 - 4].base.name; - } + mpchr = MPCHR(a1); if (v0 == data->list.value) { - return (s32) name; + return (s32) mpchr->name; } v0++; @@ -216,16 +212,10 @@ char *mpMenuTextWeaponDescription(struct menuitem *item) char *mpMenuTitleStatsFor(struct menudialog *dialog) { - char *name; - - if (g_MpSelectedPlayersForStats[g_MpPlayerNum] < 4) { - name = g_PlayerConfigsArray[g_MpSelectedPlayersForStats[g_MpPlayerNum]].base.name; - } else { - name = g_BotConfigsArray[g_MpSelectedPlayersForStats[g_MpPlayerNum] - 4].base.name; - } + struct mpchrconfig *mpchr = MPCHR(g_MpSelectedPlayersForStats[g_MpPlayerNum]); // "Stats for %s" - sprintf(g_StringPointer, langGet(L_MPMENU_280), name); + sprintf(g_StringPointer, langGet(L_MPMENU_280), mpchr->name); return g_StringPointer; } diff --git a/src/game/mplayer/mplayer.c b/src/game/mplayer/mplayer.c index 2e0fcb3a6..6babd1cc6 100644 --- a/src/game/mplayer/mplayer.c +++ b/src/game/mplayer/mplayer.c @@ -246,13 +246,7 @@ void mpInit(void) } for (i = 0; i != 12; i++) { - struct mpchrconfig *mpchr; - - if (i < 4) { - mpchr = &g_PlayerConfigsArray[i].base; - } else { - mpchr = &g_BotConfigsArray[i - 4].base; - } + struct mpchrconfig *mpchr = MPCHR(i); func0f187838(mpchr); @@ -1121,11 +1115,7 @@ s32 mpCalculateTeamScore(s32 teamnum, s32 *result) for (i = 0; i < 12; i++) { if (g_MpSetup.chrslots & (1 << i)) { - if (i < 4) { - mpchr = &g_PlayerConfigsArray[i].base; - } else { - mpchr = &g_BotConfigsArray[i - 4].base; - } + mpchr = MPCHR(i); if (mpchr->team == teamnum) { scenarioCalculatePlayerScore(mpchr, i, &score, &deaths); @@ -7217,11 +7207,7 @@ void mpFindUnusedHeadAndBody(u8 *mpheadnum, u8 *mpbodynum) for (i = 0; i < 12; i++) { if (g_MpSetup.chrslots & (1 << i)) { - if (i < 4) { - mpchr = &g_PlayerConfigsArray[i].base; - } else { - mpchr = &g_BotConfigsArray[i - 4].base; - } + mpchr = MPCHR(i); if (mpchr->mpheadnum == trympheadnum) { available = false; @@ -7940,11 +7926,7 @@ struct mpchrconfig *mpGetChrConfigBySlotNum(s32 slot) for (i = 0; i < 12; i++) { if (g_MpSetup.chrslots & (1 << i)) { if (count == slot) { - if (i < 4) { - result = &g_PlayerConfigsArray[i].base; - } else { - result = &g_BotConfigsArray[i - 4].base; - } + result = MPCHR(i); break; } @@ -8002,13 +7984,7 @@ u8 mpFindUnusedTeamNum(void) for (i = 0; i < 12; i++) { if (g_MpSetup.chrslots & (1 << i)) { - struct mpchrconfig *mpchr; - - if (i < 4) { - mpchr = &g_PlayerConfigsArray[i].base; - } else { - mpchr = &g_BotConfigsArray[i - 4].base; - } + struct mpchrconfig *mpchr = MPCHR(i); if (mpchr->team == teamnum) { available = false; diff --git a/src/game/mplayer/scenarios.c b/src/game/mplayer/scenarios.c index f26e58e10..8b4d523e0 100644 --- a/src/game/mplayer/scenarios.c +++ b/src/game/mplayer/scenarios.c @@ -35,21 +35,44 @@ #include "data.h" #include "types.h" +/** + * There are six multiplayer scenarios: + * + * - Combat + * - Hold the Briefcase (HTB) + * - Capture the Case (CTC) + * - Hack that Mac (HTM) - labelled as Hacker Central + * - King of the Hill (KOH) + * - Pop a Cap (PAC) + * + * Each scenario registers callback functions for certain events. For example, + * code elsewhere in the game may call scenarioTick, then scenarioTick checks + * if the current scenario has a tick callback defined (eg. htbTick). If so, + * that scenario's callback is run. + */ + +struct mpscenario { + struct menudialog *optionsdialog; + void (*initfunc)(void); + s32 (*numpropsfunc)(void); + void (*initpropsfunc)(void); + void (*tickfunc)(void); + void (*tickchrfunc)(struct chrdata *chr); + Gfx *(*hudfunc)(Gfx *gdl); + void (*calcscorefunc)(struct mpchrconfig *mpchr, s32 chrnum, s32 *score, s32 *deaths); + Gfx *(*radarextrafunc)(Gfx *gdl); + bool (*radarchrfunc)(Gfx **gdl, struct prop *prop); + bool (*highlightpropfunc)(struct prop *prop, s32 *colour); + bool (*spawnfunc)(f32 arg0, struct coord *pos, s16 *rooms, struct prop *prop, f32 *arg4); + s32 (*maxteamsfunc)(void); + bool (*isroomhighlightedfunc)(s16 room); + void (*highlightroomfunc)(s16 room, s32 *arg1, s32 *arg2, s32 *arg3); + void *unk3c; // never hooked into nor fired + void (*readsavefunc)(struct savebuffer *buffer); + void (*writesavefunc)(struct savebuffer *buffer); +}; + struct scenariodata g_ScenarioData; -u32 var800ac254; - -struct weaponobj g_HtbTokenObj; - -struct weaponobj g_CtcTokenObj0; -struct weaponobj g_CtcTokenObj1; -struct weaponobj g_CtcTokenObj2; -struct weaponobj g_CtcTokenObj3; - -struct weaponobj g_HtmUplinkObj; - -struct mpscenario g_MpScenarios[6]; - -const char var7f1b8440[] = "CaptureTheBriefcaseAddBankPad -> Adding New Pad %d - Pad Id = %d-> Saving Pad\n"; s32 menuhandlerMpDisplayTeam(s32 operation, struct menuitem *item, union handlerdata *data) { @@ -120,2400 +143,102 @@ s32 menuhandlerMpSlowMotion(s32 operation, struct menuitem *item, union handlerd return 0; } -void scenarioHtbInit(void) -{ - g_ScenarioData.htb.token = NULL; -} +// Include the code files where each scenario implements its callbacks +#include "scenarios/combat.inc" +#include "scenarios/holdthebriefcase.inc" +#include "scenarios/capturethecase.inc" +#include "scenarios/kingofthehill.inc" +#include "scenarios/hackthatmac.inc" +#include "scenarios/popacap.inc" -void mpHtbAddPad(s16 padnum) -{ - struct scenariodata_htb *data = &g_ScenarioData.htb; - -#if VERSION >= VERSION_NTSC_1_0 - if (data->nextindex < ARRAYCOUNT(data->padnums)) -#endif +// Define the scenario callbacks +struct mpscenario g_MpScenarios[] = { { - data->padnums[data->nextindex] = padnum; - data->nextindex++; - } -} - -s32 scenarioHtbCallback08(void) -{ - return 1; -} - -void scenarioHtmRemoveAmmoCrateAtPad(s16 padnum) -{ - struct prop *prop = g_Vars.activeprops; - - while (prop) { - if (prop->type == PROPTYPE_OBJ) { - struct defaultobj *obj = prop->obj; - - if (obj->pad == padnum - && (obj->type == OBJTYPE_AMMOCRATE || obj->type == OBJTYPE_MULTIAMMOCRATE) - && obj->modelnum == MODEL_MULTI_AMMO_CRATE) { - obj->hidden |= OBJHFLAG_REAPABLE; - obj->hidden2 &= ~OBJH2FLAG_CANREGEN; - return; - } - } - - prop = prop->next; - } -} - -void func0f180078(void) -{ - s32 i; - - g_ScenarioData.htb.nextindex = 0; - - for (i = 0; i < ARRAYCOUNT(g_ScenarioData.htb.padnums); i++) { - g_ScenarioData.htb.padnums[i] = -1; - } -} - -struct menuitem g_MpCombatOptionsMenuItems[] = { - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" - { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_228, MPOPTION_NOPLAYERHIGHLIGHT, menuhandlerMpCheckboxOption }, // "No Player Highlight" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_229, MPOPTION_NOPICKUPHIGHLIGHT, menuhandlerMpCheckboxOption }, // "No Pickup Highlight" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" - { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + &g_MpCombatOptionsMenuDialog, + }, { + &g_HtbOptionsMenuDialog, + htbInit, + htbNumProps, + htbInitProps, + htbTick, + htbTickChr, + htbRenderHud, + htbCalculatePlayerScore, + htbRadarExtra, + htbRadarChr, + htbHighlightProp, + }, { + &g_HtmOptionsMenuDialog, + htmInit, + htmNumProps, + htmInitProps, + htmTick, + htmTickChr, + htmRenderHud, + htmCalculatePlayerScore, + htmRadarExtra, + htmRadarChr, + htmHighlightProp, + }, { + &g_PacOptionsMenuDialog, + pacInit, + NULL, + pacInitProps, + pacTick, + NULL, + pacRenderHud, + pacCalculatePlayerScore, + pacRadarExtra, + pacRadarChr, + pacHighlightProp, + }, { + &g_KohOptionsMenuDialog, + kohInit, + NULL, + kohInitProps, + kohTick, + NULL, + kohRenderHud, + kohCalculatePlayerScore, + kohRadarExtra, + NULL, + NULL, + NULL, + NULL, + kohIsRoomHighlighted, + kohHighlightRoom, + NULL, + kohReadSave, + kohWriteSave + }, { + &g_CtcOptionsMenuDialog, + ctcInit, + ctcNumProps, + ctcInitProps, + ctcTick, + ctcTickChr, + NULL, + ctcCalculatePlayerScore, + ctcRadarExtra, + ctcRadarChr, + ctcHighlightProp, + ctcChooseSpawnLocation, + ctcGetMaxTeams, + ctcIsRoomHighlighted, + ctcHighlightRoom, + }, }; -struct menudialog g_MpCombatOptionsMenuDialog = { - MENUDIALOGTYPE_DEFAULT, - L_MPMENU_215, // "Combat Options" - g_MpCombatOptionsMenuItems, - mpOptionsMenuDialog, - 0x00000010, - NULL, +struct mpscenariooverview g_MpScenarioOverviews[] = { + // name, short name, require feature, team only + { L_MPMENU_246, L_MPMENU_253, 0, false }, // "Combat", "Combat" + { L_MPMENU_247, L_MPMENU_254, MPFEATURE_SCENARIO_HTB, false }, // "Hold the Briefcase", "Briefcase" + { L_MPMENU_248, L_MPMENU_255, MPFEATURE_SCENARIO_HTM, false }, // "Hacker Central", "Hacker" + { L_MPMENU_249, L_MPMENU_256, MPFEATURE_SCENARIO_PAC, false }, // "Pop a Cap", "Pop" + { L_MPMENU_250, L_MPMENU_257, MPFEATURE_SCENARIO_KOH, true }, // "King of the Hill", "Hill" + { L_MPMENU_251, L_MPMENU_258, MPFEATURE_SCENARIO_CTC, true }, // "Capture the Case", "Capture" }; -struct menuitem g_MpBriefcaseOptionsMenuItems[] = { - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" - { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_237, MPOPTION_HTB_HIGHLIGHTBRIEFCASE, menuhandlerMpCheckboxOption }, // "Highlight Briefcase" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_238, MPOPTION_HTB_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" - { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, -}; - -struct menudialog g_MpBriefcaseOptionsMenuDialog = { - MENUDIALOGTYPE_DEFAULT, - L_MPMENU_216, // "Briefcase Options" - g_MpBriefcaseOptionsMenuItems, - mpOptionsMenuDialog, - 0x00000010, - NULL, -}; - -struct defaultobj *var800869ec = NULL; - -void scenarioHtbCreateToken(void) -{ - struct weaponobj template = { - 256, // extrascale - 0, // hidden2 - OBJTYPE_WEAPON, // type - MODEL_CHRBRIEFCASE, // modelnum - 0, // pad - OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, - OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, - 0, // flags3 - NULL, // prop - NULL, // model - 1, 0, 0, // realrot - 0, 1, 0, - 0, 0, 1, - 0, // hidden - NULL, // geo - NULL, // projectile - 0, // damage - 1000, // maxdamage - 0xff, 0xff, 0xff, 0x00, // shadecol - 0xff, 0xff, 0xff, 0x00, // nextcol - 0x0fff, // floorcol - 0, // tiles - WEAPON_BRIEFCASE2, // weaponnum - 0, // unk5d - 0, // unk5e - FUNC_PRIMARY, // gunfunc - 0, // fadeouttimer60 - -1, // dualweaponnum - -1, // timer240 - NULL, // dualweapon - }; - - struct prop *prop = g_Vars.activeprops; - struct defaultobj *obj; - s32 count = 0; - struct defaultobj *candidates[20]; - - // Build a list of candidate objects to replace. Consider only ammocrates. - // NTSC beta doesn't check the prop type, so it could potentially replace a - // player, bot, explosion or smoke. - while (prop && count < 20) { -#if VERSION >= VERSION_NTSC_1_0 - if (prop->type == PROPTYPE_OBJ) -#endif - { - obj = prop->obj; - - if (obj->type == OBJTYPE_MULTIAMMOCRATE) { - candidates[count] = obj; - count++; - } - } - - prop = prop->next; - } - - // Choose the candidate and remove it - if (count > 0) { - count = random() % count; - var800869ec = candidates[count]; - g_ScenarioData.htb.tokenpad = var800869ec->pad; - var800869ec->hidden |= OBJHFLAG_REAPABLE; - var800869ec->hidden2 |= OBJH2FLAG_CANREGEN; - } else if (g_ScenarioData.htb.nextindex > 0) { - g_ScenarioData.htb.tokenpad = g_ScenarioData.htb.padnums[random() % g_ScenarioData.htb.nextindex]; - } else { - g_ScenarioData.htb.tokenpad = 0; - } - - // Set up the token - g_HtbTokenObj = template; - g_HtbTokenObj.base.pad = g_ScenarioData.htb.tokenpad; - - weaponAssignToHome(&g_HtbTokenObj, 999); - - g_HtbTokenObj.base.hidden2 &= ~OBJH2FLAG_CANREGEN; - - g_ScenarioData.htb.token = g_HtbTokenObj.base.prop; - - if (g_ScenarioData.htb.token) { - g_ScenarioData.htb.token->forcetick = true; - } -} - -void scenarioHtbReset(void) -{ - var800869ec = NULL; - scenarioHtbCreateToken(); -} - -void scenarioHtbTick(void) -{ - s32 i; - u32 prevplayernum = g_Vars.currentplayernum; - struct prop *prop; - - if (var800869ec && var800869ec->prop) { - if (g_ScenarioData.htb.token == NULL || g_ScenarioData.htb.token->type != PROPTYPE_WEAPON) { - var800869ec = NULL; - } else { - var800869ec->prop->timetoregen = PALDOWN(1200); - } - } - - g_ScenarioData.htb.token = NULL; - - // Check if briefcase is on the ground - prop = g_Vars.activeprops; - - while (prop) { - if (prop->type == PROPTYPE_WEAPON) { - struct weaponobj *weapon = prop->weapon; - - if (weapon->weaponnum == WEAPON_BRIEFCASE2) { - g_ScenarioData.htb.token = prop; - } - } - - prop = prop->next; - } - - // Check if a player is holding it - if (g_ScenarioData.htb.token == NULL) { - for (i = 0; i < PLAYERCOUNT(); i++) { - setCurrentPlayerNum(i); - - if (invHasBriefcase()) { - g_ScenarioData.htb.token = g_Vars.currentplayer->prop; - break; - } - } - } - - setCurrentPlayerNum(prevplayernum); - - // Check if a simulant is holding it - if (g_ScenarioData.htb.token == NULL) { - for (i = PLAYERCOUNT(); i < g_MpNumChrs; i++) { -#if VERSION >= VERSION_NTSC_1_0 - if (g_MpAllChrPtrs[i]->prop && g_MpAllChrPtrs[i]->aibot->hasbriefcase) -#else - if (g_MpAllChrPtrs[i]->aibot->hasbriefcase) -#endif - { - g_ScenarioData.htb.token = g_MpAllChrPtrs[i]->prop; - break; - } - } - } - - if (g_ScenarioData.htb.token == NULL) { - scenarioHtbCreateToken(); - } - - if (g_ScenarioData.htb.token == NULL) { - g_ScenarioData.htb.pos.x = 0; - g_ScenarioData.htb.pos.y = 0; - g_ScenarioData.htb.pos.z = 0; - } else { - struct coord *pos = &g_ScenarioData.htb.pos; - pos->x = g_ScenarioData.htb.token->pos.x; - pos->y = g_ScenarioData.htb.token->pos.y; - pos->z = g_ScenarioData.htb.token->pos.z; - } -} - -void scenarioHtbCallback14(struct chrdata *chr) -{ - if (chr) { - if (chr->aibot->hasbriefcase) { - chr->aibot->unk0a0 += g_Vars.lvupdate240; - - if (chr->aibot->unk0a0 >= PALDOWN(7200)) { - sndStart(var80095200, SFX_MP_SCOREPOINT, NULL, -1, -1, -1, -1, -1); - g_MpAllChrConfigPtrs[mpPlayerGetIndex(chr)]->numpoints++; - chr->aibot->unk0a0 = 0; - } - } else { - chr->aibot->unk0a0 = 0; - } - } else { - if (invHasBriefcase()) { - g_Vars.currentplayerstats->tokenheldtime += g_Vars.lvupdate240; - - if (g_Vars.currentplayerstats->tokenheldtime >= PALDOWN(7200)) { - sndStart(var80095200, SFX_MP_SCOREPOINT, NULL, -1, -1, -1, -1, -1); - g_MpAllChrConfigPtrs[g_Vars.currentplayernum]->numpoints++; - hudmsgCreateWithFlags(langGet(L_MPWEAPONS_024), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); // "1 Point!" - g_Vars.currentplayerstats->tokenheldtime = 0; - } - } else { - g_Vars.currentplayerstats->tokenheldtime = 0; - } - } -} - -/** - * @bug: In NTSC Final, the calculation of mins and subsequent subtraction from - * time240 should use 60 * 240 instead of 30 * 240. This has no noticeable - * effect unless the score duration is increased to above 30 seconds. - * - * PAL recognises that mins will always be 0 and simplifies the calculation. - */ -Gfx *scenarioHtbRenderHud(Gfx *gdl) -{ - s32 time240; - s32 mins; - s32 secs; - s32 textwidth; - s32 textheight; - s32 x; - s32 y; - char text[64]; - - if (invHasBriefcase()) { - x = viGetViewLeft() + viGetViewWidth() / 2; - y = viGetViewTop() + 10; - -#if VERSION >= VERSION_PAL_FINAL - time240 = (30 * 200) - g_Vars.currentplayerstats->tokenheldtime; - secs = (time240 + 199) / 200; - sprintf(text, "%d:%02d", 0, secs); -#else - time240 = (30 * 240) - g_Vars.currentplayerstats->tokenheldtime; - mins = time240 / (30 * 240); - time240 -= (30 * 240) * mins; - secs = (time240 + 239) / 240; - sprintf(text, "%d:%02d", mins, secs); -#endif - - gdl = func0f153628(gdl); - textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); - - x -= textwidth / 2; - textwidth += x; - textheight += y; - -#if VERSION >= VERSION_NTSC_1_0 - gdl = func0f153990(gdl, x, y, textwidth, textheight); - gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0xa0, viGetWidth(), viGetHeight(), 0, 0); -#else - gdl = func0f153858(gdl, &x, &y, &textwidth, &textheight); - gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0x88, viGetWidth(), viGetHeight(), 0, 0); -#endif - - gdl = func0f153780(gdl); - } - - return gdl; -} - -void scenarioHtbCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) -{ - struct mpchrconfig *loopmpchr; - s32 i; - - *score = 0; - *score += mpchr->numpoints; - - if (g_MpSetup.options & MPOPTION_KILLSSCORE) { - for (i = 0; i != MAX_MPCHRS; i++) { - if (i == mpchrnum) { - *score -= mpchr->killcounts[i]; - } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - if (i < 4) { - loopmpchr = &g_PlayerConfigsArray[i].base; - } else { - loopmpchr = &g_BotConfigsArray[i - 4].base; - } - - if (loopmpchr->team == mpchr->team) { - *score -= mpchr->killcounts[i]; - } else { - *score += mpchr->killcounts[i]; - } - } else { - *score += mpchr->killcounts[i]; - } - } - } - - *deaths = mpchr->numdeaths; -} - -Gfx *scenarioHtbRadar(Gfx *gdl) -{ - if ((g_MpSetup.options & MPOPTION_HTB_SHOWONRADAR) && - g_ScenarioData.htb.token != NULL && - g_ScenarioData.htb.token->type != PROPTYPE_PLAYER && - g_ScenarioData.htb.token->type != PROPTYPE_CHR) { - struct coord dist; - dist.x = g_ScenarioData.htb.pos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = g_ScenarioData.htb.pos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = g_ScenarioData.htb.pos.z - g_Vars.currentplayer->prop->pos.z; - gdl = radarDrawDot(gdl, g_ScenarioData.htb.token, &dist, 0xff0000, 0, 1); - } - - return gdl; -} - -bool scenarioHtbRadar2(Gfx **gdl, struct prop *prop) -{ - if ((g_MpSetup.options & MPOPTION_HTB_SHOWONRADAR) && - g_ScenarioData.htb.token && - prop == g_ScenarioData.htb.token) { - if (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR) { - struct coord dist; - dist.x = prop->pos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = prop->pos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = prop->pos.z - g_Vars.currentplayer->prop->pos.z; - - if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - u32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; - *gdl = radarDrawDot(*gdl, g_ScenarioData.htb.token, &dist, colour, 0, 1); - } else { - *gdl = radarDrawDot(*gdl, g_ScenarioData.htb.token, &dist, 0xff0000, 0, 1); - } - - return true; - } - } - - return false; -} - -bool scenarioHtbHighlight(struct prop *prop, s32 *colour) -{ - if ((g_MpSetup.options & MPOPTION_HTB_HIGHLIGHTBRIEFCASE) && prop == g_ScenarioData.htb.token) { - colour[0] = 0; - colour[1] = 0xff; - colour[2] = 0; - colour[3] = 0x40; - - return true; - } - - return false; -} - -void scenarioCtcInit(void) -{ - s32 i, j, k; - g_MpSetup.options |= MPOPTION_TEAMSENABLED; - - for (i = 0; i < 4; i++) { - s32 j; - g_ScenarioData.ctc.spawnpadsperteam[i].homepad = i; - g_ScenarioData.ctc.spawnpadsperteam[i].numspawnpads = 0; - - for (j = 0; j < 6; j++) { - g_ScenarioData.ctc.spawnpadsperteam[i].spawnpads[j] = -1; - } - } - - for (i = 0; i != 4; i++) { - g_ScenarioData.ctc.playercountsperteam[i] = 0; - g_ScenarioData.ctc.teamindexes[i] = -1; - } - - for (k = 0; k < MAX_MPCHRS; k++) { - if (g_MpSetup.chrslots & (1 << k)) { - struct mpchrconfig *basedata; - - if (k < 4) { - basedata = &g_PlayerConfigsArray[k].base; - } else { - basedata = &g_BotConfigsArray[k - 4].base; - } - - while (basedata->team >= scenarioGetMaxTeams()) { - basedata->team -= scenarioGetMaxTeams(); - } - } - } -} - -s32 scenarioCtcCallback08(void) -{ - return 4; -} - -void scenarioCtcTick(void) -{ - // empty -} - -void scenarioCtcCallback14(struct chrdata *chr) -{ - if (chr); -} - -struct menuitem g_MpCaptureOptionsMenuItems[] = { - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" - { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_236, MPOPTION_CTC_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" - { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, -}; - -struct menudialog g_MpCaptureOptionsMenuDialog = { - MENUDIALOGTYPE_DEFAULT, - L_MPMENU_220, // "Capture Options" - g_MpCaptureOptionsMenuItems, - mpOptionsMenuDialog, - 0x00000010, - NULL, -}; - -void scenarioCtcReset(void) -{ - struct mpchrconfig *mpchr; - struct weaponobj *tmp; - s32 mpindex; - u32 stack; - bool teamsdone[4]; - - struct weaponobj template = { - 256, // extrascale - 0, // hidden2 - OBJTYPE_WEAPON, // type - MODEL_CHRBRIEFCASE, // modelnum - 0, // pad - OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, - OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, - 0, // flags3 - NULL, // prop - NULL, // model - 1, 0, 0, // realrot - 0, 1, 0, - 0, 0, 1, - 0, // hidden - NULL, // geo - NULL, // projectile - 0, // damage - 1000, // maxdamage - 0xff, 0xff, 0xff, 0x00, // shadecol - 0xff, 0xff, 0xff, 0x00, // nextcol - 0x0fff, // floorcol - 0, // tiles - WEAPON_BRIEFCASE2, // weaponnum - 0, // unk5d - 0, // unk5e - FUNC_PRIMARY, // gunfunc - 0, // fadeouttimer60 - -1, // dualweaponnum - -1, // timer240 - NULL, // dualweapon - }; - - s32 i; - s32 j; - s32 k; - - for (i = 0; i < 4; i++) { - for (j = 0; j < 6; j++) { - } - } - - for (i = 0; i < 4; i++) { - teamsdone[i] = false; - g_ScenarioData.ctc.playercountsperteam[i] = 0; - } - - for (i = 0; i != 4; i++) { - do { - g_ScenarioData.ctc.teamindexes[i] = random() % 4; - } while (teamsdone[g_ScenarioData.ctc.teamindexes[i]]); - - teamsdone[g_ScenarioData.ctc.teamindexes[i]] = true; - } - - for (k = 0; k < 12; k++) { - if (g_MpSetup.chrslots & (1 << k)) { - if (k < 4) { - mpchr = &g_PlayerConfigsArray[k].base; - } else { - mpchr = &g_BotConfigsArray[k - 4].base; - } - - while (mpchr->team >= scenarioGetMaxTeams()) { - mpchr->team -= scenarioGetMaxTeams(); - } - -#if VERSION >= VERSION_NTSC_1_0 - mpindex = func0f18d0e8(k); - - if (mpindex >= 0) { - struct chrdata *chr = mpGetChrFromPlayerIndex(mpindex); - - if (chr) { - chr->team = 1 << mpchr->team; - } - } -#else - if (func0f18d0e8(k) >= 0) { - struct chrdata *chr = mpGetChrFromPlayerIndex(func0f18d0e8(k)); - - if (chr) { - chr->team = 1 << mpchr->team; - } - } -#endif - - g_ScenarioData.ctc.playercountsperteam[mpchr->team]++; - } - } - - for (i = 0; i < 4; i++) { - if (g_ScenarioData.ctc.playercountsperteam[i] == 0) { - g_ScenarioData.ctc.teamindexes[i] = -1; - } - } - - for (i = 0; i < 4; i++) { - // empty - } - - for (i = 0; i < 4; i++) { - g_ScenarioData.ctc.tokens[i] = NULL; - } - - for (i = 0; i < 4; i++) { - g_ScenarioData.ctc.baserooms[i] = -1; - } - - if (g_ScenarioData.ctc.playercountsperteam[0] != 0) { - g_CtcTokenObj0 = template; - tmp = &g_CtcTokenObj0; - tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[0]].homepad; - - weaponAssignToHome(tmp, 1000); - - g_ScenarioData.ctc.tokens[0] = tmp->base.prop; - - tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; - tmp->team = 0; - - g_ScenarioData.ctc.baserooms[0] = g_ScenarioData.ctc.tokens[0]->rooms[0]; - } - - if (g_ScenarioData.ctc.playercountsperteam[1] != 0) { - g_CtcTokenObj1 = template; - tmp = &g_CtcTokenObj1; - tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[1]].homepad; - - weaponAssignToHome(tmp, 1001); - - g_ScenarioData.ctc.tokens[1] = tmp->base.prop; - - tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; - tmp->team = 1; - - g_ScenarioData.ctc.baserooms[1] = g_ScenarioData.ctc.tokens[1]->rooms[0]; - } - - if (g_ScenarioData.ctc.playercountsperteam[2] != 0) { - g_CtcTokenObj2 = template; - tmp = &g_CtcTokenObj2; - tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[2]].homepad; - - weaponAssignToHome(tmp, 1002); - - g_ScenarioData.ctc.tokens[2] = tmp->base.prop; - - tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; - tmp->team = 2; - - g_ScenarioData.ctc.baserooms[2] = g_ScenarioData.ctc.tokens[2]->rooms[0]; - } - - if (g_ScenarioData.ctc.playercountsperteam[3] != 0) { - g_CtcTokenObj3 = template; - tmp = &g_CtcTokenObj3; - tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[3]].homepad; - - weaponAssignToHome(tmp, 1003); - - g_ScenarioData.ctc.tokens[3] = tmp->base.prop; - - tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; - tmp->team = 3; - - g_ScenarioData.ctc.baserooms[3] = g_ScenarioData.ctc.tokens[3]->rooms[0]; - } - - for (i = 0; i < 4; i++) { - if (g_ScenarioData.ctc.playercountsperteam[i] && g_ScenarioData.ctc.baserooms[i] != -1) { - roomSetLighting(g_ScenarioData.ctc.baserooms[i], 5, 0, 0, 0); - } - } -} - -void scenarioCtcCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) -{ - struct mpchrconfig *loopmpchr; - s32 i; - - *score = 0; - *score += mpchr->numpoints * 3; - - if (g_MpSetup.options & MPOPTION_KILLSSCORE) { - for (i = 0; i != MAX_MPCHRS; i++) { - if (i == mpchrnum) { - *score -= mpchr->killcounts[i]; - } else { - if (i < 4) { - loopmpchr = &g_PlayerConfigsArray[i].base; - } else { - loopmpchr = &g_BotConfigsArray[i - 4].base; - } - - if (loopmpchr->team == mpchr->team) { - *score -= mpchr->killcounts[i]; - } else { - *score += mpchr->killcounts[i]; - } - } - } - } - - *deaths = mpchr->numdeaths; -} - -Gfx *scenarioCtcRadar(Gfx *gdl) -{ - if (g_MpSetup.options & MPOPTION_CTC_SHOWONRADAR) { - s32 i; - - for (i = 0; i < scenarioGetMaxTeams(); i++) { - if (g_ScenarioData.ctc.tokens[i] && - g_ScenarioData.ctc.tokens[i]->type != PROPTYPE_CHR && - g_ScenarioData.ctc.tokens[i]->type != PROPTYPE_PLAYER) { - struct coord dist; - dist.x = g_ScenarioData.ctc.tokens[i]->pos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = g_ScenarioData.ctc.tokens[i]->pos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = g_ScenarioData.ctc.tokens[i]->pos.z - g_Vars.currentplayer->prop->pos.z; - gdl = radarDrawDot(gdl, g_ScenarioData.ctc.tokens[i], &dist, g_TeamColours[i], 0, 1); - } - } - } - - return gdl; -} - -bool scenarioCtcRadar2(Gfx **gdl, struct prop *prop) -{ - s32 i; - - if (g_MpSetup.options & MPOPTION_CTC_SHOWONRADAR) { - for (i = 0; i < scenarioGetMaxTeams(); i++) { - if (prop == g_ScenarioData.ctc.tokens[i] && - (g_ScenarioData.ctc.tokens[i]->type == PROPTYPE_CHR || g_ScenarioData.ctc.tokens[i]->type == PROPTYPE_PLAYER)) { - struct coord dist; - s32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; - dist.x = g_ScenarioData.ctc.tokens[i]->pos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = g_ScenarioData.ctc.tokens[i]->pos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = g_ScenarioData.ctc.tokens[i]->pos.z - g_Vars.currentplayer->prop->pos.z; - *gdl = radarDrawDot(*gdl, g_ScenarioData.ctc.tokens[i], &dist, - g_TeamColours[i], colour, 1); - return true; - } - } - } - - return false; -} - -bool scenarioCtcHighlight(struct prop *prop, s32 *colour) -{ - struct defaultobj *obj = prop->obj; - - if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) { - if (obj->type == OBJTYPE_WEAPON) { - struct weaponobj *weapon = prop->weapon; - - if (weapon->weaponnum == WEAPON_BRIEFCASE2) { - u32 teamcolour = g_TeamColours[weapon->team]; - - colour[0] = teamcolour >> 24 & 0xff; - colour[1] = teamcolour >> 16 & 0xff; - colour[2] = teamcolour >> 8 & 0xff; - colour[3] = 75; - - return true; - } - } - } - - return false; -} - -void mpCtcAddPad(s32 *cmd) -{ - s32 i; - - if (cmd[0] == INTROCMD_CASE) { - g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].homepad = cmd[2]; - } - - if (cmd[0] == INTROCMD_CASERESPAWN) { - for (i = 0; i != ARRAYCOUNT(g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].spawnpads); i++) { - if (g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].spawnpads[i] == -1) { - g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].spawnpads[i] = cmd[2]; - g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].numspawnpads++; - return; - } - } - } -} - -bool scenarioCtcChooseSpawnLocation(f32 arg0, struct coord *pos, s16 *rooms, struct prop *prop, f32 *arg4) -{ - struct chrdata *chr = prop->chr; - s32 index = radarGetTeamIndex(chr->team); - - if (g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[index]].numspawnpads > 0) { - *arg4 = playerChooseSpawnLocation(arg0, pos, rooms, prop, - g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[index]].spawnpads, - g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[index]].numspawnpads); - return true; - } - - return false; -} - -s32 scenarioCtcGetMaxTeams(void) -{ - return 4; -} - -bool scenarioCtcIsRoomHighlighted(s16 room) -{ - s32 i; - - for (i = 0; i < 4; i++) { - if (g_ScenarioData.ctc.baserooms[i] == room && g_ScenarioData.ctc.teamindexes[i] != -1) { - return true; - } - } - - return false; -} - -void scenarioCtcCallback38(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3) -{ - s32 i; - - for (i = 0; i < 4; i++) { - if (g_ScenarioData.ctc.baserooms[i] == roomnum) { - u32 colour = g_TeamColours[i]; - f32 a = *arg1; - f32 b = *arg2; - f32 c = *arg3; - - a *= (s32)((colour >> 24 & 0xff) + 0xff) * (1.0f / 512.0f); - b *= (s32)((colour >> 16 & 0xff) + 0xff) * (1.0f / 512.0f); - c *= (s32)((colour >> 8 & 0xff) + 0xff) * (1.0f / 512.0f); - - *arg1 = a; - *arg2 = b; - *arg3 = c; - return; - } - } -} - -s32 menuhandlerMpHillTime(s32 operation, struct menuitem *item, union handlerdata *data) -{ - switch (operation) { - case MENUOP_GETSLIDER: - data->slider.value = g_Vars.mphilltime; - break; - case MENUOP_SET: - g_Vars.mphilltime = (u8)data->slider.value; - break; - case MENUOP_GETSLIDERLABEL: - sprintf(data->slider.label, langGet(L_MPWEAPONS_023), data->slider.value + 10); // "%ds/Point" - break; - } - - return 0; -} - -void scenarioKohReadSave(struct savebuffer *buffer) -{ - g_Vars.mphilltime = savebufferReadBits(buffer, 8); -} - -void scenarioKohWriteSave(struct savebuffer *buffer) -{ - savebufferOr(buffer, g_Vars.mphilltime, 8); -} - -void scenarioKohInit(void) -{ - s32 i; - - g_MpSetup.options |= MPOPTION_TEAMSENABLED; - g_ScenarioData.koh.hillindex = -1; - g_ScenarioData.koh.hillcount = 0; - g_ScenarioData.koh.unk00 = 0; - g_ScenarioData.koh.occupiedteam = -1; - g_ScenarioData.koh.elapsed240 = 0; - g_ScenarioData.koh.hillrooms[0] = -1; - g_ScenarioData.koh.hillrooms[1] = -1; - g_ScenarioData.koh.hillpos.x = 0; - g_ScenarioData.koh.hillpos.y = 0; - g_ScenarioData.koh.hillpos.z = 0; - g_ScenarioData.koh.colourfracr = 0.25; - g_ScenarioData.koh.colourfracg = 1; - g_ScenarioData.koh.colourfracb = 0.25; - - for (i = 0; i < 9; i++) { - g_ScenarioData.koh.hillpads[i] = -1; - } -} - -void scenarioKohReset(void) -{ - s16 pad_id = 0; - struct pad pad; - - if (g_ScenarioData.koh.hillcount > 1) { - g_ScenarioData.koh.hillindex = random() % g_ScenarioData.koh.hillcount; - pad_id = g_ScenarioData.koh.hillpads[g_ScenarioData.koh.hillindex]; - } else { - // @bug: If a stage setup file only has one hill, pad_id is not assigned - // so it will always use the room that contains pad zero. - g_ScenarioData.koh.hillindex = 0; - } - - padUnpack(pad_id, PADFIELD_POS | PADFIELD_ROOM, &pad); - g_ScenarioData.koh.hillrooms[0] = pad.room; - g_ScenarioData.koh.hillrooms[1] = -1; - g_ScenarioData.koh.hillpos.x = pad.pos.x; - g_ScenarioData.koh.hillpos.y = pad.pos.y; - g_ScenarioData.koh.hillpos.z = pad.pos.z; - g_ScenarioData.koh.hillpos.y = cd0002a36c(&g_ScenarioData.koh.hillpos, &g_ScenarioData.koh.hillrooms[0], 0, 0); - g_ScenarioData.koh.movehill = false; - roomSetLighting(g_ScenarioData.koh.hillrooms[0], LIGHTOP_5, 0, 0, 0); -} - -/** - * A match for this function has only been possible by making heavy reuse of - * variables, but this impacts readability significantly. - * - * To make this code readable, constants have been used to map appropriate names - * to the underlying variable. - */ -void scenarioKohTick(void) -{ - s32 i; - s32 hillteam; - s32 s1; - s32 s2; - s32 numchrsinhill; - s32 dualoccupancy; - s32 s0; - s32 previndex; - f32 targetr; - f32 targetg; - f32 targetb; - char text[64]; - s32 teamsinhill[8]; - struct pad pad; - struct prop *chrsinhill[12]; - struct prop *prop; - struct chrdata *chr; - s32 padnum; - s32 teamindex; - -#define hillteam s0 -#define inhill s1 -#define mostchrs s1 -#define playernum1 s1 -#define prevplayernum1 s1 -#define numteamsinhill s2 -#define prevplayernum2 s2 -#define playernum2 s2 - - if (g_ScenarioData.koh.hillindex == -1) { - return; - } - - dualoccupancy = 0; - - if (g_ScenarioData.koh.movehill) { - // The hill is moving, but first it needs to be returned to the natural - // colour. This is done using a fade over several frames. - g_ScenarioData.koh.occupiedteam = -1; - g_ScenarioData.koh.elapsed240 = 0; - - targetr = 1; - targetg = 1; - targetb = 1; - - if (g_ScenarioData.koh.colourfracr >= .95f - && g_ScenarioData.koh.colourfracg >= .95f - && g_ScenarioData.koh.colourfracb >= .95f) { - // The old hill is now "natural enough" to set it back to full - // natural colour and actually choose a new hill. - roomSetLighting(g_ScenarioData.koh.hillrooms[0], 0, 0, 0, 0); - - // Choose the new hill. Note that hillcount refers to the number of - // hill options, which is always >= 2. - padnum = 0; - - if (g_ScenarioData.koh.hillcount >= 2) { - previndex = g_ScenarioData.koh.hillindex; - - do { - g_ScenarioData.koh.hillindex = random() % g_ScenarioData.koh.hillcount; - } while (g_ScenarioData.koh.hillindex == previndex); - - padnum = g_ScenarioData.koh.hillpads[g_ScenarioData.koh.hillindex]; - } else { - g_ScenarioData.koh.hillindex = 0; - } - - padUnpack(padnum, PADFIELD_POS | PADFIELD_ROOM, &pad); - - g_ScenarioData.koh.hillrooms[0] = pad.room; - g_ScenarioData.koh.hillrooms[1] = -1; - - g_ScenarioData.koh.hillpos.x = pad.pos.x; - g_ScenarioData.koh.hillpos.y = pad.pos.y; - g_ScenarioData.koh.hillpos.z = pad.pos.z; - - g_ScenarioData.koh.hillpos.y = cd0002a36c(&g_ScenarioData.koh.hillpos, g_ScenarioData.koh.hillrooms, NULL, NULL); - - roomSetLighting(g_ScenarioData.koh.hillrooms[0], 5, 0, 0, 0); - - g_ScenarioData.koh.occupiedteam = -1; - g_ScenarioData.koh.elapsed240 = 0; - g_ScenarioData.koh.movehill = false; - } - } else { - // The hill is not moving on this frame - // Build an array of chr props who are in the hill - numchrsinhill = 0; - prop = g_Vars.activeprops; - - while (prop) { - if (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR) { - inhill = false; - - if (prop->rooms[0] == g_ScenarioData.koh.hillrooms[0]) { - inhill = true; - } - - if (inhill) { - chr = prop->chr; - - if (!chrIsDead(chr)) { - chrsinhill[numchrsinhill] = prop; - numchrsinhill++; - } - } - } - - prop = prop->next; - } - - // Use the chrshillhill array to build an array of all teams who have - // chrs in the hill. During development, this array likely stored a - // count of that team's chrs but was later changed to just be 0 or 1 - // to denote if they have any chrs in the hill. - for (s0 = 0; s0 < 8; s0++) { - teamsinhill[s0] = 0; - } - - for (s0 = 0, numteamsinhill = 0; s0 < numchrsinhill; s0++) { - chr = chrsinhill[s0]->chr; - teamindex = radarGetTeamIndex(chr->team); - - if (teamsinhill[teamindex] == 0) { - numteamsinhill++; - teamsinhill[teamindex] = 1; - } - } - - if (numteamsinhill == 0) { - g_ScenarioData.koh.occupiedteam = -1; - g_ScenarioData.koh.elapsed240 = 0; - } else { - if (numteamsinhill == 1) { - // Set hillteam for later - for (hillteam = 0; hillteam < 8; hillteam++) { - if (teamsinhill[hillteam]) { - break; - } - } - } else { - // There are multiple teams in the hill. - // This code attempts to filter the teamsinhill array to only - // those which have the most chrs, but the teamsinhill array - // only contains values 0 or 1 so it effectively does nothing. - mostchrs = 0; - - for (s0 = 0; s0 < 8; s0++) { - if (teamsinhill[s0] > mostchrs) { - mostchrs = teamsinhill[s0]; - } - } - - for (s0 = 0; s0 < 8; s0++) { - if (teamsinhill[s0] != mostchrs) { - teamsinhill[s0] = false; - } - } - - // Count the number of teams who are tied for the most chrs in - // the hill. Or rather, because the teamsinhill array only - // contains 0 or 1 values, this is just recounting the number of - // teams who have presence in the hill. - for (s0 = 0; s0 < 8; s0++) { - if (teamsinhill[s0]) { - dualoccupancy++; - } - } - - dualoccupancy = dualoccupancy >= 2 ? true : false; - - // Set the hillteam to whoever was holding it previously - // so the hill remains the same colour - for (hillteam = 0; hillteam < 8; hillteam++) { - if (teamsinhill[hillteam] && hillteam == g_ScenarioData.koh.occupiedteam) { - break; - } - } - - if (hillteam == 8) { - // This happens if the controlling team leaves the hill - // and there are two other teams still in the hill. - // The hill goes green until one team holds it exclusively. - g_ScenarioData.koh.occupiedteam = -1; - hillteam = -1; - } - } - - // At this point we know there is a team in the hill on this frame. - // So if these don't match then the hill is turning into a team - // colour rather than going green. - if (hillteam != g_ScenarioData.koh.occupiedteam) { - sndStart(var80095200, SFX_MP_HILLENTERED, 0, -1, -1, -1, -1, -1); - - g_ScenarioData.koh.occupiedteam = hillteam; - g_ScenarioData.koh.elapsed240 = 0; - - // "%has captured the Hill!" - sprintf(text, langGet(L_MPWEAPONS_022), &g_BossFile.teamnames[hillteam]); - - prevplayernum2 = g_Vars.currentplayernum; - - for (playernum1 = 0; playernum1 < PLAYERCOUNT(); playernum1++) { - setCurrentPlayerNum(playernum1); - - chr = g_Vars.currentplayer->prop->chr; - - if (radarGetTeamIndex(chr->team) == g_ScenarioData.koh.occupiedteam) { - // "We have the Hill!" - hudmsgCreateWithFlags(langGet(L_MPWEAPONS_021), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); - } else { - hudmsgCreateWithFlags(text, HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); - } - } - - setCurrentPlayerNum(prevplayernum2); - } else { - // A team is remaining in the hill. - // Only tick the hill timer if they have exclusive occupancy. - if (!dualoccupancy) { - g_ScenarioData.koh.elapsed240 += g_Vars.lvupdate240; - - if (g_ScenarioData.koh.elapsed240 >= g_Vars.mphilltime * PALDOWN(240) + PALDOWN(2400)) { - // Scored a point - sndStart(var80095200, SFX_MP_SCOREPOINT, 0, -1, -1, -1, -1, -1); - - // @bug: There is no check for a chr being dead here. - // If a player dies in the hill and waits on the - // "press start" screen while their team mate scores the - // hill, the dead player will always be awarded a point. - for (playernum2 = 0; playernum2 < g_MpNumChrs; playernum2++) { - if (radarGetTeamIndex(g_MpAllChrPtrs[playernum2]->team) == g_ScenarioData.koh.occupiedteam) { - prop = g_MpAllChrPtrs[playernum2]->prop; - - if (prop->rooms[0] == g_ScenarioData.koh.hillrooms[0]) { - g_MpAllChrConfigPtrs[playernum2]->numpoints++; - } - } - } - - prevplayernum1 = g_Vars.currentplayernum; - - for (playernum2 = 0; playernum2 < g_MpNumChrs; playernum2++) { - if (g_MpAllChrPtrs[playernum2]->aibot == NULL - && radarGetTeamIndex(g_MpAllChrPtrs[playernum2]->team) == g_ScenarioData.koh.occupiedteam) { - setCurrentPlayerNum(playernum2); - - // "King of the Hill!" - hudmsgCreateWithFlags(langGet(L_MPWEAPONS_020), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); - } - } - - setCurrentPlayerNum(prevplayernum1); - - g_ScenarioData.koh.occupiedteam = -1; - g_ScenarioData.koh.elapsed240 = 0; - - if (g_MpSetup.options & MPOPTION_KOH_MOBILEHILL) { - g_ScenarioData.koh.movehill = true; - } - } - } - } - } - - // Calculate what colour the hill should tween towards - if (g_ScenarioData.koh.occupiedteam == -1) { - targetr = 0.25f; - targetg = 1; - targetb = 0.25f; - } else { - u32 colour = g_TeamColours[g_ScenarioData.koh.occupiedteam]; - targetr = ((s32)(colour >> 24 & 0xff) + 0xff) * (1.0f / 512.0f); - targetg = ((s32)(colour >> 16 & 0xff) + 0xff) * (1.0f / 512.0f); - targetb = ((s32)(colour >> 8 & 0xff) + 0xff) * (1.0f / 512.0f); - } - } - - // Tween the colour components towards the target colour. - // @bug: This increments using g_Vars.diffframe60, which is updated while - // the game is paused. Because of this, if you pause as soon as a hill is - // scored then the colour fade and selection of the new hill will happen - // while paused. - if (g_ScenarioData.koh.colourfracr != targetr) { - for (i = 0; i < g_Vars.diffframe60; i++) { -#if PAL - g_ScenarioData.koh.colourfracr = 0.0597f * targetr + 0.9403f * g_ScenarioData.koh.colourfracr; -#else - g_ScenarioData.koh.colourfracr = 0.05f * targetr + 0.95f * g_ScenarioData.koh.colourfracr; -#endif - } - } - - if (g_ScenarioData.koh.colourfracg != targetg) { - for (i = 0; i < g_Vars.diffframe60; i++) { -#if PAL - g_ScenarioData.koh.colourfracg = 0.0597f * targetg + 0.9403f * g_ScenarioData.koh.colourfracg; -#else - g_ScenarioData.koh.colourfracg = 0.05f * targetg + 0.95f * g_ScenarioData.koh.colourfracg; -#endif - } - } - - if (g_ScenarioData.koh.colourfracb != targetb) { - for (i = 0; i < g_Vars.diffframe60; i++) { -#if PAL - g_ScenarioData.koh.colourfracb = 0.0597f * targetb + 0.9403f * g_ScenarioData.koh.colourfracb; -#else - g_ScenarioData.koh.colourfracb = 0.05f * targetb + 0.95f * g_ScenarioData.koh.colourfracb; -#endif - } - } -} - -Gfx *scenarioKohRenderHud(Gfx *gdl) -{ - s32 time240; - s32 mins; - s32 secs; - s32 textwidth; - s32 textheight; - s32 x; - s32 y; - struct chrdata *chr = g_Vars.currentplayer->prop->chr; - char text[64]; - - if (radarGetTeamIndex(chr->team) == g_ScenarioData.koh.occupiedteam && !g_ScenarioData.koh.movehill) { - x = viGetViewLeft() + viGetViewWidth() / 2; - y = viGetViewTop() + 10; - - time240 = g_Vars.mphilltime * PALDOWN(240) - g_ScenarioData.koh.elapsed240; - time240 += PAL ? 2199 : 2400; - mins = time240 / PALDOWN(60 * 240); - time240 -= PALDOWN(60 * 240) * mins; - -#if PAL - secs = time240 / PALDOWN(240); -#else - secs = (time240 + (PALDOWN(240) - 1)) / PALDOWN(240); -#endif - - if ((g_Vars.mphilltime * 60 + 600) / 3600) { - sprintf(text, "%d:%02d", mins, secs); - } else { - sprintf(text, "%02d", secs); - } - - gdl = func0f153628(gdl); - textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); - - x -= textwidth / 2; - textwidth += x; - textheight += y; - -#if VERSION >= VERSION_NTSC_1_0 - gdl = func0f153990(gdl, x, y, textwidth, textheight); - gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0xa0, viGetWidth(), viGetHeight(), 0, 0); -#else - gdl = func0f153858(gdl, &x, &y, &textwidth, &textheight); - gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0x88, viGetWidth(), viGetHeight(), 0, 0); -#endif - gdl = func0f153780(gdl); - } - - return gdl; -} - -const char var7f1b84a8[] = "HackThatMacAddBankPad -> Adding New Pad %d - Pad Id = %d-> Saving Pad\n"; -const char var7f1b84f0[] = "HackThatMacReset -> Working\n"; - -void scenarioKohCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) -{ - struct mpchrconfig *loopmpchr; - s32 i; - - *score = 0; - *score += mpchr->numpoints; - - if (g_MpSetup.options & MPOPTION_KILLSSCORE) { - for (i = 0; i != MAX_MPCHRS; i++) { - if (i == mpchrnum) { - *score -= mpchr->killcounts[i]; - } else { - if (i < 4) { - loopmpchr = &g_PlayerConfigsArray[i].base; - } else { - loopmpchr = &g_BotConfigsArray[i - 4].base; - } - - if (loopmpchr->team == mpchr->team) { - *score -= mpchr->killcounts[i]; - } else { - *score += mpchr->killcounts[i]; - } - } - } - } - - *deaths = mpchr->numdeaths; -} - -Gfx *scenarioKohRadar(Gfx *gdl) -{ - if (g_MpSetup.options & MPOPTION_KOH_HILLONRADAR && !g_ScenarioData.koh.movehill) { - struct coord dist; - u32 colour; - dist.x = g_ScenarioData.koh.hillpos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = g_ScenarioData.koh.hillpos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = g_ScenarioData.koh.hillpos.z - g_Vars.currentplayer->prop->pos.z; - - if (g_ScenarioData.koh.occupiedteam == -1) { - colour = 0xff0000; - } else { - colour = g_TeamColours[g_ScenarioData.koh.occupiedteam]; - } - - gdl = radarDrawDot(gdl, NULL, &dist, colour, 0, 1); - } - - return gdl; -} - -void mpKohAddHill(s32 *cmd) -{ - if (g_ScenarioData.koh.hillcount < ARRAYCOUNT(g_ScenarioData.koh.hillpads)) { - g_ScenarioData.koh.hillpads[g_ScenarioData.koh.hillcount] = cmd[1]; - g_ScenarioData.koh.hillcount++; - } -} - -bool scenarioKohIsRoomHighlighted(s16 room) -{ - return room == g_ScenarioData.koh.hillrooms[0]; -} - -void scenarioKohCallback38(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3) -{ - if (roomnum == g_ScenarioData.koh.hillrooms[0]) { - f32 a = *arg1; - f32 b = *arg2; - f32 c = *arg3; - - a *= g_ScenarioData.koh.colourfracr; - b *= g_ScenarioData.koh.colourfracg; - c *= g_ScenarioData.koh.colourfracb; - - *arg1 = a; - *arg2 = b; - *arg3 = c; - } -} - -void scenarioHtmInit(void) -{ - g_ScenarioData.htm.uplink = NULL; -} - -s32 scenarioHtmCallback08(void) -{ - return 2; -} - -void mpHtmAddPad(s16 padnum) -{ - struct scenariodata_htm *data = &g_ScenarioData.htm; - -#if VERSION >= VERSION_NTSC_1_0 - if (data->nextindex < 60) -#endif - { - data->padnums[data->nextindex] = padnum; - data->nextindex++; - } -} - -void func0f182bf4(void) -{ - s32 i; - - g_ScenarioData.htm.nextindex = 0; - g_ScenarioData.htm.unk002 = 0; - g_ScenarioData.htm.unk138 = 0; - g_ScenarioData.htm.dlplayernum = -1; - g_ScenarioData.htm.playernuminrange = -1; - g_ScenarioData.htm.dlterminalnum = -1; - g_ScenarioData.htm.unk140 = 0; - - for (i = 0; i < ARRAYCOUNT(g_ScenarioData.htm.numpoints); i++) { - g_ScenarioData.htm.numpoints[i] = 0; - g_ScenarioData.htm.dltime240[i] = 0; - } - - for (i = 0; i < ARRAYCOUNT(g_ScenarioData.htm.padnums); i++) { - g_ScenarioData.htm.padnums[i] = -1; - } - - for (i = 0; i < 1; i++) { - g_ScenarioData.htm.terminals[i].unk00 = 0; - g_ScenarioData.htm.terminals[i].prop = NULL; - g_ScenarioData.htm.terminals[i].padnum = -1; - g_ScenarioData.htm.terminals[i].team = 255; - g_ScenarioData.htm.terminals[i].unk0b = 255; - } -} - -struct menuitem g_MpHillOptionsMenuItems[] = { - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" - { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_233, MPOPTION_KOH_HILLONRADAR, menuhandlerMpCheckboxOption }, // "Hill on Radar" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_234, MPOPTION_KOH_MOBILEHILL, menuhandlerMpCheckboxOption }, // "Mobile Hill" - { MENUITEMTYPE_SLIDER, 0, 0x00020000, L_MPMENU_235, 0x0000006e, menuhandlerMpHillTime }, // "Time" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" - { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, -}; - -struct menudialog g_MpHillOptionsMenuDialog = { - MENUDIALOGTYPE_DEFAULT, - L_MPMENU_219, // "Hill Options" - g_MpHillOptionsMenuItems, - mpOptionsMenuDialog, - 0x00000010, - NULL, -}; - -struct menuitem g_MpHackerOptionsMenuItems[] = { - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" - { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_231, MPOPTION_HTM_HIGHLIGHTTERMINAL, menuhandlerMpCheckboxOption }, // "Highlight Terminal" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_238, MPOPTION_HTM_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" - { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, -}; - -struct menudialog g_MpHackerOptionsMenuDialog = { - MENUDIALOGTYPE_DEFAULT, - L_MPMENU_217, // "Hacker Options" - g_MpHackerOptionsMenuItems, - mpOptionsMenuDialog, - 0x00000010, - NULL, -}; - -void scenarioHtmCreateUplink(void) -{ - struct weaponobj template = { - 512, // extrascale - 0, // hidden2 - OBJTYPE_WEAPON, // type - MODEL_CHRDATATHIEF, // modelnum - 0, // pad - OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, - OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, - 0, // flags3 - NULL, // prop - NULL, // model - 1, 0, 0, // realrot - 0, 1, 0, - 0, 0, 1, - 0, // hidden - NULL, // geo - NULL, // projectile - 0, // damage - 1000, // maxdamage - 0xff, 0xff, 0xff, 0x00, // shadecol - 0xff, 0xff, 0xff, 0x00, // nextcol - 0x0fff, // floorcol - 0, // tiles - WEAPON_DATAUPLINK, // weaponnum - 0, // unk5d - 0, // unk5e - FUNC_PRIMARY, // gunfunc - 0, // fadeouttimer60 - -1, // dualweaponnum - -1, // timer240 - NULL, // dualweapon - }; - - struct prop *prop = g_Vars.activeprops; - struct defaultobj *obj; - s32 padnum; - s32 count = 0; - struct defaultobj *candidates[20]; - - while (prop && count < 20) { -#if VERSION >= VERSION_NTSC_1_0 - if (prop->type == PROPTYPE_OBJ) -#endif - { - obj = prop->obj; - - if (obj->type == OBJTYPE_MULTIAMMOCRATE) { - candidates[count] = obj; - count++; - } - } - - prop = prop->next; - } - - if (count > 0) { - count = random() % count; - var800869ec = candidates[count]; - var800869ec->hidden |= OBJHFLAG_REAPABLE; - var800869ec->hidden2 |= OBJH2FLAG_CANREGEN; - padnum = var800869ec->pad; - } else if (g_ScenarioData.htm.nextindex > 0) { - padnum = g_ScenarioData.htm.padnums[random() % g_ScenarioData.htm.nextindex]; - } else { - padnum = 0; - } - - g_HtmUplinkObj = template; - g_HtmUplinkObj.base.pad = padnum; - - weaponAssignToHome(&g_HtmUplinkObj, 999); - - g_HtmUplinkObj.base.hidden2 &= ~OBJH2FLAG_CANREGEN; - - g_ScenarioData.htm.uplink = g_HtmUplinkObj.base.prop; - - if (g_ScenarioData.htm.uplink) { - g_ScenarioData.htm.uplink->forcetick = true; - } -} - -void scenarioHtmReset(void) -{ - struct scenariodata_htm *data = &g_ScenarioData.htm; - struct prop *prop = g_Vars.activeprops; - s32 i = 0; - s32 rand; - - osSyncPrintf("HackThatMacInitProps -> Start : %d Bank Pads\n", data->unk002); - - while (prop) { - if (prop->type == PROPTYPE_OBJ) { - struct defaultobj *obj = prop->obj; - - if (obj->type == OBJTYPE_AMMOCRATE || obj->type == OBJTYPE_MULTIAMMOCRATE) { - if (obj->modelnum == MODEL_MULTI_AMMO_CRATE) { - osSyncPrintf("HackThatMacInitProps -> Adding prop %d (%x)\n", i, obj->pad); - mpHtmAddPad(obj->pad); - } - } - } - - prop = prop->next; - i++; - } - - osSyncPrintf("HackThatMacInitProps -> Mid : %d Bank Pads\n", data->nextindex); - osSyncPrintf("HackThatMacInitProps -> Generating %d random box pads from %d in the bank\n", scenarioHtmCallback08(), data->nextindex); - - data->unk002 = 0; - - while (data->unk002 < scenarioHtmCallback08()) { - s32 padnum; - - do { - rand = random() % data->nextindex; - padnum = data->padnums[rand]; - } while (padnum <= 0); - - data->terminals[data->unk002].padnum = padnum; - data->unk002++; - data->padnums[rand] = -1; - } - - osSyncPrintf("HackThatMacInitProps -> %d/%d Random box pads generated - Listing\n", data->unk002, scenarioHtmCallback08()); - - for (i = 0; i < data->unk002; i++) { - osSyncPrintf("Pad %d -> Pad Id = %d\n", i, data->terminals[i].padnum); - } - - for (i = 0; i < 1; i++) { - data->terminals[i].prop = scenarioCreateObj(MODEL_GOODPC, data->terminals[i].padnum, 0.2f, - OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, - OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, - OBJFLAG3_HTMTERMINAL | OBJFLAG3_INTERACTABLE); - osSyncPrintf("HackThatMacInitProps -> Building and adding custom prop %d - Pad=%d, Ptr=%08x\n", - i, data->terminals[i].padnum, data->terminals[i].prop); - scenarioHtmRemoveAmmoCrateAtPad(data->terminals[i].padnum); - } - - var800869ec = NULL; - - scenarioHtmCreateUplink(); - - osSyncPrintf("HackThatMacInitProps -> End\n"); -} - -void scenarioHtmTick(void) -{ - u8 stack[8]; - s32 i; - u32 prevplayernum = g_Vars.currentplayernum; - struct prop *prop; - - if (var800869ec && var800869ec->prop) { - if (g_ScenarioData.htm.uplink == NULL || g_ScenarioData.htm.uplink->type != PROPTYPE_WEAPON) { - var800869ec = NULL; - } else { - var800869ec->prop->timetoregen = PALDOWN(1200); - } - } - - g_ScenarioData.htm.uplink = NULL; - - // Check if uplink is on the ground - prop = g_Vars.activeprops; - - while (prop) { - if (prop->type == PROPTYPE_WEAPON) { - struct weaponobj *weapon = prop->weapon; - - if (weapon->weaponnum == WEAPON_DATAUPLINK) { - g_ScenarioData.htm.uplink = prop; - } - } - - prop = prop->next; - } - - // Check if a player is holding it - if (g_ScenarioData.htm.uplink == NULL) { - for (i = 0; i < PLAYERCOUNT(); i++) { - setCurrentPlayerNum(i); - - if (invHasDataUplink()) { - g_ScenarioData.htm.uplink = g_Vars.currentplayer->prop; - break; - } - } - } - - setCurrentPlayerNum(prevplayernum); - - // Check if a simulant is holding it - if (g_ScenarioData.htm.uplink == NULL) { - for (i = PLAYERCOUNT(); i < g_MpNumChrs; i++) { - if (g_MpAllChrPtrs[i]->aibot->hasuplink) { - g_ScenarioData.htm.uplink = g_MpAllChrPtrs[i]->prop; - break; - } - } - } - - if (g_ScenarioData.htm.uplink == NULL) { - scenarioHtmCreateUplink(); - } -} - -void scenarioHtmCallback14(struct chrdata *chr) -{ - struct scenariodata_htm *data = &g_ScenarioData.htm; - bool hasuplink; - s32 playernum; - s32 i; - s32 *time; - - if (chr) { - hasuplink = chr->aibot->hasuplink; - playernum = mpPlayerGetIndex(chr); - } else { - hasuplink = invHasDataUplink() && bgunGetWeaponNum(HAND_RIGHT) == WEAPON_DATAUPLINK; - playernum = g_Vars.currentplayernum; - } - - time = &data->dltime240[playernum]; - - for (i = 0; i < 1; i++) { - if (data->terminals[i].prop) { - struct prop *prop = data->terminals[i].prop; - struct defaultobj *obj = prop->obj; - s32 activatedbyplayernum = -1; - - if (chr) { - if (hasuplink) { - activatedbyplayernum = playernum; - } - } else { - if (obj->hidden & OBJHFLAG_ACTIVATED_BY_BOND) { - activatedbyplayernum = (obj->hidden & 0xf0000000) >> 28; - } - } - - if (playernum == activatedbyplayernum) { - obj->hidden &= ~OBJHFLAG_ACTIVATED_BY_BOND; - obj->hidden &= ~0xf0000000; - - if (hasuplink) { - if (data->dlterminalnum == -1) { - data->dlterminalnum = i; - data->dlplayernum = playernum; - data->playernuminrange = playernum; - *time = 0; - - if (chr == NULL) { - hudmsgCreateWithFlags(langGet(L_MPWEAPONS_018), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); // "Starting download." - func0f0939f8(NULL, data->terminals[data->dlterminalnum].prop, SFX_01BF, -1, - -1, 2, 2, 0, NULL, -1, NULL, -1, -1, -1, -1); - } - } - } else { - if (chr == NULL) { - hudmsgCreateWithFlags(langGet(L_MPWEAPONS_019), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); // "You need to use the Data Uplink." - snd00010718(NULL, 0, 0x7fff, 0x40, SFX_01CC, 1, 1, -1, 1); - } - } - } - } - } - - if (playernum == data->dlplayernum && data->dlterminalnum != -1) { - struct coord *terminalpos = &data->terminals[data->dlterminalnum].prop->pos; - f32 angle; - f32 relangle; - f32 rangexz; - f32 rangey; - struct coord *chrpos; - struct coord dist; - bool holdinguplink; - - if (chr) { - chrpos = &chr->prop->pos; - angle = (M_BADTAU - chrGetInverseTheta(chr)) * 57.295776367188f; - holdinguplink = chr->aibot->weaponnum == WEAPON_UNARMED; - } else { - chrpos = &g_Vars.currentplayer->prop->pos; - angle = g_Vars.currentplayer->vv_theta; - holdinguplink = bgunGetWeaponNum(HAND_RIGHT) == WEAPON_DATAUPLINK; - } - - dist.x = terminalpos->x - chrpos->x; - dist.y = terminalpos->y - chrpos->y; - dist.z = terminalpos->z - chrpos->z; - - rangexz = sqrtf(dist.x * dist.x + dist.z * dist.z); - - rangey = ABS(dist.y); - - relangle = atan2f(dist.x, dist.z) * 57.295776367188f + angle; - - while (relangle < 180) { - relangle += 360; - } - - while (relangle > 180) { - relangle -= 360; - } - - if (relangle > 0) { - // empty - } else { - relangle = -relangle; - } - - osSyncPrintf("HTM : Player %d - Term Pos = (%d,%d,%d)", playernum, (s32)terminalpos->x, (s32)terminalpos->y, (s32)terminalpos->z); - osSyncPrintf("HTM : Player %d - Play Pos = (%d,%d,%d)", playernum, (s32)chrpos->x, (s32)chrpos->y, (s32)chrpos->z); - osSyncPrintf("HTM : Player %d - T/P Rel = (%d,%d,%d)", playernum, (s32)dist.x, (s32)dist.y, (s32)dist.z); - - osSyncPrintf("HTM : Player %d - Range XZ = %d", playernum, rangexz); - osSyncPrintf("HTM : Player %d - Range Y = %d", playernum, rangey); - osSyncPrintf("HTM : Player %d - Angle XZ = %d", playernum, relangle); - - if (rangexz > 250 || rangey > 200 || relangle > 45 || !holdinguplink) { - if (rangexz < 250 && rangey < 200) { - data->playernuminrange = playernum; - } else { - data->playernuminrange = -1; - } - - if (chr == NULL) { - // "Connection broken." - hudmsgCreateWithFlags(langGet(L_MPWEAPONS_017), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); - func0f0926bc(data->terminals[data->dlterminalnum].prop, 1, 0xffff); - snd00010718(NULL, 0, 0x7fff, 0x40, SFX_01CC, 1, 1, -1, 1); - } - - data->dlterminalnum = -1; - data->dlplayernum = -1; - *time = 0; - } else { - *time += g_Vars.lvupdate240; - - if (*time > 20 * PALDOWN(240)) { - data->numpoints[playernum]++; - data->playernuminrange = playernum; - - if (chr == NULL) { - // "Download successful." - hudmsgCreateWithFlags(langGet(L_MPWEAPONS_016), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); - func0f0926bc(data->terminals[data->dlterminalnum].prop, 1, 0xffff); - snd00010718(NULL, 0, 0x7fff, 0x40, SFX_01C1, 1, 1, -1, 1); - } - - data->dlterminalnum = -1; - data->dlplayernum = -1; - *time = 0; - } - } - - osSyncPrintf("HTM : Player %d - Dwnld Plr=%d, Dwnld Prop=%d\n", playernum, data->playernuminrange, data->terminals[data->dlterminalnum].prop); - osSyncPrintf("HTM : Player %d - Download Time = %d", playernum, *time); - } -} - -const char var7f1b87e4[] = "PopACapReset -> num_mplayers=%d : Working\n"; -const char var7f1b8810[] = "PopACapReset -> Generated %d victims for this game : Listing\n"; -const char var7f1b8850[] = "PopACapReset -> Victim %d is player %d\n"; -const char var7f1b8878[] = "PopACapReset -> Done\n"; -const char var7f1b8890[] = "PopACapTick : Current Victim = %d (Player %d)\n"; - -Gfx *scenarioHtmRenderHud(Gfx *gdl) -{ - struct scenariodata_htm *data = &g_ScenarioData.htm; - s32 dltime; - s32 viewleft; - s32 viewright; - s32 viewtop; - s32 a0; - s32 a1; - s32 barleft; - s32 barright; - s32 t1; - s32 t6; - s32 v1; - s32 s1; - - dltime = data->dltime240[g_Vars.currentplayernum]; - - if (data->dlterminalnum != -1 && g_Vars.currentplayernum == data->dlplayernum) { - viewleft = viGetViewLeft(); - viewright = viGetViewLeft() + viGetViewWidth(); - viewtop = viGetViewTop(); - t6 = (viewleft + viewright) / 2; - a1 = viGetViewWidth() / 3; - barleft = t6 - a1 / 2; - barright = t6 + a1 / 2; - s1 = barleft + (s32) (a1 * (dltime / PALDOWN(4800.0f))); - - gdl = func0f153628(gdl); - gdl = gfxSetPrimColour(gdl, 0x60000060); - - gDPFillRectangle(gdl++, barleft, viewtop + 8, barright, viewtop + 16); - - gdl = func0f153838(gdl); - gdl = gfxSetPrimColour(gdl, 0xc00000d0); - - v1 = barleft + 1; - a0 = barleft; - - while (v1 < s1) { - gDPFillRectangle(gdl++, a0, viewtop + 8, v1, viewtop + 16); - v1 += 2; - a0 += 2; - } - - gdl = func0f153838(gdl); - gdl = func0f153780(gdl); - } - - return gdl; -} - -void scenarioHtmCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) -{ - struct mpchrconfig *loopmpchr; - s32 i; - s32 index; - - *score = 0; - index = func0f18d0e8(mpchrnum); - - if (index >= 0) { - *score += g_ScenarioData.htm.numpoints[index] * 2; - } - - if (g_MpSetup.options & MPOPTION_KILLSSCORE) { - for (i = 0; i != MAX_MPCHRS; i++) { - if (i == mpchrnum) { - *score -= mpchr->killcounts[i]; - } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - if (i < 4) { - loopmpchr = &g_PlayerConfigsArray[i].base; - } else { - loopmpchr = &g_BotConfigsArray[i - 4].base; - } - - if (loopmpchr->team == mpchr->team) { - *score -= mpchr->killcounts[i]; - } else { - *score += mpchr->killcounts[i]; - } - } else { - *score += mpchr->killcounts[i]; - } - } - } - - *deaths = mpchr->numdeaths; -} - -Gfx *scenarioHtmRadar(Gfx *gdl) -{ - struct scenariodata_htm *data = &g_ScenarioData.htm; - struct coord dist; - s32 i; - struct coord sp88; - - // Red/green/blue/alpha as float and integer - f32 rf; - f32 gf; - f32 bf; - f32 af; - - u32 ri; - u32 gi; - u32 bi; - u32 ai; - - if (g_MpSetup.options & MPOPTION_HTM_SHOWONRADAR) { - // Show the uplink - if (data->uplink && data->uplink->type != PROPTYPE_PLAYER && data->uplink->type != PROPTYPE_CHR) { - dist.x = data->uplink->pos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = data->uplink->pos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = data->uplink->pos.z - g_Vars.currentplayer->prop->pos.z; - - gdl = radarDrawDot(gdl, data->uplink, &dist, 0x00ff0000, 0x00000000, true); - } - - // Show the terminal - for (i = 0; i < 1; i++) { - if (data->terminals[i].prop) { - sp88.x = data->terminals[i].prop->pos.x - g_Vars.currentplayer->prop->pos.x; - sp88.y = data->terminals[i].prop->pos.y - g_Vars.currentplayer->prop->pos.y; - sp88.z = data->terminals[i].prop->pos.z - g_Vars.currentplayer->prop->pos.z; - - if (data->terminals[i].team == 255) { - rf = 0; - gf = 255; - bf = 0; - af = 0; - } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - u32 colour = g_TeamColours[radarGetTeamIndex(data->terminals[i].team)]; - rf = (colour >> 24) & 0xff; - gf = ((colour >> 16) & 0xff); - bf = ((colour >> 8) & 0xff); - af = colour & 0xff; - } else { - rf = 0; - gf = 255; - bf = 0; - af = 0; - } - - ri = rf; - gi = gf; - bi = bf; - ai = af; - - if (ri > 255) { - ri = 255; - } - - if (gi > 255) { - gi = 255; - } - - if (bi > 255) { - bi = 255; - } - - if (ai > 255) { - ai = 255; - } - - gdl = radarDrawDot(gdl, data->terminals[i].prop, &sp88, - (ri << 24) | (gi << 16) | (bi << 8) | ai, - 0x00000000, true); - } - } - } - - return gdl; -} - -bool scenarioHtmRadar2(Gfx **gdl, struct prop *prop) -{ - if ((g_MpSetup.options & MPOPTION_HTM_SHOWONRADAR) && g_ScenarioData.htm.uplink) { - if (prop == g_ScenarioData.htm.uplink && - (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR)) { - struct coord dist; - dist.x = prop->pos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = prop->pos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = prop->pos.z - g_Vars.currentplayer->prop->pos.z; - - if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - u32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; - *gdl = radarDrawDot(*gdl, g_ScenarioData.htm.uplink, &dist, colour, 0, 1); - } else { - *gdl = radarDrawDot(*gdl, g_ScenarioData.htm.uplink, &dist, 0x00ff0000, 0, 1); - } - - return true; - } - } - - return false; -} - -bool scenarioHtmHighlight(struct prop *prop, s32 *colour) -{ - if (g_MpSetup.options & MPOPTION_HTM_HIGHLIGHTTERMINAL) { - bool highlight = false; - - if (prop == g_ScenarioData.htm.uplink) { - highlight = true; - } else { - s32 i; - - for (i = 0; i < 1; i++) { - if (g_ScenarioData.htm.terminals[i].prop == prop) { - highlight = true; - break; - } - } - } - - if (highlight) { - colour[0] = 0; - colour[1] = 0xff; - colour[2] = 0; - colour[3] = 0x40; - - return true; - } - } - - return false; -} - -void scenarioPacChooseVictims(void) -{ - s32 i; - s32 j; - struct scenariodata_pac *data = &g_ScenarioData.pac; - - data->victimindex = -1; - data->age240 = 0; - - for (i = 0; i != MAX_MPCHRS; i++) { - data->unk20[i] = 0; - data->wincounts[i] = 0; - } - - i = 0; - - while (i < g_MpNumChrs) { - bool isnew; - s32 victimplayernum = random() % g_MpNumChrs; - - for (j = 0, isnew = true; j < i; j++) { - if (data->victims[j] == victimplayernum) { - isnew = false; - break; - } - } - - if (isnew) { - data->victims[i] = victimplayernum; - i++; - } - } - - for (j = 0; j < g_MpNumChrs; j++) { - // This loop probably printed debug messages - } -} - -void scenarioPacInit(void) -{ - scenarioPacChooseVictims(); -} - -void scenarioPacReset(void) -{ - scenarioPacChooseVictims(); -} - -bool scenarioPacHighlight(struct prop *prop, s32 *colour) -{ - struct scenariodata_pac *data = &g_ScenarioData.pac; - - if (g_MpSetup.options & MPOPTION_PAC_HIGHLIGHTTARGET - && (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR) - && data->victimindex != -1 - && prop->chr == g_MpAllChrPtrs[data->victims[data->victimindex]]) { - colour[0] = 0; - colour[1] = 0xff; - colour[2] = 0; - colour[3] = 0x40; - return true; - } - - return false; -} - -void scenarioPacApplyNextVictim(void) -{ - struct scenariodata_pac *data = &g_ScenarioData.pac; - s32 vplayernum; - char text[64]; - s32 i; - - data->victimindex++; - - if (data->victimindex == g_MpNumChrs) { - data->victimindex = 0; - } - - data->age240 = 0; - - vplayernum = data->victims[data->victimindex]; - - for (i = 0; i < PLAYERCOUNT(); i++) { - if (vplayernum == i) { - sprintf(text, langGet(L_MPWEAPONS_013)); // "You are the victim!" - } else if (mpChrsAreSameTeam(vplayernum, i)) { - sprintf(text, langGet(L_MPWEAPONS_014), g_MpAllChrConfigPtrs[vplayernum]->name); // "Protect %s!" - } else { - sprintf(text, langGet(L_MPWEAPONS_015), g_MpAllChrConfigPtrs[vplayernum]->name); // "Get %s!" - } - - mpCreateScenarioHudmsg(i, text); - } -} - -void scenarioPacHandleDeath(s32 aplayernum, s32 vplayernum) -{ - struct scenariodata_pac *data = &g_ScenarioData.pac; - - if (data->victimindex >= 0 && vplayernum == data->victims[data->victimindex]) { - if (aplayernum != vplayernum) { - if (aplayernum >= 0) { - if (mpChrsAreSameTeam(aplayernum, vplayernum)) { - mpCreateScenarioHudmsg(aplayernum, langGet(L_MPWEAPONS_008)); // "You're supposed to look" - mpCreateScenarioHudmsg(aplayernum, langGet(L_MPWEAPONS_009)); // "after your friends!" - } else { - data->unk20[aplayernum]++; - mpCreateScenarioHudmsg(aplayernum, langGet(L_MPWEAPONS_010)); // "Well done!" - mpCreateScenarioHudmsg(aplayernum, langGet(L_MPWEAPONS_011)); // "You popped a cap!" - mpCreateScenarioHudmsg(aplayernum, langGet(L_MPWEAPONS_012)); // "Have 2 Points..." - } - } - - scenarioPacApplyNextVictim(); - } else { -#if VERSION >= VERSION_NTSC_1_0 - data->age240 = 0; -#endif - } - } -} - -void scenarioPacTick(void) -{ - struct scenariodata_pac *data = &g_ScenarioData.pac; - - if (data->victimindex == -1) { - scenarioPacApplyNextVictim(); - } - - if (data->victimindex >= 0) { -#if VERSION >= VERSION_NTSC_1_0 - if (data->victims[data->victimindex] >= PLAYERCOUNT() || - g_Vars.players[data->victims[data->victimindex]]->isdead == false) -#endif - { - data->age240 += g_Vars.lvupdate240; - - if (data->age240 > (u32)PALDOWN(240 * 60)) { - data->age240 = 0; - data->wincounts[data->victims[data->victimindex]]++; - mpCreateScenarioHudmsg(data->victims[data->victimindex], langGet(L_MPWEAPONS_007)); // "Have a point for living!" - } - } - } -} - -Gfx *scenarioPacRenderHud(Gfx *gdl) -{ - struct scenariodata_pac *data = &g_ScenarioData.pac; - s32 time240; - s32 mins; - s32 secs; - s32 textwidth; - s32 textheight; - s32 x; - s32 y; - char text[64]; - -#if VERSION >= VERSION_NTSC_1_0 - if (g_Vars.currentplayernum == data->victims[data->victimindex] && !g_Vars.currentplayer->isdead) -#else - if (g_Vars.currentplayernum == data->victims[data->victimindex]) -#endif - { - time240 = PALDOWN(60 * 240) - data->age240; - x = viGetViewLeft() + viGetViewWidth() / 2; - y = viGetViewTop() + 10; - - if (time240 < 0) { - time240 = 0; - } - - mins = time240 / PALDOWN(60 * 240); - time240 -= PALDOWN(60 * 240) * mins; - secs = (time240 + (PALDOWN(240) - 1)) / PALDOWN(240); - sprintf(text, "%d:%02d", mins, secs); - - gdl = func0f153628(gdl); - textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); - - x -= textwidth / 2; - textwidth += x; - textheight += y; - -#if VERSION >= VERSION_NTSC_1_0 - gdl = func0f153990(gdl, x, y, textwidth, textheight); - gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0xa0, viGetWidth(), viGetHeight(), 0, 0); -#else - gdl = func0f153858(gdl, &x, &y, &textwidth, &textheight); - gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0x88, viGetWidth(), viGetHeight(), 0, 0); -#endif - gdl = func0f153780(gdl); - } - - return gdl; -} - -void scenarioPacCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *arg3) -{ - struct mpchrconfig *loopmpchr; - s32 i; - s32 index; - - *score = 0; - index = func0f18d0e8(mpchrnum); - - if (index >= 0) { - *score += g_ScenarioData.pac.unk20[index] * 2; - *score += g_ScenarioData.pac.wincounts[index]; - } - - if (g_MpSetup.options & MPOPTION_KILLSSCORE) { - for (i = 0; i != MAX_MPCHRS; i++) { - if (i == mpchrnum) { - *score -= mpchr->killcounts[i]; - } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - if (i < 4) { - loopmpchr = &g_PlayerConfigsArray[i].base; - } else { - loopmpchr = &g_BotConfigsArray[i - 4].base; - } - - if (loopmpchr->team == mpchr->team) { - *score -= mpchr->killcounts[i]; - } else { - *score += mpchr->killcounts[i]; - } - } else { - *score += mpchr->killcounts[i]; - } - } - } - - *arg3 = mpchr->numdeaths; -} - -Gfx *scenarioPacRadar(Gfx *gdl) -{ - return gdl; -} - -bool scenarioPacRadar2(Gfx **gdl, struct prop *prop) -{ - struct scenariodata_pac *data = &g_ScenarioData.pac; - struct coord dist; - - if ((g_MpSetup.options & MPOPTION_PAC_SHOWONRADAR) && data->victimindex >= 0) { - struct prop *vprop = g_MpAllChrPtrs[data->victims[data->victimindex]]->prop; - - if (vprop == prop) { - dist.x = prop->pos.x - g_Vars.currentplayer->prop->pos.x; - dist.y = prop->pos.y - g_Vars.currentplayer->prop->pos.y; - dist.z = prop->pos.z - g_Vars.currentplayer->prop->pos.z; - - if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - u32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; - *gdl = radarDrawDot(*gdl, vprop, &dist, colour, 0, 1); - } else { - *gdl = radarDrawDot(*gdl, vprop, &dist, 0xff0000, 0, 1); - } - - return true; - } - } - - return false; -} - /** * While the options dialog is open, check if another player has changed the * scenario to a different one. If so, replace this dialog with the new one. @@ -2553,118 +278,6 @@ char *mpMenuTextScenarioName(struct menuitem *item) return g_StringPointer; } -struct menuitem g_MpPopacapOptionsMenuItems[] = { - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" - { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_230, MPOPTION_PAC_HIGHLIGHTTARGET, menuhandlerMpCheckboxOption }, // "Highlight Target" - { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_238, MPOPTION_PAC_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" - { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, - { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" - { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, -}; - -struct menudialog g_MpPopacapOptionsMenuDialog = { - MENUDIALOGTYPE_DEFAULT, - L_MPMENU_218, // "Pop a Cap Options" - g_MpPopacapOptionsMenuItems, - mpOptionsMenuDialog, - 0x00000010, - NULL, -}; - -struct mpscenario g_MpScenarios[6] = { - { - &g_MpCombatOptionsMenuDialog, - }, { - &g_MpBriefcaseOptionsMenuDialog, - scenarioHtbInit, - scenarioHtbCallback08, - scenarioHtbReset, - scenarioHtbTick, - scenarioHtbCallback14, - scenarioHtbRenderHud, - scenarioHtbCalculatePlayerScore, - scenarioHtbRadar, - scenarioHtbRadar2, - scenarioHtbHighlight, - }, { - &g_MpHackerOptionsMenuDialog, - scenarioHtmInit, - scenarioHtmCallback08, - scenarioHtmReset, - scenarioHtmTick, - scenarioHtmCallback14, - scenarioHtmRenderHud, - scenarioHtmCalculatePlayerScore, - scenarioHtmRadar, - scenarioHtmRadar2, - scenarioHtmHighlight, - }, { - &g_MpPopacapOptionsMenuDialog, - scenarioPacInit, - NULL, - scenarioPacReset, - scenarioPacTick, - NULL, - scenarioPacRenderHud, - scenarioPacCalculatePlayerScore, - scenarioPacRadar, - scenarioPacRadar2, - scenarioPacHighlight, - }, { - &g_MpHillOptionsMenuDialog, - scenarioKohInit, - NULL, - scenarioKohReset, - scenarioKohTick, - NULL, - scenarioKohRenderHud, - scenarioKohCalculatePlayerScore, - scenarioKohRadar, - NULL, - NULL, - NULL, - NULL, - scenarioKohIsRoomHighlighted, - scenarioKohCallback38, - NULL, - scenarioKohReadSave, - scenarioKohWriteSave - }, { - &g_MpCaptureOptionsMenuDialog, - scenarioCtcInit, - scenarioCtcCallback08, - scenarioCtcReset, - scenarioCtcTick, - scenarioCtcCallback14, - NULL, - scenarioCtcCalculatePlayerScore, - scenarioCtcRadar, - scenarioCtcRadar2, - scenarioCtcHighlight, - scenarioCtcChooseSpawnLocation, - scenarioCtcGetMaxTeams, - scenarioCtcIsRoomHighlighted, - scenarioCtcCallback38, - }, -}; - -struct mpscenariooverview g_MpScenarioOverviews[] = { - // Full name, short name, require feature, team only - { L_MPMENU_246, L_MPMENU_253, 0, false }, // "Combat", "Combat" - { L_MPMENU_247, L_MPMENU_254, MPFEATURE_SCENARIO_HTB, false }, // "Hold the Briefcase", "Briefcase" - { L_MPMENU_248, L_MPMENU_255, MPFEATURE_SCENARIO_HTM, false }, // "Hacker Central", "Hacker" - { L_MPMENU_249, L_MPMENU_256, MPFEATURE_SCENARIO_PAC, false }, // "Pop a Cap", "Pop" - { L_MPMENU_250, L_MPMENU_257, MPFEATURE_SCENARIO_KOH, true }, // "King of the Hill", "Hill" - { L_MPMENU_251, L_MPMENU_258, MPFEATURE_SCENARIO_CTC, true }, // "Capture the Case", "Capture" -}; - struct scenariogroup { s32 startindex; u16 textid; @@ -2773,6 +386,14 @@ s32 menuhandlerMpOpenOptions(s32 operation, struct menuitem *item, union handler return 0; } +/** + * Allow a callback to read data from the setup file's save file buffer. + * + * There is one byte available to be read. If not handled, the byte will be + * consumed and ignored. + * + * Used by KOH to read the mphilltime. + */ void scenarioReadSave(struct savebuffer *buffer) { if (g_MpScenarios[g_MpSetup.scenario].readsavefunc) { @@ -2782,6 +403,14 @@ void scenarioReadSave(struct savebuffer *buffer) } } +/** + * Allow a callback to write data to the setup file's save file buffer. + * + * There is one byte available to be written. If not handled, the byte will be + * written as 0. + * + * Used by KOH to write the mphilltime. + */ void scenarioWriteSave(struct savebuffer *buffer) { if (g_MpScenarios[g_MpSetup.scenario].writesavefunc) { @@ -2791,6 +420,11 @@ void scenarioWriteSave(struct savebuffer *buffer) } } +/** + * Called whenever a scenario is selected/applied in the match settings. + * + * The callback should initialise all the properties in g_ScenarioData. + */ void scenarioInit(void) { if (g_MpScenarios[g_MpSetup.scenario].initfunc) { @@ -2798,25 +432,36 @@ void scenarioInit(void) } } -s32 scenarioCallback08(void) +/** + * Return the number of additional props that will be created, such as + * briefcases and uplinks. + */ +s32 scenarioNumProps(void) { s32 result = 0; - if (g_MpScenarios[g_MpSetup.scenario].unk08) { - result = g_MpScenarios[g_MpSetup.scenario].unk08(); + if (g_MpScenarios[g_MpSetup.scenario].numpropsfunc) { + result = g_MpScenarios[g_MpSetup.scenario].numpropsfunc(); } return result; } -void scenarioReset(void) +/** + * Create the additional props, such as briefcases and uplinks. + */ +void scenarioInitProps(void) { - if (g_MpScenarios[g_MpSetup.scenario].resetfunc) { - g_MpScenarios[g_MpSetup.scenario].resetfunc(); + if (g_MpScenarios[g_MpSetup.scenario].initpropsfunc) { + g_MpScenarios[g_MpSetup.scenario].initpropsfunc(); } } -void mpCreateMatchStartHudmsgs(void) +/** + * At the start of each match, a hud message appears for all players containing + * the challenge name if it's a challenge, or the scenario name if not. + */ +void scenarioCreateMatchStartHudmsgs(void) { s32 i; s32 prevplayernum = g_Vars.currentplayernum; @@ -2844,11 +489,14 @@ void mpCreateMatchStartHudmsgs(void) setCurrentPlayerNum(prevplayernum); } +/** + * Called on every frame during a match, including while paused. + */ void scenarioTick(void) { if (g_Vars.normmplayerisrunning) { if (g_Vars.lvframenum == 5) { - mpCreateMatchStartHudmsgs(); + scenarioCreateMatchStartHudmsgs(); } if (g_MpScenarios[g_MpSetup.scenario].tickfunc) { @@ -2857,13 +505,22 @@ void scenarioTick(void) } } -void scenarioCallback14(struct chrdata *chr) +/** + * Tick a single chr. + * + * If chr is provided then it will be a bot. + * If chr is NULL then the handler should tick the current player instead. + */ +void scenarioTickChr(struct chrdata *chr) { - if (g_Vars.normmplayerisrunning && g_MpScenarios[g_MpSetup.scenario].unk14) { - g_MpScenarios[g_MpSetup.scenario].unk14(chr); + if (g_Vars.normmplayerisrunning && g_MpScenarios[g_MpSetup.scenario].tickchrfunc) { + g_MpScenarios[g_MpSetup.scenario].tickchrfunc(chr); } } +/** + * Render any HUD information such as timers. + */ Gfx *scenarioRenderHud(Gfx *gdl) { s32 viewleft; @@ -2953,6 +610,11 @@ Gfx *scenarioRenderHud(Gfx *gdl) return gdl; } +/** + * Calculate the score and number of deaths for the given mpchr. + * + * If no callback is registered, the default calculation below will apply. + */ void scenarioCalculatePlayerScore(struct mpchrconfig *mpchr, s32 chrnum, s32 *score, s32 *deaths) { struct mpchrconfig *othermpchr; @@ -2967,11 +629,7 @@ void scenarioCalculatePlayerScore(struct mpchrconfig *mpchr, s32 chrnum, s32 *sc if (i == chrnum) { *score -= mpchr->killcounts[i]; } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { - if (i < 4) { - othermpchr = &g_PlayerConfigsArray[i].base; - } else { - othermpchr = &g_BotConfigsArray[i - 4].base; - } + othermpchr = MPCHR(i); if (othermpchr->team == mpchr->team) { *score -= mpchr->killcounts[i]; @@ -2987,28 +645,41 @@ void scenarioCalculatePlayerScore(struct mpchrconfig *mpchr, s32 chrnum, s32 *sc } } -Gfx *scenarioRadar(Gfx *gdl) +/** + * Draw anything extra to the radar, such as props or the king of the hill room. + */ +Gfx *scenarioRadarExtra(Gfx *gdl) { - if (g_Vars.normmplayerisrunning && g_MpScenarios[g_MpSetup.scenario].radarfunc) { - return g_MpScenarios[g_MpSetup.scenario].radarfunc(gdl); + if (g_Vars.normmplayerisrunning && g_MpScenarios[g_MpSetup.scenario].radarextrafunc) { + return g_MpScenarios[g_MpSetup.scenario].radarextrafunc(gdl); } return gdl; } -bool scenarioRadar2(Gfx **gdl, struct prop *prop) +/** + * Render a player or bot on the radar. + * + * Return true if handled or false if the generic radar code should render it. + */ +bool scenarioRadarChr(Gfx **gdl, struct prop *prop) { - if (g_Vars.normmplayerisrunning && g_MpScenarios[g_MpSetup.scenario].radar2func) { - return g_MpScenarios[g_MpSetup.scenario].radar2func(gdl, prop); + if (g_Vars.normmplayerisrunning && g_MpScenarios[g_MpSetup.scenario].radarchrfunc) { + return g_MpScenarios[g_MpSetup.scenario].radarchrfunc(gdl, prop); } return false; } -bool scenarioHighlight(struct prop *prop, s32 *colour) +/** + * Set the highlight colour of the given prop. + * + * The prop may be a chr, weapon or object. + */ +bool scenarioHighlightProp(struct prop *prop, s32 *colour) { - if (g_MpScenarios[g_MpSetup.scenario].highlightfunc) { - if (g_MpScenarios[g_MpSetup.scenario].highlightfunc(prop, colour)) { + if (g_MpScenarios[g_MpSetup.scenario].highlightpropfunc) { + if (g_MpScenarios[g_MpSetup.scenario].highlightpropfunc(prop, colour)) { return true; } } @@ -3082,6 +753,11 @@ bool scenarioHighlight(struct prop *prop, s32 *colour) return false; } +/** + * Choose a spawn location for a chr. + * + * CTC uses this to ensure the chrs spawn near their base. + */ f32 scenarioChooseSpawnLocation(f32 arg0, struct coord *pos, s16 *rooms, struct prop *prop) { f32 result; @@ -3094,7 +770,14 @@ f32 scenarioChooseSpawnLocation(f32 arg0, struct coord *pos, s16 *rooms, struct return playerChooseGeneralSpawnLocation(arg0, pos, rooms, prop); } -void mpPrepareScenario(void) +/** + * Called when starting a match. + * + * The function clears anything in the g_ScenarioData struct that is + * stage specific such as pad and room numbers, then re-reads the setup + * file to reload that information. + */ +void scenarioReset(void) { s32 i; s32 j; @@ -3120,10 +803,10 @@ void mpPrepareScenario(void) } break; case MPSCENARIO_HACKERCENTRAL: - func0f182bf4(); + htmReset(); break; case MPSCENARIO_HOLDTHEBRIEFCASE: - func0f180078(); + htbReset(); break; case MPSCENARIO_POPACAP: break; @@ -3138,17 +821,17 @@ void mpPrepareScenario(void) case INTROCMD_CASE: case INTROCMD_CASERESPAWN: if (g_MpSetup.scenario == MPSCENARIO_CAPTURETHECASE) { - mpCtcAddPad(cmd); + ctcAddPad(cmd); } else if (g_MpSetup.scenario == MPSCENARIO_HACKERCENTRAL) { - mpHtmAddPad(cmd[2]); + htmAddPad(cmd[2]); } else if (g_MpSetup.scenario == MPSCENARIO_HOLDTHEBRIEFCASE) { - mpHtbAddPad(cmd[2]); + htbAddPad(cmd[2]); } cmd += 3; break; case INTROCMD_HILL: if (g_MpSetup.scenario == MPSCENARIO_KINGOFTHEHILL) { - mpKohAddHill(cmd); + kohAddHill(cmd); } cmd += 2; break; @@ -3184,6 +867,11 @@ void mpPrepareScenario(void) } } +/** + * Return the maximum number of teams permitted for this scenario. + * + * CTC sets this to 4, while the others use the default limit of 8. + */ s32 scenarioGetMaxTeams(void) { if (g_MpScenarios[g_MpSetup.scenario].maxteamsfunc) { @@ -3193,6 +881,9 @@ s32 scenarioGetMaxTeams(void) return MAX_TEAMS; } +/** + * This callback is unused. + */ bool scenarioIsRoomHighlighted(s16 room) { if (g_MpScenarios[g_MpSetup.scenario].isroomhighlightedfunc) { @@ -3202,10 +893,15 @@ bool scenarioIsRoomHighlighted(s16 room) return false; } -void scenarioCallback38(s16 arg0, s32 *arg1, s32 *arg2, s32 *arg3) +/** + * Override the colour for the given room. + * + * Used in CTC for the team bases and in KOH for the hill. + */ +void scenarioHighlightRoom(s16 room, s32 *arg1, s32 *arg2, s32 *arg3) { - if (g_MpScenarios[g_MpSetup.scenario].unk38) { - g_MpScenarios[g_MpSetup.scenario].unk38(arg0, arg1, arg2, arg3); + if (g_MpScenarios[g_MpSetup.scenario].highlightroomfunc) { + g_MpScenarios[g_MpSetup.scenario].highlightroomfunc(room, arg1, arg2, arg3); } } @@ -3237,6 +933,11 @@ struct menudialog g_MpQuickTeamScenarioMenuDialog = { NULL, }; +/** + * Create a general object. + * + * This is a helper function used by HTM to create the terminal. + */ struct prop *scenarioCreateObj(s32 modelnum, s16 padnum, f32 arg2, u32 flags, u32 flags2, u32 flags3) { struct defaultobj template = { @@ -3282,7 +983,12 @@ struct prop *scenarioCreateObj(s32 modelnum, s16 padnum, f32 arg2, u32 flags, u3 return obj->prop; } -void mpCreateScenarioHudmsg(s32 playernum, char *message) +/** + * Create a HUD message for the given player. + * + * This is a helper function used by PAC. + */ +void scenarioCreateHudmsg(s32 playernum, char *message) { if (playernum >= 0 && playernum < PLAYERCOUNT()) { s32 prevplayernum = g_Vars.currentplayernum; @@ -3293,27 +999,23 @@ void mpCreateScenarioHudmsg(s32 playernum, char *message) } } -bool mpChrsAreSameTeam(s32 arg0, s32 arg1) +/** + * CHeck if two player numbers are on the same team. + * + * This is a helper function used by PAC. + */ +bool scenarioChrsAreSameTeam(s32 playernum1, s32 playernum2) { struct mpchrconfig *achr; struct mpchrconfig *bchr; - if ((g_MpSetup.options & MPOPTION_TEAMSENABLED) && arg0 >= 0 && arg1 >= 0) { - s32 a = func0f18d074(arg0); - s32 b = func0f18d074(arg1); + if ((g_MpSetup.options & MPOPTION_TEAMSENABLED) && playernum1 >= 0 && playernum2 >= 0) { + s32 a = func0f18d074(playernum1); + s32 b = func0f18d074(playernum2); if (a >= 0 && b >= 0) { - if (a < 4) { - achr = &g_PlayerConfigsArray[a].base; - } else { - achr = &g_BotConfigsArray[a - 4].base; - } - - if (b < 4) { - bchr = &g_PlayerConfigsArray[b].base; - } else { - bchr = &g_BotConfigsArray[b - 4].base; - } + achr = MPCHR(a); + bchr = MPCHR(b); return (achr->team == bchr->team) ? true : false; } @@ -3322,6 +1024,12 @@ bool mpChrsAreSameTeam(s32 arg0, s32 arg1) return false; } +/** + * Handle a player or bot picking up a briefcase, regardless of which scenario + * it is. + * + * The return value is a TICKOP constant. + */ s32 scenarioPickUpBriefcase(struct chrdata *chr, struct prop *prop) { struct defaultobj *obj = prop->obj; @@ -3533,7 +1241,12 @@ s32 scenarioPickUpBriefcase(struct chrdata *chr, struct prop *prop) return TICKOP_NONE; } -void scenarioReleaseToken(struct chrdata *chr, struct prop *prop) +/** + * Handle a token (briefcase) being dropped due to the holder being killed. + * + * For CTC, the token is warped back to its home base. + */ +void scenarioHandleDroppedToken(struct chrdata *chr, struct prop *prop) { s32 i; struct weaponobj *weapon = prop->weapon; @@ -3568,12 +1281,17 @@ void scenarioReleaseToken(struct chrdata *chr, struct prop *prop) } } -s32 chrGiveUplink(struct chrdata *chr, struct prop *prop) +/** + * Handle a player or bot picking up a data uplink, regardless of which scenario + * it is. + * + * The return value is a TICKOP constant. + */ +s32 scenarioPickUpUplink(struct chrdata *chr, struct prop *prop) { s32 i; + char message[64]; struct mpchrconfig *mpchr; - char message[60]; - s32 mpindex; u32 playernum; if (g_MpSetup.scenario == MPSCENARIO_HACKERCENTRAL) { @@ -3586,13 +1304,7 @@ s32 chrGiveUplink(struct chrdata *chr, struct prop *prop) if (chr->aibot) { mpchr = g_MpAllChrConfigPtrs[mpPlayerGetIndex(chr)]; } else { - mpindex = g_Vars.playerstats[g_Vars.currentplayernum].mpindex; - - if (mpindex < 4) { - mpchr = &g_PlayerConfigsArray[mpindex].base; - } else { - mpchr = &g_BotConfigsArray[mpindex - 4].base; - } + mpchr = MPCHR(g_Vars.playerstats[g_Vars.currentplayernum].mpindex); } #if PAL @@ -3624,7 +1336,7 @@ s32 chrGiveUplink(struct chrdata *chr, struct prop *prop) prop->obj->hidden |= OBJHFLAG_REAPABLE; #endif - return 0; + return TICKOP_NONE; } else { invGiveSingleWeapon(WEAPON_DATAUPLINK); currentPlayerQueuePickupWeaponHudmsg(WEAPON_DATAUPLINK, false); @@ -3632,23 +1344,26 @@ s32 chrGiveUplink(struct chrdata *chr, struct prop *prop) #if VERSION >= VERSION_NTSC_1_0 objFree(obj, false, obj->hidden2 & OBJH2FLAG_CANREGEN); - return 1; + return TICKOP_FREE; #else - return 4; + return TICKOP_GIVETOPLAYER; #endif } } else if (chr->aibot) { - return 0; + return TICKOP_NONE; } - return 0; + return TICKOP_NONE; } -void scenarioHtmActivateUplink(struct chrdata *chr, struct prop *terminal) +/** + * Handle a terminal being activated with the data uplink. + */ +void scenarioHandleActivatedProp(struct chrdata *chr, struct prop *prop) { if (g_MpSetup.scenario == MPSCENARIO_HACKERCENTRAL) { - struct defaultobj *obj = terminal->obj; + struct defaultobj *obj = prop->obj; if (obj->flags3 & OBJFLAG3_HTMTERMINAL) { u32 mpindex = mpPlayerGetIndex(chr); diff --git a/src/game/mplayer/scenarios/capturethecase.inc b/src/game/mplayer/scenarios/capturethecase.inc new file mode 100644 index 000000000..ba2d01943 --- /dev/null +++ b/src/game/mplayer/scenarios/capturethecase.inc @@ -0,0 +1,437 @@ +/** + * Capture the Case + * + * Each team has a base. The base is highlighted in their team colour and + * contains a briefcase on the ground. Players must infiltrate another team's + * base, collect their briefcase and then return it to their own. + * + * When a briefcase holder is killed, the briefcase is immediately returned to + * the team's base. + * + * Players cannot score unless their briefcase is at the home base. + */ + +struct menuitem g_CtcOptionsMenuItems[] = { + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" + { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_236, MPOPTION_CTC_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" + { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, +}; + +struct menudialog g_CtcOptionsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + L_MPMENU_220, // "Capture Options" + g_CtcOptionsMenuItems, + mpOptionsMenuDialog, + 0x00000010, + NULL, +}; + +struct weaponobj g_CtcTokenObj0; +struct weaponobj g_CtcTokenObj1; +struct weaponobj g_CtcTokenObj2; +struct weaponobj g_CtcTokenObj3; + +void ctcInit(void) +{ + s32 i, j, k; + g_MpSetup.options |= MPOPTION_TEAMSENABLED; + + for (i = 0; i < 4; i++) { + s32 j; + g_ScenarioData.ctc.spawnpadsperteam[i].homepad = i; + g_ScenarioData.ctc.spawnpadsperteam[i].numspawnpads = 0; + + for (j = 0; j < 6; j++) { + g_ScenarioData.ctc.spawnpadsperteam[i].spawnpads[j] = -1; + } + } + + for (i = 0; i != 4; i++) { + g_ScenarioData.ctc.playercountsperteam[i] = 0; + g_ScenarioData.ctc.teamindexes[i] = -1; + } + + for (k = 0; k < MAX_MPCHRS; k++) { + if (g_MpSetup.chrslots & (1 << k)) { + struct mpchrconfig *mpchr = MPCHR(k); + + while (mpchr->team >= scenarioGetMaxTeams()) { + mpchr->team -= scenarioGetMaxTeams(); + } + } + } +} + +s32 ctcNumProps(void) +{ + return 4; +} + +void ctcTick(void) +{ + // empty +} + +void ctcTickChr(struct chrdata *chr) +{ + if (chr); +} + +void ctcInitProps(void) +{ + struct mpchrconfig *mpchr; + struct weaponobj *tmp; + s32 mpindex; + u32 stack; + bool teamsdone[4]; + + struct weaponobj template = { + 256, // extrascale + 0, // hidden2 + OBJTYPE_WEAPON, // type + MODEL_CHRBRIEFCASE, // modelnum + 0, // pad + OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, + OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, + 0, // flags3 + NULL, // prop + NULL, // model + 1, 0, 0, // realrot + 0, 1, 0, + 0, 0, 1, + 0, // hidden + NULL, // geo + NULL, // projectile + 0, // damage + 1000, // maxdamage + 0xff, 0xff, 0xff, 0x00, // shadecol + 0xff, 0xff, 0xff, 0x00, // nextcol + 0x0fff, // floorcol + 0, // tiles + WEAPON_BRIEFCASE2, // weaponnum + 0, // unk5d + 0, // unk5e + FUNC_PRIMARY, // gunfunc + 0, // fadeouttimer60 + -1, // dualweaponnum + -1, // timer240 + NULL, // dualweapon + }; + + s32 i; + s32 j; + s32 k; + + for (i = 0; i < 4; i++) { + for (j = 0; j < 6; j++) { + } + } + + for (i = 0; i < 4; i++) { + teamsdone[i] = false; + g_ScenarioData.ctc.playercountsperteam[i] = 0; + } + + for (i = 0; i != 4; i++) { + do { + g_ScenarioData.ctc.teamindexes[i] = random() % 4; + } while (teamsdone[g_ScenarioData.ctc.teamindexes[i]]); + + teamsdone[g_ScenarioData.ctc.teamindexes[i]] = true; + } + + for (k = 0; k < 12; k++) { + if (g_MpSetup.chrslots & (1 << k)) { + mpchr = MPCHR(k); + + while (mpchr->team >= scenarioGetMaxTeams()) { + mpchr->team -= scenarioGetMaxTeams(); + } + +#if VERSION >= VERSION_NTSC_1_0 + mpindex = func0f18d0e8(k); + + if (mpindex >= 0) { + struct chrdata *chr = mpGetChrFromPlayerIndex(mpindex); + + if (chr) { + chr->team = 1 << mpchr->team; + } + } +#else + if (func0f18d0e8(k) >= 0) { + struct chrdata *chr = mpGetChrFromPlayerIndex(func0f18d0e8(k)); + + if (chr) { + chr->team = 1 << mpchr->team; + } + } +#endif + + g_ScenarioData.ctc.playercountsperteam[mpchr->team]++; + } + } + + for (i = 0; i < 4; i++) { + if (g_ScenarioData.ctc.playercountsperteam[i] == 0) { + g_ScenarioData.ctc.teamindexes[i] = -1; + } + } + + for (i = 0; i < 4; i++) { + // empty + } + + for (i = 0; i < 4; i++) { + g_ScenarioData.ctc.tokens[i] = NULL; + } + + for (i = 0; i < 4; i++) { + g_ScenarioData.ctc.baserooms[i] = -1; + } + + if (g_ScenarioData.ctc.playercountsperteam[0] != 0) { + g_CtcTokenObj0 = template; + tmp = &g_CtcTokenObj0; + tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[0]].homepad; + + weaponAssignToHome(tmp, 1000); + + g_ScenarioData.ctc.tokens[0] = tmp->base.prop; + + tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; + tmp->team = 0; + + g_ScenarioData.ctc.baserooms[0] = g_ScenarioData.ctc.tokens[0]->rooms[0]; + } + + if (g_ScenarioData.ctc.playercountsperteam[1] != 0) { + g_CtcTokenObj1 = template; + tmp = &g_CtcTokenObj1; + tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[1]].homepad; + + weaponAssignToHome(tmp, 1001); + + g_ScenarioData.ctc.tokens[1] = tmp->base.prop; + + tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; + tmp->team = 1; + + g_ScenarioData.ctc.baserooms[1] = g_ScenarioData.ctc.tokens[1]->rooms[0]; + } + + if (g_ScenarioData.ctc.playercountsperteam[2] != 0) { + g_CtcTokenObj2 = template; + tmp = &g_CtcTokenObj2; + tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[2]].homepad; + + weaponAssignToHome(tmp, 1002); + + g_ScenarioData.ctc.tokens[2] = tmp->base.prop; + + tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; + tmp->team = 2; + + g_ScenarioData.ctc.baserooms[2] = g_ScenarioData.ctc.tokens[2]->rooms[0]; + } + + if (g_ScenarioData.ctc.playercountsperteam[3] != 0) { + g_CtcTokenObj3 = template; + tmp = &g_CtcTokenObj3; + tmp->base.pad = g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[3]].homepad; + + weaponAssignToHome(tmp, 1003); + + g_ScenarioData.ctc.tokens[3] = tmp->base.prop; + + tmp->base.hidden2 &= ~OBJH2FLAG_CANREGEN; + tmp->team = 3; + + g_ScenarioData.ctc.baserooms[3] = g_ScenarioData.ctc.tokens[3]->rooms[0]; + } + + for (i = 0; i < 4; i++) { + if (g_ScenarioData.ctc.playercountsperteam[i] && g_ScenarioData.ctc.baserooms[i] != -1) { + roomSetLighting(g_ScenarioData.ctc.baserooms[i], 5, 0, 0, 0); + } + } +} + +void ctcCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) +{ + struct mpchrconfig *loopmpchr; + s32 i; + + *score = 0; + *score += mpchr->numpoints * 3; + + if (g_MpSetup.options & MPOPTION_KILLSSCORE) { + for (i = 0; i != MAX_MPCHRS; i++) { + if (i == mpchrnum) { + *score -= mpchr->killcounts[i]; + } else { + loopmpchr = MPCHR(i); + + if (loopmpchr->team == mpchr->team) { + *score -= mpchr->killcounts[i]; + } else { + *score += mpchr->killcounts[i]; + } + } + } + } + + *deaths = mpchr->numdeaths; +} + +Gfx *ctcRadarExtra(Gfx *gdl) +{ + if (g_MpSetup.options & MPOPTION_CTC_SHOWONRADAR) { + s32 i; + + for (i = 0; i < scenarioGetMaxTeams(); i++) { + if (g_ScenarioData.ctc.tokens[i] && + g_ScenarioData.ctc.tokens[i]->type != PROPTYPE_CHR && + g_ScenarioData.ctc.tokens[i]->type != PROPTYPE_PLAYER) { + struct coord dist; + dist.x = g_ScenarioData.ctc.tokens[i]->pos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = g_ScenarioData.ctc.tokens[i]->pos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = g_ScenarioData.ctc.tokens[i]->pos.z - g_Vars.currentplayer->prop->pos.z; + gdl = radarDrawDot(gdl, g_ScenarioData.ctc.tokens[i], &dist, g_TeamColours[i], 0, 1); + } + } + } + + return gdl; +} + +bool ctcRadarChr(Gfx **gdl, struct prop *prop) +{ + s32 i; + + if (g_MpSetup.options & MPOPTION_CTC_SHOWONRADAR) { + for (i = 0; i < scenarioGetMaxTeams(); i++) { + if (prop == g_ScenarioData.ctc.tokens[i] && + (g_ScenarioData.ctc.tokens[i]->type == PROPTYPE_CHR || g_ScenarioData.ctc.tokens[i]->type == PROPTYPE_PLAYER)) { + struct coord dist; + s32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; + dist.x = g_ScenarioData.ctc.tokens[i]->pos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = g_ScenarioData.ctc.tokens[i]->pos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = g_ScenarioData.ctc.tokens[i]->pos.z - g_Vars.currentplayer->prop->pos.z; + *gdl = radarDrawDot(*gdl, g_ScenarioData.ctc.tokens[i], &dist, + g_TeamColours[i], colour, 1); + return true; + } + } + } + + return false; +} + +bool ctcHighlightProp(struct prop *prop, s32 *colour) +{ + struct defaultobj *obj = prop->obj; + + if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) { + if (obj->type == OBJTYPE_WEAPON) { + struct weaponobj *weapon = prop->weapon; + + if (weapon->weaponnum == WEAPON_BRIEFCASE2) { + u32 teamcolour = g_TeamColours[weapon->team]; + + colour[0] = teamcolour >> 24 & 0xff; + colour[1] = teamcolour >> 16 & 0xff; + colour[2] = teamcolour >> 8 & 0xff; + colour[3] = 75; + + return true; + } + } + } + + return false; +} + +void ctcAddPad(s32 *cmd) +{ + s32 i; + + if (cmd[0] == INTROCMD_CASE) { + g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].homepad = cmd[2]; + } + + if (cmd[0] == INTROCMD_CASERESPAWN) { + for (i = 0; i != ARRAYCOUNT(g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].spawnpads); i++) { + if (g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].spawnpads[i] == -1) { + g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].spawnpads[i] = cmd[2]; + g_ScenarioData.ctc.spawnpadsperteam[cmd[1]].numspawnpads++; + return; + } + } + } +} + +bool ctcChooseSpawnLocation(f32 arg0, struct coord *pos, s16 *rooms, struct prop *prop, f32 *arg4) +{ + struct chrdata *chr = prop->chr; + s32 index = radarGetTeamIndex(chr->team); + + if (g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[index]].numspawnpads > 0) { + *arg4 = playerChooseSpawnLocation(arg0, pos, rooms, prop, + g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[index]].spawnpads, + g_ScenarioData.ctc.spawnpadsperteam[g_ScenarioData.ctc.teamindexes[index]].numspawnpads); + return true; + } + + return false; +} + +s32 ctcGetMaxTeams(void) +{ + return 4; +} + +bool ctcIsRoomHighlighted(s16 room) +{ + s32 i; + + for (i = 0; i < 4; i++) { + if (g_ScenarioData.ctc.baserooms[i] == room && g_ScenarioData.ctc.teamindexes[i] != -1) { + return true; + } + } + + return false; +} + +void ctcHighlightRoom(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3) +{ + s32 i; + + for (i = 0; i < 4; i++) { + if (g_ScenarioData.ctc.baserooms[i] == roomnum) { + u32 colour = g_TeamColours[i]; + f32 a = *arg1; + f32 b = *arg2; + f32 c = *arg3; + + a *= (s32)((colour >> 24 & 0xff) + 0xff) * (1.0f / 512.0f); + b *= (s32)((colour >> 16 & 0xff) + 0xff) * (1.0f / 512.0f); + c *= (s32)((colour >> 8 & 0xff) + 0xff) * (1.0f / 512.0f); + + *arg1 = a; + *arg2 = b; + *arg3 = c; + return; + } + } +} diff --git a/src/game/mplayer/scenarios/combat.inc b/src/game/mplayer/scenarios/combat.inc new file mode 100644 index 000000000..9828a2797 --- /dev/null +++ b/src/game/mplayer/scenarios/combat.inc @@ -0,0 +1,31 @@ +/** + * Combat + * + * This is a general combat scenario. + * + * There are no callback functions or special logic. + */ + +struct menuitem g_MpCombatOptionsMenuItems[] = { + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" + { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_228, MPOPTION_NOPLAYERHIGHLIGHT, menuhandlerMpCheckboxOption }, // "No Player Highlight" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_229, MPOPTION_NOPICKUPHIGHLIGHT, menuhandlerMpCheckboxOption }, // "No Pickup Highlight" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" + { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, +}; + +struct menudialog g_MpCombatOptionsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + L_MPMENU_215, // "Combat Options" + g_MpCombatOptionsMenuItems, + mpOptionsMenuDialog, + 0x00000010, + NULL, +}; diff --git a/src/game/mplayer/scenarios/hackthatmac.inc b/src/game/mplayer/scenarios/hackthatmac.inc new file mode 100644 index 000000000..b42bb43c4 --- /dev/null +++ b/src/game/mplayer/scenarios/hackthatmac.inc @@ -0,0 +1,690 @@ +/** + * Hack that Mac (also known as Hacker Central) + * + * A data uplink and a terminal are spawned into a random location in the arena. + * Players must pick up the data uplink then go to the terminal and download + * data from it to score points. Each download takes 20 seconds and the player + * cannot use weapons while uplinking. + */ + +#define HTM_NUM_TERMINALS 1 + +struct menuitem g_HtmOptionsMenuItems[] = { + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" + { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_231, MPOPTION_HTM_HIGHLIGHTTERMINAL, menuhandlerMpCheckboxOption }, // "Highlight Terminal" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_238, MPOPTION_HTM_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" + { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, +}; + +struct menudialog g_HtmOptionsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + L_MPMENU_217, // "Hacker Options" + g_HtmOptionsMenuItems, + mpOptionsMenuDialog, + 0x00000010, + NULL, +}; + +struct weaponobj g_HtmUplinkObj; + +void htmInit(void) +{ + g_ScenarioData.htm.uplink = NULL; +} + +/** + * Add one for the data uplink. + */ +s32 htmNumProps(void) +{ + return HTM_NUM_TERMINALS + 1; +} + +void htmAddPad(s16 padnum) +{ + struct scenariodata_htm *data = &g_ScenarioData.htm; + +#if VERSION >= VERSION_NTSC_1_0 + if (data->numpads < ARRAYCOUNT(g_ScenarioData.htm.padnums)) +#endif + { + osSyncPrintf("HackThatMacAddBankPad -> Adding New Pad %d - Pad Id = %d-> Saving Pad\n", data->numpads, padnum); + + data->padnums[data->numpads] = padnum; + data->numpads++; + } +} + +void htmReset(void) +{ + s32 i; + + osSyncPrintf("HackThatMacReset -> Working\n"); + + g_ScenarioData.htm.numpads = 0; + g_ScenarioData.htm.numterminals = 0; + g_ScenarioData.htm.unk138 = 0; + g_ScenarioData.htm.dlplayernum = -1; + g_ScenarioData.htm.playernuminrange = -1; + g_ScenarioData.htm.dlterminalnum = -1; + g_ScenarioData.htm.unk140 = 0; + + for (i = 0; i < ARRAYCOUNT(g_ScenarioData.htm.numpoints); i++) { + g_ScenarioData.htm.numpoints[i] = 0; + g_ScenarioData.htm.dltime240[i] = 0; + } + + for (i = 0; i < ARRAYCOUNT(g_ScenarioData.htm.padnums); i++) { + g_ScenarioData.htm.padnums[i] = -1; + } + + for (i = 0; i < HTM_NUM_TERMINALS; i++) { + g_ScenarioData.htm.terminals[i].unk00 = 0; + g_ScenarioData.htm.terminals[i].prop = NULL; + g_ScenarioData.htm.terminals[i].padnum = -1; + g_ScenarioData.htm.terminals[i].team = 255; + g_ScenarioData.htm.terminals[i].unk0b = 255; + } +} + +void htbCreateUplink(void) +{ + struct weaponobj template = { + 512, // extrascale + 0, // hidden2 + OBJTYPE_WEAPON, // type + MODEL_CHRDATATHIEF, // modelnum + 0, // pad + OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, + OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, + 0, // flags3 + NULL, // prop + NULL, // model + 1, 0, 0, // realrot + 0, 1, 0, + 0, 0, 1, + 0, // hidden + NULL, // geo + NULL, // projectile + 0, // damage + 1000, // maxdamage + 0xff, 0xff, 0xff, 0x00, // shadecol + 0xff, 0xff, 0xff, 0x00, // nextcol + 0x0fff, // floorcol + 0, // tiles + WEAPON_DATAUPLINK, // weaponnum + 0, // unk5d + 0, // unk5e + FUNC_PRIMARY, // gunfunc + 0, // fadeouttimer60 + -1, // dualweaponnum + -1, // timer240 + NULL, // dualweapon + }; + + struct prop *prop = g_Vars.activeprops; + struct defaultobj *obj; + s32 padnum; + s32 count = 0; + struct defaultobj *candidates[20]; + + while (prop && count < 20) { +#if VERSION >= VERSION_NTSC_1_0 + if (prop->type == PROPTYPE_OBJ) +#endif + { + obj = prop->obj; + + if (obj->type == OBJTYPE_MULTIAMMOCRATE) { + candidates[count] = obj; + count++; + } + } + + prop = prop->next; + } + + if (count > 0) { + count = random() % count; + var800869ec = candidates[count]; + var800869ec->hidden |= OBJHFLAG_REAPABLE; + var800869ec->hidden2 |= OBJH2FLAG_CANREGEN; + padnum = var800869ec->pad; + } else if (g_ScenarioData.htm.numpads > 0) { + padnum = g_ScenarioData.htm.padnums[random() % g_ScenarioData.htm.numpads]; + } else { + padnum = 0; + } + + g_HtmUplinkObj = template; + g_HtmUplinkObj.base.pad = padnum; + + weaponAssignToHome(&g_HtmUplinkObj, 999); + + g_HtmUplinkObj.base.hidden2 &= ~OBJH2FLAG_CANREGEN; + + g_ScenarioData.htm.uplink = g_HtmUplinkObj.base.prop; + + if (g_ScenarioData.htm.uplink) { + g_ScenarioData.htm.uplink->forcetick = true; + } +} + +void htmInitProps(void) +{ + struct scenariodata_htm *data = &g_ScenarioData.htm; + struct prop *prop = g_Vars.activeprops; + s32 i = 0; + s32 rand; + + osSyncPrintf("HackThatMacInitProps -> Start : %d Bank Pads\n", data->numterminals); + + while (prop) { + if (prop->type == PROPTYPE_OBJ) { + struct defaultobj *obj = prop->obj; + + if (obj->type == OBJTYPE_AMMOCRATE || obj->type == OBJTYPE_MULTIAMMOCRATE) { + if (obj->modelnum == MODEL_MULTI_AMMO_CRATE) { + osSyncPrintf("HackThatMacInitProps -> Adding prop %d (%x)\n", i, obj->pad); + htmAddPad(obj->pad); + } + } + } + + prop = prop->next; + i++; + } + + osSyncPrintf("HackThatMacInitProps -> Mid : %d Bank Pads\n", data->numpads); + osSyncPrintf("HackThatMacInitProps -> Generating %d random box pads from %d in the bank\n", htmNumProps(), data->numpads); + + data->numterminals = 0; + + // @bug: This should be < htmNumProps() - 1 to account for the data uplink, + // or just < HTM_NUM_TERMINALS. If HTM_NUM_TERMINALS is set to 7 then it + // will overflow the array. + while (data->numterminals < htmNumProps()) { + s32 padnum; + + do { + rand = random() % data->numpads; + padnum = data->padnums[rand]; + } while (padnum <= 0); + + data->terminals[data->numterminals].padnum = padnum; + data->numterminals++; + data->padnums[rand] = -1; + } + + osSyncPrintf("HackThatMacInitProps -> %d/%d Random box pads generated - Listing\n", data->numterminals, htmNumProps()); + + for (i = 0; i < data->numterminals; i++) { + osSyncPrintf("Pad %d -> Pad Id = %d\n", i, data->terminals[i].padnum); + } + + for (i = 0; i < HTM_NUM_TERMINALS; i++) { + data->terminals[i].prop = scenarioCreateObj(MODEL_GOODPC, data->terminals[i].padnum, 0.2f, + OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, + OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, + OBJFLAG3_HTMTERMINAL | OBJFLAG3_INTERACTABLE); + osSyncPrintf("HackThatMacInitProps -> Building and adding custom prop %d - Pad=%d, Ptr=%08x\n", + i, data->terminals[i].padnum, data->terminals[i].prop); + htbRemoveAmmoCrateAtPad(data->terminals[i].padnum); + } + + var800869ec = NULL; + + htbCreateUplink(); + + osSyncPrintf("HackThatMacInitProps -> End\n"); +} + +void htmTick(void) +{ + u8 stack[8]; + s32 i; + u32 prevplayernum = g_Vars.currentplayernum; + struct prop *prop; + + if (var800869ec && var800869ec->prop) { + if (g_ScenarioData.htm.uplink == NULL || g_ScenarioData.htm.uplink->type != PROPTYPE_WEAPON) { + var800869ec = NULL; + } else { + var800869ec->prop->timetoregen = PALDOWN(1200); + } + } + + g_ScenarioData.htm.uplink = NULL; + + // Check if uplink is on the ground + prop = g_Vars.activeprops; + + while (prop) { + if (prop->type == PROPTYPE_WEAPON) { + struct weaponobj *weapon = prop->weapon; + + if (weapon->weaponnum == WEAPON_DATAUPLINK) { + g_ScenarioData.htm.uplink = prop; + } + } + + prop = prop->next; + } + + // Check if a player is holding it + if (g_ScenarioData.htm.uplink == NULL) { + for (i = 0; i < PLAYERCOUNT(); i++) { + setCurrentPlayerNum(i); + + if (invHasDataUplink()) { + g_ScenarioData.htm.uplink = g_Vars.currentplayer->prop; + break; + } + } + } + + setCurrentPlayerNum(prevplayernum); + + // Check if a simulant is holding it + if (g_ScenarioData.htm.uplink == NULL) { + for (i = PLAYERCOUNT(); i < g_MpNumChrs; i++) { + if (g_MpAllChrPtrs[i]->aibot->hasuplink) { + g_ScenarioData.htm.uplink = g_MpAllChrPtrs[i]->prop; + break; + } + } + } + + if (g_ScenarioData.htm.uplink == NULL) { + htbCreateUplink(); + } +} + +void htmTickChr(struct chrdata *chr) +{ + struct scenariodata_htm *data = &g_ScenarioData.htm; + bool hasuplink; + s32 playernum; + s32 i; + s32 *time; + + if (chr) { + hasuplink = chr->aibot->hasuplink; + playernum = mpPlayerGetIndex(chr); + } else { + hasuplink = invHasDataUplink() && bgunGetWeaponNum(HAND_RIGHT) == WEAPON_DATAUPLINK; + playernum = g_Vars.currentplayernum; + } + + time = &data->dltime240[playernum]; + + for (i = 0; i < HTM_NUM_TERMINALS; i++) { + if (data->terminals[i].prop) { + struct prop *prop = data->terminals[i].prop; + struct defaultobj *obj = prop->obj; + s32 activatedbyplayernum = -1; + + if (chr) { + if (hasuplink) { + activatedbyplayernum = playernum; + } + } else { + if (obj->hidden & OBJHFLAG_ACTIVATED_BY_BOND) { + activatedbyplayernum = (obj->hidden & 0xf0000000) >> 28; + } + } + + if (playernum == activatedbyplayernum) { + obj->hidden &= ~OBJHFLAG_ACTIVATED_BY_BOND; + obj->hidden &= ~0xf0000000; + + if (hasuplink) { + if (data->dlterminalnum == -1) { + data->dlterminalnum = i; + data->dlplayernum = playernum; + data->playernuminrange = playernum; + *time = 0; + + if (chr == NULL) { + hudmsgCreateWithFlags(langGet(L_MPWEAPONS_018), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); // "Starting download." + func0f0939f8(NULL, data->terminals[data->dlterminalnum].prop, SFX_01BF, -1, + -1, 2, 2, 0, NULL, -1, NULL, -1, -1, -1, -1); + } + } + } else { + if (chr == NULL) { + hudmsgCreateWithFlags(langGet(L_MPWEAPONS_019), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); // "You need to use the Data Uplink." + snd00010718(NULL, 0, 0x7fff, 0x40, SFX_01CC, 1, 1, -1, 1); + } + } + } + } + } + + if (playernum == data->dlplayernum && data->dlterminalnum != -1) { + struct coord *terminalpos = &data->terminals[data->dlterminalnum].prop->pos; + f32 angle; + f32 relangle; + f32 rangexz; + f32 rangey; + struct coord *chrpos; + struct coord dist; + bool holdinguplink; + + if (chr) { + chrpos = &chr->prop->pos; + angle = (M_BADTAU - chrGetInverseTheta(chr)) * 57.295776367188f; + holdinguplink = chr->aibot->weaponnum == WEAPON_UNARMED; + } else { + chrpos = &g_Vars.currentplayer->prop->pos; + angle = g_Vars.currentplayer->vv_theta; + holdinguplink = bgunGetWeaponNum(HAND_RIGHT) == WEAPON_DATAUPLINK; + } + + dist.x = terminalpos->x - chrpos->x; + dist.y = terminalpos->y - chrpos->y; + dist.z = terminalpos->z - chrpos->z; + + rangexz = sqrtf(dist.x * dist.x + dist.z * dist.z); + + rangey = ABS(dist.y); + + relangle = atan2f(dist.x, dist.z) * 57.295776367188f + angle; + + while (relangle < 180) { + relangle += 360; + } + + while (relangle > 180) { + relangle -= 360; + } + + if (relangle > 0) { + // empty + } else { + relangle = -relangle; + } + + osSyncPrintf("HTM : Player %d - Term Pos = (%d,%d,%d)", playernum, (s32)terminalpos->x, (s32)terminalpos->y, (s32)terminalpos->z); + osSyncPrintf("HTM : Player %d - Play Pos = (%d,%d,%d)", playernum, (s32)chrpos->x, (s32)chrpos->y, (s32)chrpos->z); + osSyncPrintf("HTM : Player %d - T/P Rel = (%d,%d,%d)", playernum, (s32)dist.x, (s32)dist.y, (s32)dist.z); + + osSyncPrintf("HTM : Player %d - Range XZ = %d", playernum, rangexz); + osSyncPrintf("HTM : Player %d - Range Y = %d", playernum, rangey); + osSyncPrintf("HTM : Player %d - Angle XZ = %d", playernum, relangle); + + if (rangexz > 250 || rangey > 200 || relangle > 45 || !holdinguplink) { + if (rangexz < 250 && rangey < 200) { + data->playernuminrange = playernum; + } else { + data->playernuminrange = -1; + } + + if (chr == NULL) { + // "Connection broken." + hudmsgCreateWithFlags(langGet(L_MPWEAPONS_017), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); + func0f0926bc(data->terminals[data->dlterminalnum].prop, 1, 0xffff); + snd00010718(NULL, 0, 0x7fff, 0x40, SFX_01CC, 1, 1, -1, 1); + } + + data->dlterminalnum = -1; + data->dlplayernum = -1; + *time = 0; + } else { + *time += g_Vars.lvupdate240; + + if (*time > 20 * PALDOWN(240)) { + data->numpoints[playernum]++; + data->playernuminrange = playernum; + + if (chr == NULL) { + // "Download successful." + hudmsgCreateWithFlags(langGet(L_MPWEAPONS_016), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); + func0f0926bc(data->terminals[data->dlterminalnum].prop, 1, 0xffff); + snd00010718(NULL, 0, 0x7fff, 0x40, SFX_01C1, 1, 1, -1, 1); + } + + data->dlterminalnum = -1; + data->dlplayernum = -1; + *time = 0; + } + } + + osSyncPrintf("HTM : Player %d - Dwnld Plr=%d, Dwnld Prop=%d\n", playernum, data->playernuminrange, data->terminals[data->dlterminalnum].prop); + osSyncPrintf("HTM : Player %d - Download Time = %d", playernum, *time); + } +} + +Gfx *htmRenderHud(Gfx *gdl) +{ + struct scenariodata_htm *data = &g_ScenarioData.htm; + s32 dltime; + s32 viewleft; + s32 viewright; + s32 viewtop; + s32 a0; + s32 a1; + s32 barleft; + s32 barright; + s32 t1; + s32 t6; + s32 v1; + s32 s1; + + dltime = data->dltime240[g_Vars.currentplayernum]; + + if (data->dlterminalnum != -1 && g_Vars.currentplayernum == data->dlplayernum) { + viewleft = viGetViewLeft(); + viewright = viGetViewLeft() + viGetViewWidth(); + viewtop = viGetViewTop(); + t6 = (viewleft + viewright) / 2; + a1 = viGetViewWidth() / 3; + barleft = t6 - a1 / 2; + barright = t6 + a1 / 2; + s1 = barleft + (s32) (a1 * (dltime / PALDOWN(4800.0f))); + + gdl = func0f153628(gdl); + gdl = gfxSetPrimColour(gdl, 0x60000060); + + gDPFillRectangle(gdl++, barleft, viewtop + 8, barright, viewtop + 16); + + gdl = func0f153838(gdl); + gdl = gfxSetPrimColour(gdl, 0xc00000d0); + + v1 = barleft + 1; + a0 = barleft; + + while (v1 < s1) { + gDPFillRectangle(gdl++, a0, viewtop + 8, v1, viewtop + 16); + v1 += 2; + a0 += 2; + } + + gdl = func0f153838(gdl); + gdl = func0f153780(gdl); + } + + return gdl; +} + +void htmCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) +{ + struct mpchrconfig *loopmpchr; + s32 i; + s32 index; + + *score = 0; + index = func0f18d0e8(mpchrnum); + + if (index >= 0) { + *score += g_ScenarioData.htm.numpoints[index] * 2; + } + + if (g_MpSetup.options & MPOPTION_KILLSSCORE) { + for (i = 0; i != MAX_MPCHRS; i++) { + if (i == mpchrnum) { + *score -= mpchr->killcounts[i]; + } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { + loopmpchr = MPCHR(i); + + if (loopmpchr->team == mpchr->team) { + *score -= mpchr->killcounts[i]; + } else { + *score += mpchr->killcounts[i]; + } + } else { + *score += mpchr->killcounts[i]; + } + } + } + + *deaths = mpchr->numdeaths; +} + +Gfx *htmRadarExtra(Gfx *gdl) +{ + struct scenariodata_htm *data = &g_ScenarioData.htm; + struct coord dist; + s32 i; + struct coord sp88; + + // Red/green/blue/alpha as float and integer + f32 rf; + f32 gf; + f32 bf; + f32 af; + + u32 ri; + u32 gi; + u32 bi; + u32 ai; + + if (g_MpSetup.options & MPOPTION_HTM_SHOWONRADAR) { + // Show the uplink + if (data->uplink && data->uplink->type != PROPTYPE_PLAYER && data->uplink->type != PROPTYPE_CHR) { + dist.x = data->uplink->pos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = data->uplink->pos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = data->uplink->pos.z - g_Vars.currentplayer->prop->pos.z; + + gdl = radarDrawDot(gdl, data->uplink, &dist, 0x00ff0000, 0x00000000, true); + } + + // Show the terminal + for (i = 0; i < HTM_NUM_TERMINALS; i++) { + if (data->terminals[i].prop) { + sp88.x = data->terminals[i].prop->pos.x - g_Vars.currentplayer->prop->pos.x; + sp88.y = data->terminals[i].prop->pos.y - g_Vars.currentplayer->prop->pos.y; + sp88.z = data->terminals[i].prop->pos.z - g_Vars.currentplayer->prop->pos.z; + + if (data->terminals[i].team == 255) { + rf = 0; + gf = 255; + bf = 0; + af = 0; + } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { + u32 colour = g_TeamColours[radarGetTeamIndex(data->terminals[i].team)]; + rf = (colour >> 24) & 0xff; + gf = ((colour >> 16) & 0xff); + bf = ((colour >> 8) & 0xff); + af = colour & 0xff; + } else { + rf = 0; + gf = 255; + bf = 0; + af = 0; + } + + ri = rf; + gi = gf; + bi = bf; + ai = af; + + if (ri > 255) { + ri = 255; + } + + if (gi > 255) { + gi = 255; + } + + if (bi > 255) { + bi = 255; + } + + if (ai > 255) { + ai = 255; + } + + gdl = radarDrawDot(gdl, data->terminals[i].prop, &sp88, + (ri << 24) | (gi << 16) | (bi << 8) | ai, + 0x00000000, true); + } + } + } + + return gdl; +} + +bool htmRadarChr(Gfx **gdl, struct prop *prop) +{ + if ((g_MpSetup.options & MPOPTION_HTM_SHOWONRADAR) && g_ScenarioData.htm.uplink) { + if (prop == g_ScenarioData.htm.uplink && + (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR)) { + struct coord dist; + dist.x = prop->pos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = prop->pos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = prop->pos.z - g_Vars.currentplayer->prop->pos.z; + + if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { + u32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; + *gdl = radarDrawDot(*gdl, g_ScenarioData.htm.uplink, &dist, colour, 0, 1); + } else { + *gdl = radarDrawDot(*gdl, g_ScenarioData.htm.uplink, &dist, 0x00ff0000, 0, 1); + } + + return true; + } + } + + return false; +} + +bool htmHighlightProp(struct prop *prop, s32 *colour) +{ + if (g_MpSetup.options & MPOPTION_HTM_HIGHLIGHTTERMINAL) { + bool highlight = false; + + if (prop == g_ScenarioData.htm.uplink) { + highlight = true; + } else { + s32 i; + + for (i = 0; i < HTM_NUM_TERMINALS; i++) { + if (g_ScenarioData.htm.terminals[i].prop == prop) { + highlight = true; + break; + } + } + } + + if (highlight) { + colour[0] = 0; + colour[1] = 0xff; + colour[2] = 0; + colour[3] = 0x40; + + return true; + } + } + + return false; +} diff --git a/src/game/mplayer/scenarios/holdthebriefcase.inc b/src/game/mplayer/scenarios/holdthebriefcase.inc new file mode 100644 index 000000000..c31c821b9 --- /dev/null +++ b/src/game/mplayer/scenarios/holdthebriefcase.inc @@ -0,0 +1,434 @@ +/** + * Hold the Briefcase + * + * A briefcase is spawned into a random location in the arena. Players must pick + * up the briefcase and then stay alive for as long as possible. The player + * scores a point every 30 seconds while the briefcase is held. + * + * While holding the briefcase, the player cannot pick up shields. The move + * slightly slower but still have full access to their weaponry. + */ + +struct menuitem g_HtbOptionsMenuItems[] = { + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" + { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_237, MPOPTION_HTB_HIGHLIGHTBRIEFCASE, menuhandlerMpCheckboxOption }, // "Highlight Briefcase" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_238, MPOPTION_HTB_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" + { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, +}; + +struct menudialog g_HtbOptionsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + L_MPMENU_216, // "Briefcase Options" + g_HtbOptionsMenuItems, + mpOptionsMenuDialog, + 0x00000010, + NULL, +}; + +struct defaultobj *var800869ec = NULL; + +struct weaponobj g_HtbTokenObj; + +void htbInit(void) +{ + g_ScenarioData.htb.token = NULL; +} + +void htbAddPad(s16 padnum) +{ + struct scenariodata_htb *data = &g_ScenarioData.htb; + +#if VERSION >= VERSION_NTSC_1_0 + if (data->nextindex < ARRAYCOUNT(data->padnums)) +#endif + { + osSyncPrintf("CaptureTheBriefcaseAddBankPad -> Adding New Pad %d - Pad Id = %d-> Saving Pad\n", data->nextindex, padnum); + + data->padnums[data->nextindex] = padnum; + data->nextindex++; + } +} + +s32 htbNumProps(void) +{ + return 1; +} + +void htbRemoveAmmoCrateAtPad(s16 padnum) +{ + struct prop *prop = g_Vars.activeprops; + + while (prop) { + if (prop->type == PROPTYPE_OBJ) { + struct defaultobj *obj = prop->obj; + + if (obj->pad == padnum + && (obj->type == OBJTYPE_AMMOCRATE || obj->type == OBJTYPE_MULTIAMMOCRATE) + && obj->modelnum == MODEL_MULTI_AMMO_CRATE) { + obj->hidden |= OBJHFLAG_REAPABLE; + obj->hidden2 &= ~OBJH2FLAG_CANREGEN; + return; + } + } + + prop = prop->next; + } +} + +void htbReset(void) +{ + s32 i; + + g_ScenarioData.htb.nextindex = 0; + + for (i = 0; i < ARRAYCOUNT(g_ScenarioData.htb.padnums); i++) { + g_ScenarioData.htb.padnums[i] = -1; + } +} + +void htbCreateToken(void) +{ + struct weaponobj template = { + 256, // extrascale + 0, // hidden2 + OBJTYPE_WEAPON, // type + MODEL_CHRBRIEFCASE, // modelnum + 0, // pad + OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000, + OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000, + 0, // flags3 + NULL, // prop + NULL, // model + 1, 0, 0, // realrot + 0, 1, 0, + 0, 0, 1, + 0, // hidden + NULL, // geo + NULL, // projectile + 0, // damage + 1000, // maxdamage + 0xff, 0xff, 0xff, 0x00, // shadecol + 0xff, 0xff, 0xff, 0x00, // nextcol + 0x0fff, // floorcol + 0, // tiles + WEAPON_BRIEFCASE2, // weaponnum + 0, // unk5d + 0, // unk5e + FUNC_PRIMARY, // gunfunc + 0, // fadeouttimer60 + -1, // dualweaponnum + -1, // timer240 + NULL, // dualweapon + }; + + struct prop *prop = g_Vars.activeprops; + struct defaultobj *obj; + s32 count = 0; + struct defaultobj *candidates[20]; + + // Build a list of candidate objects to replace. Consider only ammocrates. + // NTSC beta doesn't check the prop type, so it could potentially replace a + // player, bot, explosion or smoke. + while (prop && count < 20) { +#if VERSION >= VERSION_NTSC_1_0 + if (prop->type == PROPTYPE_OBJ) +#endif + { + obj = prop->obj; + + if (obj->type == OBJTYPE_MULTIAMMOCRATE) { + candidates[count] = obj; + count++; + } + } + + prop = prop->next; + } + + // Choose the candidate and remove it + if (count > 0) { + count = random() % count; + var800869ec = candidates[count]; + g_ScenarioData.htb.tokenpad = var800869ec->pad; + var800869ec->hidden |= OBJHFLAG_REAPABLE; + var800869ec->hidden2 |= OBJH2FLAG_CANREGEN; + } else if (g_ScenarioData.htb.nextindex > 0) { + g_ScenarioData.htb.tokenpad = g_ScenarioData.htb.padnums[random() % g_ScenarioData.htb.nextindex]; + } else { + g_ScenarioData.htb.tokenpad = 0; + } + + // Set up the token + g_HtbTokenObj = template; + g_HtbTokenObj.base.pad = g_ScenarioData.htb.tokenpad; + + weaponAssignToHome(&g_HtbTokenObj, 999); + + g_HtbTokenObj.base.hidden2 &= ~OBJH2FLAG_CANREGEN; + + g_ScenarioData.htb.token = g_HtbTokenObj.base.prop; + + if (g_ScenarioData.htb.token) { + g_ScenarioData.htb.token->forcetick = true; + } +} + +void htbInitProps(void) +{ + var800869ec = NULL; + htbCreateToken(); +} + +void htbTick(void) +{ + s32 i; + u32 prevplayernum = g_Vars.currentplayernum; + struct prop *prop; + + if (var800869ec && var800869ec->prop) { + if (g_ScenarioData.htb.token == NULL || g_ScenarioData.htb.token->type != PROPTYPE_WEAPON) { + var800869ec = NULL; + } else { + var800869ec->prop->timetoregen = PALDOWN(1200); + } + } + + g_ScenarioData.htb.token = NULL; + + // Check if briefcase is on the ground + prop = g_Vars.activeprops; + + while (prop) { + if (prop->type == PROPTYPE_WEAPON) { + struct weaponobj *weapon = prop->weapon; + + if (weapon->weaponnum == WEAPON_BRIEFCASE2) { + g_ScenarioData.htb.token = prop; + } + } + + prop = prop->next; + } + + // Check if a player is holding it + if (g_ScenarioData.htb.token == NULL) { + for (i = 0; i < PLAYERCOUNT(); i++) { + setCurrentPlayerNum(i); + + if (invHasBriefcase()) { + g_ScenarioData.htb.token = g_Vars.currentplayer->prop; + break; + } + } + } + + setCurrentPlayerNum(prevplayernum); + + // Check if a simulant is holding it + if (g_ScenarioData.htb.token == NULL) { + for (i = PLAYERCOUNT(); i < g_MpNumChrs; i++) { +#if VERSION >= VERSION_NTSC_1_0 + if (g_MpAllChrPtrs[i]->prop && g_MpAllChrPtrs[i]->aibot->hasbriefcase) +#else + if (g_MpAllChrPtrs[i]->aibot->hasbriefcase) +#endif + { + g_ScenarioData.htb.token = g_MpAllChrPtrs[i]->prop; + break; + } + } + } + + if (g_ScenarioData.htb.token == NULL) { + htbCreateToken(); + } + + if (g_ScenarioData.htb.token == NULL) { + g_ScenarioData.htb.pos.x = 0; + g_ScenarioData.htb.pos.y = 0; + g_ScenarioData.htb.pos.z = 0; + } else { + struct coord *pos = &g_ScenarioData.htb.pos; + pos->x = g_ScenarioData.htb.token->pos.x; + pos->y = g_ScenarioData.htb.token->pos.y; + pos->z = g_ScenarioData.htb.token->pos.z; + } +} + +void htbTickChr(struct chrdata *chr) +{ + if (chr) { + if (chr->aibot->hasbriefcase) { + chr->aibot->unk0a0 += g_Vars.lvupdate240; + + if (chr->aibot->unk0a0 >= PALDOWN(7200)) { + sndStart(var80095200, SFX_MP_SCOREPOINT, NULL, -1, -1, -1, -1, -1); + g_MpAllChrConfigPtrs[mpPlayerGetIndex(chr)]->numpoints++; + chr->aibot->unk0a0 = 0; + } + } else { + chr->aibot->unk0a0 = 0; + } + } else { + if (invHasBriefcase()) { + g_Vars.currentplayerstats->tokenheldtime += g_Vars.lvupdate240; + + if (g_Vars.currentplayerstats->tokenheldtime >= PALDOWN(7200)) { + sndStart(var80095200, SFX_MP_SCOREPOINT, NULL, -1, -1, -1, -1, -1); + g_MpAllChrConfigPtrs[g_Vars.currentplayernum]->numpoints++; + hudmsgCreateWithFlags(langGet(L_MPWEAPONS_024), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); // "1 Point!" + g_Vars.currentplayerstats->tokenheldtime = 0; + } + } else { + g_Vars.currentplayerstats->tokenheldtime = 0; + } + } +} + +/** + * @bug: In NTSC Final, the calculation of mins and subsequent subtraction from + * time240 should use 60 * 240 instead of 30 * 240. This has no noticeable + * effect unless the score duration is increased to above 30 seconds. + * + * PAL recognises that mins will always be 0 and simplifies the calculation. + */ +Gfx *htbRenderHud(Gfx *gdl) +{ + s32 time240; + s32 mins; + s32 secs; + s32 textwidth; + s32 textheight; + s32 x; + s32 y; + char text[64]; + + if (invHasBriefcase()) { + x = viGetViewLeft() + viGetViewWidth() / 2; + y = viGetViewTop() + 10; + +#if VERSION >= VERSION_PAL_FINAL + time240 = (30 * 200) - g_Vars.currentplayerstats->tokenheldtime; + secs = (time240 + 199) / 200; + sprintf(text, "%d:%02d", 0, secs); +#else + time240 = (30 * 240) - g_Vars.currentplayerstats->tokenheldtime; + mins = time240 / (30 * 240); + time240 -= (30 * 240) * mins; + secs = (time240 + 239) / 240; + sprintf(text, "%d:%02d", mins, secs); +#endif + + gdl = func0f153628(gdl); + textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); + + x -= textwidth / 2; + textwidth += x; + textheight += y; + +#if VERSION >= VERSION_NTSC_1_0 + gdl = func0f153990(gdl, x, y, textwidth, textheight); + gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0xa0, viGetWidth(), viGetHeight(), 0, 0); +#else + gdl = func0f153858(gdl, &x, &y, &textwidth, &textheight); + gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0x88, viGetWidth(), viGetHeight(), 0, 0); +#endif + + gdl = func0f153780(gdl); + } + + return gdl; +} + +void htbCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) +{ + struct mpchrconfig *loopmpchr; + s32 i; + + *score = 0; + *score += mpchr->numpoints; + + if (g_MpSetup.options & MPOPTION_KILLSSCORE) { + for (i = 0; i != MAX_MPCHRS; i++) { + if (i == mpchrnum) { + *score -= mpchr->killcounts[i]; + } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { + loopmpchr = MPCHR(i); + + if (loopmpchr->team == mpchr->team) { + *score -= mpchr->killcounts[i]; + } else { + *score += mpchr->killcounts[i]; + } + } else { + *score += mpchr->killcounts[i]; + } + } + } + + *deaths = mpchr->numdeaths; +} + +Gfx *htbRadarExtra(Gfx *gdl) +{ + if ((g_MpSetup.options & MPOPTION_HTB_SHOWONRADAR) && + g_ScenarioData.htb.token != NULL && + g_ScenarioData.htb.token->type != PROPTYPE_PLAYER && + g_ScenarioData.htb.token->type != PROPTYPE_CHR) { + struct coord dist; + dist.x = g_ScenarioData.htb.pos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = g_ScenarioData.htb.pos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = g_ScenarioData.htb.pos.z - g_Vars.currentplayer->prop->pos.z; + gdl = radarDrawDot(gdl, g_ScenarioData.htb.token, &dist, 0x00ff0000, 0, 1); + } + + return gdl; +} + +bool htbRadarChr(Gfx **gdl, struct prop *prop) +{ + if ((g_MpSetup.options & MPOPTION_HTB_SHOWONRADAR) && + g_ScenarioData.htb.token && + prop == g_ScenarioData.htb.token) { + if (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR) { + struct coord dist; + dist.x = prop->pos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = prop->pos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = prop->pos.z - g_Vars.currentplayer->prop->pos.z; + + if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { + u32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; + *gdl = radarDrawDot(*gdl, g_ScenarioData.htb.token, &dist, colour, 0, 1); + } else { + *gdl = radarDrawDot(*gdl, g_ScenarioData.htb.token, &dist, 0x00ff0000, 0, 1); + } + + return true; + } + } + + return false; +} + +bool htbHighlightProp(struct prop *prop, s32 *colour) +{ + if ((g_MpSetup.options & MPOPTION_HTB_HIGHLIGHTBRIEFCASE) && prop == g_ScenarioData.htb.token) { + colour[0] = 0; + colour[1] = 0xff; + colour[2] = 0; + colour[3] = 0x40; + + return true; + } + + return false; +} diff --git a/src/game/mplayer/scenarios/kingofthehill.inc b/src/game/mplayer/scenarios/kingofthehill.inc new file mode 100644 index 000000000..a999e7487 --- /dev/null +++ b/src/game/mplayer/scenarios/kingofthehill.inc @@ -0,0 +1,571 @@ +/** + * King of the Hill + * + * At the start of the match, a room is chosen as the "hill" and is highlighted + * in green. When a player enters the hill it changes to their team colour. + * If the player remains in the hill for the "mphilltime" (this is configurable) + * then they score a point and a new room is chosen as the next hill. + * + * If a second team enters the hill then the timer is paused until the hill is + * exclusive again. The enemy team must completely clear the hill of opposing + * players before it is switched to their team. + * + * Points are awarded to each player in the hill at the time that it is won. + */ + +s32 menuhandlerMpHillTime(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = g_Vars.mphilltime; + break; + case MENUOP_SET: + g_Vars.mphilltime = (u8)data->slider.value; + break; + case MENUOP_GETSLIDERLABEL: + sprintf(data->slider.label, langGet(L_MPWEAPONS_023), data->slider.value + 10); // "%ds/Point" + break; + } + + return 0; +} + +struct menuitem g_KohOptionsMenuItems[] = { + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" + { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_233, MPOPTION_KOH_HILLONRADAR, menuhandlerMpCheckboxOption }, // "Hill on Radar" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_234, MPOPTION_KOH_MOBILEHILL, menuhandlerMpCheckboxOption }, // "Mobile Hill" + { MENUITEMTYPE_SLIDER, 0, 0x00020000, L_MPMENU_235, 0x0000006e, menuhandlerMpHillTime }, // "Time" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" + { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, +}; + +struct menudialog g_KohOptionsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + L_MPMENU_219, // "Hill Options" + g_KohOptionsMenuItems, + mpOptionsMenuDialog, + 0x00000010, + NULL, +}; + +void kohReadSave(struct savebuffer *buffer) +{ + g_Vars.mphilltime = savebufferReadBits(buffer, 8); +} + +void kohWriteSave(struct savebuffer *buffer) +{ + savebufferOr(buffer, g_Vars.mphilltime, 8); +} + +void kohInit(void) +{ + s32 i; + + g_MpSetup.options |= MPOPTION_TEAMSENABLED; + g_ScenarioData.koh.hillindex = -1; + g_ScenarioData.koh.hillcount = 0; + g_ScenarioData.koh.unk00 = 0; + g_ScenarioData.koh.occupiedteam = -1; + g_ScenarioData.koh.elapsed240 = 0; + g_ScenarioData.koh.hillrooms[0] = -1; + g_ScenarioData.koh.hillrooms[1] = -1; + g_ScenarioData.koh.hillpos.x = 0; + g_ScenarioData.koh.hillpos.y = 0; + g_ScenarioData.koh.hillpos.z = 0; + g_ScenarioData.koh.colourfracr = 0.25; + g_ScenarioData.koh.colourfracg = 1; + g_ScenarioData.koh.colourfracb = 0.25; + + for (i = 0; i < 9; i++) { + g_ScenarioData.koh.hillpads[i] = -1; + } +} + +void kohInitProps(void) +{ + s16 pad_id = 0; + struct pad pad; + + if (g_ScenarioData.koh.hillcount > 1) { + g_ScenarioData.koh.hillindex = random() % g_ScenarioData.koh.hillcount; + pad_id = g_ScenarioData.koh.hillpads[g_ScenarioData.koh.hillindex]; + } else { + // @bug: If a stage setup file only has one hill, pad_id is not assigned + // so it will always use the room that contains pad zero. + g_ScenarioData.koh.hillindex = 0; + } + + padUnpack(pad_id, PADFIELD_POS | PADFIELD_ROOM, &pad); + g_ScenarioData.koh.hillrooms[0] = pad.room; + g_ScenarioData.koh.hillrooms[1] = -1; + g_ScenarioData.koh.hillpos.x = pad.pos.x; + g_ScenarioData.koh.hillpos.y = pad.pos.y; + g_ScenarioData.koh.hillpos.z = pad.pos.z; + g_ScenarioData.koh.hillpos.y = cd0002a36c(&g_ScenarioData.koh.hillpos, &g_ScenarioData.koh.hillrooms[0], 0, 0); + g_ScenarioData.koh.movehill = false; + roomSetLighting(g_ScenarioData.koh.hillrooms[0], LIGHTOP_5, 0, 0, 0); +} + +/** + * A match for this function has only been possible by making heavy reuse of + * variables, but this impacts readability significantly. + * + * To make this code readable, constants have been used to map appropriate names + * to the underlying variable. + */ +void kohTick(void) +{ + s32 i; + s32 hillteam; + s32 s1; + s32 s2; + s32 numchrsinhill; + s32 dualoccupancy; + s32 s0; + s32 previndex; + f32 targetr; + f32 targetg; + f32 targetb; + char text[64]; + s32 teamsinhill[8]; + struct pad pad; + struct prop *chrsinhill[12]; + struct prop *prop; + struct chrdata *chr; + s32 padnum; + s32 teamindex; + +#define hillteam s0 +#define inhill s1 +#define mostchrs s1 +#define playernum1 s1 +#define prevplayernum1 s1 +#define numteamsinhill s2 +#define prevplayernum2 s2 +#define playernum2 s2 + + if (g_ScenarioData.koh.hillindex == -1) { + return; + } + + dualoccupancy = 0; + + if (g_ScenarioData.koh.movehill) { + // The hill is moving, but first it needs to be returned to the natural + // colour. This is done using a fade over several frames. + g_ScenarioData.koh.occupiedteam = -1; + g_ScenarioData.koh.elapsed240 = 0; + + targetr = 1; + targetg = 1; + targetb = 1; + + if (g_ScenarioData.koh.colourfracr >= .95f + && g_ScenarioData.koh.colourfracg >= .95f + && g_ScenarioData.koh.colourfracb >= .95f) { + // The old hill is now "natural enough" to set it back to full + // natural colour and actually choose a new hill. + roomSetLighting(g_ScenarioData.koh.hillrooms[0], 0, 0, 0, 0); + + // Choose the new hill. Note that hillcount refers to the number of + // hill options, which is always >= 2. + padnum = 0; + + if (g_ScenarioData.koh.hillcount >= 2) { + previndex = g_ScenarioData.koh.hillindex; + + do { + g_ScenarioData.koh.hillindex = random() % g_ScenarioData.koh.hillcount; + } while (g_ScenarioData.koh.hillindex == previndex); + + padnum = g_ScenarioData.koh.hillpads[g_ScenarioData.koh.hillindex]; + } else { + g_ScenarioData.koh.hillindex = 0; + } + + padUnpack(padnum, PADFIELD_POS | PADFIELD_ROOM, &pad); + + g_ScenarioData.koh.hillrooms[0] = pad.room; + g_ScenarioData.koh.hillrooms[1] = -1; + + g_ScenarioData.koh.hillpos.x = pad.pos.x; + g_ScenarioData.koh.hillpos.y = pad.pos.y; + g_ScenarioData.koh.hillpos.z = pad.pos.z; + + g_ScenarioData.koh.hillpos.y = cd0002a36c(&g_ScenarioData.koh.hillpos, g_ScenarioData.koh.hillrooms, NULL, NULL); + + roomSetLighting(g_ScenarioData.koh.hillrooms[0], 5, 0, 0, 0); + + g_ScenarioData.koh.occupiedteam = -1; + g_ScenarioData.koh.elapsed240 = 0; + g_ScenarioData.koh.movehill = false; + } + } else { + // The hill is not moving on this frame + // Build an array of chr props who are in the hill + numchrsinhill = 0; + prop = g_Vars.activeprops; + + while (prop) { + if (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR) { + inhill = false; + + if (prop->rooms[0] == g_ScenarioData.koh.hillrooms[0]) { + inhill = true; + } + + if (inhill) { + chr = prop->chr; + + if (!chrIsDead(chr)) { + chrsinhill[numchrsinhill] = prop; + numchrsinhill++; + } + } + } + + prop = prop->next; + } + + // Use the chrshillhill array to build an array of all teams who have + // chrs in the hill. During development, this array likely stored a + // count of that team's chrs but was later changed to just be 0 or 1 + // to denote if they have any chrs in the hill. + for (s0 = 0; s0 < 8; s0++) { + teamsinhill[s0] = 0; + } + + for (s0 = 0, numteamsinhill = 0; s0 < numchrsinhill; s0++) { + chr = chrsinhill[s0]->chr; + teamindex = radarGetTeamIndex(chr->team); + + if (teamsinhill[teamindex] == 0) { + numteamsinhill++; + teamsinhill[teamindex] = 1; + } + } + + if (numteamsinhill == 0) { + g_ScenarioData.koh.occupiedteam = -1; + g_ScenarioData.koh.elapsed240 = 0; + } else { + if (numteamsinhill == 1) { + // Set hillteam for later + for (hillteam = 0; hillteam < 8; hillteam++) { + if (teamsinhill[hillteam]) { + break; + } + } + } else { + // There are multiple teams in the hill. + // This code attempts to filter the teamsinhill array to only + // those which have the most chrs, but the teamsinhill array + // only contains values 0 or 1 so it effectively does nothing. + mostchrs = 0; + + for (s0 = 0; s0 < 8; s0++) { + if (teamsinhill[s0] > mostchrs) { + mostchrs = teamsinhill[s0]; + } + } + + for (s0 = 0; s0 < 8; s0++) { + if (teamsinhill[s0] != mostchrs) { + teamsinhill[s0] = false; + } + } + + // Count the number of teams who are tied for the most chrs in + // the hill. Or rather, because the teamsinhill array only + // contains 0 or 1 values, this is just recounting the number of + // teams who have presence in the hill. + for (s0 = 0; s0 < 8; s0++) { + if (teamsinhill[s0]) { + dualoccupancy++; + } + } + + dualoccupancy = dualoccupancy >= 2 ? true : false; + + // Set the hillteam to whoever was holding it previously + // so the hill remains the same colour + for (hillteam = 0; hillteam < 8; hillteam++) { + if (teamsinhill[hillteam] && hillteam == g_ScenarioData.koh.occupiedteam) { + break; + } + } + + if (hillteam == 8) { + // This happens if the controlling team leaves the hill + // and there are two other teams still in the hill. + // The hill goes green until one team holds it exclusively. + g_ScenarioData.koh.occupiedteam = -1; + hillteam = -1; + } + } + + // At this point we know there is a team in the hill on this frame. + // So if these don't match then the hill is turning into a team + // colour rather than going green. + if (hillteam != g_ScenarioData.koh.occupiedteam) { + sndStart(var80095200, SFX_MP_HILLENTERED, 0, -1, -1, -1, -1, -1); + + g_ScenarioData.koh.occupiedteam = hillteam; + g_ScenarioData.koh.elapsed240 = 0; + + // "%has captured the Hill!" + sprintf(text, langGet(L_MPWEAPONS_022), &g_BossFile.teamnames[hillteam]); + + prevplayernum2 = g_Vars.currentplayernum; + + for (playernum1 = 0; playernum1 < PLAYERCOUNT(); playernum1++) { + setCurrentPlayerNum(playernum1); + + chr = g_Vars.currentplayer->prop->chr; + + if (radarGetTeamIndex(chr->team) == g_ScenarioData.koh.occupiedteam) { + // "We have the Hill!" + hudmsgCreateWithFlags(langGet(L_MPWEAPONS_021), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); + } else { + hudmsgCreateWithFlags(text, HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); + } + } + + setCurrentPlayerNum(prevplayernum2); + } else { + // A team is remaining in the hill. + // Only tick the hill timer if they have exclusive occupancy. + if (!dualoccupancy) { + g_ScenarioData.koh.elapsed240 += g_Vars.lvupdate240; + + if (g_ScenarioData.koh.elapsed240 >= g_Vars.mphilltime * PALDOWN(240) + PALDOWN(2400)) { + // Scored a point + sndStart(var80095200, SFX_MP_SCOREPOINT, 0, -1, -1, -1, -1, -1); + + // @bug: There is no check for a chr being dead here. + // If a player dies in the hill and waits on the + // "press start" screen while their team mate scores the + // hill, the dead player will always be awarded a point. + for (playernum2 = 0; playernum2 < g_MpNumChrs; playernum2++) { + if (radarGetTeamIndex(g_MpAllChrPtrs[playernum2]->team) == g_ScenarioData.koh.occupiedteam) { + prop = g_MpAllChrPtrs[playernum2]->prop; + + if (prop->rooms[0] == g_ScenarioData.koh.hillrooms[0]) { + g_MpAllChrConfigPtrs[playernum2]->numpoints++; + } + } + } + + prevplayernum1 = g_Vars.currentplayernum; + + for (playernum2 = 0; playernum2 < g_MpNumChrs; playernum2++) { + if (g_MpAllChrPtrs[playernum2]->aibot == NULL + && radarGetTeamIndex(g_MpAllChrPtrs[playernum2]->team) == g_ScenarioData.koh.occupiedteam) { + setCurrentPlayerNum(playernum2); + + // "King of the Hill!" + hudmsgCreateWithFlags(langGet(L_MPWEAPONS_020), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); + } + } + + setCurrentPlayerNum(prevplayernum1); + + g_ScenarioData.koh.occupiedteam = -1; + g_ScenarioData.koh.elapsed240 = 0; + + if (g_MpSetup.options & MPOPTION_KOH_MOBILEHILL) { + g_ScenarioData.koh.movehill = true; + } + } + } + } + } + + // Calculate what colour the hill should tween towards + if (g_ScenarioData.koh.occupiedteam == -1) { + targetr = 0.25f; + targetg = 1; + targetb = 0.25f; + } else { + u32 colour = g_TeamColours[g_ScenarioData.koh.occupiedteam]; + targetr = ((s32)(colour >> 24 & 0xff) + 0xff) * (1.0f / 512.0f); + targetg = ((s32)(colour >> 16 & 0xff) + 0xff) * (1.0f / 512.0f); + targetb = ((s32)(colour >> 8 & 0xff) + 0xff) * (1.0f / 512.0f); + } + } + + // Tween the colour components towards the target colour. + // @bug: This increments using g_Vars.diffframe60, which is updated while + // the game is paused. Because of this, if you pause as soon as a hill is + // scored then the colour fade and selection of the new hill will happen + // while paused. + if (g_ScenarioData.koh.colourfracr != targetr) { + for (i = 0; i < g_Vars.diffframe60; i++) { +#if PAL + g_ScenarioData.koh.colourfracr = 0.0597f * targetr + 0.9403f * g_ScenarioData.koh.colourfracr; +#else + g_ScenarioData.koh.colourfracr = 0.05f * targetr + 0.95f * g_ScenarioData.koh.colourfracr; +#endif + } + } + + if (g_ScenarioData.koh.colourfracg != targetg) { + for (i = 0; i < g_Vars.diffframe60; i++) { +#if PAL + g_ScenarioData.koh.colourfracg = 0.0597f * targetg + 0.9403f * g_ScenarioData.koh.colourfracg; +#else + g_ScenarioData.koh.colourfracg = 0.05f * targetg + 0.95f * g_ScenarioData.koh.colourfracg; +#endif + } + } + + if (g_ScenarioData.koh.colourfracb != targetb) { + for (i = 0; i < g_Vars.diffframe60; i++) { +#if PAL + g_ScenarioData.koh.colourfracb = 0.0597f * targetb + 0.9403f * g_ScenarioData.koh.colourfracb; +#else + g_ScenarioData.koh.colourfracb = 0.05f * targetb + 0.95f * g_ScenarioData.koh.colourfracb; +#endif + } + } +} + +Gfx *kohRenderHud(Gfx *gdl) +{ + s32 time240; + s32 mins; + s32 secs; + s32 textwidth; + s32 textheight; + s32 x; + s32 y; + struct chrdata *chr = g_Vars.currentplayer->prop->chr; + char text[64]; + + if (radarGetTeamIndex(chr->team) == g_ScenarioData.koh.occupiedteam && !g_ScenarioData.koh.movehill) { + x = viGetViewLeft() + viGetViewWidth() / 2; + y = viGetViewTop() + 10; + + time240 = g_Vars.mphilltime * PALDOWN(240) - g_ScenarioData.koh.elapsed240; + time240 += PAL ? 2199 : 2400; + mins = time240 / PALDOWN(60 * 240); + time240 -= PALDOWN(60 * 240) * mins; + +#if PAL + secs = time240 / PALDOWN(240); +#else + secs = (time240 + (PALDOWN(240) - 1)) / PALDOWN(240); +#endif + + if ((g_Vars.mphilltime * 60 + 600) / 3600) { + sprintf(text, "%d:%02d", mins, secs); + } else { + sprintf(text, "%02d", secs); + } + + gdl = func0f153628(gdl); + textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); + + x -= textwidth / 2; + textwidth += x; + textheight += y; + +#if VERSION >= VERSION_NTSC_1_0 + gdl = func0f153990(gdl, x, y, textwidth, textheight); + gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0xa0, viGetWidth(), viGetHeight(), 0, 0); +#else + gdl = func0f153858(gdl, &x, &y, &textwidth, &textheight); + gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0x88, viGetWidth(), viGetHeight(), 0, 0); +#endif + gdl = func0f153780(gdl); + } + + return gdl; +} + +void kohCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths) +{ + struct mpchrconfig *loopmpchr; + s32 i; + + *score = 0; + *score += mpchr->numpoints; + + if (g_MpSetup.options & MPOPTION_KILLSSCORE) { + for (i = 0; i != MAX_MPCHRS; i++) { + if (i == mpchrnum) { + *score -= mpchr->killcounts[i]; + } else { + loopmpchr = MPCHR(i); + + if (loopmpchr->team == mpchr->team) { + *score -= mpchr->killcounts[i]; + } else { + *score += mpchr->killcounts[i]; + } + } + } + } + + *deaths = mpchr->numdeaths; +} + +Gfx *kohRadarExtra(Gfx *gdl) +{ + if (g_MpSetup.options & MPOPTION_KOH_HILLONRADAR && !g_ScenarioData.koh.movehill) { + struct coord dist; + u32 colour; + dist.x = g_ScenarioData.koh.hillpos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = g_ScenarioData.koh.hillpos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = g_ScenarioData.koh.hillpos.z - g_Vars.currentplayer->prop->pos.z; + + if (g_ScenarioData.koh.occupiedteam == -1) { + colour = 0x00ff0000; + } else { + colour = g_TeamColours[g_ScenarioData.koh.occupiedteam]; + } + + gdl = radarDrawDot(gdl, NULL, &dist, colour, 0, 1); + } + + return gdl; +} + +void kohAddHill(s32 *cmd) +{ + if (g_ScenarioData.koh.hillcount < ARRAYCOUNT(g_ScenarioData.koh.hillpads)) { + g_ScenarioData.koh.hillpads[g_ScenarioData.koh.hillcount] = cmd[1]; + g_ScenarioData.koh.hillcount++; + } +} + +bool kohIsRoomHighlighted(s16 room) +{ + return room == g_ScenarioData.koh.hillrooms[0]; +} + +void kohHighlightRoom(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3) +{ + if (roomnum == g_ScenarioData.koh.hillrooms[0]) { + f32 a = *arg1; + f32 b = *arg2; + f32 c = *arg3; + + a *= g_ScenarioData.koh.colourfracr; + b *= g_ScenarioData.koh.colourfracg; + c *= g_ScenarioData.koh.colourfracb; + + *arg1 = a; + *arg2 = b; + *arg3 = c; + } +} diff --git a/src/game/mplayer/scenarios/popacap.inc b/src/game/mplayer/scenarios/popacap.inc new file mode 100644 index 000000000..9444629db --- /dev/null +++ b/src/game/mplayer/scenarios/popacap.inc @@ -0,0 +1,307 @@ +/** + * Pop a Cap + * + * At the start of the match, a single player is randomly chosen as the victim. + * Players must kill the victim in order to score, while the victim scores a + * point for every 60 seconds they remain alive. Once killed, another player is + * chosen as the victim. + */ + +struct menuitem g_PacOptionsMenuItems[] = { + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills" + { MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_230, MPOPTION_PAC_HIGHLIGHTTARGET, menuhandlerMpCheckboxOption }, // "Highlight Target" + { MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_238, MPOPTION_PAC_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar" + { MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, + { MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back" + { MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL }, +}; + +struct menudialog g_PacOptionsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + L_MPMENU_218, // "Pop a Cap Options" + g_PacOptionsMenuItems, + mpOptionsMenuDialog, + 0x00000010, + NULL, +}; + +void pacReset(void) +{ + s32 i; + s32 j; + struct scenariodata_pac *data = &g_ScenarioData.pac; + + data->victimindex = -1; + data->age240 = 0; + + osSyncPrintf("PopACapReset -> num_mplayers=%d : Working\n", g_MpNumChrs); + + for (i = 0; i != MAX_MPCHRS; i++) { + data->killcounts[i] = 0; + data->survivalcounts[i] = 0; + } + + i = 0; + + while (i < g_MpNumChrs) { + bool isnew; + s32 victimplayernum = random() % g_MpNumChrs; + + for (j = 0, isnew = true; j < i; j++) { + if (data->victims[j] == victimplayernum) { + isnew = false; + break; + } + } + + if (isnew) { + data->victims[i] = victimplayernum; + i++; + } + } + + osSyncPrintf("PopACapReset -> Generated %d victims for this game : Listing\n", i); + + for (j = 0; j < g_MpNumChrs; j++) { + osSyncPrintf("PopACapReset -> Victim %d is player %d\n", j, data->victims[j]); + } + + osSyncPrintf("PopACapReset -> Done\n"); +} + +void pacInit(void) +{ + pacReset(); +} + +void pacInitProps(void) +{ + pacReset(); +} + +bool pacHighlightProp(struct prop *prop, s32 *colour) +{ + struct scenariodata_pac *data = &g_ScenarioData.pac; + + if (g_MpSetup.options & MPOPTION_PAC_HIGHLIGHTTARGET + && (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR) + && data->victimindex != -1 + && prop->chr == g_MpAllChrPtrs[data->victims[data->victimindex]]) { + colour[0] = 0; + colour[1] = 0xff; + colour[2] = 0; + colour[3] = 0x40; + return true; + } + + return false; +} + +void pacApplyNextVictim(void) +{ + struct scenariodata_pac *data = &g_ScenarioData.pac; + s32 vplayernum; + char text[64]; + s32 i; + + data->victimindex++; + + if (data->victimindex == g_MpNumChrs) { + data->victimindex = 0; + } + + data->age240 = 0; + + vplayernum = data->victims[data->victimindex]; + + for (i = 0; i < PLAYERCOUNT(); i++) { + if (vplayernum == i) { + sprintf(text, langGet(L_MPWEAPONS_013)); // "You are the victim!" + } else if (scenarioChrsAreSameTeam(vplayernum, i)) { + sprintf(text, langGet(L_MPWEAPONS_014), g_MpAllChrConfigPtrs[vplayernum]->name); // "Protect %s!" + } else { + sprintf(text, langGet(L_MPWEAPONS_015), g_MpAllChrConfigPtrs[vplayernum]->name); // "Get %s!" + } + + scenarioCreateHudmsg(i, text); + } +} + +void pacHandleDeath(s32 aplayernum, s32 vplayernum) +{ + struct scenariodata_pac *data = &g_ScenarioData.pac; + + if (data->victimindex >= 0 && vplayernum == data->victims[data->victimindex]) { + if (aplayernum != vplayernum) { + if (aplayernum >= 0) { + if (scenarioChrsAreSameTeam(aplayernum, vplayernum)) { + scenarioCreateHudmsg(aplayernum, langGet(L_MPWEAPONS_008)); // "You're supposed to look" + scenarioCreateHudmsg(aplayernum, langGet(L_MPWEAPONS_009)); // "after your friends!" + } else { + data->killcounts[aplayernum]++; + scenarioCreateHudmsg(aplayernum, langGet(L_MPWEAPONS_010)); // "Well done!" + scenarioCreateHudmsg(aplayernum, langGet(L_MPWEAPONS_011)); // "You popped a cap!" + scenarioCreateHudmsg(aplayernum, langGet(L_MPWEAPONS_012)); // "Have 2 Points..." + } + } + + pacApplyNextVictim(); + } else { +#if VERSION >= VERSION_NTSC_1_0 + data->age240 = 0; +#endif + } + } +} + +void pacTick(void) +{ + struct scenariodata_pac *data = &g_ScenarioData.pac; + + if (data->victimindex == -1) { + pacApplyNextVictim(); + osSyncPrintf("PopACapTick : Current Victim = %d (Player %d)\n", + data->victimindex, data->victims[data->victimindex]); + } + + if (data->victimindex >= 0) { +#if VERSION >= VERSION_NTSC_1_0 + if (data->victims[data->victimindex] >= PLAYERCOUNT() || + g_Vars.players[data->victims[data->victimindex]]->isdead == false) +#endif + { + data->age240 += g_Vars.lvupdate240; + + if (data->age240 > (u32)PALDOWN(240 * 60)) { + data->age240 = 0; + data->survivalcounts[data->victims[data->victimindex]]++; + scenarioCreateHudmsg(data->victims[data->victimindex], langGet(L_MPWEAPONS_007)); // "Have a point for living!" + } + } + } +} + +Gfx *pacRenderHud(Gfx *gdl) +{ + struct scenariodata_pac *data = &g_ScenarioData.pac; + s32 time240; + s32 mins; + s32 secs; + s32 textwidth; + s32 textheight; + s32 x; + s32 y; + char text[64]; + +#if VERSION >= VERSION_NTSC_1_0 + if (g_Vars.currentplayernum == data->victims[data->victimindex] && !g_Vars.currentplayer->isdead) +#else + if (g_Vars.currentplayernum == data->victims[data->victimindex]) +#endif + { + time240 = PALDOWN(60 * 240) - data->age240; + x = viGetViewLeft() + viGetViewWidth() / 2; + y = viGetViewTop() + 10; + + if (time240 < 0) { + time240 = 0; + } + + mins = time240 / PALDOWN(60 * 240); + time240 -= PALDOWN(60 * 240) * mins; + secs = (time240 + (PALDOWN(240) - 1)) / PALDOWN(240); + sprintf(text, "%d:%02d", mins, secs); + + gdl = func0f153628(gdl); + textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); + + x -= textwidth / 2; + textwidth += x; + textheight += y; + +#if VERSION >= VERSION_NTSC_1_0 + gdl = func0f153990(gdl, x, y, textwidth, textheight); + gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0xa0, viGetWidth(), viGetHeight(), 0, 0); +#else + gdl = func0f153858(gdl, &x, &y, &textwidth, &textheight); + gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0x88, viGetWidth(), viGetHeight(), 0, 0); +#endif + gdl = func0f153780(gdl); + } + + return gdl; +} + +void pacCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *arg3) +{ + struct mpchrconfig *loopmpchr; + s32 i; + s32 index; + + *score = 0; + index = func0f18d0e8(mpchrnum); + + if (index >= 0) { + *score += g_ScenarioData.pac.killcounts[index] * 2; + *score += g_ScenarioData.pac.survivalcounts[index]; + } + + if (g_MpSetup.options & MPOPTION_KILLSSCORE) { + for (i = 0; i != MAX_MPCHRS; i++) { + if (i == mpchrnum) { + *score -= mpchr->killcounts[i]; + } else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { + loopmpchr = MPCHR(i); + + if (loopmpchr->team == mpchr->team) { + *score -= mpchr->killcounts[i]; + } else { + *score += mpchr->killcounts[i]; + } + } else { + *score += mpchr->killcounts[i]; + } + } + } + + *arg3 = mpchr->numdeaths; +} + +Gfx *pacRadarExtra(Gfx *gdl) +{ + return gdl; +} + +bool pacRadarChr(Gfx **gdl, struct prop *prop) +{ + struct scenariodata_pac *data = &g_ScenarioData.pac; + struct coord dist; + + if ((g_MpSetup.options & MPOPTION_PAC_SHOWONRADAR) && data->victimindex >= 0) { + struct prop *vprop = g_MpAllChrPtrs[data->victims[data->victimindex]]->prop; + + if (vprop == prop) { + dist.x = prop->pos.x - g_Vars.currentplayer->prop->pos.x; + dist.y = prop->pos.y - g_Vars.currentplayer->prop->pos.y; + dist.z = prop->pos.z - g_Vars.currentplayer->prop->pos.z; + + if (g_MpSetup.options & MPOPTION_TEAMSENABLED) { + u32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)]; + *gdl = radarDrawDot(*gdl, vprop, &dist, colour, 0, 1); + } else { + *gdl = radarDrawDot(*gdl, vprop, &dist, 0x00ff0000, 0, 1); + } + + return true; + } + } + + return false; +} diff --git a/src/game/mplayer/setup.c b/src/game/mplayer/setup.c index 36af9004d..dc4b672de 100644 --- a/src/game/mplayer/setup.c +++ b/src/game/mplayer/setup.c @@ -6219,13 +6219,7 @@ s32 menuhandlerMpMaximumTeams(s32 operation, struct menuitem *item, union handle for (i = 0; i != MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { - struct mpchrconfig *mpchr; - - if (i < 4) { - mpchr = &g_PlayerConfigsArray[i].base; - } else { - mpchr = &g_BotConfigsArray[i - 4].base; - } + struct mpchrconfig *mpchr = MPCHR(i); mpchr->team = team++; @@ -6248,13 +6242,7 @@ s32 menuhandlerMpHumansVsSimulants(s32 operation, struct menuitem *item, union h for (i = 0; i != MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { - struct mpchrconfig *mpchr; - - if (i < 4) { - mpchr = &g_PlayerConfigsArray[i].base; - } else { - mpchr = &g_BotConfigsArray[i - 4].base; - } + struct mpchrconfig *mpchr = MPCHR(i); mpchr->team = i < 4 ? 0 : 1; } @@ -6276,13 +6264,7 @@ s32 menuhandlerMpHumanSimulantPairs(s32 operation, struct menuitem *item, union for (i = 0; i != MAX_MPCHRS; i++) { if (g_MpSetup.chrslots & (1 << i)) { - struct mpchrconfig *mpchr; - - if (i < 4) { - mpchr = &g_PlayerConfigsArray[i].base; - } else { - mpchr = &g_BotConfigsArray[i - 4].base; - } + struct mpchrconfig *mpchr = MPCHR(i); if (i < 4) { mpchr->team = team_ids[playerindex++]; @@ -8427,17 +8409,17 @@ glabel func0f17fa28 /* f17fa68: 3c148008 */ lui $s4,%hi(g_MpChangeSimulantMenuDialog) /* f17fa6c: 3c158008 */ lui $s5,%hi(g_MpEditSimulantMenuDialog) /* f17fa70: 3c168008 */ lui $s6,%hi(g_MpCombatOptionsMenuDialog) -/* f17fa74: 3c178008 */ lui $s7,%hi(g_MpBriefcaseOptionsMenuDialog) -/* f17fa78: 3c1e8008 */ lui $s8,%hi(g_MpCaptureOptionsMenuDialog) -/* f17fa7c: 3c098008 */ lui $t1,%hi(g_MpPopacapOptionsMenuDialog) -/* f17fa80: 3c088008 */ lui $t0,%hi(g_MpHackerOptionsMenuDialog) -/* f17fa84: 3c078008 */ lui $a3,%hi(g_MpHillOptionsMenuDialog) +/* f17fa74: 3c178008 */ lui $s7,%hi(g_HtbOptionsMenuDialog) +/* f17fa78: 3c1e8008 */ lui $s8,%hi(g_CtcOptionsMenuDialog) +/* f17fa7c: 3c098008 */ lui $t1,%hi(g_PacOptionsMenuDialog) +/* f17fa80: 3c088008 */ lui $t0,%hi(g_HtmOptionsMenuDialog) +/* f17fa84: 3c078008 */ lui $a3,%hi(g_KohOptionsMenuDialog) /* f17fa88: afb00018 */ sw $s0,0x18($sp) -/* f17fa8c: 24e76ce0 */ addiu $a3,$a3,%lo(g_MpHillOptionsMenuDialog) -/* f17fa90: 25086dfc */ addiu $t0,$t0,%lo(g_MpHackerOptionsMenuDialog) -/* f17fa94: 25296f80 */ addiu $t1,$t1,%lo(g_MpPopacapOptionsMenuDialog) -/* f17fa98: 27de6b48 */ addiu $s8,$s8,%lo(g_MpCaptureOptionsMenuDialog) -/* f17fa9c: 26f769d4 */ addiu $s7,$s7,%lo(g_MpBriefcaseOptionsMenuDialog) +/* f17fa8c: 24e76ce0 */ addiu $a3,$a3,%lo(g_KohOptionsMenuDialog) +/* f17fa90: 25086dfc */ addiu $t0,$t0,%lo(g_HtmOptionsMenuDialog) +/* f17fa94: 25296f80 */ addiu $t1,$t1,%lo(g_PacOptionsMenuDialog) +/* f17fa98: 27de6b48 */ addiu $s8,$s8,%lo(g_CtcOptionsMenuDialog) +/* f17fa9c: 26f769d4 */ addiu $s7,$s7,%lo(g_HtbOptionsMenuDialog) /* f17faa0: 26d668b8 */ addiu $s6,$s6,%lo(g_MpCombatOptionsMenuDialog) /* f17faa4: 26b5592c */ addiu $s5,$s5,%lo(g_MpEditSimulantMenuDialog) /* f17faa8: 26945834 */ addiu $s4,$s4,%lo(g_MpChangeSimulantMenuDialog) @@ -8558,14 +8540,14 @@ glabel func0f17fa28 /* f17fc30: 00000000 */ nop /* f17fc34: 0fc3cdb7 */ jal menuPopDialog /* f17fc38: 00000000 */ nop -/* f17fc3c: 3c078008 */ lui $a3,%hi(g_MpHillOptionsMenuDialog) -/* f17fc40: 3c088008 */ lui $t0,%hi(g_MpHackerOptionsMenuDialog) -/* f17fc44: 3c098008 */ lui $t1,%hi(g_MpPopacapOptionsMenuDialog) +/* f17fc3c: 3c078008 */ lui $a3,%hi(g_KohOptionsMenuDialog) +/* f17fc40: 3c088008 */ lui $t0,%hi(g_HtmOptionsMenuDialog) +/* f17fc44: 3c098008 */ lui $t1,%hi(g_PacOptionsMenuDialog) /* f17fc48: 3c1f8007 */ lui $ra,%hi(g_MpPlayerNum) /* f17fc4c: 27ff1448 */ addiu $ra,$ra,%lo(g_MpPlayerNum) -/* f17fc50: 25296f80 */ addiu $t1,$t1,%lo(g_MpPopacapOptionsMenuDialog) -/* f17fc54: 25086dfc */ addiu $t0,$t0,%lo(g_MpHackerOptionsMenuDialog) -/* f17fc58: 24e76ce0 */ addiu $a3,$a3,%lo(g_MpHillOptionsMenuDialog) +/* f17fc50: 25296f80 */ addiu $t1,$t1,%lo(g_PacOptionsMenuDialog) +/* f17fc54: 25086dfc */ addiu $t0,$t0,%lo(g_HtmOptionsMenuDialog) +/* f17fc58: 24e76ce0 */ addiu $a3,$a3,%lo(g_KohOptionsMenuDialog) .L0f17fc5c: /* f17fc5c: 5200ffa7 */ beqzl $s0,.L0f17fafc /* f17fc60: 8fe20000 */ lw $v0,0x0($ra) diff --git a/src/game/mpstats.c b/src/game/mpstats.c index 50016b329..bd89cf8a5 100644 --- a/src/game/mpstats.c +++ b/src/game/mpstats.c @@ -178,11 +178,7 @@ void mpstatsRecordPlayerSuicide(void) time = getMissionTime(); mpindex = g_Vars.currentplayerstats->mpindex; - if (mpindex < 4) { - mpchr = &g_PlayerConfigsArray[mpindex].base; - } else { - mpchr = &g_BotConfigsArray[mpindex - 4].base; - } + mpchr = MPCHR(mpindex); // Show HUD message // "Suicide count: %d" @@ -238,7 +234,7 @@ void mpstatsRecordDeath(s32 aplayernum, s32 vplayernum) char text[256]; if (g_Vars.normmplayerisrunning && g_MpSetup.scenario == MPSCENARIO_POPACAP) { - scenarioPacHandleDeath(aplayernum, vplayernum); + pacHandleDeath(aplayernum, vplayernum); } // Find attacker and victim mpchrs @@ -246,11 +242,7 @@ void mpstatsRecordDeath(s32 aplayernum, s32 vplayernum) ampindex = func0f18d074(aplayernum); if (ampindex >= 0) { - if (ampindex < 4) { - ampchr = &g_PlayerConfigsArray[ampindex].base; - } else { - ampchr = &g_BotConfigsArray[ampindex - 4].base; - } + ampchr = MPCHR(ampindex); } } @@ -258,11 +250,7 @@ void mpstatsRecordDeath(s32 aplayernum, s32 vplayernum) vmpindex = func0f18d074(vplayernum); if (vmpindex >= 0) { - if (vmpindex < 4) { - vmpchr = &g_PlayerConfigsArray[vmpindex].base; - } else { - vmpchr = &g_BotConfigsArray[vmpindex - 4].base; - } + vmpchr = MPCHR(vmpindex); } } diff --git a/src/game/propobj.c b/src/game/propobj.c index ae209d332..76955008e 100644 --- a/src/game/propobj.c +++ b/src/game/propobj.c @@ -3340,7 +3340,7 @@ glabel var7f1aa1d8 /* f0692ac: 27a70050 */ addiu $a3,$sp,0x50 /* f0692b0: 460a8481 */ sub.s $f18,$f16,$f10 /* f0692b4: e7b2005c */ swc1 $f18,0x5c($sp) -/* f0692b8: 0fc6192e */ jal scenarioCallback38 +/* f0692b8: 0fc6192e */ jal scenarioHighlightRoom /* f0692bc: 85c40028 */ lh $a0,0x28($t6) /* f0692c0: 920f0000 */ lbu $t7,0x0($s0) /* f0692c4: 8fb80058 */ lw $t8,0x58($sp) @@ -3684,7 +3684,7 @@ glabel var7f1aa1d8 /* f068518: 27a70050 */ addiu $a3,$sp,0x50 /* f06851c: 460a8481 */ sub.s $f18,$f16,$f10 /* f068520: e7b2005c */ swc1 $f18,0x5c($sp) -/* f068524: 0fc60248 */ jal scenarioCallback38 +/* f068524: 0fc60248 */ jal scenarioHighlightRoom /* f068528: 85c40028 */ lh $a0,0x28($t6) /* f06852c: 920f0000 */ lbu $t7,0x0($s0) /* f068530: 8fb80058 */ lw $t8,0x58($sp) @@ -52709,7 +52709,7 @@ glabel var7f1aa82c /* f0818b0: 8cca0318 */ lw $t2,0x318($a2) /* f0818b4: 51400006 */ beqzl $t2,.L0f0818d0 /* f0818b8: 8cc20284 */ lw $v0,0x284($a2) -/* f0818bc: 0fc61788 */ jal scenarioHighlight +/* f0818bc: 0fc61788 */ jal scenarioHighlightProp /* f0818c0: 8fa400f8 */ lw $a0,0xf8($sp) /* f0818c4: 3c06800a */ lui $a2,%hi(g_Vars) /* f0818c8: 24c69fc0 */ addiu $a2,$a2,%lo(g_Vars) @@ -53344,7 +53344,7 @@ glabel var7f1aa82c /* f0818b0: 8cca0318 */ lw $t2,0x318($a2) /* f0818b4: 51400006 */ beqzl $t2,.L0f0818d0 /* f0818b8: 8cc20284 */ lw $v0,0x284($a2) -/* f0818bc: 0fc61788 */ jal scenarioHighlight +/* f0818bc: 0fc61788 */ jal scenarioHighlightProp /* f0818c0: 8fa400f8 */ lw $a0,0xf8($sp) /* f0818c4: 3c06800a */ lui $a2,%hi(g_Vars) /* f0818c8: 24c69fc0 */ addiu $a2,$a2,%lo(g_Vars) @@ -53755,7 +53755,7 @@ glabel var7f1aa82c // } // // if (g_Vars.normmplayerisrunning) { -// scenarioHighlight(prop, colour); +// scenarioHighlightProp(prop, colour); // } // // if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) { @@ -61018,7 +61018,7 @@ bool propobjInteract(struct prop *prop) } if (g_Vars.normmplayerisrunning) { - scenarioHtmActivateUplink(g_Vars.currentplayer->prop->chr, prop); + scenarioHandleActivatedProp(g_Vars.currentplayer->prop->chr, prop); } else { if (g_Vars.currentplayernum == g_Vars.coopplayernum) { obj->hidden |= OBJHFLAG_ACTIVATED_BY_COOP; @@ -62731,7 +62731,7 @@ glabel var7f1aae70 /* f088a94: 8d0f04cc */ lw $t7,0x4cc($t0) /* f088a98: 8c4c00bc */ lw $t4,0xbc($v0) /* f088a9c: 8fa500a0 */ lw $a1,0xa0($sp) -/* f088aa0: 0fc61d04 */ jal chrGiveUplink +/* f088aa0: 0fc61d04 */ jal scenarioPickUpUplink /* f088aa4: 8d840004 */ lw $a0,0x4($t4) /* f088aa8: 10400006 */ beqz $v0,.L0f088ac4 /* f088aac: 00401825 */ or $v1,$v0,$zero @@ -63378,7 +63378,7 @@ glabel var7f1aae70 /* f088a94: 8d0f04cc */ lw $t7,0x4cc($t0) /* f088a98: 8c4c00bc */ lw $t4,0xbc($v0) /* f088a9c: 8fa500a0 */ lw $a1,0xa0($sp) -/* f088aa0: 0fc61d04 */ jal chrGiveUplink +/* f088aa0: 0fc61d04 */ jal scenarioPickUpUplink /* f088aa4: 8d840004 */ lw $a0,0x4($t4) /* f088aa8: 10400006 */ beqz $v0,.L0f088ac4 /* f088aac: 00401825 */ or $v1,$v0,$zero @@ -64025,7 +64025,7 @@ glabel var7f1aae70 /* f088a94: 8d0f04cc */ lw $t7,0x4cc($t0) /* f088a98: 8c4c00bc */ lw $t4,0xbc($v0) /* f088a9c: 8fa500a0 */ lw $a1,0xa0($sp) -/* f088aa0: 0fc61d04 */ jal chrGiveUplink +/* f088aa0: 0fc61d04 */ jal scenarioPickUpUplink /* f088aa4: 8d840004 */ lw $a0,0x4($t4) /* f088aa8: 10400006 */ beqz $v0,.L0f088ac4 /* f088aac: 00401825 */ or $v1,$v0,$zero @@ -64500,7 +64500,7 @@ glabel var7f1aae70 // } // // if (weapon->weaponnum == WEAPON_DATAUPLINK) { -// result = chrGiveUplink(g_Vars.currentplayer->prop->chr, prop); +// result = scenarioPickUpUplink(g_Vars.currentplayer->prop->chr, prop); // // if (result) { // weaponPlayPickupSound(weapon->weaponnum); @@ -69892,7 +69892,7 @@ void weaponCreateForPlayerDrop(s32 weaponnum) objDrop(prop, true); if (weaponnum == WEAPON_BRIEFCASE2) { - scenarioReleaseToken(chr, prop); + scenarioHandleDroppedToken(chr, prop); } } } diff --git a/src/game/radar.c b/src/game/radar.c index c87f115a0..2ad78187b 100644 --- a/src/game/radar.c +++ b/src/game/radar.c @@ -331,7 +331,7 @@ Gfx *radarRender(Gfx *gdl) if (i != playernum) { if (g_Vars.players[i]->isdead == false && (g_Vars.players[i]->prop->chr->hidden & CHRHFLAG_CLOAKED) == 0 - && scenarioRadar2(&gdl, g_Vars.players[i]->prop) == false) { + && scenarioRadarChr(&gdl, g_Vars.players[i]->prop) == false) { pos.x = g_Vars.players[i]->prop->pos.x - g_Vars.currentplayer->prop->pos.x; pos.y = g_Vars.players[i]->prop->pos.y - g_Vars.currentplayer->prop->pos.y; pos.z = g_Vars.players[i]->prop->pos.z - g_Vars.currentplayer->prop->pos.z; @@ -372,7 +372,7 @@ Gfx *radarRender(Gfx *gdl) for (i = 0; i < g_BotCount; i++) { if (!chrIsDead(g_MpBotChrPtrs[i]) && (g_MpBotChrPtrs[i]->hidden & CHRHFLAG_CLOAKED) == 0 - && scenarioRadar2(&gdl, g_MpBotChrPtrs[i]->prop) == false) { + && scenarioRadarChr(&gdl, g_MpBotChrPtrs[i]->prop) == false) { pos.x = g_MpBotChrPtrs[i]->prop->pos.x - g_Vars.currentplayer->prop->pos.x; pos.y = g_MpBotChrPtrs[i]->prop->pos.y - g_Vars.currentplayer->prop->pos.y; pos.z = g_MpBotChrPtrs[i]->prop->pos.z - g_Vars.currentplayer->prop->pos.z; @@ -388,7 +388,7 @@ Gfx *radarRender(Gfx *gdl) } } - gdl = scenarioRadar(gdl); + gdl = scenarioRadarExtra(gdl); // Draw dots for r-tracked props if (g_Vars.currentplayer->devicesactive & ~g_Vars.currentplayer->devicesinhibit & DEVICE_RTRACKER) { @@ -396,7 +396,7 @@ Gfx *radarRender(Gfx *gdl) } // Draw dot for the current player - if (scenarioRadar2(&gdl, g_Vars.currentplayer->prop) == 0) { + if (scenarioRadarChr(&gdl, g_Vars.currentplayer->prop) == false) { pos.x = 0; pos.y = 0; pos.z = 0; diff --git a/src/include/data.h b/src/include/data.h index 50f8c1a2c..1b53fa37c 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -489,12 +489,11 @@ extern struct menudialog g_MpQuickTeamGameSetupMenuDialog; extern struct menudialog g_MpQuickTeamMenuDialog; extern struct menudialog g_CombatSimulatorMenuDialog; extern struct menudialog g_MpCombatOptionsMenuDialog; -extern struct menudialog g_MpBriefcaseOptionsMenuDialog; -extern struct menudialog g_MpCaptureOptionsMenuDialog; -extern struct menudialog g_MpHillOptionsMenuDialog; -extern struct menudialog g_MpHackerOptionsMenuDialog; -extern struct menudialog g_MpPopacapOptionsMenuDialog; -extern struct mpscenariooverview g_MpScenarioOverviews[]; +extern struct menudialog g_HtbOptionsMenuDialog; +extern struct menudialog g_CtcOptionsMenuDialog; +extern struct menudialog g_KohOptionsMenuDialog; +extern struct menudialog g_HtmOptionsMenuDialog; +extern struct menudialog g_PacOptionsMenuDialog; extern struct menudialog g_MpScenarioMenuDialog; extern struct menudialog g_MpQuickTeamScenarioMenuDialog; extern s32 var80087260; diff --git a/src/include/game/mplayer/scenarios.h b/src/include/game/mplayer/scenarios.h index 182be5a57..f2566bf76 100644 --- a/src/include/game/mplayer/scenarios.h +++ b/src/include/game/mplayer/scenarios.h @@ -7,55 +7,27 @@ extern struct menudialog g_MpScenarioMenuDialog; extern struct menudialog g_MpQuickTeamScenarioMenuDialog; -void scenarioHtbInit(void); -s32 scenarioHtbCallback08(void); -void scenarioHtbReset(void); -void scenarioHtbTick(void); -void scenarioHtbCallback14(struct chrdata *chr); -void scenarioHtbCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths); -Gfx *scenarioHtbRadar(Gfx *gdl); -bool scenarioHtbRadar2(Gfx **gdl, struct prop *prop); -bool scenarioHtbHighlight(struct prop *prop, s32 *colour); -void scenarioCtcInit(void); -s32 scenarioCtcCallback08(void); -void scenarioCtcTick(void); -void scenarioCtcCallback14(struct chrdata *chr); -void scenarioCtcCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths); -Gfx *scenarioCtcRadar(Gfx *gdl); -bool scenarioCtcRadar2(Gfx **gdl, struct prop *prop); -bool scenarioCtcChooseSpawnLocation(f32 arg0, struct coord *pos, s16 *rooms, struct prop *prop, f32 *arg4); -s32 scenarioCtcGetMaxTeams(void); -bool scenarioCtcIsRoomHighlighted(s16 room); -s32 menuhandlerMpHillTime(s32 operation, struct menuitem *item, union handlerdata *data); -void scenarioKohReadSave(struct savebuffer *buffer); -void scenarioKohWriteSave(struct savebuffer *buffer); -void scenarioKohInit(void); -void scenarioKohReset(void); -void scenarioKohCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths); -Gfx *scenarioKohRadar(Gfx *gdl); -bool scenarioKohIsRoomHighlighted(s16 arg0); -void scenarioHtmInit(void); -s32 scenarioHtmCallback08(void); -void scenarioHtmTick(void); -void scenarioHtmCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths); -bool scenarioHtmRadar2(Gfx **gdl, struct prop *prop); -bool scenarioHtmHighlight(struct prop *prop, s32 *colour); -void scenarioPacInit(void); -void scenarioPacReset(void); -void scenarioPacCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths); -Gfx *scenarioPacRadar(Gfx *gdl); +struct mpscenariooverview { + u16 name; + u16 shortname; + u8 requirefeature; + u8 teamonly; +}; + +extern struct mpscenariooverview g_MpScenarioOverviews[6]; + s32 menuhandlerMpOpenOptions(s32 operation, struct menuitem *item, union handlerdata *data); void scenarioReadSave(struct savebuffer *buffer); void scenarioWriteSave(struct savebuffer *buffer); void scenarioInit(void); -s32 scenarioCallback08(void); -void scenarioReset(void); +s32 scenarioNumProps(void); +void scenarioInitProps(void); void scenarioTick(void); -void scenarioCallback14(struct chrdata *chr); -Gfx *scenarioRadar(Gfx *gdl); -bool scenarioRadar2(Gfx **gdl, struct prop *prop); +void scenarioTickChr(struct chrdata *chr); +Gfx *scenarioRadarExtra(Gfx *gdl); +bool scenarioRadarChr(Gfx **gdl, struct prop *prop); f32 scenarioChooseSpawnLocation(f32 arg0, struct coord *pos, s16 *rooms, struct prop *prop); s32 scenarioGetMaxTeams(void); -void scenarioCallback38(s16 arg0, s32 *arg1, s32 *arg2, s32 *arg3); +void scenarioHighlightRoom(s16 room, s32 *arg1, s32 *arg2, s32 *arg3); #endif diff --git a/src/include/game/mplayer/setup.h b/src/include/game/mplayer/setup.h index 468f6d634..a89a360a1 100644 --- a/src/include/game/mplayer/setup.h +++ b/src/include/game/mplayer/setup.h @@ -73,49 +73,49 @@ s32 mpQuickTeamSimulantDifficultyHandler(s32 operation, struct menuitem *item, u u32 func0f17fa28(void); void func0f17fcb0(s32 silent); s32 menuhandlerMpSlowMotion(s32 operation, struct menuitem *item, union handlerdata *data); -void mpHtbAddPad(s16 padnum); -void scenarioHtmRemoveAmmoCrateAtPad(s16 padnum); -void func0f180078(void); -void scenarioHtbCreateToken(void); -Gfx *scenarioHtbRenderHud(Gfx *gdl); -void scenarioCtcReset(void); -bool scenarioCtcHighlight(struct prop *prop, s32 *colour); -void mpCtcAddPad(s32 *cmd); -void scenarioCtcCallback38(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3); -void scenarioKohTick(void); -Gfx *scenarioKohRenderHud(Gfx *gdl); -void mpKohAddHill(s32 *cmd); -void scenarioKohCallback38(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3); -void mpHtmAddPad(s16 padnum); -void func0f182bf4(void); -void scenarioHtmCreateUplink(void); -void scenarioHtmReset(void); -void scenarioHtmCallback14(struct chrdata *chr); -Gfx *scenarioHtmRenderHud(Gfx *gdl); -Gfx *scenarioHtmRadar(Gfx *gdl); -void scenarioPacChooseVictims(void); -bool scenarioPacHighlight(struct prop *prop, s32 *colour); -void scenarioPacApplyNextVictim(void); -void scenarioPacHandleDeath(s32 cplayernum, s32 vplayernum); -void scenarioPacTick(void); -Gfx *scenarioPacRenderHud(Gfx *gdl); -bool scenarioPacRadar2(Gfx **gdl, struct prop *prop); +void htbAddPad(s16 padnum); +void htbRemoveAmmoCrateAtPad(s16 padnum); +void htbReset(void); +void htbCreateToken(void); +Gfx *htbRenderHud(Gfx *gdl); +void ctcInitProps(void); +bool ctcHighlightProp(struct prop *prop, s32 *colour); +void ctcAddPad(s32 *cmd); +void ctcHighlightRoom(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3); +void kohTick(void); +Gfx *kohRenderHud(Gfx *gdl); +void kohAddHill(s32 *cmd); +void kohHighlightRoom(s16 roomnum, s32 *arg1, s32 *arg2, s32 *arg3); +void htmAddPad(s16 padnum); +void htmReset(void); +void htbCreateUplink(void); +void htmInitProps(void); +void htmTickChr(struct chrdata *chr); +Gfx *htmRenderHud(Gfx *gdl); +Gfx *htmRadarExtra(Gfx *gdl); +void pacReset(void); +bool pacHighlightProp(struct prop *prop, s32 *colour); +void pacApplyNextVictim(void); +void pacHandleDeath(s32 cplayernum, s32 vplayernum); +void pacTick(void); +Gfx *pacRenderHud(Gfx *gdl); +bool pacRadarChr(Gfx **gdl, struct prop *prop); s32 mpOptionsMenuDialog(s32 operation, struct menudialog *dialog, union handlerdata *data); char *mpMenuTextScenarioShortName(struct menuitem *item); char *mpMenuTextScenarioName(struct menuitem *item); s32 scenarioScenarioMenuHandler(s32 operation, struct menuitem *item, union handlerdata *data); -void mpCreateMatchStartHudmsgs(void); +void scenarioCreateMatchStartHudmsgs(void); Gfx *scenarioRenderHud(Gfx *gdl); void scenarioCalculatePlayerScore(struct mpchrconfig *mpchr, s32 chrnum, s32 *arg2, s32 *arg3); -bool scenarioHighlight(struct prop *prop, s32 *colour); -void mpPrepareScenario(void); +bool scenarioHighlightProp(struct prop *prop, s32 *colour); +void scenarioReset(void); struct prop *scenarioCreateObj(s32 modelnum, s16 padnum, f32 arg2, u32 flags, u32 flags2, u32 flags3); -void mpCreateScenarioHudmsg(s32 playernum, char *message); -bool mpChrsAreSameTeam(s32 arg0, s32 arg1); +void scenarioCreateHudmsg(s32 playernum, char *message); +bool scenarioChrsAreSameTeam(s32 playernum1, s32 playernum2); s32 scenarioPickUpBriefcase(struct chrdata *chr, struct prop *prop); -void scenarioReleaseToken(struct chrdata *chr, struct prop *prop); -s32 chrGiveUplink(struct chrdata *chr, struct prop *prop); -void scenarioHtmActivateUplink(struct chrdata *chr, struct prop *prop); +void scenarioHandleDroppedToken(struct chrdata *chr, struct prop *prop); +s32 scenarioPickUpUplink(struct chrdata *chr, struct prop *prop); +void scenarioHandleActivatedProp(struct chrdata *chr, struct prop *prop); s32 menuhandlerMpDropOut(s32 operation, struct menuitem *item, union handlerdata *data); s32 menuhandlerMpTeamsLabel(s32 operation, struct menuitem *item, union handlerdata *data); s32 mpGetNumStages(void); diff --git a/src/include/types.h b/src/include/types.h index 5db6a2c17..5dfb4f3ed 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -4673,34 +4673,6 @@ struct savebuffer { u8 bytes[220]; }; -struct mpscenario { - struct menudialog *optionsdialog; - void (*initfunc)(void); - s32 (*unk08)(void); - void (*resetfunc)(void); - void (*tickfunc)(void); - void (*unk14)(struct chrdata *chr); - Gfx *(*hudfunc)(Gfx *gdl); - void (*calcscorefunc)(struct mpchrconfig *mpchr, s32 chrnum, s32 *score, s32 *deaths); - Gfx *(*radarfunc)(Gfx *gdl); - bool (*radar2func)(Gfx **gdl, struct prop *prop); - bool (*highlightfunc)(struct prop *prop, s32 *colour); - bool (*spawnfunc)(f32 arg0, struct coord *pos, s16 *rooms, struct prop *prop, f32 *arg4); - s32 (*maxteamsfunc)(void); - bool (*isroomhighlightedfunc)(s16 room); - void (*unk38)(s16 arg0, s32 *arg1, s32 *arg2, s32 *arg3); - void *unk3c; - void (*readsavefunc)(struct savebuffer *buffer); - void (*writesavefunc)(struct savebuffer *buffer); -}; - -struct mpscenariooverview { - u16 name; - u16 shortname; - u8 requirefeature; - u8 teamonly; -}; - struct mparena { s16 stagenum; u8 requirefeature; @@ -4768,8 +4740,8 @@ struct htmterminal { }; struct scenariodata_htm { - /*0x800ac110*/ s16 nextindex; - /*0x800ac112*/ s16 unk002; + /*0x800ac110*/ s16 numpads; + /*0x800ac112*/ s16 numterminals; /*0x800ac114*/ s16 padnums[60]; /*0x800ac18c*/ struct htmterminal terminals[7]; // only the first element is used /*0x800ac1e0*/ s16 dlplayernum; @@ -4780,6 +4752,7 @@ struct scenariodata_htm { /*0x800ac248*/ u32 unk138; /*0x800ac24c*/ struct prop *uplink; /*0x800ac250*/ u32 unk140; + /*0x800ac250*/ u32 unk144; }; struct scenariodata_pac { @@ -4787,8 +4760,8 @@ struct scenariodata_pac { u16 age240; s32 victimindex; s16 victims[12]; // shuffled list of player numbers - s16 unk20[12]; - s16 wincounts[12]; // indexed by player num + s16 killcounts[12]; // indexed by player num + s16 survivalcounts[12]; // indexed by player num }; struct scenariodata_koh {