Files
perfect-dark/src/game/training.c
T
2022-07-06 19:22:01 +10:00

4034 lines
104 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/chraction.h"
#include "game/dlights.h"
#include "game/prop.h"
#include "game/propsnd.h"
#include "game/objectives.h"
#include "game/atan2f.h"
#include "game/bondgun.h"
#include "game/game_0b0fd0.h"
#include "game/player.h"
#include "game/hudmsg.h"
#include "game/menu.h"
#include "game/filemgr.h"
#include "game/inv.h"
#include "game/explosions.h"
#include "game/game_1531a0.h"
#include "game/bg.h"
#include "game/trainingmenus.h"
#include "game/training.h"
#include "game/gamefile.h"
#include "game/lang.h"
#include "game/pad.h"
#include "game/padhalllv.h"
#include "game/propobj.h"
#include "game/shards.h"
#include "game/wallhit.h"
#include "bss.h"
#include "lib/vi.h"
#include "lib/dma.h"
#include "lib/main.h"
#include "lib/snd.h"
#include "lib/memp.h"
#include "lib/rng.h"
#include "lib/mtx.h"
#include "data.h"
#include "types.h"
#define FRSCRIPTINDEX_WEAPONS 0x00
#define FRSCRIPTINDEX_TARGETS 0x22
#define FRSCRIPTINDEX_HELP 0x71
extern u8 *_firingrangeSegmentRomStart;
extern u8 *_firingrangeSegmentRomEnd;
struct frdata g_FrData;
struct trainingdata g_DtData;
struct trainingdata g_HtData;
u16 *g_FrScriptOffsets = NULL;
u8 g_FrIsValidWeapon = false;
u8 g_FrDataLoaded = false;
u8 g_FrNumSounds = 0;
u8 *g_FrRomData = NULL;
u16 g_FrPads[] = {
0x00d6, 0x00d7, 0x00d9, 0x00d8, 0x00da, 0x00db, 0x00dc, 0x00dd,
0x00de, 0x00df, 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5,
0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00f4, 0x00f3,
0x00f2, 0x00f1, 0x00f0, 0x00ef, 0x00ee, 0x00ed, 0x00ec,
};
bool ciIsTourDone(void)
{
return gamefileHasFlag(GAMEFILEFLAG_CI_TOUR_DONE);
}
u8 ciGetFiringRangeScore(s32 weaponindex)
{
// Data at firingrangescores is a u8 array where each score uses 2 bits
#if VERSION == VERSION_JPN_FINAL
if (weaponindex == frGetWeaponIndexByWeapon(WEAPON_COMBATKNIFE)) {
// The knife doesn't exist in the JPN version.
// Treat it as completed so unlockables still work.
return 3;
}
#endif
return (g_GameFile.firingrangescores[weaponindex >> 2] >> (weaponindex % 4) * 2) & 3;
}
void frSaveScoreIfBest(s32 weaponindex, s32 difficulty)
{
if (ciGetFiringRangeScore(weaponindex) < difficulty) {
u32 byteindex = weaponindex >> 2;
u32 shiftamount = (weaponindex % 4) * 2;
u32 value = g_GameFile.firingrangescores[byteindex];
u32 mask = (1 << shiftamount) + (1 << (shiftamount + 1));
value &= 255 - mask;
value += (difficulty << shiftamount) & mask;
g_GameFile.firingrangescores[byteindex] = value;
}
}
s32 func0f19ca78(u32 weaponnum)
{
s32 slot = -1;
s32 i;
for (i = 0; i <= WEAPON_HORIZONSCANNER; i++) {
switch (i) {
case WEAPON_FALCON2:
case WEAPON_FALCON2_SCOPE:
case WEAPON_FALCON2_SILENCER:
case WEAPON_MAGSEC4:
case WEAPON_MAULER:
case WEAPON_PHOENIX:
case WEAPON_DY357MAGNUM:
case WEAPON_DY357LX:
case WEAPON_CMP150:
case WEAPON_CYCLONE:
case WEAPON_CALLISTO:
case WEAPON_RCP120:
case WEAPON_LAPTOPGUN:
case WEAPON_DRAGON:
case WEAPON_K7AVENGER:
case WEAPON_AR34:
case WEAPON_SUPERDRAGON:
case WEAPON_SHOTGUN:
case WEAPON_SNIPERRIFLE:
case WEAPON_FARSIGHT:
case WEAPON_CROSSBOW:
case WEAPON_TRANQUILIZER:
case WEAPON_REAPER:
case WEAPON_DEVASTATOR:
case WEAPON_ROCKETLAUNCHER:
case WEAPON_SLAYER:
case WEAPON_COMBATKNIFE:
case WEAPON_LASER:
case WEAPON_GRENADE:
case WEAPON_TIMEDMINE:
case WEAPON_PROXIMITYMINE:
case WEAPON_REMOTEMINE:
slot++;
}
if (i == weaponnum) {
return slot;
}
}
return -1;
}
u8 frIsWeaponFound(s32 weaponnum)
{
u32 byteindex;
if (weaponnum <= WEAPON_UNARMED) {
return true;
}
#if VERSION >= VERSION_NTSC_1_0
if (weaponnum < (s32)sizeof(g_GameFile.weaponsfound) * 8) {
byteindex = weaponnum >> 3;
return g_GameFile.weaponsfound[byteindex] & (1 << (weaponnum % 8));
}
return false;
#else
byteindex = weaponnum >> 3;
return g_GameFile.weaponsfound[byteindex] & (1 << (weaponnum % 8));
#endif
}
void frSetWeaponFound(s32 weaponnum)
{
if (weaponnum < (s32)sizeof(g_GameFile.weaponsfound) * 8) {
u32 byteindex = weaponnum >> 3;
u32 value = g_GameFile.weaponsfound[byteindex];
value |= (1 << (weaponnum % 8));
g_GameFile.weaponsfound[byteindex] = value;
}
}
s32 ciIsStageComplete(s32 stageindex)
{
return g_GameFile.besttimes[stageindex][0]
|| g_GameFile.besttimes[stageindex][1]
|| g_GameFile.besttimes[stageindex][2];
}
bool func0f19cbcc(s32 weapon)
{
if (weapon <= 0 || weapon == WEAPON_PSYCHOSISGUN) {
return false;
}
if (weapon == WEAPON_XRAYSCANNER && ciIsStageComplete(SOLOSTAGEINDEX_INFILTRATION)) {
return true;
}
if (weapon == WEAPON_CLOAKINGDEVICE && ciIsStageComplete(SOLOSTAGEINDEX_CHICAGO)) {
return true;
}
return frIsWeaponFound(weapon);
}
bool frIsWeaponAvailable(s32 weapon)
{
#if VERSION == VERSION_JPN_FINAL
if (weapon == WEAPON_COMBATKNIFE) {
return false;
}
#endif
if (weapon < WEAPON_FALCON2 || weapon > WEAPON_REMOTEMINE
|| weapon == WEAPON_PSYCHOSISGUN
|| weapon == WEAPON_COMBATBOOST
|| weapon == WEAPON_NBOMB) {
return false;
}
if (weapon == WEAPON_FALCON2 || weapon == WEAPON_CMP150) {
return true;
}
#if VERSION < VERSION_NTSC_1_0
if (debugIsAllTrainingEnabled() && weapon <= WEAPON_XRAYSCANNER) {
return true;
}
#endif
return frIsWeaponFound(weapon);
}
u32 frGetWeaponIndexByWeapon(u32 weaponnum)
{
switch (weaponnum) {
case WEAPON_FALCON2: return 0;
case WEAPON_FALCON2_SCOPE: return 1;
case WEAPON_FALCON2_SILENCER: return 2;
case WEAPON_MAGSEC4: return 3;
case WEAPON_MAULER: return 4;
case WEAPON_PHOENIX: return 5;
case WEAPON_DY357MAGNUM: return 6;
case WEAPON_DY357LX: return 7;
case WEAPON_CMP150: return 8;
case WEAPON_CYCLONE: return 9;
case WEAPON_CALLISTO: return 10;
case WEAPON_RCP120: return 11;
case WEAPON_LAPTOPGUN: return 12;
case WEAPON_DRAGON: return 13;
case WEAPON_K7AVENGER: return 14;
case WEAPON_AR34: return 15;
case WEAPON_SUPERDRAGON: return 16;
case WEAPON_SHOTGUN: return 17;
case WEAPON_SNIPERRIFLE: return 18;
case WEAPON_FARSIGHT: return 19;
case WEAPON_CROSSBOW: return 20;
case WEAPON_TRANQUILIZER: return 21;
case WEAPON_REAPER: return 22;
case WEAPON_DEVASTATOR: return 23;
case WEAPON_ROCKETLAUNCHER: return 24;
case WEAPON_SLAYER: return 25;
case WEAPON_COMBATKNIFE: return 26;
case WEAPON_LASER: return 27;
case WEAPON_GRENADE: return 28;
case WEAPON_TIMEDMINE: return 29;
case WEAPON_PROXIMITYMINE: return 30;
case WEAPON_REMOTEMINE: return 31;
}
return 0;
}
u32 frGetWeaponScriptIndex(u32 weaponnum)
{
switch (weaponnum) {
case WEAPON_FALCON2: return 1;
case WEAPON_FALCON2_SCOPE: return 2;
case WEAPON_FALCON2_SILENCER: return 3;
case WEAPON_MAGSEC4: return 4;
case WEAPON_MAULER: return 5;
case WEAPON_PHOENIX: return 6;
case WEAPON_DY357MAGNUM: return 7;
case WEAPON_DY357LX: return 8;
case WEAPON_CMP150: return 9;
case WEAPON_CYCLONE: return 10;
case WEAPON_CALLISTO: return 11;
case WEAPON_RCP120: return 12;
case WEAPON_LAPTOPGUN: return 13;
case WEAPON_DRAGON: return 14;
case WEAPON_K7AVENGER: return 15;
case WEAPON_AR34: return 16;
case WEAPON_SUPERDRAGON: return 17;
case WEAPON_SHOTGUN: return 18;
case WEAPON_SNIPERRIFLE: return 19;
case WEAPON_FARSIGHT: return 20;
case WEAPON_CROSSBOW: return 21;
case WEAPON_TRANQUILIZER: return 22;
case WEAPON_REAPER: return 23;
case WEAPON_DEVASTATOR: return 24;
case WEAPON_ROCKETLAUNCHER: return 25;
case WEAPON_SLAYER: return 26;
case WEAPON_COMBATKNIFE: return 27;
case WEAPON_LASER: return 28;
case WEAPON_GRENADE: return 29;
case WEAPON_TIMEDMINE: return 31;
case WEAPON_PROXIMITYMINE: return 32;
case WEAPON_REMOTEMINE: return 33;
}
return 0;
}
s32 frIsClassicWeaponUnlocked(u32 weapon)
{
switch (weapon) {
case WEAPON_PP9I:
return ciGetFiringRangeScore(0) == 3
&& ciGetFiringRangeScore(1) == 3
&& ciGetFiringRangeScore(2) == 3;
case WEAPON_CC13:
return ciGetFiringRangeScore(3) == 3
&& ciGetFiringRangeScore(4) == 3
&& ciGetFiringRangeScore(5) == 3
&& ciGetFiringRangeScore(6) == 3
&& ciGetFiringRangeScore(7) == 3;
case WEAPON_KL01313:
return ciGetFiringRangeScore(8) == 3
&& ciGetFiringRangeScore(9) == 3
&& ciGetFiringRangeScore(10) == 3
&& ciGetFiringRangeScore(11) == 3;
case WEAPON_KF7SPECIAL:
return ciGetFiringRangeScore(12) == 3
&& ciGetFiringRangeScore(13) == 3
&& ciGetFiringRangeScore(14) == 3
&& ciGetFiringRangeScore(15) == 3
&& ciGetFiringRangeScore(16) == 3;
case WEAPON_ZZT:
return ciGetFiringRangeScore(17) == 3
&& ciGetFiringRangeScore(18) == 3
&& ciGetFiringRangeScore(24) == 3
&& ciGetFiringRangeScore(25) == 3;
case WEAPON_DMC:
#if VERSION >= VERSION_NTSC_1_0
return ciGetFiringRangeScore(29) == 3
&& ciGetFiringRangeScore(30) == 3
&& ciGetFiringRangeScore(31) == 3;
#else
return ciGetFiringRangeScore(29) == 3
&& ciGetFiringRangeScore(30) == 3
&& ciGetFiringRangeScore(32) == 3
&& ciGetFiringRangeScore(33) == 3
&& ciGetFiringRangeScore(34) == 3;
#endif
case WEAPON_AR53:
return ciGetFiringRangeScore(19) == 3
&& ciGetFiringRangeScore(20) == 3
&& ciGetFiringRangeScore(26) == 3
&& ciGetFiringRangeScore(28) == 3;
case WEAPON_RCP45:
return ciGetFiringRangeScore(21) == 3
&& ciGetFiringRangeScore(22) == 3
&& ciGetFiringRangeScore(23) == 3;
}
return false;
}
s32 frGetSlot(void)
{
return g_FrData.slot;
}
void frSetSlot(s32 slot)
{
g_FrData.slot = slot;
}
u32 frGetWeaponBySlot(s32 slot)
{
s32 index = -1;
s32 weapon;
for (weapon = WEAPON_NONE; weapon <= WEAPON_HORIZONSCANNER; weapon++) {
if (frIsWeaponAvailable(weapon)) {
index++;
}
if (slot == index) {
return weapon;
}
}
return WEAPON_UNARMED;
}
s32 frGetNumWeaponsAvailable(void)
{
s32 count = 0;
s32 i;
for (i = WEAPON_UNARMED; i <= WEAPON_HORIZONSCANNER; i++) {
if (frIsWeaponAvailable(i)) {
count++;
}
}
return count;
}
void frInitLighting(void)
{
if (g_FrData.donelighting == false) {
s32 roomnum;
for (roomnum = 7; roomnum < 10; roomnum++) {
roomSetLighting(roomnum, LIGHTOP_3, 50, 100, TICKS(32));
}
roomSetLighting(ROOM_DISH_FIRINGRANGE, LIGHTOP_3, 25, 100, TICKS(32));
g_FrData.donelighting = true;
sndStart(var80095200, SFX_FR_LIGHTSON, NULL, -1, -1, -1, -1, -1);
}
chrSetStageFlag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
void frRestoreLighting(void)
{
if (g_FrData.donelighting == true) {
s32 roomnum;
for (roomnum = 7; roomnum < 10; roomnum++) {
roomSetLighting(roomnum, LIGHTOP_3, 100, 50, TICKS(8));
}
roomSetLighting(ROOM_DISH_FIRINGRANGE, LIGHTOP_3, 100, 25, TICKS(8));
g_FrData.donelighting = false;
sndStart(var80095200, SFX_FR_LIGHTSOFF, NULL, -1, -1, -1, -1, -1);
}
}
void frReset(void)
{
s32 i;
#if VERSION >= VERSION_NTSC_1_0
g_FrScriptOffsets = NULL;
#endif
g_FrDataLoaded = false;
g_FrIsValidWeapon = false;
g_FrRomData = NULL;
g_FrData.helpscriptindex = 0;
g_FrData.helpscriptoffset = 0;
g_FrData.helpscriptenabled = false;
g_FrData.helpscriptsleep = 0;
#if VERSION >= VERSION_NTSC_1_0
g_FrData.menucountdown = 0;
g_FrData.maxactivetargets = 0;
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
g_FrData.targets[i].prop = NULL;
g_FrData.targets[i].inuse = false;
}
g_FrNumSounds = 0;
#endif
}
void *frLoadRomData(u32 len)
{
g_FrRomData = mempAlloc(ALIGN16(len), MEMPOOL_STAGE);
if (g_FrRomData) {
return dmaExecWithAutoAlign(g_FrRomData, (u32)&_firingrangeSegmentRomStart, len);
}
return NULL;
}
void frSetDifficulty(s32 difficulty)
{
if (difficulty < FRDIFFICULTY_BRONZE) {
difficulty = FRDIFFICULTY_BRONZE;
}
if (difficulty > FRDIFFICULTY_GOLD) {
difficulty = FRDIFFICULTY_GOLD;
}
g_FrData.difficulty = difficulty;
}
u32 frGetDifficulty(void)
{
return g_FrData.difficulty;
}
void frInitDefaults(void)
{
s32 i;
struct pad pad;
g_FrNumSounds = 0;
padUnpack(g_FrPads[0], PADFIELD_POS, &pad);
g_FrData.maxactivetargets = 0;
g_FrData.goalscore = 0;
g_FrData.timelimit = 200;
g_FrData.ammolimit = 255;
g_FrData.sdgrenadelimit = 255;
g_FrData.goalaccuracy = 0;
g_FrData.goaltargets = 255;
g_FrData.speed = 1;
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
g_FrData.targets[i].dstpos.x = pad.pos.x;
g_FrData.targets[i].dstpos.y = pad.pos.y;
g_FrData.targets[i].dstpos.z = pad.pos.z;
#if VERSION >= VERSION_NTSC_1_0
g_FrData.targets[i].dstpos.z += 6.0f * i;
#endif
g_FrData.targets[i].inuse = false;
g_FrData.targets[i].rotateoncloak = false;
g_FrData.targets[i].destroyed = false;
g_FrData.targets[i].damage = 0;
g_FrData.targets[i].scriptoffset = 0;
g_FrData.targets[i].travelspeed = 0;
g_FrData.targets[i].scriptsleep = SECSTOTIME60(255);
g_FrData.targets[i].timeuntilrotate = 0;
g_FrData.targets[i].rotating = false;
g_FrData.targets[i].rotatespeed = 0;
g_FrData.targets[i].angle = 0;
g_FrData.targets[i].rotatetoangle = 0;
g_FrData.targets[i].silent = 0;
g_FrData.targets[i].donestopsound = false;
g_FrData.targets[i].travelling = false;
g_FrData.targets[i].invincibletimer = 0;
g_FrData.targets[i].frpadnum = -1;
wallhitsFreeByProp(g_FrData.targets[i].prop, 0);
wallhitsFreeByProp(g_FrData.targets[i].prop, 1);
}
g_FrData.timetaken = TICKS(-240);
g_FrData.score = 0;
g_FrData.numtargets = 0;
g_FrData.targetsdestroyed = 0;
g_FrData.menucountdown = 0;
g_FrData.padindexoffset = 0;
g_FrData.feedbackzone = 0;
g_FrData.feedbackttl = 0;
g_FrData.failreason = 0;
g_FrData.numshots = 0;
g_FrData.numhitsring3 = 0;
g_FrData.numhitsring2 = 0;
g_FrData.numhitsring1 = 0;
g_FrData.numhitsbullseye = 0;
g_FrData.helpscriptindex = 0;
g_FrData.helpscriptoffset = 0;
g_FrData.helpscriptenabled = false;
g_FrData.helpscriptsleep = 0;
g_FrData.proxyendtimer = 0;
g_FrData.donealarm = false;
g_FrData.ammohasgrace = true;
g_FrData.ammoextra = -1;
}
struct frdata *frGetData(void)
{
return &g_FrData;
}
u32 frResolveFrPad(u32 arg0)
{
switch (arg0) {
case 31: return random() % 9 + 4; // 4 - 12
case 32: return random() % 9 + 13; // 13 - 21
case 33: return random() % 9 + 22; // 22 - 30
case 34: return random() % 27 + 4; // 4 - 30
}
return g_FrData.padindexoffset + arg0;
}
bool frIsDifficulty(u32 flags)
{
if (g_FrData.difficulty == FRDIFFICULTY_BRONZE) {
if ((flags & FRTARGETFLAG_BRONZE) == 0) {
return false;
}
} else if (g_FrData.difficulty == FRDIFFICULTY_SILVER) {
if ((flags & FRTARGETFLAG_SILVER) == 0) {
return false;
}
} else if (g_FrData.difficulty == FRDIFFICULTY_GOLD) {
if ((flags & FRTARGETFLAG_GOLD) == 0) {
return false;
}
}
return true;
}
void frExecuteWeaponScript(s32 scriptindex)
{
s32 offset = 0;
if (scriptindex >= FRSCRIPTINDEX_WEAPONS && scriptindex < FRSCRIPTINDEX_TARGETS) {
u8 *script = &g_FrRomData[g_FrScriptOffsets[scriptindex]];
u8 mult = 1;
u32 stack[5];
s32 start;
s32 capacity;
s32 index;
u8 *subscript;
s32 i;
s32 val;
while (script[offset] != FRCMD_END) {
switch (script[offset]) {
case FRCMD_SETPADINDEXOFFSET:
g_FrData.padindexoffset = script[offset + 1];
offset += 2;
break;
case FRCMD_ADDTARGET:
if (!frIsDifficulty(script[offset + 4])) {
offset += 5;
break;
}
if (g_FrData.numtargets < ARRAYCOUNT(g_FrData.targets)) {
g_FrData.targets[g_FrData.numtargets].frpadindex = frResolveFrPad(script[offset + 1]);
g_FrData.targets[g_FrData.numtargets].scriptindex = script[offset + 2];
g_FrData.targets[g_FrData.numtargets].maxdamage = script[offset + 3];
g_FrData.targets[g_FrData.numtargets].inuse = true;
g_FrData.targets[g_FrData.numtargets].flags = script[offset + 4];
if (g_FrData.targets[g_FrData.numtargets].flags & FRTARGETFLAG_ROTATEONCLOAK) {
g_FrData.targets[g_FrData.numtargets].rotateoncloak = true;
}
if (g_FrData.targets[g_FrData.numtargets].flags & FRTARGETFLAG_ONEHITEXPLODE) {
g_FrData.targets[g_FrData.numtargets].maxdamage = 1;
}
g_FrData.numtargets++;
}
offset += 5;
break;
case FRCMD_SETMAXACTIVETARGETS:
g_FrData.maxactivetargets = script[g_FrData.difficulty + offset + 1];
offset += 4;
break;
case FRCMD_SETSCOREMULTIPLIER:
if (script[g_FrData.difficulty + offset + 1] > 0) {
mult = script[g_FrData.difficulty + offset + 1];
} else {
mult = 1;
}
offset += 4;
break;
case FRCMD_SETGOALSCORE:
g_FrData.goalscore = script[g_FrData.difficulty + offset + 1] * mult;
offset += 4;
break;
case FRCMD_SETTIMELIMIT:
g_FrData.timelimit = script[g_FrData.difficulty + offset + 1];
if (g_FrData.timelimit == 255) {
g_FrData.timelimit = 120;
}
offset += 4;
break;
case FRCMD_SETAMMOLIMIT:
capacity = bgunGetCapacityByAmmotype(bgunGetAmmoTypeForWeapon(frGetWeaponBySlot(g_FrData.slot), 0));
g_FrData.ammolimit = script[g_FrData.difficulty + offset + 1];
if (g_FrData.ammolimit != 255) {
if (g_FrData.ammolimit > capacity) {
g_FrData.ammoextra = g_FrData.ammolimit - capacity;
} else {
g_FrData.ammoextra = 0;
}
}
offset += 4;
break;
case FRCMD_SETGRENADELIMIT:
capacity = bgunGetCapacityByAmmotype(AMMOTYPE_DEVASTATOR);
g_FrData.sdgrenadelimit = script[g_FrData.difficulty + offset + 1];
if (g_FrData.sdgrenadelimit != 255) {
if (g_FrData.sdgrenadelimit > capacity) {
g_FrData.sdgrenadeextra = g_FrData.sdgrenadelimit - capacity;
} else {
g_FrData.sdgrenadeextra = 0;
}
}
offset += 4;
break;
case FRCMD_SETEXTRASPEED:
g_FrData.speed = script[g_FrData.difficulty + offset + 1] * 0.1f + 1.0f;
offset += 4;
break;
case FRCMD_SETGOALACCURACY:
g_FrData.goalaccuracy = script[g_FrData.difficulty + offset + 1];
offset += 4;
break;
case FRCMD_SETGOALTARGETS:
g_FrData.goaltargets = script[g_FrData.difficulty + offset + 1];
offset += 4;
break;
case FRCMD_SETHELPSCRIPT:
g_FrData.helpscriptindex = script[offset + 1];
g_FrData.helpscriptenabled = true;
index = FRSCRIPTINDEX_HELP + g_FrData.helpscriptindex;
if (&g_FrRomData[g_FrScriptOffsets[index]]);
subscript = &g_FrRomData[g_FrScriptOffsets[index]];
offset += 2;
if (g_FrData.difficulty == FRDIFFICULTY_BRONZE) {
start = FRCMD_IFBRONZE;
} else if (g_FrData.difficulty == FRDIFFICULTY_SILVER) {
start = FRCMD_IFSILVER;
} else if (g_FrData.difficulty == FRDIFFICULTY_GOLD) {
start = FRCMD_IFGOLD;
}
g_FrData.helpscriptoffset = 0;
i = 0;
while (1) {
g_FrData.helpscriptoffset++;
if (subscript[i] == start) {
i++;
val = subscript[i];
if (val >= FRCMD_IFBRONZE) {
g_FrData.helpscriptoffset++;
val = subscript[i + 1];
}
if (val >= FRCMD_IFBRONZE) {
g_FrData.helpscriptoffset++;
}
break;
}
i++;
}
break;
}
}
}
}
void frSetTargetProps(void)
{
s32 i;
u32 targets[] = {
0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x11,
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
};
for (i = 0; i < ARRAYCOUNT(targets); i++) {
struct defaultobj *obj = objFindByTagId(targets[i]);
if (obj) {
g_FrData.targets[i].prop = obj->prop;
obj->flags2 |= OBJFLAG2_INVISIBLE;
}
}
}
s32 g_FrWeaponNum = WEAPON_UNARMED;
bool frTargetIsAtScriptStart(s32 targetnum)
{
return g_FrData.targets[targetnum].scriptoffset == 0;
}
/**
* 0 => "FIRING\n Press Z Button to fire gun.\n"
* 1 => "AUTO RELOAD\n Release Z Button when out of ammo.\n"
* 2 => "MANUAL RELOAD\n Press B Button to reload early if magazine not full.\n"
* 3 => "Aiming: Hold down R Button to enter Aim mode.\n"
* 4 => "Use Control Stick to move aiming sight.\n"
* 5 => "AUTO FIRE\n Hold Z Button to repeatedly fire automatically.\n"
* 6 => "ALTER AIM\n Press Up C Button or Down C Button to move sight up/down.\n"
* 7 => "ZOOM\n Hold R Button to enter Zoom mode.\n"
* 8 => "FAST FIRE\n Press Z Button quickly to fire faster.\n"
*/
char *frGetInstructionalText(u32 index)
{
u16 textid = (u16)(g_FrRomData[index * 2] << 8) | g_FrRomData[index * 2 + 1];
return langGet(textid);
}
void frExecuteHelpScript(void)
{
if (!g_FrData.helpscriptenabled || g_Vars.lvupdate240 == 0) {
return;
}
if (g_FrData.helpscriptsleep == 0) {
s32 index = FRSCRIPTINDEX_HELP + g_FrData.helpscriptindex;
u8 *script = &g_FrRomData[g_FrScriptOffsets[index]];
u32 offset = g_FrData.helpscriptoffset;
switch (script[offset]) {
case FRCMD_END:
case FRCMD_IFBRONZE:
case FRCMD_IFSILVER:
case FRCMD_IFGOLD:
g_FrData.helpscriptenabled = false;
break;
case FRCMD_HUDMSG:
hudmsgCreate(frGetInstructionalText(script[offset + 1]), HUDMSGTYPE_TRAINING);
g_FrData.helpscriptoffset += 2;
break;
case FRCMD_HELPWAITSECONDS:
#if PAL
g_FrData.helpscriptsleep = script[offset + 1] * 50;
#else
g_FrData.helpscriptsleep = SECSTOTIME60(script[offset + 1]);
#endif
g_FrData.helpscriptoffset += 2;
break;
case FRCMD_WAITUNTILSHOOT:
if (g_FrData.numshots) {
g_FrData.helpscriptoffset++;
}
break;
}
} else {
g_FrData.helpscriptsleep -= g_Vars.lvupdate240_60;
if (g_FrData.helpscriptsleep <= 0) {
g_FrData.helpscriptsleep = 0;
}
}
}
bool frExecuteTargetScript(s32 targetnum)
{
if (g_FrData.targets[targetnum].inuse) {
s32 index = FRSCRIPTINDEX_TARGETS + g_FrData.targets[targetnum].scriptindex;
u8 *script = &g_FrRomData[g_FrScriptOffsets[index]];
s32 offset = g_FrData.targets[targetnum].scriptoffset;
struct pad pad;
s32 frpadnum;
switch (script[offset]) {
case FRCMD_END:
g_FrData.targets[targetnum].scriptenabled = true;
g_FrData.targets[targetnum].scriptsleep = 255 * 60;
return true;
case FRCMD_GOTOPAD:
frpadnum = frResolveFrPad(script[offset + 1]);
if (frpadnum == g_FrData.targets[targetnum].frpadnum) {
g_FrData.targets[targetnum].scriptoffset += 4;
return false;
}
g_FrData.targets[targetnum].frpadnum = frpadnum;
padUnpack(g_FrPads[frpadnum], PADFIELD_POS, &pad);
g_FrData.targets[targetnum].dstpos.x = pad.pos.x;
g_FrData.targets[targetnum].dstpos.y = pad.pos.y;
g_FrData.targets[targetnum].dstpos.z = pad.pos.z;
#if VERSION >= VERSION_NTSC_1_0
g_FrData.targets[targetnum].dstpos.z += 6.0f * targetnum;
#endif
if (script[offset + 2] == 0xff) {
g_FrData.targets[targetnum].travelspeed = -1;
g_FrData.targets[targetnum].travelling = true;
} else {
if (g_FrNumSounds < 3) {
g_FrNumSounds++;
propsnd0f0939f8(NULL, g_FrData.targets[targetnum].prop, SFX_FR_CONVEYER, -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
}
g_FrData.targets[targetnum].travelspeed = script[offset + 2] / 60.0f * g_FrData.speed;
g_FrData.targets[targetnum].travelling = true;
}
g_FrData.targets[targetnum].scriptsleep = script[offset + 3] * TICKS(60);
g_FrData.targets[targetnum].donestopsound = false;
g_FrData.targets[targetnum].scriptoffset += 4;
return true;
case FRCMD_RESTART:
g_FrData.targets[targetnum].scriptoffset = 0;
return true;
case FRCMD_WAITSECONDS:
g_FrData.targets[targetnum].scriptenabled = true;
g_FrData.targets[targetnum].scriptsleep = script[offset + 1] * TICKS(60);
g_FrData.targets[targetnum].scriptoffset += 2;
return true;
case FRCMD_ROTATE:
if (g_FrData.targets[targetnum].rotateoncloak == false) {
f32 angles[4];
angles[0] = DEG2RAD(-90);
angles[1] = DEG2RAD(-180);
angles[2] = DEG2RAD(90);
angles[3] = DEG2RAD(180);
g_FrData.targets[targetnum].rotatetoangle = g_FrData.targets[targetnum].angle + angles[script[offset + 1]];
g_FrData.targets[targetnum].rotatespeed = angles[script[offset + 1]] / (script[offset + 2] * 15);
g_FrData.targets[targetnum].rotating = true;
g_FrData.targets[targetnum].scriptenabled = false;
}
if (1);
g_FrData.targets[targetnum].scriptoffset += 3;
return true;
}
}
return true;
}
void frHideAllTargets(void)
{
s32 i;
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
struct prop *prop = g_FrData.targets[i].prop;
struct defaultobj *target = prop->obj;
target->flags2 |= OBJFLAG2_INVISIBLE;
func0f0926bc(prop, 1, 0xffff);
}
}
void frInitTargets(void)
{
s32 count = 0;
s32 i;
struct prop *prop;
struct defaultobj *obj;
struct pad pad;
struct coord pos;
Mtxf sp144;
f32 sp108[3][3];
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
prop = g_FrData.targets[i].prop;
#if VERSION >= VERSION_NTSC_1_0
if (prop)
#endif
{
obj = prop->obj;
objFree(obj, false, true);
obj->damage = 0;
prop->timetoregen = 0;
if (g_FrData.targets[i].inuse) {
g_FrData.targets[i].scriptenabled = false;
g_FrData.targets[i].destroyed = false;
if (count < g_FrData.maxactivetargets) {
obj->flags2 &= ~OBJFLAG2_INVISIBLE;
g_FrData.targets[i].active = true;
} else {
obj->flags2 |= OBJFLAG2_INVISIBLE;
g_FrData.targets[i].active = false;
}
padUnpack(g_FrPads[g_FrData.targets[i].frpadindex], PADFIELD_POS, &pad);
pos.f[0] = pad.pos.f[0];
pos.f[1] = pad.pos.f[1];
#if VERSION >= VERSION_NTSC_1_0
pos.f[2] = pad.pos.f[2] + 6.0f * i;
#else
pos.f[2] = pad.pos.f[2];
#endif
frExecuteTargetScript(i);
if (g_FrData.targets[i].travelspeed == -1) {
pos.x = g_FrData.targets[i].dstpos.x;
pos.y = g_FrData.targets[i].dstpos.y;
pos.z = g_FrData.targets[i].dstpos.z;
}
count++;
} else {
obj->flags2 |= OBJFLAG2_INVISIBLE;
}
if (obj->flags2 & OBJFLAG2_INVISIBLE) {
#if VERSION < VERSION_NTSC_1_0
padUnpack(g_FrPads[g_FrData.targets[i].frpadindex], PADFIELD_POS, &pad);
pos.x = 0.0f;
pos.y = 5000.0f;
pos.z = 0.0f;
#else
pos.x = 0.0f;
pos.y = 5000.0f;
pos.z = 6.0f * i;
#endif
}
if (g_FrData.targets[i].flags & FRTARGETFLAG_SPAWNFACINGAWAY) {
mtx4LoadYRotation(0.0f, &sp144);
g_FrData.targets[i].angle = M_PI;
} else {
mtx4LoadYRotation(M_PI, &sp144);
}
mtx00015f04(obj->model->scale, &sp144);
mtx4ToMtx3(&sp144, sp108);
mtx3Copy(sp108, obj->realrot);
prop->pos.x = pos.x;
prop->pos.y = pos.y;
prop->pos.z = pos.z;
func0f069c70(obj, true, false);
}
}
}
void frCloseAndLockDoor(void)
{
struct defaultobj *obj = objFindByTagId(0x91);
if (obj && obj->prop && obj->prop->type == PROPTYPE_DOOR) {
struct doorobj *door = (struct doorobj *)obj;
door->keyflags |= 0x40;
doorsRequestMode(door, DOORMODE_CLOSING);
}
}
void frUnlockDoor(void)
{
struct defaultobj *obj = objFindByTagId(0x91);
if (obj && obj->prop && obj->prop->type == PROPTYPE_DOOR) {
struct doorobj *door = (struct doorobj *)obj;
door->keyflags &= ~0x40;
}
}
void frLoadData(void)
{
if (!g_FrDataLoaded) {
s32 len = (s32)&_firingrangeSegmentRomEnd - (s32)&_firingrangeSegmentRomStart;
s32 index = 0;
u32 i;
u32 numscripts = 1;
s32 size;
if (index);
g_FrDataLoaded = true;
frLoadRomData(len);
for (i = 0x12; i < len; i++) {
if (g_FrRomData[i] == FRCMD_START) {
numscripts++;
}
}
size = numscripts * sizeof(*g_FrScriptOffsets);
g_FrScriptOffsets = mempAlloc(ALIGN16(size), MEMPOOL_STAGE);
if (numscripts < 0);
if (g_FrScriptOffsets) {
for (i = 0x12; i < len; i++) {
if (g_FrRomData[i] == FRCMD_START) {
g_FrScriptOffsets[index] = i + 1;
index++;
}
}
}
frSetTargetProps();
g_FrData.slot = 0;
g_FrData.difficulty = FRDIFFICULTY_BRONZE;
g_FrData.donelighting = false;
}
}
u32 frInitAmmo(s32 weaponnum)
{
u32 scriptindex;
u32 ammotype = bgunGetAmmoTypeForWeapon(weaponnum, 0);
u32 capacity = bgunGetCapacityByAmmotype(ammotype);
frInitDefaults();
scriptindex = frGetWeaponScriptIndex(weaponnum);
frExecuteWeaponScript(scriptindex);
if (g_FrData.ammolimit == 255) {
bgunSetAmmoQuantity(ammotype, capacity);
} else {
bgunSetAmmoQuantity(ammotype, g_FrData.ammolimit);
}
if (weaponnum == WEAPON_SUPERDRAGON) {
if (g_FrData.sdgrenadelimit == 255) {
bgunSetAmmoQuantity(AMMOTYPE_DEVASTATOR, capacity);
} else {
bgunSetAmmoQuantity(AMMOTYPE_DEVASTATOR, g_FrData.sdgrenadelimit);
}
}
return scriptindex;
}
void frBeginSession(s32 weapon)
{
s32 i;
struct defaultobj *obj = objFindByTagId(0x7f); // computer
if (obj) {
obj->flags |= OBJFLAG_CANNOT_ACTIVATE;
}
frCloseAndLockDoor();
for (i = 0; i < 2; i++) {
if (g_Vars.currentplayer->gunctrl.ammotypes[i] >= 0) {
g_Vars.currentplayer->hands[0].loadedammo[i] = 0;
g_Vars.currentplayer->hands[1].loadedammo[i] = 0;
}
}
g_FrIsValidWeapon = frInitAmmo(weapon) == 0 ? false : true;
frInitTargets();
bgunSetPassiveMode(false);
}
char *frGetWeaponDescription(void)
{
u32 weapon = frGetWeaponBySlot(g_FrData.slot);
switch (weapon) {
#if VERSION >= VERSION_PAL_BETA
case WEAPON_FALCON2: return langGet(L_DISH_283);
case WEAPON_FALCON2_SCOPE: return langGet(L_DISH_284);
case WEAPON_FALCON2_SILENCER: return langGet(L_DISH_285);
case WEAPON_MAGSEC4: return langGet(L_DISH_286);
case WEAPON_MAULER: return langGet(L_DISH_287);
case WEAPON_PHOENIX: return langGet(L_DISH_288);
case WEAPON_DY357MAGNUM: return langGet(L_DISH_289);
case WEAPON_DY357LX: return langGet(L_DISH_290);
case WEAPON_CMP150: return langGet(L_DISH_291);
case WEAPON_CYCLONE: return langGet(L_DISH_292);
case WEAPON_CALLISTO: return langGet(L_DISH_293);
case WEAPON_RCP120: return langGet(L_DISH_294);
case WEAPON_LAPTOPGUN: return langGet(L_DISH_295);
case WEAPON_DRAGON: return langGet(L_DISH_296);
case WEAPON_K7AVENGER: return langGet(L_DISH_297);
case WEAPON_AR34: return langGet(L_DISH_298);
case WEAPON_SUPERDRAGON: return langGet(L_DISH_299);
case WEAPON_SHOTGUN: return langGet(L_DISH_300);
case WEAPON_SNIPERRIFLE: return langGet(L_DISH_301);
case WEAPON_FARSIGHT: return langGet(L_DISH_302);
case WEAPON_CROSSBOW: return langGet(L_DISH_303);
case WEAPON_TRANQUILIZER: return langGet(L_DISH_304);
case WEAPON_REAPER: return langGet(L_DISH_305);
case WEAPON_DEVASTATOR: return langGet(L_DISH_306);
case WEAPON_ROCKETLAUNCHER: return langGet(L_DISH_307);
case WEAPON_SLAYER: return langGet(L_DISH_308);
case WEAPON_COMBATKNIFE: return langGet(L_DISH_309);
case WEAPON_LASER: return langGet(L_DISH_310);
case WEAPON_GRENADE: return langGet(L_DISH_311);
case WEAPON_NBOMB: return langGet(L_DISH_312);
case WEAPON_TIMEDMINE: return langGet(L_DISH_313);
case WEAPON_PROXIMITYMINE: return langGet(L_DISH_314);
case WEAPON_REMOTEMINE: return langGet(L_DISH_315);
#else
case WEAPON_FALCON2: return langGet(L_MISC_377);
case WEAPON_FALCON2_SCOPE: return langGet(L_MISC_378);
case WEAPON_FALCON2_SILENCER: return langGet(L_MISC_379);
case WEAPON_MAGSEC4: return langGet(L_MISC_380);
case WEAPON_MAULER: return langGet(L_MISC_381);
case WEAPON_PHOENIX: return langGet(L_MISC_382);
case WEAPON_DY357MAGNUM: return langGet(L_MISC_383);
case WEAPON_DY357LX: return langGet(L_MISC_384);
case WEAPON_CMP150: return langGet(L_MISC_385);
case WEAPON_CYCLONE: return langGet(L_MISC_386);
case WEAPON_CALLISTO: return langGet(L_MISC_387);
case WEAPON_RCP120: return langGet(L_MISC_388);
case WEAPON_LAPTOPGUN: return langGet(L_MISC_389);
case WEAPON_DRAGON: return langGet(L_MISC_390);
case WEAPON_K7AVENGER: return langGet(L_MISC_391);
case WEAPON_AR34: return langGet(L_MISC_392);
case WEAPON_SUPERDRAGON: return langGet(L_MISC_393);
case WEAPON_SHOTGUN: return langGet(L_MISC_394);
case WEAPON_SNIPERRIFLE: return langGet(L_MISC_395);
case WEAPON_FARSIGHT: return langGet(L_MISC_396);
case WEAPON_CROSSBOW: return langGet(L_MISC_397);
case WEAPON_TRANQUILIZER: return langGet(L_MISC_398);
case WEAPON_REAPER: return langGet(L_MISC_399);
case WEAPON_DEVASTATOR: return langGet(L_MISC_400);
case WEAPON_ROCKETLAUNCHER: return langGet(L_MISC_401);
case WEAPON_SLAYER: return langGet(L_MISC_402);
case WEAPON_COMBATKNIFE: return langGet(L_MISC_403);
case WEAPON_LASER: return langGet(L_MISC_404);
case WEAPON_GRENADE: return langGet(L_MISC_405);
case WEAPON_NBOMB: return langGet(L_MISC_406);
case WEAPON_TIMEDMINE: return langGet(L_MISC_407);
case WEAPON_PROXIMITYMINE: return langGet(L_MISC_408);
case WEAPON_REMOTEMINE: return langGet(L_MISC_409);
#endif
}
return NULL;
}
void frEndSession(bool hidetargets)
{
s32 i;
s32 j;
s16 propnums[256];
s16 *propnumptr;
s16 rooms[20];
u32 stack1;
s16 rooms2[10];
u32 stack2;
if (g_FrDataLoaded) {
struct defaultobj *terminal = objFindByTagId(0x7f);
if (terminal) {
terminal->flags &= ~OBJFLAG_CANNOT_ACTIVATE;
}
frUnlockDoor();
if (g_Vars.currentplayer->visionmode == VISIONMODE_SLAYERROCKET) {
g_Vars.currentplayer->visionmode = VISIONMODE_NORMAL;
}
bgunSetPassiveMode(true);
g_FrIsValidWeapon = 0;
frRestoreLighting();
if (hidetargets) {
frHideAllTargets();
}
if (g_ThrownLaptops[0].base.prop) {
objFreePermanently(&g_ThrownLaptops[0].base, true);
}
roomsCopy(g_Vars.currentplayer->prop->rooms, rooms);
for (i = 0; g_Vars.currentplayer->prop->rooms[i] != -1; i++) {
roomGetNeighbours(g_Vars.currentplayer->prop->rooms[i], rooms2, 10);
roomsAppend(rooms2, rooms, 20);
}
// Remove projectiles and throwables
roomGetProps(rooms, propnums, 256);
propnumptr = propnums;
while (*propnumptr >= 0) {
struct prop *prop = &g_Vars.props[*propnumptr];
if (prop) {
struct defaultobj *obj = prop->obj;
if (prop->type == PROPTYPE_WEAPON) {
if (obj->type == OBJTYPE_AUTOGUN) {
objFreePermanently(obj, true);
}
if (obj->type == OBJTYPE_WEAPON) {
struct weaponobj *weapon = (struct weaponobj *)obj;
if (weapon->weaponnum == WEAPON_NBOMB
|| weapon->weaponnum == WEAPON_BOLT
|| weapon->weaponnum == WEAPON_COMBATKNIFE
|| weapon->weaponnum == WEAPON_HOMINGROCKET
|| weapon->weaponnum == WEAPON_GRENADE
|| weapon->weaponnum == WEAPON_GRENADEROUND
|| weapon->weaponnum == WEAPON_PROXIMITYMINE
|| weapon->weaponnum == WEAPON_REMOTEMINE
|| weapon->weaponnum == WEAPON_ROCKET
|| weapon->weaponnum == WEAPON_TIMEDMINE
|| weapon->weaponnum == WEAPON_SKROCKET
|| (weapon->weaponnum == WEAPON_DRAGON && weapon->gunfunc == FUNC_SECONDARY)
|| (weapon->weaponnum == WEAPON_LAPTOPGUN && weapon->gunfunc == FUNC_SECONDARY)) {
objFreePermanently(obj, true);
}
}
}
}
propnumptr++;
}
// Remove explosions
for (i = 0; i < g_MaxExplosions; i++) {
g_Explosions[i].age = 256;
for (j = 0; j < 40; j++) {
g_Explosions[i].parts[j].frame = 0;
}
}
// Remove smoke
for (i = 0; i < g_MaxSmokes; i++) {
g_Smokes[i].age = 256;
for (j = 0; j < 10; j++) {
g_Smokes[i].parts[j].size = 0;
}
}
}
playerDisplayHealth();
g_Vars.currentplayer->bondhealth = 1;
}
bool frWasTooInaccurate(void)
{
f32 sum = (g_FrData.numhitsring3 +
+ g_FrData.numhitsbullseye
+ g_FrData.numhitsring1
+ g_FrData.numhitsring2) * 100.0f;
if (g_FrData.numshots) {
f32 accuracy = sum / g_FrData.numshots;
if (accuracy < g_FrData.goalaccuracy) {
return true;
}
}
return false;
}
void frSetFailReason(s32 failreason)
{
frEndSession(false);
g_FrData.failreason = frWasTooInaccurate() ? FRFAILREASON_INACCURATE : failreason;
g_FrData.menutype = FRMENUTYPE_FAILED;
g_FrData.menucountdown = TICKS(60);
}
void frSetCompleted(void)
{
frEndSession(false);
if (frWasTooInaccurate()) {
g_FrData.failreason = FRFAILREASON_INACCURATE;
g_FrData.menutype = FRMENUTYPE_FAILED;
} else {
u32 frweaponindex = frGetWeaponIndexByWeapon(frGetWeaponBySlot(g_FrData.slot));
frSaveScoreIfBest(frweaponindex, g_FrData.difficulty + 1);
g_FrData.menutype = FRMENUTYPE_COMPLETED;
}
g_FrData.menucountdown = TICKS(60);
}
bool frIsTargetOneHitExplodable(struct prop *prop)
{
s32 i;
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse
&& g_FrData.targets[i].destroyed == false
&& g_FrData.targets[i].active
&& prop == g_FrData.targets[i].prop) {
if (g_FrData.targets[i].flags & FRTARGETFLAG_ONEHITEXPLODE) {
return true;
}
return false;
}
}
return false;
}
f32 frGetTargetAngleToPos(struct coord *targetpos, f32 targetangle, struct coord *pos)
{
f32 xdiff = targetpos->x - pos->x;
f32 zdiff = targetpos->z - pos->z;
f32 directangle = atan2f(xdiff, zdiff);
f32 relativeangle = directangle - targetangle;
if (directangle < targetangle) {
relativeangle += M_BADTAU;
}
return relativeangle;
}
bool frIsTargetFacingPos(struct prop *prop, struct coord *pos)
{
s32 i;
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (prop == g_FrData.targets[i].prop) {
f32 angle;
if (g_FrData.targets[i].destroyed) {
return false;
}
angle = frGetTargetAngleToPos(&prop->pos, g_FrData.targets[i].angle, pos);
//if (angle > DEG2RAD(90) && angle < DEG2RAD(270)) {
if (angle > 1.5707963705063f && angle < 4.7116389274597f) {
return false;
}
return true;
}
}
return true;
}
struct prop *frChooseAutogunTarget(struct coord *autogunpos)
{
f32 closestdist = 0x20000000;
s32 facingtargets[ARRAYCOUNT(g_FrData.targets)];
s32 len = 0;
struct prop *closesttarget = NULL;
s32 i;
// Make list of targets which are facing the laptop gun
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse
&& g_FrData.targets[i].destroyed == false
&& g_FrData.targets[i].active) {
f32 angle = frGetTargetAngleToPos(&g_FrData.targets[i].prop->pos, g_FrData.targets[i].angle, autogunpos);
if (!(angle > 1.5707963705063f && angle < 4.7116389274597f)) {
facingtargets[len++] = i;
}
}
}
// Determine which of the facing targets is closest
for (i = 0; i < len; i++) {
struct prop *prop = g_FrData.targets[facingtargets[i]].prop;
f32 xdiff = prop->pos.f[0] - autogunpos->f[0];
f32 ydiff = prop->pos.f[1] - autogunpos->f[1];
f32 zdiff = prop->pos.f[2] - autogunpos->f[2];
f32 dist = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
if (dist < closestdist) {
closestdist = dist;
closesttarget = prop;
}
}
if (facingtargets);
return closesttarget;
}
bool frIsAmmoWasted(void)
{
s32 weaponnum = frGetWeaponBySlot(g_FrData.slot);
s32 i;
s32 priammotype = bgunGetAmmoTypeForWeapon(weaponnum, 0);
s32 secammotype = bgunGetAmmoTypeForWeapon(weaponnum, 1);
struct hand *hand0 = &g_Vars.currentplayer->hands[0];
struct hand *hand1 = &g_Vars.currentplayer->hands[1];
s32 ammoloaded[2];
s32 ammototal[2];
s16 *propnumptr;
s16 propnums[258];
s16 rooms20[22];
s16 rooms10[12];
u32 stack[4];
s32 ammotype;
struct hand *hand;
struct prop *prop;
struct prop *child;
// Laser has unlimited ammo
if (weaponnum == WEAPON_LASER) {
return false;
}
// Check if player has ammo
ammoloaded[0] = hand0->loadedammo[0] + hand1->loadedammo[0];
ammoloaded[1] = hand0->loadedammo[1] + hand1->loadedammo[1];
ammototal[0] = bgunGetReservedAmmoCount(priammotype) + ammoloaded[0];
ammototal[1] = bgunGetReservedAmmoCount(secammotype) + ammoloaded[1];
if (ammototal[0] <= 0 && ammototal[1] <= 0) {
// Don't do any further checks if this is the first frame where we've
// gotten this far. I'm guessing this fixes a frame perfect bug, however
// testing with this check removed doesn't cause any unusual behaviour.
if (g_FrData.ammohasgrace) {
g_FrData.ammohasgrace = false;
return false;
}
// Check if there are any explosions
for (i = 0; i != 6; i++) {
if (g_Explosions[i].prop) {
return false;
}
}
// Check projectiles
if (weaponnum == WEAPON_ROCKETLAUNCHER
|| weaponnum == WEAPON_SLAYER
|| weaponnum == WEAPON_DEVASTATOR
|| weaponnum == WEAPON_SUPERDRAGON
|| weaponnum == WEAPON_COMBATKNIFE
|| weaponnum == WEAPON_CROSSBOW
|| weaponnum == WEAPON_GRENADE
|| weaponnum == WEAPON_NBOMB
|| weaponnum == WEAPON_TIMEDMINE
|| weaponnum == WEAPON_PROXIMITYMINE
|| weaponnum == WEAPON_REMOTEMINE) {
roomsCopy(g_Vars.currentplayer->prop->rooms, rooms20);
for (i = 0; g_Vars.currentplayer->prop->rooms[i] != -1; i++) {
roomGetNeighbours(g_Vars.currentplayer->prop->rooms[i], rooms10, 10);
roomsAppend(rooms10, rooms20, 20);
}
roomGetProps(rooms20, propnums, 256);
propnumptr = propnums;
while (*propnumptr >= 0) {
prop = &g_Vars.props[*propnumptr];
child = prop->child;
if ((child && child->type == PROPTYPE_WEAPON && child->weapon->weaponnum == WEAPON_TIMEDMINE)
|| (child && child->type == PROPTYPE_WEAPON && child->weapon->weaponnum == WEAPON_REMOTEMINE)
|| (child && child->type == PROPTYPE_WEAPON && child->weapon->weaponnum == WEAPON_PROXIMITYMINE)
|| (child && child->type == PROPTYPE_WEAPON && child->weapon->weaponnum == WEAPON_GRENADEROUND)) {
return false;
}
if (prop->type == PROPTYPE_WEAPON) {
if (prop->weapon->weaponnum == WEAPON_ROCKET
|| prop->weapon->weaponnum == WEAPON_HOMINGROCKET
|| prop->weapon->weaponnum == WEAPON_GRENADE
|| prop->weapon->weaponnum == WEAPON_GRENADEROUND) {
return false;
}
if (prop->weapon->weaponnum == WEAPON_BOLT
|| prop->weapon->weaponnum == WEAPON_COMBATKNIFE) {
if (prop->obj->hidden & OBJHFLAG_AIRBORNE) {
return false;
}
} else if (prop->weapon->weaponnum == WEAPON_TIMEDMINE
|| prop->weapon->weaponnum == WEAPON_REMOTEMINE) {
return false;
} else if (prop->weapon->weaponnum == WEAPON_PROXIMITYMINE) {
if (g_FrData.proxyendtimer == -255) {
return false;
}
if (g_FrData.proxyendtimer == 0) {
// Initial state - set the timer to 5 seconds if player is now out of mines
ammotype = bgunGetAmmoTypeForWeapon(weaponnum, 0);
hand = &g_Vars.currentplayer->hands[HAND_RIGHT];
if (bgunGetReservedAmmoCount(ammotype) + hand->loadedammo[0] == 0) {
g_FrData.proxyendtimer = TICKS(300);
}
return false;
}
g_FrData.proxyendtimer -= g_Vars.lvupdate240_60;
if (g_FrData.proxyendtimer <= 0) {
// Timer has just hit zero - remove all proxy items
for (i = 0; i < ARRAYCOUNT(g_Proxies); i++) {
if (g_Proxies[i]) {
g_Proxies[i]->timer240 = 0;
}
}
g_FrData.proxyendtimer = -255;
return true;
}
return false;
}
}
propnumptr++;
}
}
return true;
}
return false;
}
void frTick(void)
{
s32 ammotype;
s32 capacity;
s32 weaponnum;
struct coord diff;
struct coord newpos;
u8 weaponnum2;
struct prop *prop;
struct defaultobj *obj;
struct defaultobj *obj2;
s32 invincible;
s32 i;
s32 j;
f32 dist;
u32 stack;
struct inventory_ammo *ammo;
u8 exploding;
bool oldside;
struct modelrodata_bbox *bbox;
s32 tmp;
#if VERSION >= VERSION_NTSC_1_0
f32 mult;
#endif
bool newside;
struct chrdata *chr;
bool cloaked;
f32 toangle;
f32 speed;
Mtxf spbc;
f32 sp98[3][3];
if (g_FrIsValidWeapon
&& g_Vars.currentplayer->gunctrl.unk1583_04 == 0
&& invHasSingleWeaponIncAllGuns(frGetWeaponBySlot(g_FrData.slot))) {
bgunEquipWeapon(frGetWeaponBySlot(g_FrData.slot));
}
// NTSC beta does the room code then menu code,
// while everything else does the menu code then room code
#if VERSION < VERSION_NTSC_1_0
// End the session if the player slipped through the door before it closed
if (g_Vars.currentplayer->prop->rooms[0] != ROOM_DISH_FIRINGRANGE) {
if (g_FrIsValidWeapon) {
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse
&& g_FrData.targets[i].destroyed == false
&& g_FrData.targets[i].silent == false
&& g_FrData.targets[i].travelling) {
g_FrData.targets[i].silent = true;
func0f0926bc(g_FrData.targets[i].prop, 1, 0xffff);
}
}
g_Vars.currentplayer->training = false;
frEndSession(true);
g_FrData.menucountdown = 0; // This assignment is in NTSC beta only
chrUnsetStageFlag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
return;
}
#endif
// Handle the menu countdown
if (g_FrData.menucountdown != 0) {
g_FrData.menucountdown -= g_Vars.lvupdate240_60;
// Prevent showing the menu until gun is put away
if (g_FrData.menucountdown <= 0) {
if ((g_FrData.menutype == FRMENUTYPE_FAILED || g_FrData.menutype == FRMENUTYPE_COMPLETED)
&& g_Vars.currentplayer->hands[HAND_RIGHT].gset.weaponnum != WEAPON_UNARMED) {
g_FrData.menucountdown = 1;
}
}
if (g_FrData.menucountdown <= 0) {
g_FrData.menucountdown = 0;
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].prop) {
func0f0926bc(g_FrData.targets[i].prop, 1, 0xffff);
}
}
switch (g_FrData.menutype) {
case FRMENUTYPE_WEAPONLIST:
func0f0f85e0(ciGetFrWeaponListMenuDialog(), MENUROOT_TRAINING);
break;
case FRMENUTYPE_DETAILS:
func0f0f85e0(&g_FrTrainingInfoPreGameMenuDialog, MENUROOT_TRAINING);
break;
case FRMENUTYPE_FAILED:
sndStart(var80095200, SFX_TRAINING_FAIL, NULL, -1, -1, -1, -1, -1);
func0f0f85e0(&g_FrFailedMenuDialog, MENUROOT_TRAINING);
break;
case FRMENUTYPE_COMPLETED:
sndStart(var80095200, SFX_TRAINING_COMPLETE, NULL, -1, -1, -1, -1, -1);
func0f0f85e0(&g_FrCompletedMenuDialog, MENUROOT_TRAINING);
filemgrSaveOrLoad(&g_GameFileGuid, FILEOP_SAVE_GAME_000, 0);
break;
}
}
return;
}
#if VERSION >= VERSION_NTSC_1_0
// End the session if the player slipped through the door before it closed
if (g_Vars.currentplayer->prop->rooms[0] != ROOM_DISH_FIRINGRANGE) {
if (g_FrIsValidWeapon) {
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse
&& g_FrData.targets[i].destroyed == false
&& g_FrData.targets[i].silent == false
&& g_FrData.targets[i].travelling) {
g_FrData.targets[i].silent = true;
func0f0926bc(g_FrData.targets[i].prop, 1, 0xffff);
}
}
g_Vars.currentplayer->training = false;
frEndSession(true);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
return;
}
#endif
if (!g_FrIsValidWeapon) {
return;
}
if (g_Vars.currentplayer->isdead) {
frEndSession(false);
}
// If paused, stop any target sounds
if (g_Vars.lvupdate240 == 0) {
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse
&& g_FrData.targets[i].destroyed == false
&& g_FrData.targets[i].silent == false
&& g_FrData.targets[i].travelling) {
g_FrData.targets[i].silent = true;
func0f0926bc(g_FrData.targets[i].prop, 1, 0xffff);
}
}
return;
}
g_Vars.currentplayer->training = true;
frExecuteHelpScript();
// Top up the player's ammo if the config defined more ammo than the
// weapon allows, or if it defined unlimited ammo
if (g_FrData.numshotssincetopup != 0) {
weaponnum = frGetWeaponBySlot(g_FrData.slot);
ammotype = bgunGetAmmoTypeForWeapon(weaponnum, 0);
capacity = bgunGetCapacityByAmmotype(ammotype);
ammo = weaponGetAmmoByFunction(weaponnum, 0);
capacity -= (ammo ? ammo->clipsize : 0);
if (g_FrData.ammoextra > 0) {
tmp = bgunGetReservedAmmoCount(ammotype);
g_FrData.ammoextra -= g_FrData.numshotssincetopup;
if (g_FrData.ammoextra < 0) {
g_FrData.ammoextra = 0;
}
capacity = tmp + g_FrData.numshotssincetopup;
bgunSetAmmoQuantity(ammotype, capacity);
} else if (g_FrData.ammoextra == -1) {
bgunSetAmmoQuantity(ammotype, capacity);
}
if (weaponnum == WEAPON_SUPERDRAGON) {
capacity = bgunGetCapacityByAmmotype(AMMOTYPE_DEVASTATOR);
if (g_FrData.sdgrenadeextra > 0) {
tmp = bgunGetReservedAmmoCount(AMMOTYPE_DEVASTATOR);
g_FrData.sdgrenadeextra -= g_FrData.numshotssincetopup;
if (g_FrData.sdgrenadeextra < 0) {
g_FrData.sdgrenadeextra = 0;
}
capacity = tmp + g_FrData.numshotssincetopup;
bgunSetAmmoQuantity(AMMOTYPE_DEVASTATOR, capacity);
} else if (g_FrData.sdgrenadeextra == -1) {
bgunSetAmmoQuantity(AMMOTYPE_DEVASTATOR, capacity);
}
}
g_FrData.numshotssincetopup = 0;
}
g_FrData.timetaken += g_Vars.lvupdate240_60;
// Handle prestart
if (g_FrData.timetaken < 0) {
if (g_FrData.numshots == 0) {
if (g_FrData.donealarm == false && g_FrData.timetaken > TICKS(-180)) {
g_FrData.donealarm = true;
sndStart(var80095200, SFX_FR_ALARM, NULL, -1, -1, -1, -1, -1);
}
if (!g_FrData.donelighting && g_FrData.timetaken > TICKS(-225)) {
frInitLighting();
}
return;
}
// Fired a shot during prestart
if (!g_FrData.donelighting) {
frInitLighting();
}
g_FrData.timetaken = 0;
g_FrData.donealarm = true;
}
// Iterate each target and handle their health active/inactive state
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse && g_FrData.targets[i].destroyed == false && g_FrData.targets[i].active) {
invincible = false;
exploding = false;
weaponnum2 = frGetWeaponBySlot(g_FrData.slot);
prop = g_FrData.targets[i].prop;
obj = prop->obj;
switch (weaponnum2) {
case WEAPON_GRENADE:
case WEAPON_PROXIMITYMINE:
coordTriggerProxies(&prop->pos, true);
break;
}
if (g_FrData.targets[i].travelling && g_FrData.targets[i].silent && g_FrData.targets[i].travelspeed != -1) {
g_FrData.targets[i].silent = false;
propsnd0f0939f8(NULL, g_FrData.targets[i].prop, SFX_FR_CONVEYER, -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
}
if (g_FrData.targets[i].angle > 2.2915925979614f && g_FrData.targets[i].angle < 3.9915928840637f) {
obj->damage = 0;
}
if ((g_FrData.targets[i].flags & FRTARGETFLAG_TMPINVINCIBLE)
&& g_FrData.targets[i].invincibletimer < TICKS(300)) {
invincible = true;
g_FrData.targets[i].invincibletimer += g_Vars.lvupdate240_60;
}
if (obj->damage > 0) {
if (invincible || g_FrData.targets[i].angle == M_PI) {
obj->damage = 0;
} else if (g_FrData.targets[i].flags & FRTARGETFLAG_ONEHITEXPLODE
|| obj->damage >= obj->maxdamage
|| frGetWeaponBySlot(g_FrData.slot) == WEAPON_PHOENIX) {
g_FrData.numhitsbullseye++;
g_FrData.score += 10;
exploding = true;
g_FrData.feedbackttl = TICKS(60);
g_FrData.feedbackzone = FRZONE_EXPLODE;
}
}
// Handle target being destroyed
if (exploding || (g_FrData.targets[i].maxdamage != 255
&& g_FrData.targets[i].damage >= g_FrData.targets[i].maxdamage)) {
bbox = objFindBboxRodata(obj);
if (g_FrNumSounds && g_FrData.targets[i].travelling) {
g_FrNumSounds--;
func0f0926bc(prop, 1, 0xffff);
}
if (g_FrNumSounds);
shardsCreate(&prop->pos, &obj->realrot[0][0], &obj->realrot[1][0], &obj->realrot[2][0],
bbox->xmin, bbox->xmax, bbox->ymin, bbox->ymax, 2, prop);
g_FrData.targetsdestroyed++;
if (g_FrData.targets[i].flags & FRTARGETFLAG_ONEHITEXPLODE) {
explosionCreateSimple(g_FrData.targets[i].prop, &g_FrData.targets[i].prop->pos,
g_FrData.targets[i].prop->rooms, EXPLOSIONTYPE_FRTARGET, 1);
}
g_FrData.targets[i].travelling = false;
g_FrData.targets[i].active = false;
g_FrData.targets[i].destroyed = true;
obj->flags2 |= OBJFLAG2_INVISIBLE;
prop->pos.x = 0;
prop->pos.y = -5000;
prop->pos.z = 0;
func0f069c70(obj, true, false);
// Activate another target
for (j = 0; j < ARRAYCOUNT(g_FrData.targets); j++) {
if (g_FrData.targets[j].destroyed == false
&& g_FrData.targets[j].inuse
&& g_FrData.targets[j].active == false) {
obj2 = g_FrData.targets[j].prop->obj;
g_FrData.targets[j].active = true;
obj2->flags2 &= ~OBJFLAG2_INVISIBLE;
break;
}
}
}
}
}
// Check if the session should end
if (g_FrData.goaltargets == 255) {
if (g_FrData.goalscore && g_FrData.score >= g_FrData.goalscore) {
frSetCompleted();
return;
}
} else {
if (g_FrData.targetsdestroyed >= g_FrData.goaltargets
&& (g_FrData.goalscore == 0 || g_FrData.score >= g_FrData.goalscore)) {
frSetCompleted();
return;
}
}
if (g_FrData.targetsdestroyed >= g_FrData.numtargets) {
frSetFailReason(FRFAILREASON_SCOREUNATTAINABLE);
return;
}
if (frIsAmmoWasted()) {
frSetFailReason(FRFAILREASON_OUTOFAMMO);
return;
}
if (g_FrData.timelimit != 255 && g_FrData.timetaken >= g_FrData.timelimit * TICKS(60)) {
frSetFailReason(FRFAILREASON_TIMEOVER);
return;
}
// Tick each target
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse
&& g_FrData.targets[i].destroyed == false
&& g_FrData.targets[i].active) {
prop = g_FrData.targets[i].prop;
obj = prop->obj;
if (g_FrData.targets[i].travelling) {
if (g_FrData.targets[i].travelspeed == -1) {
g_FrData.targets[i].donestopsound = true;
g_FrData.targets[i].travelling = false;
#if VERSION >= VERSION_NTSC_1_0
mult = 1;
#endif
dist = -2;
} else {
diff.x = g_FrData.targets[i].dstpos.x - prop->pos.x;
diff.y = g_FrData.targets[i].dstpos.y - prop->pos.y;
diff.z = g_FrData.targets[i].dstpos.z - prop->pos.z;
dist = sqrtf(diff.f[0] * diff.f[0] + diff.f[1] * diff.f[1] + diff.f[2] * diff.f[2]);
#if VERSION >= VERSION_NTSC_1_0
mult = 1;
#endif
if (dist != 0) {
#if VERSION >= VERSION_NTSC_1_0
#if VERSION >= VERSION_PAL_BETA
mult = g_FrData.targets[i].travelspeed * g_Vars.lvupdate240freal;
#else
mult = (g_FrData.targets[i].travelspeed * g_Vars.lvupdate240) * 0.25f;
#endif
diff.x *= 1.0f / dist;
diff.y *= 1.0f / dist;
diff.z *= 1.0f / dist;
newpos.x = diff.x * mult + prop->pos.x;
newpos.y = diff.y * mult + prop->pos.y;
newpos.z = diff.z * mult + prop->pos.z;
#else
diff.x *= 1.0f / dist;
diff.y *= 1.0f / dist;
diff.z *= 1.0f / dist;
newpos.x = diff.x * g_FrData.targets[i].travelspeed * g_Vars.lvupdate240 * 0.25f + prop->pos.x;
newpos.y = diff.y * g_FrData.targets[i].travelspeed * g_Vars.lvupdate240 * 0.25f + prop->pos.y;
newpos.z = diff.z * g_FrData.targets[i].travelspeed * g_Vars.lvupdate240 * 0.25f + prop->pos.z;
#endif
} else {
dist = -2;
}
}
#if VERSION >= VERSION_NTSC_1_0
if (mult >= dist)
#else
if (dist < g_FrData.targets[i].travelspeed)
#endif
{
// Target is stopping
newpos.x = g_FrData.targets[i].dstpos.x;
newpos.y = g_FrData.targets[i].dstpos.y;
newpos.z = g_FrData.targets[i].dstpos.z;
g_FrData.targets[i].scriptenabled = true;
g_FrData.targets[i].travelling = false;
if (g_FrData.targets[i].donestopsound == false) {
g_FrData.targets[i].donestopsound = true;
if (g_FrNumSounds) {
g_FrNumSounds--;
}
func0f0926bc(prop, 1, 0xffff);
propsnd0f0939f8(NULL, prop, SFX_FR_CONVEYER_STOP, -1,
-1, 1024, 0, 0, 0, -1, 0, -1, -1, -1, -1);
if (g_FrNumSounds);
}
}
prop->pos.x = newpos.x;
prop->pos.y = newpos.y;
prop->pos.z = newpos.z;
func0f069c70(obj, true, false);
}
if (g_FrData.targets[i].rotateoncloak && g_FrData.targets[i].rotating == false) {
if (g_FrData.targets[i].timeuntilrotate == 0) {
chr = g_Vars.currentplayer->prop->chr;
cloaked = chr->hidden & CHRHFLAG_CLOAKED;
if (cloaked) {
if (g_FrData.targets[i].angle == M_PI) {
g_FrData.targets[i].timeuntilrotate = TICKS(60);
g_FrData.targets[i].rotatetoangle = 0;
g_FrData.targets[i].rotatespeed = -M_PI / 90;
}
} else {
if (g_FrData.targets[i].angle == 0) {
g_FrData.targets[i].timeuntilrotate = TICKS(60);
g_FrData.targets[i].rotatetoangle = M_PI;
g_FrData.targets[i].rotatespeed = M_PI / 90;
}
}
} else {
g_FrData.targets[i].timeuntilrotate -= g_Vars.lvupdate240_60;
if (g_FrData.targets[i].timeuntilrotate <= 0) {
g_FrData.targets[i].timeuntilrotate = 0;
g_FrData.targets[i].rotating = true;
}
}
} else if (g_FrData.targets[i].rotating) {
toangle = g_FrData.targets[i].rotatetoangle;
speed = g_FrData.targets[i].rotatespeed;
if (toangle);
oldside = 0;
if (g_FrData.targets[i].angle < toangle) {
oldside = 1;
}
oldside = (u8)oldside;
#if VERSION >= VERSION_PAL_BETA
g_FrData.targets[i].angle += speed * g_Vars.lvupdate240freal;
#else
g_FrData.targets[i].angle += speed * g_Vars.lvupdate240 * 0.25f;
#endif
newside = 0;
toangle = g_FrData.targets[i].rotatetoangle;
if (g_FrData.targets[i].angle < toangle) {
newside = 1;
}
newside = (u8)newside;
if (newside != oldside || g_FrData.targets[i].angle == toangle) {
// Reached desired angle
g_FrData.targets[i].angle = g_FrData.targets[i].rotatetoangle;
g_FrData.targets[i].rotating = false;
g_FrData.targets[i].scriptenabled = true;
g_FrData.targets[i].scriptsleep = 0;
while (g_FrData.targets[i].angle > M_BADTAU) {
g_FrData.targets[i].angle -= M_BADTAU;
}
while (g_FrData.targets[i].angle < 0) {
g_FrData.targets[i].angle += M_BADTAU;
}
}
mtx4LoadYRotation(g_FrData.targets[i].angle + M_PI, &spbc);
mtx00015f04(obj->model->scale, &spbc);
mtx4ToMtx3(&spbc, sp98);
mtx3Copy(sp98, obj->realrot);
}
if (g_FrData.targets[i].scriptenabled && g_FrData.targets[i].scriptsleep != SECSTOTIME60(255)) {
g_FrData.targets[i].scriptsleep -= g_Vars.lvupdate240_60;
if (g_FrData.targets[i].scriptsleep <= 0) {
g_FrData.targets[i].scriptenabled = false;
while (!frExecuteTargetScript(i));
if (frTargetIsAtScriptStart(i)) {
while (!frExecuteTargetScript(i));
}
}
}
}
}
}
void func0f1a0924(struct prop *prop)
{
struct defaultobj *obj = prop->obj;
s32 i;
if (obj->modelnum == MODEL_TARGET) {
f32 sp68;
f32 sp64;
f32 sp60;
f32 sp56;
sp64 = -1;
sp68 = -1;
sp56 = -2;
sp60 = -2;
modelGetScreenCoords(obj->model, &sp56, &sp64, &sp60, &sp68);
for (i = 0; i < 4; i++) {
if (g_Vars.currentplayer->trackedprops[i].prop == prop) {
return;
}
if (g_Vars.currentplayer->trackedprops[i].prop == NULL) {
g_Vars.currentplayer->trackedprops[i].prop = prop;
g_Vars.currentplayer->trackedprops[i].x1 = sp64 - 2;
g_Vars.currentplayer->trackedprops[i].x2 = sp56 + 2;
g_Vars.currentplayer->trackedprops[i].y1 = sp68 - 2;
g_Vars.currentplayer->trackedprops[i].y2 = sp60 + 2;
g_Vars.currentplayer->targetset[i] = 0;
return;
}
}
}
}
bool frChooseFarsightTarget(void)
{
struct prop *bestprop = NULL;
f32 bestvalue = 1;
f32 bestdist = -1;
bool found = false;
s32 i;
if (bgunGetWeaponNum(HAND_RIGHT) == WEAPON_FARSIGHT) {
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
if (g_FrData.targets[i].inuse
&& g_FrData.targets[i].destroyed == false
&& g_FrData.targets[i].active
&& g_FrData.targets[i].flags & FRTARGETFLAG_FARSIGHTAUTOTARGETABLE) {
struct prop *prop = g_FrData.targets[i].prop;
f32 xdiff = g_Vars.currentplayer->bond2.unk10.x - prop->pos.x;
f32 ydiff = g_Vars.currentplayer->bond2.unk10.y - prop->pos.y;
f32 zdiff = g_Vars.currentplayer->bond2.unk10.z - prop->pos.z;
f32 dist = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
if (dist > 0) {
f32 value = (xdiff * g_Vars.currentplayer->bond2.unk1c.f[0]
+ ydiff * g_Vars.currentplayer->bond2.unk1c.f[1]
+ zdiff * g_Vars.currentplayer->bond2.unk1c.f[2]) / dist;
if (value);
if (value < 0 && value < bestvalue) {
bestvalue = value;
bestprop = prop;
bestdist = dist;
found = true;
}
}
}
}
}
g_Vars.currentplayer->autoeraserdist = bestdist;
g_Vars.currentplayer->autoerasertarget = bestprop;
return found;
}
s32 frIsInTraining(void)
{
if (g_FrData.menucountdown > 0 &&
(g_FrData.menutype == FRMENUTYPE_FAILED || g_FrData.menutype == FRMENUTYPE_COMPLETED)) {
return true;
}
return g_Vars.currentplayer->prop->rooms[0] == ROOM_DISH_FIRINGRANGE
&& g_FrIsValidWeapon
&& mainGetStageNum() == STAGE_CITRAINING;
}
void frCalculateHit(struct defaultobj *obj, struct coord *hitpos, f32 maulercharge)
{
s32 i;
if (g_FrIsValidWeapon == false) {
return;
}
for (i = 0; i < ARRAYCOUNT(g_FrData.targets); i++) {
struct prop *prop = g_FrData.targets[i].prop;
if (obj == prop->obj) {
f32 xdiff = hitpos->x - prop->pos.x;
f32 ydiff = hitpos->y - prop->pos.y;
f32 zdiff = hitpos->z - prop->pos.z;
f32 dist = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
if (g_FrData.targets[i].flags & FRTARGETFLAG_ONEHITEXPLODE) {
g_FrData.targets[i].damage = g_FrData.targets[i].maxdamage;
} else if (frGetWeaponBySlot(g_FrData.slot) == WEAPON_MAULER) {
g_FrData.targets[i].damage += (f32)((s32)(maulercharge * 0.1f) + 1);
} else if ((g_FrData.targets[i].flags & FRTARGETFLAG_TMPINVINCIBLE) == 0
|| g_FrData.targets[i].invincibletimer >= TICKS(300)) {
g_FrData.targets[i].damage++;
}
if (dist < 18) {
g_FrData.feedbackzone = FRZONE_BULLSEYE;
g_FrData.numhitsbullseye++;
} else if (dist < 37) {
g_FrData.feedbackzone = FRZONE_RING1;
g_FrData.numhitsring1++;
} else if (dist < 56) {
g_FrData.feedbackzone = FRZONE_RING2;
g_FrData.numhitsring2++;
} else {
g_FrData.feedbackzone = FRZONE_RING3;
g_FrData.numhitsring3++;
}
g_FrData.feedbackttl = TICKS(60);
g_FrData.score += g_FrData.feedbackzone;
}
}
}
void frIncrementNumShots(void)
{
g_FrData.numshots++;
g_FrData.numshotssincetopup++;
}
bool ciIsChrBioUnlocked(u32 bodynum)
{
switch (bodynum) {
case BODY_DARK_COMBAT:
case BODY_CARRINGTON:
return true;
case BODY_CASSANDRA:
return ciIsStageComplete(SOLOSTAGEINDEX_DEFECTION);
case BODY_DRCAROLL:
return ciIsStageComplete(SOLOSTAGEINDEX_INVESTIGATION);
case BODY_MRBLONDE:
return ciIsStageComplete(SOLOSTAGEINDEX_EXTRACTION);
case BODY_TRENT:
return ciIsStageComplete(SOLOSTAGEINDEX_G5BUILDING);
case BODY_JONATHAN:
return ciIsStageComplete(SOLOSTAGEINDEX_INFILTRATION);
case BODY_THEKING:
return ciIsStageComplete(SOLOSTAGEINDEX_RESCUE);
case BODY_PRESIDENT:
return ciIsStageComplete(SOLOSTAGEINDEX_AIRFORCEONE);
}
return false;
}
u8 g_ChrBioSlot = 0;
struct chrbio *ciGetChrBioByBodynum(u32 bodynum)
{
struct chrbio bios[] = {
// name, race, age, profile
#if VERSION >= VERSION_PAL_BETA
/*0*/ { L_DISH_125, L_DISH_126, L_DISH_127, L_DISH_128 }, // Joanna Dark
/*1*/ { L_DISH_129, L_DISH_130, L_DISH_131, L_DISH_132 }, // Jonathan
/*2*/ { L_DISH_133, L_DISH_134, L_DISH_135, L_DISH_136 }, // Daniel Carrington
/*3*/ { L_DISH_137, L_DISH_138, L_DISH_139, L_DISH_140 }, // Cassandra De Vries
/*4*/ { L_DISH_141, L_DISH_142, L_DISH_143, L_DISH_144 }, // Trent Easton
/*5*/ { L_DISH_145, L_DISH_146, L_DISH_147, L_DISH_148 }, // Dr. Caroll
/*6*/ { L_DISH_149, L_DISH_150, L_DISH_151, L_DISH_152 }, // Elvis
/*7*/ { L_DISH_153, L_DISH_154, L_DISH_155, L_DISH_156 }, // Mr. Blonde
/*8*/ { L_DISH_157, L_DISH_158, L_DISH_159, L_DISH_160 }, // Mr. Blonde (repeat)
/*9*/ { L_DISH_161, L_DISH_162, L_DISH_163, L_DISH_164 }, // The U.S. President
#else
/*0*/ { L_MISC_219, L_MISC_220, L_MISC_221, L_MISC_222 }, // Joanna Dark
/*1*/ { L_MISC_223, L_MISC_224, L_MISC_225, L_MISC_226 }, // Jonathan
/*2*/ { L_MISC_227, L_MISC_228, L_MISC_229, L_MISC_230 }, // Daniel Carrington
/*3*/ { L_MISC_231, L_MISC_232, L_MISC_233, L_MISC_234 }, // Cassandra De Vries
/*4*/ { L_MISC_235, L_MISC_236, L_MISC_237, L_MISC_238 }, // Trent Easton
/*5*/ { L_MISC_239, L_MISC_240, L_MISC_241, L_MISC_242 }, // Dr. Caroll
/*6*/ { L_MISC_243, L_MISC_244, L_MISC_245, L_MISC_246 }, // Elvis
/*7*/ { L_MISC_247, L_MISC_248, L_MISC_249, L_MISC_250 }, // Mr. Blonde
/*8*/ { L_MISC_251, L_MISC_252, L_MISC_253, L_MISC_254 }, // Mr. Blonde (repeat)
/*9*/ { L_MISC_255, L_MISC_256, L_MISC_257, L_MISC_258 }, // The U.S. President
#endif
};
switch (bodynum) {
case BODY_DARK_COMBAT:
return &bios[0];
case BODY_JONATHAN:
return &bios[1];
case BODY_CARRINGTON:
return &bios[2];
case BODY_CASSANDRA:
return &bios[3];
case BODY_TRENT:
return &bios[4];
case BODY_DRCAROLL:
return &bios[5];
case BODY_THEKING:
return &bios[6];
case BODY_MRBLONDE:
if (ciIsStageComplete(SOLOSTAGEINDEX_CRASHSITE)) {
return &bios[8];
}
return &bios[7];
case BODY_PRESIDENT:
return &bios[9];
}
return NULL;
}
char *ciGetChrBioDescription(void)
{
struct chrbio *bio = ciGetChrBioByBodynum(ciGetChrBioBodynumBySlot(g_ChrBioSlot));
return langGet(bio->description);
}
s32 ciGetNumUnlockedChrBios(void)
{
s32 count = 0;
s32 bodynum;
for (bodynum = 0; bodynum < NUM_BODIES; bodynum++) {
if (ciIsChrBioUnlocked(bodynum)) {
count++;
}
}
return count;
}
s32 ciGetChrBioBodynumBySlot(s32 slot)
{
s32 index = -1;
s32 bodynum;
for (bodynum = 0; bodynum < NUM_BODIES; bodynum++) {
if (ciIsChrBioUnlocked(bodynum)) {
index++;
}
if (index == slot) {
return bodynum;
}
}
return 0;
}
struct miscbio *ciGetMiscBio(s32 index)
{
struct miscbio bios[] = {
// name, description
#if VERSION >= VERSION_PAL_BETA
{ L_DISH_165, L_DISH_166 },
{ L_DISH_167, L_DISH_168 },
{ L_DISH_169, L_DISH_170 },
{ L_DISH_171, L_DISH_172 },
#else
{ L_MISC_259, L_MISC_260 },
{ L_MISC_261, L_MISC_262 },
{ L_MISC_263, L_MISC_264 },
{ L_MISC_265, L_MISC_266 },
#endif
};
switch (index) {
case MISCBIO_MAIANS: return &bios[0];
case MISCBIO_SKEDAR: return &bios[1];
case MISCBIO_BACKGROUND: return &bios[2];
case MISCBIO_STORY: return &bios[3];
}
return NULL;
}
bool ciIsMiscBioUnlocked(s32 index)
{
switch (index) {
case MISCBIO_MAIANS:
return ciIsStageComplete(SOLOSTAGEINDEX_RESCUE);
case MISCBIO_SKEDAR:
return ciIsStageComplete(SOLOSTAGEINDEX_ATTACKSHIP);
case MISCBIO_BACKGROUND:
case MISCBIO_STORY:
return ciIsStageComplete(SOLOSTAGEINDEX_MBR);
}
return false;
}
s32 ciGetNumUnlockedMiscBios(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 4; i++) {
if (ciIsMiscBioUnlocked(i)) {
count++;
}
}
return count;
}
s32 ciGetMiscBioIndexBySlot(s32 slot)
{
s32 index = -1;
s32 i;
for (i = 0; i < 4; i++) {
if (ciIsMiscBioUnlocked(i)) {
index++;
}
if (index == slot) {
return i;
}
}
return 0;
}
char *ciGetMiscBioDescription(void)
{
s32 index = ciGetMiscBioIndexBySlot(g_ChrBioSlot - ciGetNumUnlockedChrBios());
struct miscbio *bio = ciGetMiscBio(index);
return langGet(bio->description);
}
bool ciIsHangarBioAVehicle(s32 index)
{
return index >= HANGARBIO_JUMPSHIP;
}
u8 g_HangarBioSlot = 0;
struct hangarbio *ciGetHangarBio(s32 index)
{
struct hangarbio bios[] = {
// name, description
#if VERSION >= VERSION_PAL_BETA
{ L_DISH_196, L_DISH_219 }, // Carrington Institute
{ L_DISH_197, L_DISH_220 }, // Lucerne Tower
{ L_DISH_198, L_DISH_221 }, // Laboratory Basement
{ L_DISH_199, L_DISH_222 }, // Carrington Villa
{ L_DISH_200, L_DISH_223 }, // Chicago
{ L_DISH_201, L_DISH_224 }, // G5 Building
{ L_DISH_202, L_DISH_225 }, // Area 51
{ L_DISH_203, L_DISH_226 }, // Alaskan Air Base
{ L_DISH_204, L_DISH_227 }, // Air Force One
{ L_DISH_205, L_DISH_228 }, // Crash Site
{ L_DISH_206, L_DISH_229 }, // Pelagic II
{ L_DISH_207, L_DISH_230 }, // Cetan Ship
{ L_DISH_208, L_DISH_231 }, // Skedar Assault Ship
{ L_DISH_209, L_DISH_232 }, // Skedar Homeworld
{ L_DISH_210, L_DISH_233 }, // Jumpship
{ L_DISH_211, L_DISH_234 }, // HoverCrate
{ L_DISH_212, L_DISH_235 }, // HoverBike
{ L_DISH_213, L_DISH_236 }, // Cleaning Hovbot
{ L_DISH_214, L_DISH_237 }, // Hovercopter
{ L_DISH_215, L_DISH_238 }, // G5 Robot
{ L_DISH_216, L_DISH_239 }, // A51 Interceptor
{ L_DISH_217, L_DISH_240 }, // Maian Vessel
{ L_DISH_218, L_DISH_241 }, // Skedar Shuttle
#else
{ L_MISC_290, L_MISC_313 }, // Carrington Institute
{ L_MISC_291, L_MISC_314 }, // Lucerne Tower
{ L_MISC_292, L_MISC_315 }, // Laboratory Basement
{ L_MISC_293, L_MISC_316 }, // Carrington Villa
{ L_MISC_294, L_MISC_317 }, // Chicago
{ L_MISC_295, L_MISC_318 }, // G5 Building
{ L_MISC_296, L_MISC_319 }, // Area 51
{ L_MISC_297, L_MISC_320 }, // Alaskan Air Base
{ L_MISC_298, L_MISC_321 }, // Air Force One
{ L_MISC_299, L_MISC_322 }, // Crash Site
{ L_MISC_300, L_MISC_323 }, // Pelagic II
{ L_MISC_301, L_MISC_324 }, // Cetan Ship
{ L_MISC_302, L_MISC_325 }, // Skedar Assault Ship
{ L_MISC_303, L_MISC_326 }, // Skedar Homeworld
{ L_MISC_304, L_MISC_327 }, // Jumpship
{ L_MISC_305, L_MISC_328 }, // HoverCrate
{ L_MISC_306, L_MISC_329 }, // HoverBike
{ L_MISC_307, L_MISC_330 }, // Cleaning Hovbot
{ L_MISC_308, L_MISC_331 }, // Hovercopter
{ L_MISC_309, L_MISC_332 }, // G5 Robot
{ L_MISC_310, L_MISC_333 }, // A51 Interceptor
{ L_MISC_311, L_MISC_334 }, // Maian Vessel
{ L_MISC_312, L_MISC_335 }, // Skedar Shuttle
#endif
};
switch (index) {
case HANGARBIO_INSTITUTE: return &bios[0];
case HANGARBIO_DDTOWER: return &bios[1];
case HANGARBIO_LABBASEMENT: return &bios[2];
case HANGARBIO_VILLA: return &bios[3];
case HANGARBIO_CHICAGO: return &bios[4];
case HANGARBIO_G5: return &bios[5];
case HANGARBIO_AREA51: return &bios[6];
case HANGARBIO_AIRBASE: return &bios[7];
case HANGARBIO_AIRFORCEONE: return &bios[8];
case HANGARBIO_CRASHSITE: return &bios[9];
case HANGARBIO_PELAGIC: return &bios[10];
case HANGARBIO_DEEPSEA: return &bios[11];
case HANGARBIO_ATTACKSHIP: return &bios[12];
case HANGARBIO_SKEDARRUINS: return &bios[13];
case HANGARBIO_JUMPSHIP: return &bios[14];
case HANGARBIO_HOVERCRATE: return &bios[15];
case HANGARBIO_HOVERBIKE: return &bios[16];
case HANGARBIO_HOVERBOT: return &bios[17];
case HANGARBIO_HOVERCOPTER: return &bios[18];
case HANGARBIO_G5ROBOT: return &bios[19];
case HANGARBIO_A51INTERCEPTOR: return &bios[20];
case HANGARBIO_MAIANVESSEL: return &bios[21];
case HANGARBIO_SKEDARSHUTTLE: return &bios[22];
}
return NULL;
}
u8 g_DtSlot = 0;
u8 var80088adc = 0;
bool ciIsHangarBioUnlocked(u32 bioindex)
{
u32 stage;
switch (bioindex) {
case HANGARBIO_INSTITUTE:
case HANGARBIO_HOVERCRATE:
return true;
case HANGARBIO_DDTOWER:
stage = SOLOSTAGEINDEX_DEFECTION;
break;
case HANGARBIO_LABBASEMENT:
case HANGARBIO_HOVERBOT:
stage = SOLOSTAGEINDEX_INVESTIGATION;
break;
case HANGARBIO_HOVERCOPTER:
stage = SOLOSTAGEINDEX_EXTRACTION;
break;
case HANGARBIO_VILLA:
case HANGARBIO_JUMPSHIP:
stage = SOLOSTAGEINDEX_VILLA;
break;
case HANGARBIO_CHICAGO:
stage = SOLOSTAGEINDEX_CHICAGO;
break;
case HANGARBIO_G5:
case HANGARBIO_G5ROBOT:
stage = SOLOSTAGEINDEX_G5BUILDING;
break;
case HANGARBIO_AREA51:
case HANGARBIO_HOVERBIKE:
case HANGARBIO_A51INTERCEPTOR:
stage = SOLOSTAGEINDEX_INFILTRATION;
break;
case HANGARBIO_AIRBASE:
stage = SOLOSTAGEINDEX_AIRBASE;
break;
case HANGARBIO_AIRFORCEONE:
stage = SOLOSTAGEINDEX_AIRFORCEONE;
break;
case HANGARBIO_CRASHSITE:
case HANGARBIO_MAIANVESSEL:
stage = SOLOSTAGEINDEX_CRASHSITE;
break;
case HANGARBIO_PELAGIC:
stage = SOLOSTAGEINDEX_PELAGIC;
break;
case HANGARBIO_DEEPSEA:
stage = SOLOSTAGEINDEX_DEEPSEA;
break;
case HANGARBIO_ATTACKSHIP:
case HANGARBIO_SKEDARSHUTTLE:
stage = SOLOSTAGEINDEX_DEFENSE;
break;
case HANGARBIO_SKEDARRUINS:
stage = SOLOSTAGEINDEX_ATTACKSHIP;
break;
default:
return false;
}
return ciIsStageComplete(stage);
}
s32 ciGetNumUnlockedLocationBios(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 23; i++) {
if (ciIsHangarBioAVehicle(i)) {
return count;
}
if (ciIsHangarBioUnlocked(i)) {
count++;
}
}
return count;
}
s32 ciGetNumUnlockedHangarBios(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 23; i++) {
if (ciIsHangarBioUnlocked(i)) {
count++;
}
}
return count;
}
s32 ciGetHangarBioIndexBySlot(s32 slot)
{
s32 index = -1;
s32 i;
for (i = 0; i < 23; i++) {
if (ciIsHangarBioUnlocked(i)) {
index++;
}
if (index == slot) {
return i;
}
}
return 0;
}
char *ciGetHangarBioDescription(void)
{
struct hangarbio *bio = ciGetHangarBio(ciGetHangarBioIndexBySlot(g_HangarBioSlot));
return langGet(bio->description);
}
struct trainingdata *dtGetData(void)
{
return &g_DtData;
}
void dtRestorePlayer(void)
{
bgunSetPassiveMode(true);
if (g_DtData.obj) {
objFreePermanently(g_DtData.obj, true);
}
g_DtData.obj = NULL;
if (dtGetWeaponByDeviceIndex(dtGetIndexBySlot(g_DtSlot)) == WEAPON_ECMMINE) {
bgunSetAmmoQuantity(AMMOTYPE_ECM_MINE, 0);
}
if (g_Vars.currentplayer->eyespy) {
struct chrdata *chr = g_Vars.currentplayer->eyespy->prop->chr;
g_Vars.currentplayer->eyespy->deployed = false;
g_Vars.currentplayer->eyespy->held = true;
g_Vars.currentplayer->eyespy->active = false;
chr->chrflags |= CHRCFLAG_HIDDEN;
func0f0926bc(g_Vars.currentplayer->eyespy->prop, 1, 0xffff);
g_Vars.currentplayer->devicesactive &= ~DEVICE_EYESPY;
}
}
void dtPushEndscreen(void)
{
if (g_DtData.completed) {
func0f0f85e0(&g_DtCompletedMenuDialog, MENUROOT_TRAINING);
} else if (g_DtData.failed) {
func0f0f85e0(&g_DtFailedMenuDialog, MENUROOT_TRAINING);
}
g_DtData.timeleft = 0;
g_DtData.completed = false;
g_DtData.failed = false;
g_DtData.finished = false;
g_DtData.holographedpc = false;
}
void dtTick(void)
{
if (var80088adc) {
if (g_DtData.intraining) {
g_DtData.timetaken += g_Vars.lvupdate240_60;
if (g_Vars.currentplayer->isdead) {
dtEnd();
}
if (chrHasStageFlag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE)) {
dtEnd();
g_DtData.failed = true;
g_DtData.timeleft = 1;
g_DtData.finished = true;
} else if (chrHasStageFlag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_SUCCESS)) {
dtEnd();
g_DtData.completed = true;
g_DtData.timeleft = 1;
g_DtData.finished = true;
}
} else if (g_DtData.finished) {
if (g_DtData.timeleft <= 0) {
dtPushEndscreen();
} else {
g_DtData.timeleft -= g_Vars.lvupdate240_60;
}
}
}
}
void func0f1a1ac0(void)
{
if (var80088adc == false) {
var80088adc = true;
g_DtData.intraining = false;
g_DtData.failed = false;
g_DtData.completed = false;
g_DtData.finished = false;
g_DtData.timeleft = 0;
g_DtData.holographedpc = false;
g_DtData.timetaken = 0;
g_DtData.obj = NULL;
chrUnsetStageFlag(NULL, STAGEFLAG_CI_DEVICE_ABORTING);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_SUCCESS);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE);
}
}
void dtBegin(void)
{
g_DtData.intraining = true;
g_DtData.timetaken = 0;
chrUnsetStageFlag(NULL, STAGEFLAG_CI_DEVICE_ABORTING);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_SUCCESS);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE);
chrSetStageFlag(NULL, ciGetStageFlagByDeviceIndex(dtGetIndexBySlot(g_DtSlot)));
g_Vars.currentplayer->training = true;
bgunSetPassiveMode(false);
chrSetStageFlag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
void dtEnd(void)
{
g_DtData.intraining = false;
dtRestorePlayer();
bgunSetAmmoQuantity(AMMOTYPE_CLOAK, 0);
chrSetStageFlag(NULL, STAGEFLAG_CI_DEVICE_ABORTING);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE);
chrUnsetStageFlag(NULL, ciGetStageFlagByDeviceIndex(dtGetIndexBySlot(g_DtSlot)));
g_Vars.currentplayer->training = false;
chrUnsetStageFlag(NULL, STAGEFLAG_CI_IN_TRAINING);
playerDisplayHealth();
g_Vars.currentplayer->bondhealth = 1;
}
bool dtIsAvailable(s32 deviceindex)
{
u8 flags[] = {
GAMEFILEFLAG_CI_UPLINK_DONE,
GAMEFILEFLAG_CI_ECMMINE_DONE,
GAMEFILEFLAG_CI_CAMSPY_DONE,
GAMEFILEFLAG_CI_NIGHTVISION_DONE,
GAMEFILEFLAG_CI_DOORDECODER_DONE,
GAMEFILEFLAG_CI_RTRACKER_DONE,
GAMEFILEFLAG_CI_IR_DONE,
GAMEFILEFLAG_CI_XRAY_DONE,
GAMEFILEFLAG_CI_DISGUISE_DONE,
GAMEFILEFLAG_CI_CLOAK_DONE,
};
deviceindex--;
if (deviceindex >= 10) {
return true;
}
if (deviceindex < 0 || gamefileHasFlag(flags[deviceindex])) {
return true;
}
return false;
}
s32 dtGetNumAvailable(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 10; i++) {
if (dtIsAvailable(i)) {
count++;
}
}
return count;
}
s32 dtGetIndexBySlot(s32 wantindex)
{
s32 index = -1;
s32 i;
for (i = 0; i < 10; i++) {
if (dtIsAvailable(i)) {
index++;
}
if (index == wantindex) {
return i;
}
}
return 0;
}
u32 dtGetWeaponByDeviceIndex(s32 deviceindex)
{
u32 weapons[] = {
WEAPON_DATAUPLINK,
WEAPON_ECMMINE,
WEAPON_EYESPY,
WEAPON_NIGHTVISION,
WEAPON_DOORDECODER,
WEAPON_RTRACKER,
WEAPON_IRSCANNER,
WEAPON_XRAYSCANNER,
WEAPON_DISGUISE41,
WEAPON_CLOAKINGDEVICE,
};
return weapons[deviceindex];
}
u32 ciGetStageFlagByDeviceIndex(u32 deviceindex)
{
u32 flags[] = {
STAGEFLAG_CI_TRIGGER_UPLINK,
STAGEFLAG_CI_TRIGGER_ECMMINE,
STAGEFLAG_CI_TRIGGER_CAMSPY,
STAGEFLAG_CI_TRIGGER_NIGHTVISION,
STAGEFLAG_CI_TRIGGER_DOORDECODER,
STAGEFLAG_CI_TRIGGER_RTRACKER,
STAGEFLAG_CI_TRIGGER_IR,
STAGEFLAG_CI_TRIGGER_XRAY,
STAGEFLAG_CI_TRIGGER_DISGUISE,
STAGEFLAG_CI_TRIGGER_CLOAK,
};
return flags[deviceindex];
}
char *dtGetDescription(void)
{
u32 texts[] = {
#if VERSION >= VERSION_PAL_BETA
/*0*/ L_DISH_186, // Data uplink
/*1*/ L_DISH_185, // ECM mine
/*2*/ L_DISH_177, // CamSpy
/*3*/ L_DISH_178, // Night vision
/*4*/ L_DISH_179, // Door decoder
/*5*/ L_DISH_183, // R-tracker
/*6*/ L_DISH_182, // IR scanner
/*7*/ L_DISH_180, // X-ray scanner
/*8*/ L_DISH_181, // Disguise
/*9*/ L_DISH_184, // Cloak
#else
/*0*/ L_MISC_280, // Data uplink
/*1*/ L_MISC_279, // ECM mine
/*2*/ L_MISC_271, // CamSpy
/*3*/ L_MISC_272, // Night vision
/*4*/ L_MISC_273, // Door decoder
/*5*/ L_MISC_277, // R-tracker
/*6*/ L_MISC_276, // IR scanner
/*7*/ L_MISC_274, // X-ray scanner
/*8*/ L_MISC_275, // Disguise
/*9*/ L_MISC_278, // Cloak
#endif
};
return langGet(texts[dtGetIndexBySlot(g_DtSlot)]);
}
char *dtGetTip1(void)
{
u32 texts[] = {
#if VERSION >= VERSION_PAL_BETA
/*0*/ L_DISH_263,
/*1*/ L_DISH_264,
/*2*/ L_DISH_265,
/*3*/ L_DISH_266,
/*4*/ L_DISH_267,
/*5*/ L_DISH_268,
/*6*/ L_DISH_269,
/*7*/ L_DISH_270,
/*8*/ L_DISH_271,
/*9*/ L_DISH_272,
#else
/*0*/ L_MISC_357,
/*1*/ L_MISC_358,
/*2*/ L_MISC_359,
/*3*/ L_MISC_360,
/*4*/ L_MISC_361,
/*5*/ L_MISC_362,
/*6*/ L_MISC_363,
/*7*/ L_MISC_364,
/*8*/ L_MISC_365,
/*9*/ L_MISC_366,
#endif
};
return langGet(texts[dtGetIndexBySlot(g_DtSlot)]);
}
char *dtGetTip2(void)
{
u32 texts[] = {
#if VERSION >= VERSION_PAL_BETA
/*0*/ L_DISH_273,
/*1*/ L_DISH_274,
/*2*/ L_DISH_275,
/*3*/ L_DISH_276,
/*4*/ L_DISH_277,
/*5*/ L_DISH_278,
/*6*/ L_DISH_279,
/*7*/ L_DISH_280,
/*8*/ L_DISH_281,
/*9*/ L_DISH_282,
#else
/*0*/ L_MISC_367,
/*1*/ L_MISC_368,
/*2*/ L_MISC_369,
/*3*/ L_MISC_370,
/*4*/ L_MISC_371,
/*5*/ L_MISC_372,
/*6*/ L_MISC_373,
/*7*/ L_MISC_374,
/*8*/ L_MISC_375,
/*9*/ L_MISC_376,
#endif
};
return langGet(texts[dtGetIndexBySlot(g_DtSlot)]);
}
struct trainingdata *getHoloTrainingData(void)
{
return &g_HtData;
}
void htPushEndscreen(void)
{
if (g_HtData.completed) {
func0f0f85e0(&g_HtCompletedMenuDialog, MENUROOT_TRAINING);
} else if (g_HtData.failed) {
func0f0f85e0(&g_HtFailedMenuDialog, MENUROOT_TRAINING);
}
g_HtData.timeleft = 0;
g_HtData.completed = false;
g_HtData.failed = false;
g_HtData.finished = false;
}
u8 var80088bb4 = 0;
u8 var80088bb8 = 0;
void htTick(void)
{
if (var80088bb8) {
if (g_HtData.intraining) {
g_HtData.timetaken += g_Vars.lvupdate240_60;
if (g_Vars.currentplayer->isdead) {
htEnd();
}
if (chrHasStageFlag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE)) {
htEnd();
g_HtData.failed = true;
g_HtData.timeleft = 1;
g_HtData.finished = true;
} else if (chrHasStageFlag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_SUCCESS)) {
htEnd();
g_HtData.completed = true;
g_HtData.timeleft = 1;
g_HtData.finished = true;
}
} else if (g_HtData.finished) {
if (g_HtData.timeleft <= 0) {
htPushEndscreen();
} else {
g_HtData.timeleft -= g_Vars.lvupdate240_60;
}
}
}
}
void func0f1a2198(void)
{
if (var80088bb8 == false) {
var80088bb8 = true;
g_HtData.intraining = false;
g_HtData.failed = false;
g_HtData.completed = false;
g_HtData.finished = false;
g_HtData.timeleft = 0;
g_HtData.timetaken = 0;
chrUnsetStageFlag(NULL, STAGEFLAG_CI_HOLO_ABORTING);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_SUCCESS);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE);
}
}
void htBegin(void)
{
struct waypoint *waypoints = g_StageSetup.waypoints;
g_HtData.intraining = true;
g_HtData.timetaken = 0;
chrUnsetStageFlag(NULL, STAGEFLAG_CI_HOLO_ABORTING);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_SUCCESS);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE);
chrSetStageFlag(NULL, func0f1a25c0(htGetIndexBySlot(var80088bb4)));
// Disable segment leading out of the door
waypointDisableSegment(&waypoints[0x20], &waypoints[0x31]);
g_Vars.currentplayer->training = true;
bgunSetPassiveMode(false);
chrSetStageFlag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
void htEnd(void)
{
struct prop *prop;
s16 *propnum;
s16 propnums[256];
s16 rooms[5] = { 0x0016, 0x0017, 0x0018, 0x0019, -1 };
struct waypoint *waypoints = g_StageSetup.waypoints;
g_HtData.intraining = false;
chrSetStageFlag(NULL, STAGEFLAG_CI_HOLO_ABORTING);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE);
chrUnsetStageFlag(NULL, func0f1a25c0(htGetIndexBySlot(var80088bb4)));
// Enable segment leading out of the door
waypointEnableSegment(&waypoints[0x20], &waypoints[0x31]);
g_Vars.currentplayer->training = false;
roomGetProps(rooms, propnums, 256);
propnum = &propnums[0];
// Remove dropped weapons
while (*propnum >= 0) {
prop = &g_Vars.props[*propnum];
if (prop && prop->type == PROPTYPE_WEAPON) {
struct defaultobj *obj = prop->obj;
if (obj->type == OBJTYPE_WEAPON) {
objFreePermanently(obj, true);
}
}
propnum++;
}
bgunSetPassiveMode(true);
chrUnsetStageFlag(NULL, STAGEFLAG_CI_IN_TRAINING);
playerDisplayHealth();
g_Vars.currentplayer->bondhealth = 1;
}
bool htIsUnlocked(u32 value)
{
switch (value) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
return true;
}
return false;
}
s32 htGetNumUnlocked(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 7; i++) {
if (htIsUnlocked(i)) {
count++;
}
}
return count;
}
s32 htGetIndexBySlot(s32 slot)
{
s32 index = -1;
s32 i;
for (i = 0; i < 7; i++) {
if (htIsUnlocked(i)) {
index++;
}
if (index == slot) {
return i;
}
}
return 0;
}
char *htGetName(s32 index)
{
u32 texts[] = {
#if VERSION >= VERSION_PAL_BETA
L_DISH_316, // "Holo 1 - Looking Around"
L_DISH_317, // "Holo 2 - Movement 1"
L_DISH_318, // "Holo 3 - Movement 2"
L_DISH_319, // "Holo 4 - Unarmed Combat 1"
L_DISH_320, // "Holo 5 - Unarmed Combat 2"
L_DISH_321, // "Holo 6 - Live Combat 1"
L_DISH_322, // "Holo 7 - Live Combat 2"
#else
L_MISC_410, // "Holo 1 - Looking Around"
L_MISC_411, // "Holo 2 - Movement 1"
L_MISC_412, // "Holo 3 - Movement 2"
L_MISC_413, // "Holo 4 - Unarmed Combat 1"
L_MISC_414, // "Holo 5 - Unarmed Combat 2"
L_MISC_415, // "Holo 6 - Live Combat 1"
L_MISC_416, // "Holo 7 - Live Combat 2"
#endif
};
return langGet(texts[index]);
}
u32 func0f1a25c0(s32 index)
{
u32 flags[] = {
STAGEFLAG_CI_IN_HOLO1,
STAGEFLAG_CI_IN_HOLO2,
STAGEFLAG_CI_IN_HOLO3,
STAGEFLAG_CI_IN_HOLO4,
STAGEFLAG_CI_IN_HOLO5,
STAGEFLAG_CI_IN_HOLO6,
STAGEFLAG_CI_IN_HOLO7,
STAGEFLAG_CI_GENERAL_PURPOSE,
};
return flags[index];
}
char *htGetDescription(void)
{
u32 texts[] = {
#if VERSION >= VERSION_PAL_BETA
L_DISH_242,
L_DISH_243,
L_DISH_244,
L_DISH_245,
L_DISH_246,
L_DISH_247,
L_DISH_248,
#else
L_MISC_336,
L_MISC_337,
L_MISC_338,
L_MISC_339,
L_MISC_340,
L_MISC_341,
L_MISC_342,
#endif
};
return langGet(texts[htGetIndexBySlot(var80088bb4)]);
}
char *htGetTip1(void)
{
u32 texts[] = {
#if VERSION >= VERSION_PAL_BETA
L_DISH_249, // "For greater precision..."
L_DISH_250, // "Think about where you want to go..."
L_DISH_251, // "Ducking enables you to..."
L_DISH_252, // "Attacking opponents from behind..."
L_DISH_253, // "Only stay close long enough..."
L_DISH_254, // "Don't hang around and wait..."
L_DISH_255, // "Go for the armed opponents..."
#else
L_MISC_343, // "For greater precision..."
L_MISC_344, // "Think about where you want to go..."
L_MISC_345, // "Ducking enables you to..."
L_MISC_346, // "Attacking opponents from behind..."
L_MISC_347, // "Only stay close long enough..."
L_MISC_348, // "Don't hang around and wait..."
L_MISC_349, // "Go for the armed opponents..."
#endif
};
return langGet(texts[htGetIndexBySlot(var80088bb4)]);
}
char *htGetTip2(void)
{
u32 texts[] = {
#if VERSION >= VERSION_PAL_BETA
L_DISH_256, // "For greater precision..."
L_DISH_257, // "Sidestepping and strafing..."
L_DISH_258, // "Ducking enables you to..."
L_DISH_259, // "Attacking opponents from behind..."
L_DISH_260, // "Only stay close long enough..."
L_DISH_261, // "Don't hang around and wait..."
L_DISH_262, // "Go for the armed opponents..."
#else
L_MISC_350, // "For greater precision..."
L_MISC_351, // "Sidestepping and strafing..."
L_MISC_352, // "Ducking enables you to..."
L_MISC_353, // "Attacking opponents from behind..."
L_MISC_354, // "Only stay close long enough..."
L_MISC_355, // "Don't hang around and wait..."
L_MISC_356, // "Go for the armed opponents..."
#endif
};
return langGet(texts[htGetIndexBySlot(var80088bb4)]);
}
#if VERSION >= VERSION_JPN_FINAL
void frGetGoalTargetsText(char *buffer, char *buffer2)
{
sprintf(buffer, "%s", langGet(L_MISC_417));
sprintf(buffer2, "%d\n", g_FrData.goaltargets);
}
#else
void frGetGoalTargetsText(char *buffer)
{
// "GOAL TARGETS:"
sprintf(buffer, "%s %d\n", langGet(L_MISC_417), g_FrData.goaltargets);
}
#endif
void frGetTargetsDestroyedValue(char *buffer)
{
sprintf(buffer, "%02d\n", g_FrData.targetsdestroyed);
}
void frGetScoreValue(char *buffer)
{
sprintf(buffer, "%03d\n", g_FrData.score);
}
#if VERSION >= VERSION_JPN_FINAL
void frGetGoalScoreText(char *buffer1, char *buffer2)
{
if (g_FrData.goalscore) {
sprintf(buffer1, "%s", langGet(L_MISC_418));
sprintf(buffer2, "%d\n", g_FrData.goalscore);
} else {
sprintf(buffer1, "");
sprintf(buffer2, "");
}
}
#else
void frGetGoalScoreText(char *buffer)
{
if (g_FrData.goalscore) {
// "GOAL SCORE:"
sprintf(buffer, "%s %d\n", langGet(L_MISC_418), g_FrData.goalscore);
} else {
sprintf(buffer, "");
}
}
#endif
f32 frGetAccuracy(char *buffer)
{
f32 sum = (g_FrData.numhitsring3
+ g_FrData.numhitsbullseye
+ g_FrData.numhitsring1
+ g_FrData.numhitsring2) * 100.0f;
f32 accuracy = 100.0f;
if (g_FrData.numshots) {
accuracy = sum / (f32)g_FrData.numshots;
}
if (accuracy > 100.0f) {
accuracy = 100.0f;
}
sprintf(buffer, "%s%s%.2f%%\n", "", "", accuracy);
return accuracy;
}
#if VERSION >= VERSION_JPN_FINAL
bool frGetMinAccuracy(char *buffer1, f32 accuracy, char *buffer2)
{
sprintf(buffer1, "%s", langGet(L_MISC_419));
sprintf(buffer2, "%d%%\n", g_FrData.goalaccuracy);
return accuracy < g_FrData.goalaccuracy;
}
#else
bool frGetMinAccuracy(char *buffer, f32 accuracy)
{
// "MIN ACCURACY:"
sprintf(buffer, "%s %d%%\n", langGet(L_MISC_419), g_FrData.goalaccuracy);
return accuracy < g_FrData.goalaccuracy;
}
#endif
/**
* Formats either the time taken or time limit into buffer, and returns true if
* the time should induce a failure.
*
* The time limit will be used if it exists and the time take exceeds it,
* otherwise time taken will be used.
*
* Negative time taken (such as when the player aborts before the challenge
* starts) is wrapped to positive and will induce a failure.
*/
bool frFormatTime(char *buffer)
{
s32 mins = 0;
s32 mult = 1;
f32 secs = g_FrData.timetaken / TICKS(60.0f);
u8 failed = false;
if (g_FrData.timelimit != 255 && secs >= g_FrData.timelimit) {
failed = true;
secs = g_FrData.timelimit;
} else if (g_FrData.timetaken < 0) {
failed = true;
}
if (secs < 0) {
mult = -1;
secs = -secs;
}
if (secs >= 60) {
while (secs >= 60) {
secs -= 60;
mins++;
}
}
sprintf(buffer, "%02d:%02d\n", mult * mins, (s32)secs);
return failed;
}
#if VERSION >= VERSION_JPN_FINAL
bool frGetHudMiddleSubtext(char *buffer1, char *buffer2)
{
s32 secs;
s32 mins;
sprintf(buffer2, "");
if (g_FrData.timetaken < TICKS(-180)) {
sprintf(buffer1, "%s", langGet(L_MISC_420)); // "FIRE TO START"
return false;
}
if (g_FrData.timetaken < 0) {
sprintf(buffer1, "%s", langGet(L_MISC_421)); // "GET READY!"
return true;
}
if (g_FrData.timelimit == 255) {
return false;
}
secs = g_FrData.timelimit;
mins = 0;
if (secs >= 60) {
while (secs >= 60) {
secs -= 60;
mins++;
}
}
sprintf(buffer1, "%s", langGet(L_MISC_422)); // "LIMIT:"
sprintf(buffer2, "%02d:%02d\n", mins, secs);
return true;
}
#else
bool frGetHudMiddleSubtext(char *buffer)
{
s32 secs;
s32 mins;
if (g_FrData.timetaken < TICKS(-180)) {
sprintf(buffer, "%s", langGet(L_MISC_420)); // "FIRE TO START"
return false;
}
if (g_FrData.timetaken < 0) {
sprintf(buffer, "%s", langGet(L_MISC_421)); // "GET READY!"
return true;
}
if (g_FrData.timelimit == 255) {
return false;
}
secs = g_FrData.timelimit;
mins = 0;
if (secs >= 60) {
while (secs >= 60) {
secs -= 60;
mins++;
}
}
sprintf(buffer, "%s %02d:%02d\n", langGet(L_MISC_422), mins, secs); // "LIMIT:"
return true;
}
#endif
#if VERSION >= VERSION_JPN_FINAL
bool frGetFeedback(char *scorebuffer, char *zonebuffer, char *extrabuffer)
{
u32 texts[] = {
L_MISC_423, // "ZONE 3"
L_MISC_424, // "ZONE 2"
L_MISC_425, // "ZONE 1"
L_MISC_426, // "BULL'S-EYE"
L_MISC_427, // "EXPLODED"
};
sprintf(extrabuffer, "");
if (g_FrData.feedbackzone) {
g_FrData.feedbackttl -= g_Vars.lvupdate240_60;
if (g_FrData.feedbackttl <= 0) {
g_FrData.feedbackzone = 0;
g_FrData.feedbackttl = 0;
return false;
}
if (g_FrData.feedbackzone == FRZONE_EXPLODE) {
sprintf(scorebuffer, "010\n");
} else {
sprintf(scorebuffer, "%03d\n", g_FrData.feedbackzone);
}
switch (g_FrData.feedbackzone) {
case FRZONE_RING3:
sprintf(zonebuffer, "%s", langGet(texts[0]));
return true;
case FRZONE_RING2:
sprintf(zonebuffer, "%s", langGet(texts[1]));
return true;
case FRZONE_RING1:
sprintf(zonebuffer, "%s", langGet(texts[2]));
return true;
case FRZONE_BULLSEYE:
sprintf(zonebuffer, "%s", langGet(texts[3]));
return true;
case FRZONE_EXPLODE:
sprintf(zonebuffer, "%s", langGet(texts[4]));
return true;
}
sprintf(zonebuffer, "\n");
return true;
}
return false;
}
#else
bool frGetFeedback(char *scorebuffer, char *zonebuffer)
{
u32 texts[] = {
L_MISC_423, // "ZONE 3"
L_MISC_424, // "ZONE 2"
L_MISC_425, // "ZONE 1"
L_MISC_426, // "BULL'S-EYE"
L_MISC_427, // "EXPLODED"
};
if (g_FrData.feedbackzone) {
g_FrData.feedbackttl -= g_Vars.lvupdate240_60;
if (g_FrData.feedbackttl <= 0) {
g_FrData.feedbackzone = 0;
g_FrData.feedbackttl = 0;
return false;
}
if (g_FrData.feedbackzone == FRZONE_EXPLODE) {
sprintf(scorebuffer, "010\n");
} else {
sprintf(scorebuffer, "%03d\n", g_FrData.feedbackzone);
}
switch (g_FrData.feedbackzone) {
case FRZONE_RING3:
sprintf(zonebuffer, "%s", langGet(texts[0]));
return true;
case FRZONE_RING2:
sprintf(zonebuffer, "%s", langGet(texts[1]));
return true;
case FRZONE_RING1:
sprintf(zonebuffer, "%s", langGet(texts[2]));
return true;
case FRZONE_BULLSEYE:
sprintf(zonebuffer, "%s", langGet(texts[3]));
return true;
case FRZONE_EXPLODE:
sprintf(zonebuffer, "%s", langGet(texts[4]));
return true;
}
sprintf(zonebuffer, "\n");
return true;
}
return false;
}
#endif
#if VERSION >= VERSION_JPN_FINAL
GLOBAL_ASM(
glabel frRenderHudElement
/* f1a3788: 27bdff88 */ addiu $sp,$sp,-120
/* f1a378c: afa60080 */ sw $a2,0x80($sp)
/* f1a3790: 3c0e8008 */ lui $t6,0x8008
/* f1a3794: 8dce0150 */ lw $t6,0x150($t6)
/* f1a3798: afb00034 */ sw $s0,0x34($sp)
/* f1a379c: afa70084 */ sw $a3,0x84($sp)
/* f1a37a0: 00e03025 */ move $a2,$a3
/* f1a37a4: 00808025 */ move $s0,$a0
/* f1a37a8: afbf003c */ sw $ra,0x3c($sp)
/* f1a37ac: afa5007c */ sw $a1,0x7c($sp)
/* f1a37b0: 3c078008 */ lui $a3,0x8008
/* f1a37b4: afb10038 */ sw $s1,0x38($sp)
/* f1a37b8: 8ce70154 */ lw $a3,0x154($a3)
/* f1a37bc: 27a50070 */ addiu $a1,$sp,0x70
/* f1a37c0: 27a40074 */ addiu $a0,$sp,0x74
/* f1a37c4: afa00014 */ sw $zero,0x14($sp)
/* f1a37c8: 0fc55d49 */ jal textMeasure
/* f1a37cc: afae0010 */ sw $t6,0x10($sp)
/* f1a37d0: 8fa80070 */ lw $t0,0x70($sp)
/* f1a37d4: 8faf007c */ lw $t7,0x7c($sp)
/* f1a37d8: 8fa30080 */ lw $v1,0x80($sp)
/* f1a37dc: 0008c043 */ sra $t8,$t0,0x1
/* f1a37e0: 27aa0074 */ addiu $t2,$sp,0x74
/* f1a37e4: 01f8c823 */ subu $t9,$t7,$t8
/* f1a37e8: afb9006c */ sw $t9,0x6c($sp)
/* f1a37ec: afaa0010 */ sw $t2,0x10($sp)
/* f1a37f0: 02002025 */ move $a0,$s0
/* f1a37f4: 27a5006c */ addiu $a1,$sp,0x6c
/* f1a37f8: 27a60068 */ addiu $a2,$sp,0x68
/* f1a37fc: 27a70070 */ addiu $a3,$sp,0x70
/* f1a3800: 0fc54bed */ jal text0f153858
/* f1a3804: afa30068 */ sw $v1,0x68($sp)
/* f1a3808: 0c002eeb */ jal viGetWidth
/* f1a380c: 00408025 */ move $s0,$v0
/* f1a3810: 00028c00 */ sll $s1,$v0,0x10
/* f1a3814: 00115c03 */ sra $t3,$s1,0x10
/* f1a3818: 0c002eef */ jal viGetHeight
/* f1a381c: 01608825 */ move $s1,$t3
/* f1a3820: 93a30097 */ lbu $v1,0x97($sp)
/* f1a3824: 8fae0090 */ lw $t6,0x90($sp)
/* f1a3828: 3c0c8008 */ lui $t4,0x8008
/* f1a382c: 3c0d8008 */ lui $t5,0x8008
/* f1a3830: 2401ff00 */ li $at,-256
/* f1a3834: 8dad0150 */ lw $t5,0x150($t5)
/* f1a3838: 8d8c0154 */ lw $t4,0x154($t4)
/* f1a383c: 01c17824 */ and $t7,$t6,$at
/* f1a3840: 01e34825 */ or $t1,$t7,$v1
/* f1a3844: 00034043 */ sra $t0,$v1,0x1
/* f1a3848: afa8001c */ sw $t0,0x1c($sp)
/* f1a384c: afa80048 */ sw $t0,0x48($sp)
/* f1a3850: afa90018 */ sw $t1,0x18($sp)
/* f1a3854: afa90044 */ sw $t1,0x44($sp)
/* f1a3858: 02002025 */ move $a0,$s0
/* f1a385c: 27a5006c */ addiu $a1,$sp,0x6c
/* f1a3860: 27a60068 */ addiu $a2,$sp,0x68
/* f1a3864: 8fa70084 */ lw $a3,0x84($sp)
/* f1a3868: afb10020 */ sw $s1,0x20($sp)
/* f1a386c: afa20024 */ sw $v0,0x24($sp)
/* f1a3870: afa00028 */ sw $zero,0x28($sp)
/* f1a3874: afa0002c */ sw $zero,0x2c($sp)
/* f1a3878: afad0014 */ sw $t5,0x14($sp)
/* f1a387c: 0fc55d34 */ jal func0f1574d0jf
/* f1a3880: afac0010 */ sw $t4,0x10($sp)
/* f1a3884: 8fa60088 */ lw $a2,0x88($sp)
/* f1a3888: 00408025 */ move $s0,$v0
/* f1a388c: 27a4005c */ addiu $a0,$sp,0x5c
/* f1a3890: 10c00060 */ beqz $a2,.JF0f1a3a14
/* f1a3894: 27a50058 */ addiu $a1,$sp,0x58
/* f1a3898: 3c188008 */ lui $t8,0x8008
/* f1a389c: 8f180148 */ lw $t8,0x148($t8)
/* f1a38a0: 3c078008 */ lui $a3,0x8008
/* f1a38a4: 8ce7014c */ lw $a3,0x14c($a3)
/* f1a38a8: afa00014 */ sw $zero,0x14($sp)
/* f1a38ac: 0fc55d49 */ jal textMeasure
/* f1a38b0: afb80010 */ sw $t8,0x10($sp)
/* f1a38b4: 3c198008 */ lui $t9,0x8008
/* f1a38b8: 8f390148 */ lw $t9,0x148($t9)
/* f1a38bc: 3c078008 */ lui $a3,0x8008
/* f1a38c0: 8ce7014c */ lw $a3,0x14c($a3)
/* f1a38c4: 27a40054 */ addiu $a0,$sp,0x54
/* f1a38c8: 27a50050 */ addiu $a1,$sp,0x50
/* f1a38cc: 8fa6008c */ lw $a2,0x8c($sp)
/* f1a38d0: afa00014 */ sw $zero,0x14($sp)
/* f1a38d4: 0fc55d49 */ jal textMeasure
/* f1a38d8: afb90010 */ sw $t9,0x10($sp)
/* f1a38dc: 8fab0058 */ lw $t3,0x58($sp)
/* f1a38e0: 8fac0050 */ lw $t4,0x50($sp)
/* f1a38e4: 8fad007c */ lw $t5,0x7c($sp)
/* f1a38e8: 8fa20080 */ lw $v0,0x80($sp)
/* f1a38ec: 016c4021 */ addu $t0,$t3,$t4
/* f1a38f0: 8faa005c */ lw $t2,0x5c($sp)
/* f1a38f4: 00087043 */ sra $t6,$t0,0x1
/* f1a38f8: 27b80074 */ addiu $t8,$sp,0x74
/* f1a38fc: 01ae7823 */ subu $t7,$t5,$t6
/* f1a3900: 24420011 */ addiu $v0,$v0,0x11
/* f1a3904: afaf006c */ sw $t7,0x6c($sp)
/* f1a3908: afa20040 */ sw $v0,0x40($sp)
/* f1a390c: afa20068 */ sw $v0,0x68($sp)
/* f1a3910: afb80010 */ sw $t8,0x10($sp)
/* f1a3914: afa80070 */ sw $t0,0x70($sp)
/* f1a3918: 02002025 */ move $a0,$s0
/* f1a391c: 27a5006c */ addiu $a1,$sp,0x6c
/* f1a3920: 27a60068 */ addiu $a2,$sp,0x68
/* f1a3924: 27a70070 */ addiu $a3,$sp,0x70
/* f1a3928: 0fc54bed */ jal text0f153858
/* f1a392c: afaa0074 */ sw $t2,0x74($sp)
/* f1a3930: 0c002eeb */ jal viGetWidth
/* f1a3934: 00408025 */ move $s0,$v0
/* f1a3938: 00028c00 */ sll $s1,$v0,0x10
/* f1a393c: 0011cc03 */ sra $t9,$s1,0x10
/* f1a3940: 0c002eef */ jal viGetHeight
/* f1a3944: 03208825 */ move $s1,$t9
/* f1a3948: 3c0a8008 */ lui $t2,0x8008
/* f1a394c: 3c0b8008 */ lui $t3,0x8008
/* f1a3950: 8d6b0148 */ lw $t3,0x148($t3)
/* f1a3954: 8d4a014c */ lw $t2,0x14c($t2)
/* f1a3958: 8fac0044 */ lw $t4,0x44($sp)
/* f1a395c: 8fad0048 */ lw $t5,0x48($sp)
/* f1a3960: 02002025 */ move $a0,$s0
/* f1a3964: 27a5006c */ addiu $a1,$sp,0x6c
/* f1a3968: 27a60068 */ addiu $a2,$sp,0x68
/* f1a396c: 8fa70088 */ lw $a3,0x88($sp)
/* f1a3970: afb10020 */ sw $s1,0x20($sp)
/* f1a3974: afa20024 */ sw $v0,0x24($sp)
/* f1a3978: afa00028 */ sw $zero,0x28($sp)
/* f1a397c: afa0002c */ sw $zero,0x2c($sp)
/* f1a3980: afab0014 */ sw $t3,0x14($sp)
/* f1a3984: afaa0010 */ sw $t2,0x10($sp)
/* f1a3988: afac0018 */ sw $t4,0x18($sp)
/* f1a398c: 0fc55d34 */ jal func0f1574d0jf
/* f1a3990: afad001c */ sw $t5,0x1c($sp)
/* f1a3994: 8fa30040 */ lw $v1,0x40($sp)
/* f1a3998: 8fae006c */ lw $t6,0x6c($sp)
/* f1a399c: 00408025 */ move $s0,$v0
/* f1a39a0: 24630001 */ addiu $v1,$v1,0x1
/* f1a39a4: 25cffffc */ addiu $t7,$t6,-4
/* f1a39a8: afaf006c */ sw $t7,0x6c($sp)
/* f1a39ac: 0c002eeb */ jal viGetWidth
/* f1a39b0: afa30068 */ sw $v1,0x68($sp)
/* f1a39b4: 00028c00 */ sll $s1,$v0,0x10
/* f1a39b8: 0011c403 */ sra $t8,$s1,0x10
/* f1a39bc: 0c002eef */ jal viGetHeight
/* f1a39c0: 03008825 */ move $s1,$t8
/* f1a39c4: 3c198008 */ lui $t9,0x8008
/* f1a39c8: 3c0a8008 */ lui $t2,0x8008
/* f1a39cc: 8d4a0148 */ lw $t2,0x148($t2)
/* f1a39d0: 8f39014c */ lw $t9,0x14c($t9)
/* f1a39d4: 8fab0044 */ lw $t3,0x44($sp)
/* f1a39d8: 8fac0048 */ lw $t4,0x48($sp)
/* f1a39dc: 02002025 */ move $a0,$s0
/* f1a39e0: 27a5006c */ addiu $a1,$sp,0x6c
/* f1a39e4: 27a60068 */ addiu $a2,$sp,0x68
/* f1a39e8: 8fa7008c */ lw $a3,0x8c($sp)
/* f1a39ec: afb10020 */ sw $s1,0x20($sp)
/* f1a39f0: afa20024 */ sw $v0,0x24($sp)
/* f1a39f4: afa00028 */ sw $zero,0x28($sp)
/* f1a39f8: afa0002c */ sw $zero,0x2c($sp)
/* f1a39fc: afaa0014 */ sw $t2,0x14($sp)
/* f1a3a00: afb90010 */ sw $t9,0x10($sp)
/* f1a3a04: afab0018 */ sw $t3,0x18($sp)
/* f1a3a08: 0fc55d34 */ jal func0f1574d0jf
/* f1a3a0c: afac001c */ sw $t4,0x1c($sp)
/* f1a3a10: 00408025 */ move $s0,$v0
.JF0f1a3a14:
/* f1a3a14: 8fbf003c */ lw $ra,0x3c($sp)
/* f1a3a18: 02001025 */ move $v0,$s0
/* f1a3a1c: 8fb00034 */ lw $s0,0x34($sp)
/* f1a3a20: 8fb10038 */ lw $s1,0x38($sp)
/* f1a3a24: 03e00008 */ jr $ra
/* f1a3a28: 27bd0078 */ addiu $sp,$sp,0x78
);
//Gfx *frRenderHudElement(Gfx *gdl, s32 x, s32 y, char *string1, char *string2, char *string3, u32 colour, u8 alpha)
//{
// s32 textheight; // 74
// s32 textwidth; // 70
// s32 x2; // 6c
// s32 y2; // 68
//
// u32 halfalpha = alpha >> 1;
// u32 fullcolour = (colour & 0xffffff00) | alpha;
//
// textMeasure(&textheight, &textwidth, string1, g_CharsHandelGothicMd, g_FontHandelGothicMd, 0);
//
// x2 = x - (textwidth >> 1);
// y2 = y;
// gdl = text0f153858(gdl, &x2, &y2, &textwidth, &textheight);
//
// gdl = textRender(gdl, &x2, &y2, string1,
// g_CharsHandelGothicMd, g_FontHandelGothicMd, fullcolour, halfalpha, viGetWidth(), viGetHeight(), 0, 0);
//
// if (string2) {
// s32 textheight2; // 5c
// s32 textwidth2; // 58
// s32 textheight3; // 54
// s32 textwidth3; // 50
//
// textMeasure(&textheight2, &textwidth2, string2, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0);
// textMeasure(&textheight3, &textwidth3, string3, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0);
//
// textwidth = textwidth2 + textwidth3;
// textheight = textheight3;
// x2 = x - (textwidth >> 1);
// y2 = y + 17;
//
// gdl = text0f153858(gdl, &x2, &y2, &textwidth, &textheight);
//
// gdl = func0f1574d0jf(gdl, &x2, &y2, string2,
// g_CharsHandelGothicXs, g_FontHandelGothicXs, fullcolour, halfalpha, viGetWidth(), viGetHeight(), 0, 0);
//
// x2 -= 4;
// y2 = y + 18;
//
// gdl = func0f1574d0jf(gdl, &x2, &y2, string3,
// g_CharsHandelGothicXs, g_FontHandelGothicXs, fullcolour, halfalpha, viGetWidth(), viGetHeight(), 0, 0);
// }
//
// return gdl;
//}
#else
Gfx *frRenderHudElement(Gfx *gdl, s32 x, s32 y, char *string1, char *string2, u32 colour, u8 alpha)
{
s32 textheight;
s32 textwidth;
s32 x2;
s32 y2;
u32 halfalpha = alpha >> 1;
u32 fullcolour = (colour & 0xffffff00) | alpha;
textMeasure(&textheight, &textwidth, string1, g_CharsHandelGothicMd, g_FontHandelGothicMd, 0);
x2 = x - (textwidth >> 1);
y2 = y;
gdl = text0f153858(gdl, &x2, &y2, &textwidth, &textheight);
gdl = textRender(gdl, &x2, &y2, string1,
g_CharsHandelGothicMd, g_FontHandelGothicMd, fullcolour, halfalpha, viGetWidth(), viGetHeight(), 0, 0);
if (string2) {
textMeasure(&textheight, &textwidth, string2, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0);
x2 = x - (textwidth >> 1);
y2 = y + 17;
gdl = text0f153858(gdl, &x2, &y2, &textwidth, &textheight);
gdl = textRender(gdl, &x2, &y2, string2,
g_CharsHandelGothicXs, g_FontHandelGothicXs, fullcolour, halfalpha, viGetWidth(), viGetHeight(), 0, 0);
}
return gdl;
}
#endif
#if VERSION >= VERSION_JPN_FINAL
Gfx *frRenderHud(Gfx *gdl)
{
char string1[128];
char string2[128];
char string3[128];
bool red;
bool exists;
s32 alpha = 0xa0;
f32 mult;
if (viGetViewWidth() > 400) {
mult = 1.7f;
} else {
mult = 1;
}
if (!g_FrIsValidWeapon && g_FrData.menucountdown <= 0) {
return gdl;
}
if (g_FrData.menucountdown != 0) {
alpha = (f32)(g_FrData.menucountdown * 160) / TICKS(60.0f);
}
gdl = text0f153628(gdl);
// Time
red = frFormatTime(string1);
exists = frGetHudMiddleSubtext(string2, string3);
gdl = frRenderHudElement(gdl, viGetViewWidth() >> 1, viGetViewTop() + 12,
string1,
exists ? string2 : NULL,
exists ? string3 : NULL,
red ? 0xff4444ff : 0x00ff00a0,
alpha);
// Score
frGetScoreValue(string1);
frGetGoalScoreText(string2, string3);
gdl = frRenderHudElement(gdl, viGetViewLeft() + 65.0f * mult, viGetViewTop() + 12,
string1, string2, string3, 0x00ff00a0, alpha);
// Feedback
if (frGetFeedback(string1, string2, string3)) {
gdl = frRenderHudElement(gdl,viGetViewLeft() + 65.0f * mult, viGetViewTop() + 48,
string1, string2, string3, 0x00ff00a0, alpha);
}
if (g_FrData.goalaccuracy > 0) {
red = frGetMinAccuracy(string2, frGetAccuracy(string1), string3);
gdl = frRenderHudElement(gdl, viGetViewLeft() + viGetViewWidth() - 70.0f * mult, viGetViewTop() + 12,
string1, string2, string3,
red ? 0xff4444ff : 0x00ff00a0,
alpha);
} else if (g_FrData.goaltargets != 255) {
frGetTargetsDestroyedValue(string1);
frGetGoalTargetsText(string2, string3);
if (mult == 2) {
mult = 2.4;
}
gdl = frRenderHudElement(gdl, viGetViewLeft() + viGetViewWidth() - 70.0f * mult, viGetViewTop() + 12,
string1, string2, string3, 0x00ff00a0, alpha);
}
return text0f153780(gdl);
}
#else
Gfx *frRenderHud(Gfx *gdl)
{
char string1[128];
char string2[128];
bool red;
bool exists;
s32 alpha = 0xa0;
f32 mult;
if (viGetViewWidth() > (VERSION >= VERSION_PAL_FINAL ? 330 : 400)) {
mult = VERSION >= VERSION_PAL_FINAL ? 1.5f : 2;
} else {
mult = 1;
}
if (!g_FrIsValidWeapon && g_FrData.menucountdown <= 0) {
return gdl;
}
if (g_FrData.menucountdown != 0) {
alpha = (f32)(g_FrData.menucountdown * 160) / TICKS(60.0f);
}
gdl = text0f153628(gdl);
// Time
red = frFormatTime(string1);
exists = frGetHudMiddleSubtext(string2);
gdl = frRenderHudElement(gdl, viGetViewWidth() >> 1, viGetViewTop() + 12,
string1, exists ? string2 : NULL,
red ? 0xff0000a0 : 0x00ff00a0,
alpha);
// Score
frGetScoreValue(string1);
frGetGoalScoreText(string2);
gdl = frRenderHudElement(gdl, viGetViewLeft() + 65.0f * mult, viGetViewTop() + 12,
string1, string2, 0x00ff00a0, alpha);
// Feedback
if (frGetFeedback(string1, string2)) {
gdl = frRenderHudElement(gdl,viGetViewLeft() + 65.0f * mult, viGetViewTop() + 40,
string1, string2, 0x00ff00a0, alpha);
}
if (g_FrData.goalaccuracy > 0) {
red = frGetMinAccuracy(string2, frGetAccuracy(string1));
gdl = frRenderHudElement(gdl, viGetViewLeft() + viGetViewWidth() - 70.0f * mult, viGetViewTop() + 12,
string1, string2,
red ? 0xff0000a0 : 0x00ff00a0,
alpha);
} else if (g_FrData.goaltargets != 255) {
frGetTargetsDestroyedValue(string1);
frGetGoalTargetsText(string2);
if (mult == 2) {
mult = 2.4;
}
gdl = frRenderHudElement(gdl, viGetViewLeft() + viGetViewWidth() - 70.0f * mult, viGetViewTop() + 12,
string1, string2, 0x00ff00a0, alpha);
}
return text0f153780(gdl);
}
#endif