perfect_dark/src/game/challenge.c

984 lines
28 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/chrai.h"
#include "game/chraicommands.h"
#include "game/prop.h"
#include "game/atan2f.h"
#include "game/playermgr.h"
#include "game/mplayer/setup.h"
#include "game/bot.h"
#include "game/challenge.h"
#include "game/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"
#include "lib/rng.h"
#include "data.h"
#include "types.h"
u8 g_MpFeaturesForceUnlocked[40];
u8 g_MpFeaturesUnlocked[80];
u32 g_MpChallengeIndex = 0;
struct mpconfigfull *g_MpCurrentChallengeConfig = NULL;
struct challenge g_MpChallenges[NUM_CHALLENGES] = {
{ L_OPTIONS_406, MPCONFIG_CHALLENGE01 }, // "Challenge 1"
{ L_OPTIONS_407, MPCONFIG_CHALLENGE02 }, // "Challenge 2"
{ L_OPTIONS_408, MPCONFIG_CHALLENGE03 }, // "Challenge 3"
{ L_OPTIONS_409, MPCONFIG_CHALLENGE04 }, // "Challenge 4"
{ L_OPTIONS_410, MPCONFIG_CHALLENGE05 }, // "Challenge 5"
{ L_OPTIONS_411, MPCONFIG_CHALLENGE06 }, // "Challenge 6"
{ L_OPTIONS_412, MPCONFIG_CHALLENGE07 }, // "Challenge 7"
{ L_OPTIONS_413, MPCONFIG_CHALLENGE08 }, // "Challenge 8"
{ L_OPTIONS_414, MPCONFIG_CHALLENGE09 }, // "Challenge 9"
{ L_OPTIONS_415, MPCONFIG_CHALLENGE10 }, // "Challenge 10"
{ L_OPTIONS_416, MPCONFIG_CHALLENGE11 }, // "Challenge 11"
{ L_OPTIONS_417, MPCONFIG_CHALLENGE12 }, // "Challenge 12"
{ L_OPTIONS_418, MPCONFIG_CHALLENGE13 }, // "Challenge 13"
{ L_OPTIONS_419, MPCONFIG_CHALLENGE14 }, // "Challenge 14"
{ L_OPTIONS_420, MPCONFIG_CHALLENGE15 }, // "Challenge 15"
{ L_OPTIONS_421, MPCONFIG_CHALLENGE16 }, // "Challenge 16"
{ L_OPTIONS_422, MPCONFIG_CHALLENGE17 }, // "Challenge 17"
{ L_OPTIONS_423, MPCONFIG_CHALLENGE18 }, // "Challenge 18"
{ L_OPTIONS_424, MPCONFIG_CHALLENGE19 }, // "Challenge 19"
{ L_OPTIONS_425, MPCONFIG_CHALLENGE20 }, // "Challenge 20"
{ L_OPTIONS_426, MPCONFIG_CHALLENGE21 }, // "Challenge 21"
{ L_OPTIONS_427, MPCONFIG_CHALLENGE22 }, // "Challenge 22"
{ L_OPTIONS_428, MPCONFIG_CHALLENGE23 }, // "Challenge 23"
{ L_OPTIONS_429, MPCONFIG_CHALLENGE24 }, // "Challenge 24"
{ L_OPTIONS_430, MPCONFIG_CHALLENGE25 }, // "Challenge 25"
{ L_OPTIONS_431, MPCONFIG_CHALLENGE26 }, // "Challenge 26"
{ L_OPTIONS_432, MPCONFIG_CHALLENGE27 }, // "Challenge 27"
{ L_OPTIONS_433, MPCONFIG_CHALLENGE28 }, // "Challenge 28"
{ L_OPTIONS_434, MPCONFIG_CHALLENGE29 }, // "Challenge 29"
{ L_OPTIONS_435, MPCONFIG_CHALLENGE30 }, // "Challenge 30"
};
bool challengeIsAvailable(s32 challengeindex)
{
return (g_MpChallenges[challengeindex].availability & 1) != 0;
}
bool ChallengeIsAvailableToPlayer(s32 chrnum, s32 challengeindex)
{
if ((g_MpSetup.chrslots & (1 << chrnum)) == 0) {
return 0;
}
return ((g_MpChallenges[challengeindex].availability & (2 << chrnum)) != 0);
}
bool challengeIsAvailableToAnyPlayer(s32 challengeindex)
{
return (g_MpChallenges[challengeindex].availability & (((g_MpSetup.chrslots & 0xf) << 1) | 1)) != 0;
}
void challengeDetermineUnlockedFeatures(void)
{
s32 challengeindex;
s32 numgifted; // number of unlocked but not completed challenges
u8 flag;
s32 prev;
s32 i;
s32 j;
s32 k;
// Clear all challenge availability
for (challengeindex = 0; challengeindex < 30; challengeindex++) {
g_MpChallenges[challengeindex].availability = 0;
}
numgifted = 0;
// Mark challenges completed by any player
for (challengeindex = 0; challengeindex < 30; challengeindex++) {
flag = 0;
if (challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex, 1)
|| challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex, 2)
|| challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex, 3)
|| challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex, 4)) {
// Completed challenge
flag = 1;
} else if (challengeindex < 4) {
// Not yet completed, but challenges 1-4 are always available
flag = 1;
numgifted++;
} else if (challengeindex > 0
&& (challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex - 1, 1)
|| challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex - 1, 2)
|| challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex - 1, 3)
|| challengeIsCompletedByAnyPlayerWithNumPlayers(challengeindex - 1, 4))) {
// Challenges are available if their previous one is complete
flag = 1;
numgifted++;
}
#if VERSION == VERSION_NTSC_BETA || VERSION == VERSION_PAL_BETA
else if (debugIsAllChallengesEnabled()) {
flag = 1;
}
#endif
g_MpChallenges[challengeindex].availability |= flag;
}
// Gift up to 4 challenges
for (challengeindex = 0; numgifted < 4 && challengeindex < 30; challengeindex++) {
if ((g_MpChallenges[challengeindex].availability & 1) == 0) {
g_MpChallenges[challengeindex].availability |= 1;
numgifted++;
}
}
// Now same as above, but per player
for (j = 0; j < 4; j++) {
numgifted = 0;
for (challengeindex = 0; challengeindex < 30; challengeindex++) {
flag = 0;
if (challengeIsCompletedByPlayerWithNumPlayers(j, challengeindex, 1)
|| challengeIsCompletedByPlayerWithNumPlayers(j, challengeindex, 2)
|| challengeIsCompletedByPlayerWithNumPlayers(j, challengeindex, 3)
|| challengeIsCompletedByPlayerWithNumPlayers(j, challengeindex, 4)) {
// Completed challenge
flag = 2 << j;
} else if (challengeindex < 4) {
// Not yet completed, but challenges 1-4 are always available
flag = 2 << j;
numgifted++;
} else if (challengeindex > 0) {
// Challenges are available if their previous one is complete
prev = challengeindex - 1;
if (challengeIsCompletedByPlayerWithNumPlayers(j, prev, 1)
|| challengeIsCompletedByPlayerWithNumPlayers(j, prev, 2)
|| challengeIsCompletedByPlayerWithNumPlayers(j, prev, 3)
|| challengeIsCompletedByPlayerWithNumPlayers(j, prev, 4)) {
flag = 2 << j;
numgifted++;
}
}
g_MpChallenges[challengeindex].availability |= flag;
}
// Gift up to 4 challenges
for (challengeindex = 0; numgifted < 4 && challengeindex < 30; challengeindex++) {
if ((g_MpChallenges[challengeindex].availability & (2 << j)) == 0) {
g_MpChallenges[challengeindex].availability |= 2 << j;
numgifted++;
}
}
}
for (j = 0; j < ARRAYCOUNT(g_MpFeaturesUnlocked); j++) {
flag = 0;
for (challengeindex = 0; challengeindex < 30; challengeindex++) {
if (challengeIsAvailableToAnyPlayer(challengeindex)) {
for (i = 0; i < 16; i++) {
if (g_MpChallenges[challengeindex].unlockfeatures[i] == j) {
flag |= 1;
}
}
}
}
for (i = 0; i < ARRAYCOUNT(g_MpFeaturesForceUnlocked); i++) {
if (g_MpFeaturesForceUnlocked[i] == j) {
flag |= 1;
}
}
for (challengeindex = 0; challengeindex < 30; challengeindex++) {
for (prev = 0; prev < 4; prev++) {
if (ChallengeIsAvailableToPlayer(prev, challengeindex)) {
for (i = 0; i < 16; i++) {
if (g_MpChallenges[challengeindex].unlockfeatures[i] == j) {
flag |= 2 << prev;
}
}
}
}
}
g_MpFeaturesUnlocked[j] = flag;
}
for (j = 0; j < func0f188bcc(); j++) {
struct mpweapon *weapon = &g_MpWeapons[j];
if (weapon->unlockfeature > 0 && func0f19cbcc(weapon->weaponnum)) {
g_MpFeaturesUnlocked[weapon->unlockfeature] |= 1;
}
}
func0f1895e8();
// If the ability to have 8 simulants hasn't been unlocked, limit them to 4
if (!challengeIsFeatureUnlocked(MPFEATURE_8BOTS)) {
for (k = 4; k < MAX_BOTS; k++) {
if (g_MpSetup.chrslots & (1 << (4 + k))) {
mpRemoveSimulant(k);
}
}
if (g_Vars.mpquickteamnumsims > 4) {
g_Vars.mpquickteamnumsims = 4;
}
}
}
void challengePerformSanityChecks(void)
{
if (g_BossFile.locktype == MPLOCKTYPE_CHALLENGE) {
s32 numplayers = 0;
s32 i;
// Reset player handicaps
for (i = 0; i < 4; i++) {
if (g_MpSetup.chrslots & (1 << i)) {
g_PlayerConfigsArray[i].handicap = 0x80;
numplayers++;
}
}
// Turn off all simulants and turn them on if enabled
// for this number of players
g_MpSetup.chrslots &= 0x000f;
for (i = 0; i != MAX_BOTS; i++) {
g_BotConfigsArray[i].difficulty = g_MpSimulantDifficultiesPerNumPlayers[i][numplayers - 1];
if (g_BotConfigsArray[i].difficulty != BOTDIFF_DISABLED) {
g_MpSetup.chrslots |= 1 << (i + 4);
}
}
if (g_MpSetup.scenario == MPSCENARIO_KINGOFTHEHILL) {
g_Vars.mphilltime = 10;
}
} else if (!challengeIsFeatureUnlocked(MPFEATURE_8BOTS)) {
// Limit to 4 players and 4 simulants
g_MpSetup.chrslots &= 0x00ff;
}
}
s32 challengeGetNumAvailable(void)
{
s32 challengeindex;
s32 count = 0;
for (challengeindex = 0; challengeindex != NUM_CHALLENGES; challengeindex++) {
if (challengeIsAvailableToAnyPlayer(challengeindex)) {
count++;
}
}
return count;
}
char *challengeGetName(s32 challengeindex)
{
return langGet(g_MpChallenges[challengeindex].name);
}
char *challengeGetNameBySlot(s32 slot)
{
s32 index = 0;
s32 i;
for (i = 0; i < 30; i++) {
if (challengeIsAvailableToAnyPlayer(i)) {
if (index == slot) {
return challengeGetName(i);
}
index++;
}
}
return "";
}
void challengeSetCurrentBySlot(s32 slotnum)
{
s32 challengeindex;
g_MpChallengeIndex = 0;
for (challengeindex = 0; challengeindex != NUM_CHALLENGES; challengeindex++) {
if (challengeIsAvailableToAnyPlayer(challengeindex)) {
if (slotnum == 0) {
g_MpChallengeIndex = challengeindex;
break;
}
slotnum--;
}
}
challengeApply();
}
s32 challengeGetCurrent(void)
{
return g_MpChallengeIndex;
}
bool challengeIsCompletedByAnyChrWithNumPlayersBySlot(s32 slot, s32 numplayers)
{
s32 availableindex = 0;
s32 i;
for (i = 0; i < 30; i++) {
if (challengeIsAvailableToAnyPlayer(i)) {
if (availableindex == slot) {
return challengeIsCompletedByAnyPlayerWithNumPlayers(i, numplayers);
}
availableindex++;
}
}
return false;
}
bool challengeIsCompletedByChrWithNumPlayersBySlot(s32 mpchrnum, s32 slot, s32 numplayers)
{
s32 availableindex = 0;
s32 i;
for (i = 0; i < 30; i++) {
if (challengeIsAvailableToAnyPlayer(i)) {
if (availableindex == slot) {
return challengeIsCompletedByPlayerWithNumPlayers(mpchrnum, i, numplayers);
}
availableindex++;
}
}
return false;
}
extern u32 _mpstringsESegmentRomStart;
extern u32 _mpstringsJSegmentRomStart;
extern u32 _mpstringsPSegmentRomStart;
extern u32 _mpstringsGSegmentRomStart;
extern u32 _mpstringsFSegmentRomStart;
extern u32 _mpstringsSSegmentRomStart;
extern u32 _mpstringsISegmentRomStart;
extern u32 _mpstringsESegmentRomEnd;
extern u32 _mpstringsJSegmentRomEnd;
extern u32 _mpstringsPSegmentRomEnd;
extern u32 _mpstringsGSegmentRomEnd;
extern u32 _mpstringsFSegmentRomEnd;
extern u32 _mpstringsSSegmentRomEnd;
extern u32 _mpstringsISegmentRomEnd;
u32 *var800887c4 = &_mpstringsESegmentRomStart;
u32 *var800887c8 = &_mpstringsESegmentRomEnd;
u32 *var800887cc = &_mpstringsJSegmentRomStart;
u32 *var800887d0 = &_mpstringsJSegmentRomEnd;
u32 *var800887d4 = &_mpstringsPSegmentRomStart;
u32 *var800887d8 = &_mpstringsPSegmentRomEnd;
u32 *var800887dc = &_mpstringsGSegmentRomStart;
u32 *var800887e0 = &_mpstringsGSegmentRomEnd;
u32 *var800887e4 = &_mpstringsFSegmentRomStart;
u32 *var800887e8 = &_mpstringsFSegmentRomEnd;
u32 *var800887ec = &_mpstringsSSegmentRomStart;
u32 *var800887f0 = &_mpstringsSSegmentRomEnd;
u32 *var800887f4 = &_mpstringsISegmentRomStart;
u32 *var800887f8 = &_mpstringsISegmentRomEnd;
GLOBAL_ASM(
glabel challengeLoadConfig
/* f19b914: 27bdfe38 */ addiu $sp,$sp,-456
/* f19b918: afbf0014 */ sw $ra,0x14($sp)
/* f19b91c: afa401c8 */ sw $a0,0x1c8($sp)
/* f19b920: afa501cc */ sw $a1,0x1cc($sp)
/* f19b924: 0fc5b9b5 */ jal langGetFileNumOffset
/* f19b928: afa601d0 */ sw $a2,0x1d0($sp)
/* f19b92c: 3c0f8009 */ lui $t7,%hi(var800887c4)
/* f19b930: 25ef87c4 */ addiu $t7,$t7,%lo(var800887c4)
/* f19b934: afa20050 */ sw $v0,0x50($sp)
/* f19b938: 25e80030 */ addiu $t0,$t7,0x30
/* f19b93c: 27ae0018 */ addiu $t6,$sp,0x18
.L0f19b940:
/* f19b940: 8de10000 */ lw $at,0x0($t7)
/* f19b944: 25ef000c */ addiu $t7,$t7,0xc
/* f19b948: 25ce000c */ addiu $t6,$t6,0xc
/* f19b94c: adc1fff4 */ sw $at,-0xc($t6)
/* f19b950: 8de1fff8 */ lw $at,-0x8($t7)
/* f19b954: adc1fff8 */ sw $at,-0x8($t6)
/* f19b958: 8de1fffc */ lw $at,-0x4($t7)
/* f19b95c: 15e8fff8 */ bne $t7,$t0,.L0f19b940
/* f19b960: adc1fffc */ sw $at,-0x4($t6)
/* f19b964: 8de10000 */ lw $at,0x0($t7)
/* f19b968: 8de80004 */ lw $t0,0x4($t7)
/* f19b96c: 3c0b007d */ lui $t3,%hi(_mpconfigsSegmentRomStart)
/* f19b970: adc10000 */ sw $at,0x0($t6)
/* f19b974: adc80004 */ sw $t0,0x4($t6)
/* f19b978: 8fa901c8 */ lw $t1,0x1c8($sp)
/* f19b97c: 256b0a40 */ addiu $t3,$t3,%lo(_mpconfigsSegmentRomStart)
/* f19b980: 8fa401cc */ lw $a0,0x1cc($sp)
/* f19b984: 00095080 */ sll $t2,$t1,0x2
/* f19b988: 01495023 */ subu $t2,$t2,$t1
/* f19b98c: 000a5080 */ sll $t2,$t2,0x2
/* f19b990: 01495021 */ addu $t2,$t2,$t1
/* f19b994: 000a50c0 */ sll $t2,$t2,0x3
/* f19b998: 014b2821 */ addu $a1,$t2,$t3
/* f19b99c: 0c003522 */ jal dmaExecWithAutoAlign
/* f19b9a0: 24060068 */ addiu $a2,$zero,0x68
/* f19b9a4: 8fac0050 */ lw $t4,0x50($sp)
/* f19b9a8: 8fb901c8 */ lw $t9,0x1c8($sp)
/* f19b9ac: 27a4005c */ addiu $a0,$sp,0x5c
/* f19b9b0: 000c68c0 */ sll $t5,$t4,0x3
/* f19b9b4: 03ad1821 */ addu $v1,$sp,$t5
/* f19b9b8: 0019c080 */ sll $t8,$t9,0x2
/* f19b9bc: 8c630018 */ lw $v1,0x18($v1)
/* f19b9c0: 0319c021 */ addu $t8,$t8,$t9
/* f19b9c4: 0018c180 */ sll $t8,$t8,0x6
/* f19b9c8: afa201c4 */ sw $v0,0x1c4($sp)
/* f19b9cc: 24060140 */ addiu $a2,$zero,0x140
/* f19b9d0: 0c003522 */ jal dmaExecWithAutoAlign
/* f19b9d4: 00782821 */ addu $a1,$v1,$t8
/* f19b9d8: 8fa701c4 */ lw $a3,0x1c4($sp)
/* f19b9dc: 00404825 */ or $t1,$v0,$zero
/* f19b9e0: 244e0138 */ addiu $t6,$v0,0x138
/* f19b9e4: 00e05025 */ or $t2,$a3,$zero
.L0f19b9e8:
/* f19b9e8: 89210000 */ lwl $at,0x0($t1)
/* f19b9ec: 99210003 */ lwr $at,0x3($t1)
/* f19b9f0: 2529000c */ addiu $t1,$t1,0xc
/* f19b9f4: 254a000c */ addiu $t2,$t2,0xc
/* f19b9f8: a941005c */ swl $at,0x5c($t2)
/* f19b9fc: b941005f */ swr $at,0x5f($t2)
/* f19ba00: 8921fff8 */ lwl $at,-0x8($t1)
/* f19ba04: 9921fffb */ lwr $at,-0x5($t1)
/* f19ba08: a9410060 */ swl $at,0x60($t2)
/* f19ba0c: b9410063 */ swr $at,0x63($t2)
/* f19ba10: 8921fffc */ lwl $at,-0x4($t1)
/* f19ba14: 9921ffff */ lwr $at,-0x1($t1)
/* f19ba18: a9410064 */ swl $at,0x64($t2)
/* f19ba1c: 152efff2 */ bne $t1,$t6,.L0f19b9e8
/* f19ba20: b9410067 */ swr $at,0x67($t2)
/* f19ba24: 89210000 */ lwl $at,0x0($t1)
/* f19ba28: 99210003 */ lwr $at,0x3($t1)
/* f19ba2c: 00e01025 */ or $v0,$a3,$zero
/* f19ba30: a9410068 */ swl $at,0x68($t2)
/* f19ba34: b941006b */ swr $at,0x6b($t2)
/* f19ba38: 892e0004 */ lwl $t6,0x4($t1)
/* f19ba3c: 992e0007 */ lwr $t6,0x7($t1)
/* f19ba40: a94e006c */ swl $t6,0x6c($t2)
/* f19ba44: b94e006f */ swr $t6,0x6f($t2)
/* f19ba48: 8fbf0014 */ lw $ra,0x14($sp)
/* f19ba4c: 27bd01c8 */ addiu $sp,$sp,0x1c8
/* f19ba50: 03e00008 */ jr $ra
/* f19ba54: 00000000 */ nop
);
// Mismatch because the arguments to an addu instruction are swapped.
// It's the addu for calculating &bank[confignum].
//struct mpconfigfull *challengeLoadConfig(s32 confignum, u8 *buffer, s32 len)
//{
// struct mpconfigfull *mpconfig;
// u8 buffer2[sizeof(struct mpstrings) + 40];
// struct mpstrings *loadedstrings;
// struct mpstrings *bank;
// u32 language_id = langGetFileNumOffset();
// extern struct mpconfig _mpconfigsSegmentRomStart[];
// extern struct mpstrings _mpstringsESegmentRomStart;
// extern struct mpstrings _mpstringsJSegmentRomStart;
// extern struct mpstrings _mpstringsPSegmentRomStart;
// extern struct mpstrings _mpstringsGSegmentRomStart;
// extern struct mpstrings _mpstringsFSegmentRomStart;
// extern struct mpstrings _mpstringsSSegmentRomStart;
// extern struct mpstrings _mpstringsISegmentRomStart;
// extern struct mpstrings _mpstringsESegmentRomEnd;
// extern struct mpstrings _mpstringsJSegmentRomEnd;
// extern struct mpstrings _mpstringsPSegmentRomEnd;
// extern struct mpstrings _mpstringsGSegmentRomEnd;
// extern struct mpstrings _mpstringsFSegmentRomEnd;
// extern struct mpstrings _mpstringsSSegmentRomEnd;
// extern struct mpstrings _mpstringsISegmentRomEnd;
//
// struct mpstrings *banks[][2] = {
// { &_mpstringsESegmentRomStart, &_mpstringsESegmentRomEnd },
// { &_mpstringsJSegmentRomStart, &_mpstringsJSegmentRomEnd },
// { &_mpstringsPSegmentRomStart, &_mpstringsPSegmentRomEnd },
// { &_mpstringsGSegmentRomStart, &_mpstringsGSegmentRomEnd },
// { &_mpstringsFSegmentRomStart, &_mpstringsFSegmentRomEnd },
// { &_mpstringsSSegmentRomStart, &_mpstringsSSegmentRomEnd },
// { &_mpstringsISegmentRomStart, &_mpstringsISegmentRomEnd },
// };
//
// // Load mpconfigs
// mpconfig = dmaExecWithAutoAlign(buffer, &_mpconfigsSegmentRomStart[confignum], sizeof(struct mpconfig));
//
// // Load mpstrings
// bank = banks[language_id][0];
// loadedstrings = dmaExecWithAutoAlign(buffer2, &bank[confignum], sizeof(struct mpstrings));
//
// mpconfig->strings = *loadedstrings;
//
// return mpconfig;
//}
struct mpconfigfull *challengeLoad(s32 challengeindex, u8 *buffer, s32 len)
{
return challengeLoadConfig(g_MpChallenges[challengeindex].confignum, buffer, len);
}
struct mpconfigfull *challengeLoadBySlot(s32 n, u8 *buffer, s32 len)
{
s32 numavailable = 0;
s32 challengeindex;
for (challengeindex = 0; challengeindex != NUM_CHALLENGES; challengeindex++) {
if (challengeIsAvailableToAnyPlayer(challengeindex)) {
if (numavailable == n) {
return challengeLoad(challengeindex, buffer, len);
}
numavailable++;
}
}
return 0;
}
struct mpconfigfull *challengeLoadCurrent(u8 *buffer, s32 len)
{
return challengeLoad(g_MpChallengeIndex, buffer, len);
}
/**
* This is adding featurenum to the array, provided it's unique.
*/
s32 challengeForceUnlockFeature(s32 featurenum, u8 *array, s32 tail, s32 len)
{
s32 i;
for (i = 0; i < tail; i++) {
if (array[i] == featurenum) {
break;
}
}
if (i >= tail && tail < len) {
array[tail] = featurenum;
tail++;
}
return tail;
}
s32 challengeForceUnlockSetupFeatures(struct mpsetup *setup, u8 *array, s32 len)
{
s32 index = 0;
s32 i;
// Force unlock the weapons (if never held before)
for (i = 0; i < 6; i++) {
s32 featurenum = g_MpWeapons[setup->weapons[i]].unlockfeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, array, index, len);
}
}
// Force unlock the stage
for (i = 0; i < mpGetNumStages(); i++) {
if (g_MpArenas[i].stagenum == setup->stagenum) {
s32 featurenum = g_MpArenas[i].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, array, index, len);
}
}
}
// Force unlock the scenario
if (setup->scenario <= MPSCENARIO_CAPTURETHECASE) {
s32 featurenum = g_MpScenarioOverviews[setup->scenario].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, array, index, len);
}
}
// Force unlock the scenario options
if (setup->options & MPOPTION_ONEHITKILLS) {
index = challengeForceUnlockFeature(MPFEATURE_ONEHITKILLS, array, index, len);
}
if (setup->options & (MPOPTION_SLOWMOTION_ON | MPOPTION_SLOWMOTION_SMART)) {
index = challengeForceUnlockFeature(MPFEATURE_SLOWMOTION, array, index, len);
}
return index;
}
void challengeForceUnlockConfigFeatures(struct mpconfig *config, u8 *array, s32 len, s32 challengeindex)
{
s32 index = challengeForceUnlockSetupFeatures(&config->setup, array, len);
s32 featurenum;
s32 numplayers;
s32 i;
for (i = 0; i < 8; i++) {
s32 simtype = mpFindBotProfile(config->simulants[i].type, BOTDIFF_NORMAL);
if (simtype >= 0) {
featurenum = g_BotProfiles[simtype].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, array, index, len);
}
}
for (numplayers = 0; numplayers < 4; numplayers++) {
simtype = mpFindBotProfile(0, config->simulants[i].difficulties[numplayers]);
if (simtype >= 0) {
featurenum = g_BotProfiles[simtype].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, array, index, len);
}
}
}
if (config->simulants[i].mpbodynum < NUM_MPBODIES) {
featurenum = g_MpBodies[config->simulants[i].mpbodynum].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, array, index, len);
}
}
if (config->simulants[i].mpheadnum < NUM_MPHEADS) {
featurenum = g_MpHeads[config->simulants[i].mpheadnum].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, array, index, len);
}
}
}
#if VERSION >= VERSION_NTSC_1_0
if (challengeindex >= 25) {
index = challengeForceUnlockFeature(MPFEATURE_BOTDIFF_DARK, array, index, len);
} else if (challengeindex >= 20) {
index = challengeForceUnlockFeature(MPFEATURE_STAGE_CARPARK, array, index, len);
} else if (challengeindex >= 15) {
index = challengeForceUnlockFeature(MPFEATURE_SCENARIO_PAC, array, index, len);
}
if (challengeindex >= 10) {
index = challengeForceUnlockFeature(MPFEATURE_8BOTS, array, index, len);
}
#else
if (challengeindex >= 10) {
index = challengeForceUnlockFeature(MPFEATURE_8BOTS, array, index, len);
}
if (challengeindex >= 15) {
index = challengeForceUnlockFeature(MPFEATURE_SCENARIO_PAC, array, index, len);
}
if (challengeindex >= 20) {
index = challengeForceUnlockFeature(MPFEATURE_STAGE_CARPARK, array, index, len);
}
#endif
// Clear the remainder of the array
for (i = index; i < len; i++) {
array[i] = 0;
}
}
void challengeForceUnlockBotFeatures(void)
{
s32 numsims = 0;
s32 index = challengeForceUnlockSetupFeatures(&g_MpSetup, g_MpFeaturesForceUnlocked, ARRAYCOUNT(g_MpFeaturesForceUnlocked));
s32 i;
for (i = 0; i < 8; i++) {
// Force unlock the simulant type
s32 simtypeindex = mpFindBotProfile(g_BotConfigsArray[i].type, BOTDIFF_NORMAL);
if (simtypeindex >= 0) {
s32 featurenum = g_BotProfiles[simtypeindex].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, g_MpFeaturesForceUnlocked, index, ARRAYCOUNT(g_MpFeaturesForceUnlocked));
}
}
// Force unlock the simulant difficulty
simtypeindex = mpFindBotProfile(BOTTYPE_GENERAL, g_BotConfigsArray[i].difficulty);
if (simtypeindex >= 0) {
s32 featurenum = g_BotProfiles[simtypeindex].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, g_MpFeaturesForceUnlocked, index, ARRAYCOUNT(g_MpFeaturesForceUnlocked));
}
}
if (simtypeindex >= 0) {
numsims++;
}
// Force unlock the simulant's body
if (g_BotConfigsArray[i].base.mpbodynum < NUM_MPBODIES) {
s32 featurenum = g_MpBodies[g_BotConfigsArray[i].base.mpbodynum].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, g_MpFeaturesForceUnlocked, index, ARRAYCOUNT(g_MpFeaturesForceUnlocked));
}
}
// Force unlock the simulant's head
if (g_BotConfigsArray[i].base.mpheadnum < NUM_MPHEADS) {
s32 featurenum = g_MpHeads[g_BotConfigsArray[i].base.mpheadnum].requirefeature;
if (featurenum) {
index = challengeForceUnlockFeature(featurenum, g_MpFeaturesForceUnlocked, index, ARRAYCOUNT(g_MpFeaturesForceUnlocked));
}
}
}
// Force unlock 8 simulants
if (numsims > 4) {
index = challengeForceUnlockFeature(MPFEATURE_8BOTS, g_MpFeaturesForceUnlocked, index, ARRAYCOUNT(g_MpFeaturesForceUnlocked));
}
// Clear the remainder of the array
for (i = index; i < ARRAYCOUNT(g_MpFeaturesForceUnlocked); i++) {
g_MpFeaturesForceUnlocked[i] = 0;
}
challengeDetermineUnlockedFeatures();
}
void challengeRemoveForceUnlocks(void)
{
s32 i;
for (i = 0; i < ARRAYCOUNT(g_MpFeaturesForceUnlocked); i++) {
g_MpFeaturesForceUnlocked[i] = 0;
}
challengeDetermineUnlockedFeatures();
}
void challengeApply(void)
{
s32 i;
u8 buffer[458];
mpApplyConfig(challengeLoadCurrent(buffer, 458));
mpSetLock(MPLOCKTYPE_CHALLENGE, 5);
for (i = 0; i < 4; i++) {
g_PlayerConfigsArray[i].base.team = 0;
}
}
s32 challengeRemovePlayerLock(void)
{
return mpSetLock(MPLOCKTYPE_NONE, 0);
}
void challengeLoadAndStoreCurrent(u8 *buffer, s32 len)
{
g_MpCurrentChallengeConfig = challengeLoadCurrent(buffer, len);
}
void challengeUnsetCurrent(void)
{
g_MpCurrentChallengeConfig = NULL;
}
bool challengeIsLoaded(void)
{
return g_MpCurrentChallengeConfig != NULL;
}
char *challengeGetCurrentDescription(void)
{
if (g_MpCurrentChallengeConfig) {
return g_MpCurrentChallengeConfig->strings.description;
}
return "";
}
char *challengeGetConfigDescription(struct mpconfigfull *mpconfig)
{
if (mpconfig) {
return mpconfig->strings.description;
}
return "";
}
/**
* Return the index of the first incomplete challenge, but if it's in the first
* 4 then return index 4 because the first 4 are always shown.
*/
s32 challengeGetAutoFocusedIndex(s32 mpchrnum)
{
s32 challengeindex;
s32 index = 0;
for (challengeindex = 29; challengeindex >= 0; challengeindex--) {
if (challengeIsCompletedByPlayerWithNumPlayers(mpchrnum, challengeindex, 1) ||
challengeIsCompletedByPlayerWithNumPlayers(mpchrnum, challengeindex, 2) ||
challengeIsCompletedByPlayerWithNumPlayers(mpchrnum, challengeindex, 3) ||
challengeIsCompletedByPlayerWithNumPlayers(mpchrnum, challengeindex, 4)) {
index = challengeindex + 1;
break;
}
}
if (index < 4) {
index = 4;
}
return index;
}
char *xhallengeGetName(s32 arg0, s32 challengeindex)
{
return langGet(g_MpChallenges[challengeindex].name);
}
bool challengeIsCompletedByPlayerWithNumPlayers2(s32 mpchrnum, s32 index, s32 numplayers)
{
return challengeIsCompletedByPlayerWithNumPlayers(mpchrnum, index, numplayers);
}
bool challengeIsCompletedByAnyPlayerWithNumPlayers(s32 index, s32 numplayers)
{
return (g_MpChallenges[index].completions[numplayers - 1] & 1) != 0;
}
void challengeSetCompletedByAnyPlayerWithNumPlayers(s32 index, s32 numplayers, bool completed)
{
if (completed) {
g_MpChallenges[index].completions[numplayers - 1] |= 1;
return;
}
g_MpChallenges[index].completions[numplayers - 1] &= ~1;
}
bool challengeIsCompletedByPlayerWithNumPlayers(s32 mpchrnum, s32 index, s32 numplayers)
{
return (g_MpChallenges[index].completions[numplayers - 1] & (2 << mpchrnum)) != 0;
}
void challengeSetCompletedByPlayerWithNumPlayers(u32 mpchrnum, s32 index, s32 numplayers, bool completed)
{
if (completed) {
g_MpChallenges[index].completions[numplayers - 1] |= 2 << mpchrnum;
return;
}
g_MpChallenges[index].completions[numplayers - 1] &= ~(2 << mpchrnum);
}
bool challengeIsCompleteForEndscreen(void)
{
s32 prevplayernum = g_Vars.currentplayernum;
s32 result = false;
s32 aborted = false;
s32 i;
u32 stack;
for (i = 0; i < PLAYERCOUNT(); i++) {
setCurrentPlayerNum(i);
if (g_Vars.currentplayer->aborted) {
aborted = true;
}
}
setCurrentPlayerNum(prevplayernum);
if (!aborted) {
struct ranking rankings[12];
mpGetTeamRankings(rankings);
if (rankings[0].teamnum == 0) {
result = true;
}
}
return result;
}
void challengeConsiderMarkingComplete(void)
{
bool result = challengeIsCompleteForEndscreen();
#if VERSION == VERSION_PAL_BETA
if ((g_CheatsActiveBank0 == 0 && g_CheatsActiveBank1 == 0) && (result || debugIsSetCompleteEnabled()))
#elif VERSION >= VERSION_NTSC_1_0
if (g_CheatsActiveBank0 == 0 && g_CheatsActiveBank1 == 0 && result)
#else
if (result && g_CheatsActiveBank0 == 0 && g_CheatsActiveBank1 == 0)
#endif
{
u32 prevplayernum;
s32 i;
challengeSetCompletedByAnyPlayerWithNumPlayers(g_MpChallengeIndex, PLAYERCOUNT(), 1);
prevplayernum = g_Vars.currentplayernum;
for (i = 0; i < PLAYERCOUNT(); i++) {
setCurrentPlayerNum(i);
challengeSetCompletedByPlayerWithNumPlayers(g_Vars.currentplayerstats->mpindex, g_MpChallengeIndex, PLAYERCOUNT(), true);
}
setCurrentPlayerNum(prevplayernum);
challengeDetermineUnlockedFeatures();
}
}
bool challengeIsFeatureUnlocked(s32 featurenum)
{
if (featurenum == 0) {
return true;
}
return (g_MpFeaturesUnlocked[featurenum] & 1) != 0;
}
bool challengeIsFeatureUnlockedByPlayer(u32 numplayers, s32 featurenum)
{
if (featurenum == 0) {
return true;
}
return (g_MpFeaturesUnlocked[featurenum] & (2 << numplayers)) != 0;
}
bool challengeIsFeatureUnlockedByDefault(s32 featurenum)
{
if (featurenum) {
return false;
}
return true;
}