perfect_dark/src/game/training.c

3899 lines
100 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/atan2f.h"
#include "game/bg.h"
#include "game/bondgun.h"
#include "game/chraction.h"
#include "game/debug.h"
#include "game/dlights.h"
#include "game/explosions.h"
#include "game/filemgr.h"
#include "game/gset.h"
#include "game/text.h"
#include "game/gamefile.h"
#include "game/hudmsg.h"
#include "game/inv.h"
#include "game/lang.h"
#include "game/menu.h"
#include "game/objectives.h"
#include "game/pad.h"
#include "game/padhalllv.h"
#include "game/player.h"
#include "game/prop.h"
#include "game/propobj.h"
#include "game/propsnd.h"
#include "game/shards.h"
#include "game/training.h"
#include "game/trainingmenus.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
#define FRWEAPON_FALCON2 0
#define FRWEAPON_FALCON2_SCOPE 1
#define FRWEAPON_FALCON2_SILENCER 2
#define FRWEAPON_MAGSEC4 3
#define FRWEAPON_MAULER 4
#define FRWEAPON_PHOENIX 5
#define FRWEAPON_DY357MAGNUM 6
#define FRWEAPON_DY357LX 7
#define FRWEAPON_CMP150 8
#define FRWEAPON_CYCLONE 9
#define FRWEAPON_CALLISTO 10
#define FRWEAPON_RCP120 11
#define FRWEAPON_LAPTOPGUN 12
#define FRWEAPON_DRAGON 13
#define FRWEAPON_K7AVENGER 14
#define FRWEAPON_AR34 15
#define FRWEAPON_SUPERDRAGON 16
#define FRWEAPON_SHOTGUN 17
#define FRWEAPON_SNIPERRIFLE 18
#define FRWEAPON_FARSIGHT 19
#define FRWEAPON_CROSSBOW 20
#define FRWEAPON_TRANQUILIZER 21
#define FRWEAPON_REAPER 22
#define FRWEAPON_DEVASTATOR 23
#define FRWEAPON_ROCKETLAUNCHER 24
#define FRWEAPON_SLAYER 25
#define FRWEAPON_COMBATKNIFE 26
#define FRWEAPON_LASER 27
#define FRWEAPON_GRENADE 28
#define FRWEAPON_TIMEDMINE 29
#define FRWEAPON_PROXIMITYMINE 30
#define FRWEAPON_REMOTEMINE 31
#define FR_DIFFICULTY_TO_SCORE(diff) ((diff) + 1)
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,
};
u32 ht_get_stageflag(s32 index);
u32 ci_get_stage_flag_by_device_index(u32 deviceindex);
bool ci_is_tour_done(void)
{
return gamefile_has_flag(GAMEFILEFLAG_CI_TOUR_DONE);
}
u8 fr_get_score(s32 frweaponnum)
{
#if VERSION == VERSION_JPN_FINAL
if (frweaponnum == fr_weaponnum_to_frweaponnum(WEAPON_COMBATKNIFE)) {
// The knife doesn't exist in the JPN version.
// Treat it as completed so unlockables still work.
return FRSCORE_GOLD;
}
#endif
// Data at firingrangescores is a u8 array where each score uses 2 bits
return (g_GameFile.firingrangescores[frweaponnum >> 2] >> (frweaponnum % 4) * 2) & 3;
}
void fr_save_score_if_best(s32 frweaponnum, s32 score)
{
if (score > fr_get_score(frweaponnum)) {
u32 byteindex = frweaponnum >> 2;
u32 shiftamount = (frweaponnum % 4) * 2;
u32 value = g_GameFile.firingrangescores[byteindex];
u32 mask = (1 << shiftamount) + (1 << (shiftamount + 1));
value &= 255 - mask;
value += (score << shiftamount) & mask;
g_GameFile.firingrangescores[byteindex] = value;
}
}
s32 fr_weaponnum_to_slotnum(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 fr_is_weapon_found(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 fr_set_weapon_found(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 ci_is_stage_complete(s32 stageindex)
{
return g_GameFile.besttimes[stageindex][0]
|| g_GameFile.besttimes[stageindex][1]
|| g_GameFile.besttimes[stageindex][2];
}
bool fr_is_weapon_available_for_mp(s32 weapon)
{
if (weapon <= 0 || weapon == WEAPON_PSYCHOSISGUN) {
return false;
}
if (weapon == WEAPON_XRAYSCANNER && ci_is_stage_complete(SOLOSTAGEINDEX_INFILTRATION)) {
return true;
}
if (weapon == WEAPON_CLOAKINGDEVICE && ci_is_stage_complete(SOLOSTAGEINDEX_CHICAGO)) {
return true;
}
return fr_is_weapon_found(weapon);
}
bool fr_is_weapon_available_for_fr(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
#ifdef DEBUG
if (debug_is_all_training_enabled() && weapon <= WEAPON_XRAYSCANNER) {
return true;
}
#endif
#endif
return fr_is_weapon_found(weapon);
}
u32 fr_weaponnum_to_frweaponnum(u32 weaponnum)
{
switch (weaponnum) {
case WEAPON_FALCON2: return FRWEAPON_FALCON2;
case WEAPON_FALCON2_SCOPE: return FRWEAPON_FALCON2_SCOPE;
case WEAPON_FALCON2_SILENCER: return FRWEAPON_FALCON2_SILENCER;
case WEAPON_MAGSEC4: return FRWEAPON_MAGSEC4;
case WEAPON_MAULER: return FRWEAPON_MAULER;
case WEAPON_PHOENIX: return FRWEAPON_PHOENIX;
case WEAPON_DY357MAGNUM: return FRWEAPON_DY357MAGNUM;
case WEAPON_DY357LX: return FRWEAPON_DY357LX;
case WEAPON_CMP150: return FRWEAPON_CMP150;
case WEAPON_CYCLONE: return FRWEAPON_CYCLONE;
case WEAPON_CALLISTO: return FRWEAPON_CALLISTO;
case WEAPON_RCP120: return FRWEAPON_RCP120;
case WEAPON_LAPTOPGUN: return FRWEAPON_LAPTOPGUN;
case WEAPON_DRAGON: return FRWEAPON_DRAGON;
case WEAPON_K7AVENGER: return FRWEAPON_K7AVENGER;
case WEAPON_AR34: return FRWEAPON_AR34;
case WEAPON_SUPERDRAGON: return FRWEAPON_SUPERDRAGON;
case WEAPON_SHOTGUN: return FRWEAPON_SHOTGUN;
case WEAPON_SNIPERRIFLE: return FRWEAPON_SNIPERRIFLE;
case WEAPON_FARSIGHT: return FRWEAPON_FARSIGHT;
case WEAPON_CROSSBOW: return FRWEAPON_CROSSBOW;
case WEAPON_TRANQUILIZER: return FRWEAPON_TRANQUILIZER;
case WEAPON_REAPER: return FRWEAPON_REAPER;
case WEAPON_DEVASTATOR: return FRWEAPON_DEVASTATOR;
case WEAPON_ROCKETLAUNCHER: return FRWEAPON_ROCKETLAUNCHER;
case WEAPON_SLAYER: return FRWEAPON_SLAYER;
case WEAPON_COMBATKNIFE: return FRWEAPON_COMBATKNIFE;
case WEAPON_LASER: return FRWEAPON_LASER;
case WEAPON_GRENADE: return FRWEAPON_GRENADE;
case WEAPON_TIMEDMINE: return FRWEAPON_TIMEDMINE;
case WEAPON_PROXIMITYMINE: return FRWEAPON_PROXIMITYMINE;
case WEAPON_REMOTEMINE: return FRWEAPON_REMOTEMINE;
}
return 0;
}
u32 fr_get_weapon_script_index(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 fr_is_classic_weapon_unlocked(u32 weapon)
{
switch (weapon) {
case WEAPON_PP9I:
return fr_get_score(FRWEAPON_FALCON2) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_FALCON2_SCOPE) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_FALCON2_SILENCER) == FRSCORE_GOLD;
case WEAPON_CC13:
return fr_get_score(FRWEAPON_MAGSEC4) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_MAULER) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_PHOENIX) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_DY357MAGNUM) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_DY357LX) == FRSCORE_GOLD;
case WEAPON_KL01313:
return fr_get_score(FRWEAPON_CMP150) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_CYCLONE) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_CALLISTO) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_RCP120) == FRSCORE_GOLD;
case WEAPON_KF7SPECIAL:
return fr_get_score(FRWEAPON_LAPTOPGUN) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_DRAGON) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_K7AVENGER) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_AR34) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_SUPERDRAGON) == FRSCORE_GOLD;
case WEAPON_ZZT:
return fr_get_score(FRWEAPON_SHOTGUN) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_SNIPERRIFLE) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_ROCKETLAUNCHER) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_SLAYER) == FRSCORE_GOLD;
case WEAPON_DMC:
#if VERSION >= VERSION_NTSC_1_0
return fr_get_score(FRWEAPON_TIMEDMINE) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_PROXIMITYMINE) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_REMOTEMINE) == FRSCORE_GOLD;
#else
return fr_get_score(FRWEAPON_TIMEDMINE) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_PROXIMITYMINE) == FRSCORE_GOLD
&& fr_get_score(32) == FRSCORE_GOLD
&& fr_get_score(33) == FRSCORE_GOLD
&& fr_get_score(34) == FRSCORE_GOLD;
#endif
case WEAPON_AR53:
return fr_get_score(FRWEAPON_FARSIGHT) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_CROSSBOW) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_COMBATKNIFE) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_GRENADE) == FRSCORE_GOLD;
case WEAPON_RCP45:
return fr_get_score(FRWEAPON_TRANQUILIZER) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_REAPER) == FRSCORE_GOLD
&& fr_get_score(FRWEAPON_DEVASTATOR) == FRSCORE_GOLD;
}
return false;
}
s32 fr_get_slot(void)
{
return g_FrData.slot;
}
void fr_set_slot(s32 slot)
{
g_FrData.slot = slot;
}
u32 fr_get_weapon_by_slot(s32 slot)
{
s32 index = -1;
s32 weapon;
for (weapon = WEAPON_NONE; weapon <= WEAPON_HORIZONSCANNER; weapon++) {
if (fr_is_weapon_available_for_fr(weapon)) {
index++;
}
if (slot == index) {
return weapon;
}
}
return WEAPON_UNARMED;
}
s32 fr_get_num_weapons_available(void)
{
s32 count = 0;
s32 i;
for (i = WEAPON_UNARMED; i <= WEAPON_HORIZONSCANNER; i++) {
if (fr_is_weapon_available_for_fr(i)) {
count++;
}
}
return count;
}
void fr_init_lighting(void)
{
if (g_FrData.donelighting == false) {
s32 roomnum;
for (roomnum = ROOM_DISH_0007; roomnum <= ROOM_DISH_0009; roomnum++) {
room_set_light_op(roomnum, LIGHTOP_TRANSITION, 50, 100, TICKS(32));
}
room_set_light_op(ROOM_DISH_FIRINGRANGE, LIGHTOP_TRANSITION, 25, 100, TICKS(32));
g_FrData.donelighting = true;
snd_start(var80095200, SFX_FR_LIGHTSON, NULL, -1, -1, -1, -1, -1);
}
chr_set_stage_flag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
void fr_restore_lighting(void)
{
if (g_FrData.donelighting == true) {
s32 roomnum;
for (roomnum = ROOM_DISH_0007; roomnum <= ROOM_DISH_0009; roomnum++) {
room_set_light_op(roomnum, LIGHTOP_TRANSITION, 100, 50, TICKS(8));
}
room_set_light_op(ROOM_DISH_FIRINGRANGE, LIGHTOP_TRANSITION, 100, 25, TICKS(8));
g_FrData.donelighting = false;
snd_start(var80095200, SFX_FR_LIGHTSOFF, NULL, -1, -1, -1, -1, -1);
}
}
void fr_reset(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 *fr_load_rom_data(u32 len)
{
g_FrRomData = memp_alloc(ALIGN16(len), MEMPOOL_STAGE);
if (g_FrRomData) {
return dma_exec_with_auto_align(g_FrRomData, (romptr_t) &_firingrangeSegmentRomStart, len);
}
return NULL;
}
void fr_set_difficulty(s32 difficulty)
{
if (difficulty < FRDIFFICULTY_BRONZE) {
difficulty = FRDIFFICULTY_BRONZE;
}
if (difficulty > FRDIFFICULTY_GOLD) {
difficulty = FRDIFFICULTY_GOLD;
}
g_FrData.difficulty = difficulty;
}
u32 fr_get_difficulty(void)
{
return g_FrData.difficulty;
}
void fr_init_defaults(void)
{
s32 i;
struct pad pad;
g_FrNumSounds = 0;
pad_unpack(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;
wallhits_free_by_prop(g_FrData.targets[i].prop, 0);
wallhits_free_by_prop(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 *fr_get_data(void)
{
return &g_FrData;
}
u32 fr_resolve_fr_pad(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 fr_is_difficulty(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 fr_execute_weapon_script(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 (!fr_is_difficulty(script[offset + 4])) {
offset += 5;
break;
}
if (g_FrData.numtargets < ARRAYCOUNT(g_FrData.targets)) {
g_FrData.targets[g_FrData.numtargets].frpadindex = fr_resolve_fr_pad(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 = bgun_get_capacity_by_ammotype(bgun_get_ammo_type_for_weapon(fr_get_weapon_by_slot(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 = bgun_get_capacity_by_ammotype(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 fr_set_target_props(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 = obj_find_by_tag_id(targets[i]);
if (obj) {
g_FrData.targets[i].prop = obj->prop;
obj->flags2 |= OBJFLAG2_INVISIBLE;
}
}
}
s32 g_FrWeaponNum = WEAPON_UNARMED;
bool fr_target_is_at_script_start(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 *fr_get_instructional_text(u32 index)
{
u16 textid = (u16)(g_FrRomData[index * 2] << 8) | g_FrRomData[index * 2 + 1];
return lang_get(textid);
}
void fr_execute_help_script(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:
hudmsg_create(fr_get_instructional_text(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.lvupdate60;
if (g_FrData.helpscriptsleep <= 0) {
g_FrData.helpscriptsleep = 0;
}
}
}
bool fr_execute_target_script(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 = fr_resolve_fr_pad(script[offset + 1]);
if (frpadnum == g_FrData.targets[targetnum].frpadnum) {
g_FrData.targets[targetnum].scriptoffset += 4;
return false;
}
g_FrData.targets[targetnum].frpadnum = frpadnum;
pad_unpack(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++;
ps_create(NULL, g_FrData.targets[targetnum].prop, SFX_FR_CONVEYER, -1,
-1, 0, 0, PSTYPE_NONE, 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] = DTOR(-90);
angles[1] = DTOR(-180);
angles[2] = DTOR(90);
angles[3] = DTOR(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 fr_hide_all_targets(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;
ps_stop_sound(prop, PSTYPE_GENERAL, 0xffff);
}
}
void fr_init_targets(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;
obj_free(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;
}
pad_unpack(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
fr_execute_target_script(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
pad_unpack(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) {
mtx4_load_y_rotation(0.0f, &sp144);
g_FrData.targets[i].angle = DTOR(180);
} else {
mtx4_load_y_rotation(DTOR(180), &sp144);
}
mtx00015f04(obj->model->scale, &sp144);
mtx4_to_mtx3(&sp144, sp108);
mtx3_copy(sp108, obj->realrot);
prop->pos.x = pos.x;
prop->pos.y = pos.y;
prop->pos.z = pos.z;
obj_onmoved(obj, true, false);
}
}
}
void fr_close_and_lock_door(void)
{
struct defaultobj *obj = obj_find_by_tag_id(0x91);
if (obj && obj->prop && obj->prop->type == PROPTYPE_DOOR) {
struct doorobj *door = (struct doorobj *)obj;
door->keyflags |= 0x40;
doors_request_mode(door, DOORMODE_CLOSING);
}
}
void fr_unlock_door(void)
{
struct defaultobj *obj = obj_find_by_tag_id(0x91);
if (obj && obj->prop && obj->prop->type == PROPTYPE_DOOR) {
struct doorobj *door = (struct doorobj *)obj;
door->keyflags &= ~0x40;
}
}
void fr_load_data(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;
fr_load_rom_data(len);
for (i = 0x12; i < len; i++) {
if (g_FrRomData[i] == FRCMD_START) {
numscripts++;
}
}
size = numscripts * sizeof(*g_FrScriptOffsets);
g_FrScriptOffsets = memp_alloc(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++;
}
}
}
fr_set_target_props();
g_FrData.slot = 0;
g_FrData.difficulty = FRDIFFICULTY_BRONZE;
g_FrData.donelighting = false;
}
}
u32 fr_init_ammo(s32 weaponnum)
{
u32 scriptindex;
u32 ammotype = bgun_get_ammo_type_for_weapon(weaponnum, 0);
u32 capacity = bgun_get_capacity_by_ammotype(ammotype);
fr_init_defaults();
scriptindex = fr_get_weapon_script_index(weaponnum);
fr_execute_weapon_script(scriptindex);
if (g_FrData.ammolimit == 255) {
bgun_set_ammo_quantity(ammotype, capacity);
} else {
bgun_set_ammo_quantity(ammotype, g_FrData.ammolimit);
}
if (weaponnum == WEAPON_SUPERDRAGON) {
if (g_FrData.sdgrenadelimit == 255) {
bgun_set_ammo_quantity(AMMOTYPE_DEVASTATOR, capacity);
} else {
bgun_set_ammo_quantity(AMMOTYPE_DEVASTATOR, g_FrData.sdgrenadelimit);
}
}
return scriptindex;
}
void fr_begin_session(s32 weapon)
{
s32 i;
struct defaultobj *obj = obj_find_by_tag_id(0x7f); // computer
if (obj) {
obj->flags |= OBJFLAG_CANNOT_ACTIVATE;
}
fr_close_and_lock_door();
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 = fr_init_ammo(weapon) == 0 ? false : true;
fr_init_targets();
bgun_set_passive_mode(false);
}
char *fr_get_weapon_description(void)
{
u32 weapon = fr_get_weapon_by_slot(g_FrData.slot);
switch (weapon) {
#if VERSION >= VERSION_PAL_BETA
case WEAPON_FALCON2: return lang_get(L_DISH_283);
case WEAPON_FALCON2_SCOPE: return lang_get(L_DISH_284);
case WEAPON_FALCON2_SILENCER: return lang_get(L_DISH_285);
case WEAPON_MAGSEC4: return lang_get(L_DISH_286);
case WEAPON_MAULER: return lang_get(L_DISH_287);
case WEAPON_PHOENIX: return lang_get(L_DISH_288);
case WEAPON_DY357MAGNUM: return lang_get(L_DISH_289);
case WEAPON_DY357LX: return lang_get(L_DISH_290);
case WEAPON_CMP150: return lang_get(L_DISH_291);
case WEAPON_CYCLONE: return lang_get(L_DISH_292);
case WEAPON_CALLISTO: return lang_get(L_DISH_293);
case WEAPON_RCP120: return lang_get(L_DISH_294);
case WEAPON_LAPTOPGUN: return lang_get(L_DISH_295);
case WEAPON_DRAGON: return lang_get(L_DISH_296);
case WEAPON_K7AVENGER: return lang_get(L_DISH_297);
case WEAPON_AR34: return lang_get(L_DISH_298);
case WEAPON_SUPERDRAGON: return lang_get(L_DISH_299);
case WEAPON_SHOTGUN: return lang_get(L_DISH_300);
case WEAPON_SNIPERRIFLE: return lang_get(L_DISH_301);
case WEAPON_FARSIGHT: return lang_get(L_DISH_302);
case WEAPON_CROSSBOW: return lang_get(L_DISH_303);
case WEAPON_TRANQUILIZER: return lang_get(L_DISH_304);
case WEAPON_REAPER: return lang_get(L_DISH_305);
case WEAPON_DEVASTATOR: return lang_get(L_DISH_306);
case WEAPON_ROCKETLAUNCHER: return lang_get(L_DISH_307);
case WEAPON_SLAYER: return lang_get(L_DISH_308);
case WEAPON_COMBATKNIFE: return lang_get(L_DISH_309);
case WEAPON_LASER: return lang_get(L_DISH_310);
case WEAPON_GRENADE: return lang_get(L_DISH_311);
case WEAPON_NBOMB: return lang_get(L_DISH_312);
case WEAPON_TIMEDMINE: return lang_get(L_DISH_313);
case WEAPON_PROXIMITYMINE: return lang_get(L_DISH_314);
case WEAPON_REMOTEMINE: return lang_get(L_DISH_315);
#else
case WEAPON_FALCON2: return lang_get(L_MISC_377);
case WEAPON_FALCON2_SCOPE: return lang_get(L_MISC_378);
case WEAPON_FALCON2_SILENCER: return lang_get(L_MISC_379);
case WEAPON_MAGSEC4: return lang_get(L_MISC_380);
case WEAPON_MAULER: return lang_get(L_MISC_381);
case WEAPON_PHOENIX: return lang_get(L_MISC_382);
case WEAPON_DY357MAGNUM: return lang_get(L_MISC_383);
case WEAPON_DY357LX: return lang_get(L_MISC_384);
case WEAPON_CMP150: return lang_get(L_MISC_385);
case WEAPON_CYCLONE: return lang_get(L_MISC_386);
case WEAPON_CALLISTO: return lang_get(L_MISC_387);
case WEAPON_RCP120: return lang_get(L_MISC_388);
case WEAPON_LAPTOPGUN: return lang_get(L_MISC_389);
case WEAPON_DRAGON: return lang_get(L_MISC_390);
case WEAPON_K7AVENGER: return lang_get(L_MISC_391);
case WEAPON_AR34: return lang_get(L_MISC_392);
case WEAPON_SUPERDRAGON: return lang_get(L_MISC_393);
case WEAPON_SHOTGUN: return lang_get(L_MISC_394);
case WEAPON_SNIPERRIFLE: return lang_get(L_MISC_395);
case WEAPON_FARSIGHT: return lang_get(L_MISC_396);
case WEAPON_CROSSBOW: return lang_get(L_MISC_397);
case WEAPON_TRANQUILIZER: return lang_get(L_MISC_398);
case WEAPON_REAPER: return lang_get(L_MISC_399);
case WEAPON_DEVASTATOR: return lang_get(L_MISC_400);
case WEAPON_ROCKETLAUNCHER: return lang_get(L_MISC_401);
case WEAPON_SLAYER: return lang_get(L_MISC_402);
case WEAPON_COMBATKNIFE: return lang_get(L_MISC_403);
case WEAPON_LASER: return lang_get(L_MISC_404);
case WEAPON_GRENADE: return lang_get(L_MISC_405);
case WEAPON_NBOMB: return lang_get(L_MISC_406);
case WEAPON_TIMEDMINE: return lang_get(L_MISC_407);
case WEAPON_PROXIMITYMINE: return lang_get(L_MISC_408);
case WEAPON_REMOTEMINE: return lang_get(L_MISC_409);
#endif
}
return NULL;
}
void fr_end_session(bool hidetargets)
{
s32 i;
s32 j;
s16 propnums[256];
s16 *propnumptr;
RoomNum rooms[20];
u32 stack1;
RoomNum rooms2[10];
u32 stack2;
if (g_FrDataLoaded) {
struct defaultobj *terminal = obj_find_by_tag_id(0x7f);
if (terminal) {
terminal->flags &= ~OBJFLAG_CANNOT_ACTIVATE;
}
fr_unlock_door();
if (g_Vars.currentplayer->visionmode == VISIONMODE_SLAYERROCKET) {
g_Vars.currentplayer->visionmode = VISIONMODE_NORMAL;
}
bgun_set_passive_mode(true);
g_FrIsValidWeapon = 0;
fr_restore_lighting();
if (hidetargets) {
fr_hide_all_targets();
}
if (g_ThrownLaptops[0].base.prop) {
obj_free_permanently(&g_ThrownLaptops[0].base, true);
}
rooms_copy(g_Vars.currentplayer->prop->rooms, rooms);
for (i = 0; g_Vars.currentplayer->prop->rooms[i] != -1; i++) {
bg_room_get_neighbours(g_Vars.currentplayer->prop->rooms[i], rooms2, 10);
rooms_append(rooms2, rooms, 20);
}
// Remove projectiles and throwables
room_get_props(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) {
obj_free_permanently(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)) {
obj_free_permanently(obj, true);
}
}
}
}
propnumptr++;
}
// Remove explosions
for (i = 0; i < g_MaxExplosions; i++) {
g_Explosions[i].age = 256;
for (j = 0; j < ARRAYCOUNT(g_Explosions[i].parts); 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 < ARRAYCOUNT(g_Smokes[i].parts); j++) {
g_Smokes[i].parts[j].size = 0;
}
}
}
player_display_health();
g_Vars.currentplayer->bondhealth = 1;
}
bool fr_was_too_inaccurate(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 fr_set_fail_reason(s32 failreason)
{
fr_end_session(false);
g_FrData.failreason = fr_was_too_inaccurate() ? FRFAILREASON_INACCURATE : failreason;
g_FrData.menutype = FRMENUTYPE_FAILED;
g_FrData.menucountdown = TICKS(60);
}
void fr_set_completed(void)
{
fr_end_session(false);
if (fr_was_too_inaccurate()) {
g_FrData.failreason = FRFAILREASON_INACCURATE;
g_FrData.menutype = FRMENUTYPE_FAILED;
} else {
u32 frweaponindex = fr_weaponnum_to_frweaponnum(fr_get_weapon_by_slot(g_FrData.slot));
fr_save_score_if_best(frweaponindex, FR_DIFFICULTY_TO_SCORE(g_FrData.difficulty));
g_FrData.menutype = FRMENUTYPE_COMPLETED;
}
g_FrData.menucountdown = TICKS(60);
}
bool fr_is_target_one_hit_explodable(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 fr_get_target_angle_to_pos(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 += BADDTOR(360);
}
return relativeangle;
}
bool fr_is_target_facing_pos(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 = fr_get_target_angle_to_pos(&prop->pos, g_FrData.targets[i].angle, pos);
if (angle > DTOR(90) && angle < BADDTOR(270)) {
return false;
}
return true;
}
}
return true;
}
struct prop *fr_choose_autogun_target(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 = fr_get_target_angle_to_pos(&g_FrData.targets[i].prop->pos, g_FrData.targets[i].angle, autogunpos);
if (angle > DTOR(90) && angle < BADDTOR(270)) {
// facing away
} else {
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 fr_is_ammo_wasted(void)
{
s32 weaponnum = fr_get_weapon_by_slot(g_FrData.slot);
s32 i;
s32 priammotype = bgun_get_ammo_type_for_weapon(weaponnum, 0);
s32 secammotype = bgun_get_ammo_type_for_weapon(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];
RoomNum rooms20[22];
RoomNum 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] = bgun_get_reserved_ammo_count(priammotype) + ammoloaded[0];
ammototal[1] = bgun_get_reserved_ammo_count(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 != MAX_EXPLOSIONS; 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) {
rooms_copy(g_Vars.currentplayer->prop->rooms, rooms20);
for (i = 0; g_Vars.currentplayer->prop->rooms[i] != -1; i++) {
bg_room_get_neighbours(g_Vars.currentplayer->prop->rooms[i], rooms10, 10);
rooms_append(rooms10, rooms20, 20);
}
room_get_props(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_PROJECTILE) {
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 = bgun_get_ammo_type_for_weapon(weaponnum, 0);
hand = &g_Vars.currentplayer->hands[HAND_RIGHT];
if (bgun_get_reserved_ammo_count(ammotype) + hand->loadedammo[0] == 0) {
g_FrData.proxyendtimer = TICKS(300);
}
return false;
}
g_FrData.proxyendtimer -= g_Vars.lvupdate60;
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 fr_tick(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 ammodef *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.throwing == false
&& inv_has_single_weapon_inc_all_guns(fr_get_weapon_by_slot(g_FrData.slot))) {
bgun_equip_weapon(fr_get_weapon_by_slot(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;
ps_stop_sound(g_FrData.targets[i].prop, PSTYPE_GENERAL, 0xffff);
}
}
g_Vars.currentplayer->training = false;
fr_end_session(true);
g_FrData.menucountdown = 0; // This assignment is in NTSC beta only
chr_unset_stage_flag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
return;
}
#endif
// Handle the menu countdown
if (g_FrData.menucountdown != 0) {
g_FrData.menucountdown -= g_Vars.lvupdate60;
// 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) {
ps_stop_sound(g_FrData.targets[i].prop, PSTYPE_GENERAL, 0xffff);
}
}
switch (g_FrData.menutype) {
case FRMENUTYPE_WEAPONLIST:
menu_push_root_dialog_and_pause(ci_get_fr_weapon_list_menu_dialog(), MENUROOT_TRAINING);
break;
case FRMENUTYPE_DETAILS:
menu_push_root_dialog_and_pause(&g_FrTrainingInfoPreGameMenuDialog, MENUROOT_TRAINING);
break;
case FRMENUTYPE_FAILED:
snd_start(var80095200, SFX_TRAINING_FAIL, NULL, -1, -1, -1, -1, -1);
menu_push_root_dialog_and_pause(&g_FrFailedMenuDialog, MENUROOT_TRAINING);
break;
case FRMENUTYPE_COMPLETED:
snd_start(var80095200, SFX_TRAINING_COMPLETE, NULL, -1, -1, -1, -1, -1);
menu_push_root_dialog_and_pause(&g_FrCompletedMenuDialog, MENUROOT_TRAINING);
filemgr_save_or_load(&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;
ps_stop_sound(g_FrData.targets[i].prop, PSTYPE_GENERAL, 0xffff);
}
}
g_Vars.currentplayer->training = false;
fr_end_session(true);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
return;
}
#endif
if (!g_FrIsValidWeapon) {
return;
}
if (g_Vars.currentplayer->isdead) {
fr_end_session(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;
ps_stop_sound(g_FrData.targets[i].prop, PSTYPE_GENERAL, 0xffff);
}
}
return;
}
g_Vars.currentplayer->training = true;
fr_execute_help_script();
// 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 = fr_get_weapon_by_slot(g_FrData.slot);
ammotype = bgun_get_ammo_type_for_weapon(weaponnum, 0);
capacity = bgun_get_capacity_by_ammotype(ammotype);
ammo = gset_get_ammodef(weaponnum, 0);
capacity -= (ammo ? ammo->clipsize : 0);
if (g_FrData.ammoextra > 0) {
tmp = bgun_get_reserved_ammo_count(ammotype);
g_FrData.ammoextra -= g_FrData.numshotssincetopup;
if (g_FrData.ammoextra < 0) {
g_FrData.ammoextra = 0;
}
capacity = tmp + g_FrData.numshotssincetopup;
bgun_set_ammo_quantity(ammotype, capacity);
} else if (g_FrData.ammoextra == -1) {
bgun_set_ammo_quantity(ammotype, capacity);
}
if (weaponnum == WEAPON_SUPERDRAGON) {
capacity = bgun_get_capacity_by_ammotype(AMMOTYPE_DEVASTATOR);
if (g_FrData.sdgrenadeextra > 0) {
tmp = bgun_get_reserved_ammo_count(AMMOTYPE_DEVASTATOR);
g_FrData.sdgrenadeextra -= g_FrData.numshotssincetopup;
if (g_FrData.sdgrenadeextra < 0) {
g_FrData.sdgrenadeextra = 0;
}
capacity = tmp + g_FrData.numshotssincetopup;
bgun_set_ammo_quantity(AMMOTYPE_DEVASTATOR, capacity);
} else if (g_FrData.sdgrenadeextra == -1) {
bgun_set_ammo_quantity(AMMOTYPE_DEVASTATOR, capacity);
}
}
g_FrData.numshotssincetopup = 0;
}
g_FrData.timetaken += g_Vars.lvupdate60;
// 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;
snd_start(var80095200, SFX_FR_ALARM, NULL, -1, -1, -1, -1, -1);
}
if (!g_FrData.donelighting && g_FrData.timetaken > TICKS(-225)) {
fr_init_lighting();
}
return;
}
// Fired a shot during prestart
if (!g_FrData.donelighting) {
fr_init_lighting();
}
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 = fr_get_weapon_by_slot(g_FrData.slot);
prop = g_FrData.targets[i].prop;
obj = prop->obj;
switch (weaponnum2) {
case WEAPON_GRENADE:
case WEAPON_PROXIMITYMINE:
coord_trigger_proxies(&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;
ps_create(NULL, g_FrData.targets[i].prop, SFX_FR_CONVEYER, -1,
-1, 0, 0, PSTYPE_NONE, 0, -1, 0, -1, -1, -1, -1);
}
if (g_FrData.targets[i].angle > DTOR(131.29858422661f) && g_FrData.targets[i].angle < DTOR2(228.7014257913f)) {
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.lvupdate60;
}
if (obj->damage > 0) {
if (invincible || g_FrData.targets[i].angle == DTOR(180)) {
obj->damage = 0;
} else if (g_FrData.targets[i].flags & FRTARGETFLAG_ONEHITEXPLODE
|| obj->damage >= obj->maxdamage
|| fr_get_weapon_by_slot(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 = obj_find_bbox_rodata(obj);
if (g_FrNumSounds && g_FrData.targets[i].travelling) {
g_FrNumSounds--;
ps_stop_sound(prop, PSTYPE_GENERAL, 0xffff);
}
if (g_FrNumSounds);
shards_create(&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) {
explosion_create_simple(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;
obj_onmoved(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) {
fr_set_completed();
return;
}
} else {
if (g_FrData.targetsdestroyed >= g_FrData.goaltargets
&& (g_FrData.goalscore == 0 || g_FrData.score >= g_FrData.goalscore)) {
fr_set_completed();
return;
}
}
if (g_FrData.targetsdestroyed >= g_FrData.numtargets) {
fr_set_fail_reason(FRFAILREASON_SCOREUNATTAINABLE);
return;
}
if (fr_is_ammo_wasted()) {
fr_set_fail_reason(FRFAILREASON_OUTOFAMMO);
return;
}
if (g_FrData.timelimit != 255 && g_FrData.timetaken >= g_FrData.timelimit * TICKS(60)) {
fr_set_fail_reason(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.lvupdate60freal;
#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--;
}
ps_stop_sound(prop, PSTYPE_GENERAL, 0xffff);
ps_create(NULL, prop, SFX_FR_CONVEYER_STOP, -1,
-1, PSFLAG_0400, 0, PSTYPE_NONE, 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;
obj_onmoved(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 == DTOR(180)) {
g_FrData.targets[i].timeuntilrotate = TICKS(60);
g_FrData.targets[i].rotatetoangle = 0;
g_FrData.targets[i].rotatespeed = DTOR(-180) / 90;
}
} else {
if (g_FrData.targets[i].angle == 0) {
g_FrData.targets[i].timeuntilrotate = TICKS(60);
g_FrData.targets[i].rotatetoangle = DTOR(180);
g_FrData.targets[i].rotatespeed = DTOR(180) / 90;
}
}
} else {
g_FrData.targets[i].timeuntilrotate -= g_Vars.lvupdate60;
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.lvupdate60freal;
#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 > BADDTOR(360)) {
g_FrData.targets[i].angle -= BADDTOR(360);
}
while (g_FrData.targets[i].angle < 0) {
g_FrData.targets[i].angle += BADDTOR(360);
}
}
mtx4_load_y_rotation(g_FrData.targets[i].angle + DTOR(180), &spbc);
mtx00015f04(obj->model->scale, &spbc);
mtx4_to_mtx3(&spbc, sp98);
mtx3_copy(sp98, obj->realrot);
}
if (g_FrData.targets[i].scriptenabled && g_FrData.targets[i].scriptsleep != SECSTOTIME60(255)) {
g_FrData.targets[i].scriptsleep -= g_Vars.lvupdate60;
if (g_FrData.targets[i].scriptsleep <= 0) {
g_FrData.targets[i].scriptenabled = false;
while (!fr_execute_target_script(i));
if (fr_target_is_at_script_start(i)) {
while (!fr_execute_target_script(i));
}
}
}
}
}
}
void fr_track_target(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;
model_get_screen_coords(obj->model, &sp56, &sp64, &sp60, &sp68);
for (i = 0; i < ARRAYCOUNT(g_Vars.currentplayer->trackedprops); 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 fr_choose_farsight_target(void)
{
struct prop *bestprop = NULL;
f32 bestvalue = 1;
f32 bestdist = -1;
bool found = false;
s32 i;
if (bgun_get_weapon_num(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.pos.x - prop->pos.x;
f32 ydiff = g_Vars.currentplayer->bond2.pos.y - prop->pos.y;
f32 zdiff = g_Vars.currentplayer->bond2.pos.z - prop->pos.z;
f32 dist = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
if (dist > 0) {
f32 value = (xdiff * g_Vars.currentplayer->bond2.look.f[0]
+ ydiff * g_Vars.currentplayer->bond2.look.f[1]
+ zdiff * g_Vars.currentplayer->bond2.look.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 fr_is_in_training(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
&& main_get_stage_num() == STAGE_CITRAINING;
}
void fr_calculate_hit(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 (fr_get_weapon_by_slot(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 fr_increment_num_shots(void)
{
g_FrData.numshots++;
g_FrData.numshotssincetopup++;
}
bool ci_is_chr_bio_unlocked(u32 bodynum)
{
switch (bodynum) {
case BODY_DARK_COMBAT:
case BODY_CARRINGTON:
return true;
case BODY_CASSANDRA:
return ci_is_stage_complete(SOLOSTAGEINDEX_DEFECTION);
case BODY_DRCAROLL:
return ci_is_stage_complete(SOLOSTAGEINDEX_INVESTIGATION);
case BODY_MRBLONDE:
return ci_is_stage_complete(SOLOSTAGEINDEX_EXTRACTION);
case BODY_TRENT:
return ci_is_stage_complete(SOLOSTAGEINDEX_G5BUILDING);
case BODY_JONATHAN:
return ci_is_stage_complete(SOLOSTAGEINDEX_INFILTRATION);
case BODY_THEKING:
return ci_is_stage_complete(SOLOSTAGEINDEX_RESCUE);
case BODY_PRESIDENT:
return ci_is_stage_complete(SOLOSTAGEINDEX_AIRFORCEONE);
}
return false;
}
u8 g_ChrBioSlot = 0;
struct chrbio *ci_get_chr_bio_by_bodynum(u32 bodynum)
{
#ifdef AVOID_UB
static
#endif
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 (ci_is_stage_complete(SOLOSTAGEINDEX_CRASHSITE)) {
return &bios[8];
}
return &bios[7];
case BODY_PRESIDENT:
return &bios[9];
}
return NULL;
}
char *ci_get_chr_bio_description(void)
{
struct chrbio *bio = ci_get_chr_bio_by_bodynum(ci_get_chr_bio_bodynum_by_slot(g_ChrBioSlot));
return lang_get(bio->description);
}
s32 ci_get_num_unlocked_chr_bios(void)
{
s32 count = 0;
s32 bodynum;
for (bodynum = 0; bodynum < ARRAYCOUNT(g_HeadsAndBodies) - 1; bodynum++) {
if (ci_is_chr_bio_unlocked(bodynum)) {
count++;
}
}
return count;
}
s32 ci_get_chr_bio_bodynum_by_slot(s32 slot)
{
s32 index = -1;
s32 bodynum;
for (bodynum = 0; bodynum < ARRAYCOUNT(g_HeadsAndBodies) - 1; bodynum++) {
if (ci_is_chr_bio_unlocked(bodynum)) {
index++;
}
if (index == slot) {
return bodynum;
}
}
return 0;
}
struct miscbio *ci_get_misc_bio(s32 index)
{
#ifdef AVOID_UB
static
#endif
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 ci_is_misc_bio_unlocked(s32 index)
{
switch (index) {
case MISCBIO_MAIANS:
return ci_is_stage_complete(SOLOSTAGEINDEX_RESCUE);
case MISCBIO_SKEDAR:
return ci_is_stage_complete(SOLOSTAGEINDEX_ATTACKSHIP);
case MISCBIO_BACKGROUND:
case MISCBIO_STORY:
return ci_is_stage_complete(SOLOSTAGEINDEX_MBR);
}
return false;
}
s32 ci_get_num_unlocked_misc_bios(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 4; i++) {
if (ci_is_misc_bio_unlocked(i)) {
count++;
}
}
return count;
}
s32 ci_get_misc_bio_index_by_slot(s32 slot)
{
s32 index = -1;
s32 i;
for (i = 0; i < 4; i++) {
if (ci_is_misc_bio_unlocked(i)) {
index++;
}
if (index == slot) {
return i;
}
}
return 0;
}
char *ci_get_misc_bio_description(void)
{
s32 index = ci_get_misc_bio_index_by_slot(g_ChrBioSlot - ci_get_num_unlocked_chr_bios());
struct miscbio *bio = ci_get_misc_bio(index);
return lang_get(bio->description);
}
bool ci_is_hangar_bio_a_vehicle(s32 index)
{
return index >= HANGARBIO_JUMPSHIP;
}
u8 g_HangarBioSlot = 0;
struct hangarbio *ci_get_hangar_bio(s32 index)
{
#ifdef AVOID_UB
static
#endif
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;
}
bool ci_is_hangar_bio_unlocked(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 ci_is_stage_complete(stage);
}
s32 ci_get_num_unlocked_location_bios(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 23; i++) {
if (ci_is_hangar_bio_a_vehicle(i)) {
return count;
}
if (ci_is_hangar_bio_unlocked(i)) {
count++;
}
}
return count;
}
s32 ci_get_num_unlocked_hangar_bios(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < 23; i++) {
if (ci_is_hangar_bio_unlocked(i)) {
count++;
}
}
return count;
}
s32 ci_get_hangar_bio_index_by_slot(s32 slot)
{
s32 index = -1;
s32 i;
for (i = 0; i < 23; i++) {
if (ci_is_hangar_bio_unlocked(i)) {
index++;
}
if (index == slot) {
return i;
}
}
return 0;
}
char *ci_get_hangar_bio_description(void)
{
struct hangarbio *bio = ci_get_hangar_bio(ci_get_hangar_bio_index_by_slot(g_HangarBioSlot));
return lang_get(bio->description);
}
u8 g_DtSlot = 0;
u8 g_DtInitialised = 0;
struct trainingdata *dt_get_data(void)
{
return &g_DtData;
}
void dt_restore_player(void)
{
bgun_set_passive_mode(true);
if (g_DtData.obj) {
obj_free_permanently(g_DtData.obj, true);
}
g_DtData.obj = NULL;
if (dt_get_weapon_by_device_index(dt_get_index_by_slot(g_DtSlot)) == WEAPON_ECMMINE) {
bgun_set_ammo_quantity(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;
ps_stop_sound(g_Vars.currentplayer->eyespy->prop, PSTYPE_GENERAL, 0xffff);
g_Vars.currentplayer->devicesactive &= ~DEVICE_EYESPY;
}
}
void dt_push_endscreen(void)
{
if (g_DtData.completed) {
menu_push_root_dialog_and_pause(&g_DtCompletedMenuDialog, MENUROOT_TRAINING);
} else if (g_DtData.failed) {
menu_push_root_dialog_and_pause(&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 dt_tick(void)
{
if (g_DtInitialised) {
if (g_DtData.intraining) {
g_DtData.timetaken += g_Vars.lvupdate60;
if (g_Vars.currentplayer->isdead) {
dt_end();
}
if (chr_has_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE)) {
dt_end();
g_DtData.failed = true;
g_DtData.timeleft = 1;
g_DtData.finished = true;
} else if (chr_has_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_SUCCESS)) {
dt_end();
g_DtData.completed = true;
g_DtData.timeleft = 1;
g_DtData.finished = true;
}
} else if (g_DtData.finished) {
if (g_DtData.timeleft <= 0) {
dt_push_endscreen();
} else {
g_DtData.timeleft -= g_Vars.lvupdate60;
}
}
}
}
void dt_reset(void)
{
if (g_DtInitialised == false) {
g_DtInitialised = 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;
chr_unset_stage_flag(NULL, STAGEFLAG_CI_DEVICE_ABORTING);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_SUCCESS);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE);
}
}
void dt_begin(void)
{
g_DtData.intraining = true;
g_DtData.timetaken = 0;
chr_unset_stage_flag(NULL, STAGEFLAG_CI_DEVICE_ABORTING);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_SUCCESS);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE);
chr_set_stage_flag(NULL, ci_get_stage_flag_by_device_index(dt_get_index_by_slot(g_DtSlot)));
g_Vars.currentplayer->training = true;
bgun_set_passive_mode(false);
chr_set_stage_flag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
void dt_end(void)
{
g_DtData.intraining = false;
dt_restore_player();
bgun_set_ammo_quantity(AMMOTYPE_CLOAK, 0);
chr_set_stage_flag(NULL, STAGEFLAG_CI_DEVICE_ABORTING);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_DEVICE_FAILURE);
chr_unset_stage_flag(NULL, ci_get_stage_flag_by_device_index(dt_get_index_by_slot(g_DtSlot)));
g_Vars.currentplayer->training = false;
chr_unset_stage_flag(NULL, STAGEFLAG_CI_IN_TRAINING);
player_display_health();
g_Vars.currentplayer->bondhealth = 1;
}
bool dt_is_available(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 >= ARRAYCOUNT(flags)) {
return true;
}
if (deviceindex < 0 || gamefile_has_flag(flags[deviceindex])) {
return true;
}
return false;
}
s32 dt_get_num_available(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < NUM_DEVICETESTS; i++) {
if (dt_is_available(i)) {
count++;
}
}
return count;
}
s32 dt_get_index_by_slot(s32 wantindex)
{
s32 index = -1;
s32 i;
for (i = 0; i < NUM_DEVICETESTS; i++) {
if (dt_is_available(i)) {
index++;
}
if (index == wantindex) {
return i;
}
}
return 0;
}
u32 dt_get_weapon_by_device_index(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 ci_get_stage_flag_by_device_index(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 *dt_get_description(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 lang_get(texts[dt_get_index_by_slot(g_DtSlot)]);
}
char *dt_get_tip1(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 lang_get(texts[dt_get_index_by_slot(g_DtSlot)]);
}
char *dt_get_tip2(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 lang_get(texts[dt_get_index_by_slot(g_DtSlot)]);
}
u8 g_HtSlot = 0;
u8 g_HtInitialised = 0;
struct trainingdata *get_holo_training_data(void)
{
return &g_HtData;
}
void ht_push_endscreen(void)
{
if (g_HtData.completed) {
menu_push_root_dialog_and_pause(&g_HtCompletedMenuDialog, MENUROOT_TRAINING);
} else if (g_HtData.failed) {
menu_push_root_dialog_and_pause(&g_HtFailedMenuDialog, MENUROOT_TRAINING);
}
g_HtData.timeleft = 0;
g_HtData.completed = false;
g_HtData.failed = false;
g_HtData.finished = false;
}
void ht_tick(void)
{
if (g_HtInitialised) {
if (g_HtData.intraining) {
g_HtData.timetaken += g_Vars.lvupdate60;
if (g_Vars.currentplayer->isdead) {
ht_end();
}
if (chr_has_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE)) {
ht_end();
g_HtData.failed = true;
g_HtData.timeleft = 1;
g_HtData.finished = true;
} else if (chr_has_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_SUCCESS)) {
ht_end();
g_HtData.completed = true;
g_HtData.timeleft = 1;
g_HtData.finished = true;
}
} else if (g_HtData.finished) {
if (g_HtData.timeleft <= 0) {
ht_push_endscreen();
} else {
g_HtData.timeleft -= g_Vars.lvupdate60;
}
}
}
}
void ht_reset(void)
{
if (g_HtInitialised == false) {
g_HtInitialised = 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;
chr_unset_stage_flag(NULL, STAGEFLAG_CI_HOLO_ABORTING);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_SUCCESS);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE);
}
}
void ht_begin(void)
{
struct waypoint *waypoints = g_StageSetup.waypoints;
g_HtData.intraining = true;
g_HtData.timetaken = 0;
chr_unset_stage_flag(NULL, STAGEFLAG_CI_HOLO_ABORTING);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_SUCCESS);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE);
chr_set_stage_flag(NULL, ht_get_stageflag(ht_get_index_by_slot(g_HtSlot)));
// Disable segment leading out of the door
nav_disable_segment(&waypoints[0x20], &waypoints[0x31]);
g_Vars.currentplayer->training = true;
bgun_set_passive_mode(false);
chr_set_stage_flag(NULL, STAGEFLAG_CI_IN_TRAINING);
}
void ht_end(void)
{
struct prop *prop;
s16 *propnum;
s16 propnums[256];
RoomNum rooms[] = {
ROOM_DISH_HOLO1,
ROOM_DISH_HOLO2,
ROOM_DISH_HOLO3,
ROOM_DISH_HOLO4,
-1,
};
struct waypoint *waypoints = g_StageSetup.waypoints;
g_HtData.intraining = false;
chr_set_stage_flag(NULL, STAGEFLAG_CI_HOLO_ABORTING);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_TRIGGER_HOLO_FAILURE);
chr_unset_stage_flag(NULL, ht_get_stageflag(ht_get_index_by_slot(g_HtSlot)));
// Enable segment leading out of the door
nav_enable_segment(&waypoints[0x20], &waypoints[0x31]);
g_Vars.currentplayer->training = false;
room_get_props(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) {
obj_free_permanently(obj, true);
}
}
propnum++;
}
bgun_set_passive_mode(true);
chr_unset_stage_flag(NULL, STAGEFLAG_CI_IN_TRAINING);
player_display_health();
g_Vars.currentplayer->bondhealth = 1;
}
bool ht_is_unlocked(u32 value)
{
switch (value) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
return true;
}
return false;
}
s32 ht_get_num_unlocked(void)
{
s32 count = 0;
s32 i;
for (i = 0; i < NUM_HOLOTESTS; i++) {
if (ht_is_unlocked(i)) {
count++;
}
}
return count;
}
s32 ht_get_index_by_slot(s32 slot)
{
s32 index = -1;
s32 i;
for (i = 0; i < NUM_HOLOTESTS; i++) {
if (ht_is_unlocked(i)) {
index++;
}
if (index == slot) {
return i;
}
}
return 0;
}
char *ht_get_name(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 lang_get(texts[index]);
}
u32 ht_get_stageflag(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 *ht_get_description(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 lang_get(texts[ht_get_index_by_slot(g_HtSlot)]);
}
char *ht_get_tip1(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 lang_get(texts[ht_get_index_by_slot(g_HtSlot)]);
}
char *ht_get_tip2(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 lang_get(texts[ht_get_index_by_slot(g_HtSlot)]);
}
#if VERSION >= VERSION_JPN_FINAL
void fr_get_goal_targets_text(char *buffer, char *buffer2)
{
sprintf(buffer, "%s", lang_get(L_MISC_417));
sprintf(buffer2, "%d\n", g_FrData.goaltargets);
}
#else
void fr_get_goal_targets_text(char *buffer)
{
// "GOAL TARGETS:"
sprintf(buffer, "%s %d\n", lang_get(L_MISC_417), g_FrData.goaltargets);
}
#endif
void fr_get_targets_destroyed_value(char *buffer)
{
sprintf(buffer, "%02d\n", g_FrData.targetsdestroyed);
}
void fr_get_score_value(char *buffer)
{
sprintf(buffer, "%03d\n", g_FrData.score);
}
#if VERSION >= VERSION_JPN_FINAL
void fr_get_goal_score_text(char *buffer1, char *buffer2)
{
if (g_FrData.goalscore) {
sprintf(buffer1, "%s", lang_get(L_MISC_418));
sprintf(buffer2, "%d\n", g_FrData.goalscore);
} else {
sprintf(buffer1, "");
sprintf(buffer2, "");
}
}
#else
void fr_get_goal_score_text(char *buffer)
{
if (g_FrData.goalscore) {
// "GOAL SCORE:"
sprintf(buffer, "%s %d\n", lang_get(L_MISC_418), g_FrData.goalscore);
} else {
sprintf(buffer, "");
}
}
#endif
f32 fr_get_accuracy(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 fr_get_min_accuracy(char *buffer1, f32 accuracy, char *buffer2)
{
sprintf(buffer1, "%s", lang_get(L_MISC_419));
sprintf(buffer2, "%d%%\n", g_FrData.goalaccuracy);
return accuracy < g_FrData.goalaccuracy;
}
#else
bool fr_get_min_accuracy(char *buffer, f32 accuracy)
{
// "MIN ACCURACY:"
sprintf(buffer, "%s %d%%\n", lang_get(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 fr_format_time(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 fr_get_hud_middle_subtext(char *buffer1, char *buffer2)
{
s32 secs;
s32 mins;
sprintf(buffer2, "");
if (g_FrData.timetaken < TICKS(-180)) {
sprintf(buffer1, "%s", lang_get(L_MISC_420)); // "FIRE TO START"
return false;
}
if (g_FrData.timetaken < 0) {
sprintf(buffer1, "%s", lang_get(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", lang_get(L_MISC_422)); // "LIMIT:"
sprintf(buffer2, "%02d:%02d\n", mins, secs);
return true;
}
#else
bool fr_get_hud_middle_subtext(char *buffer)
{
s32 secs;
s32 mins;
if (g_FrData.timetaken < TICKS(-180)) {
sprintf(buffer, "%s", lang_get(L_MISC_420)); // "FIRE TO START"
return false;
}
if (g_FrData.timetaken < 0) {
sprintf(buffer, "%s", lang_get(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", lang_get(L_MISC_422), mins, secs); // "LIMIT:"
return true;
}
#endif
#if VERSION >= VERSION_JPN_FINAL
bool fr_get_feedback(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.lvupdate60;
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", lang_get(texts[0]));
return true;
case FRZONE_RING2:
sprintf(zonebuffer, "%s", lang_get(texts[1]));
return true;
case FRZONE_RING1:
sprintf(zonebuffer, "%s", lang_get(texts[2]));
return true;
case FRZONE_BULLSEYE:
sprintf(zonebuffer, "%s", lang_get(texts[3]));
return true;
case FRZONE_EXPLODE:
sprintf(zonebuffer, "%s", lang_get(texts[4]));
return true;
}
sprintf(zonebuffer, "\n");
return true;
}
return false;
}
#else
bool fr_get_feedback(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.lvupdate60;
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", lang_get(texts[0]));
return true;
case FRZONE_RING2:
sprintf(zonebuffer, "%s", lang_get(texts[1]));
return true;
case FRZONE_RING1:
sprintf(zonebuffer, "%s", lang_get(texts[2]));
return true;
case FRZONE_BULLSEYE:
sprintf(zonebuffer, "%s", lang_get(texts[3]));
return true;
case FRZONE_EXPLODE:
sprintf(zonebuffer, "%s", lang_get(texts[4]));
return true;
}
sprintf(zonebuffer, "\n");
return true;
}
return false;
}
#endif
#if VERSION >= VERSION_JPN_FINAL
Gfx *fr_render_hud_element(Gfx *gdl, s32 x, s32 y, char *string1, char *string2, char *string3, u32 colour, u8 alpha)
#else
Gfx *fr_render_hud_element(Gfx *gdl, s32 x, s32 y, char *string1, char *string2, u32 colour, u8 alpha)
#endif
{
s32 textheight;
s32 textwidth;
s32 x2;
s32 y2;
u32 halfalpha = alpha >> 1;
u32 fullcolour = (colour & 0xffffff00) | alpha;
text_measure(&textheight, &textwidth, string1, g_CharsHandelGothicMd, g_FontHandelGothicMd, 0);
x2 = x - (textwidth >> 1);
y2 = y;
gdl = text_draw_black_box(gdl, &x2, &y2, &textwidth, &textheight);
gdl = text_render_vx(gdl, &x2, &y2, string1,
g_CharsHandelGothicMd, g_FontHandelGothicMd, fullcolour, halfalpha, vi_get_width(), vi_get_height(), 0, 0);
if (string2) {
#if VERSION >= VERSION_JPN_FINAL
s32 textheight2;
s32 textwidth2;
s32 textheight3;
s32 textwidth3;
text_measure(&textheight2, &textwidth2, string2, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0);
text_measure(&textheight3, &textwidth3, string3, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0);
textheight = textheight2;
textwidth = textwidth2 + textwidth3;
x2 = x - (textwidth >> 1);
y2 = y;
y2 += 17;
gdl = text_draw_black_box(gdl, &x2, &y2, &textwidth, &textheight);
gdl = text_render_vx(gdl, &x2, &y2, string2,
g_CharsHandelGothicSm, g_FontHandelGothicSm, fullcolour, halfalpha, vi_get_width(), vi_get_height(), 0, 0);
y2 = y;
y2 += 17;
y2++;
x2 -= 4;
gdl = text_render_vx(gdl, &x2, &y2, string3,
g_CharsHandelGothicSm, g_FontHandelGothicSm, fullcolour, halfalpha, vi_get_width(), vi_get_height(), 0, 0);
#else
text_measure(&textheight, &textwidth, string2, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0);
x2 = x - (textwidth >> 1);
y2 = y + 17;
gdl = text_draw_black_box(gdl, &x2, &y2, &textwidth, &textheight);
gdl = text_render_vx(gdl, &x2, &y2, string2,
g_CharsHandelGothicXs, g_FontHandelGothicXs, fullcolour, halfalpha, vi_get_width(), vi_get_height(), 0, 0);
#endif
}
return gdl;
}
#if VERSION >= VERSION_JPN_FINAL
Gfx *fr_render_hud(Gfx *gdl)
{
char string1[128];
char string2[128];
char string3[128];
bool red;
bool exists;
s32 alpha = 0xa0;
f32 mult;
if (vi_get_view_width() > 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 = text_begin(gdl);
// Time
red = fr_format_time(string1);
exists = fr_get_hud_middle_subtext(string2, string3);
gdl = fr_render_hud_element(gdl, vi_get_view_width() >> 1, vi_get_view_top() + 12,
string1,
exists ? string2 : NULL,
exists ? string3 : NULL,
red ? 0xff4444ff : 0x00ff00a0,
alpha);
// Score
fr_get_score_value(string1);
fr_get_goal_score_text(string2, string3);
gdl = fr_render_hud_element(gdl, vi_get_view_left() + 65.0f * mult, vi_get_view_top() + 12,
string1, string2, string3, 0x00ff00a0, alpha);
// Feedback
if (fr_get_feedback(string1, string2, string3)) {
gdl = fr_render_hud_element(gdl,vi_get_view_left() + 65.0f * mult, vi_get_view_top() + 48,
string1, string2, string3, 0x00ff00a0, alpha);
}
if (g_FrData.goalaccuracy > 0) {
red = fr_get_min_accuracy(string2, fr_get_accuracy(string1), string3);
gdl = fr_render_hud_element(gdl, vi_get_view_left() + vi_get_view_width() - 70.0f * mult, vi_get_view_top() + 12,
string1, string2, string3,
red ? 0xff4444ff : 0x00ff00a0,
alpha);
} else if (g_FrData.goaltargets != 255) {
fr_get_targets_destroyed_value(string1);
fr_get_goal_targets_text(string2, string3);
if (mult == 2) {
mult = 2.4;
}
gdl = fr_render_hud_element(gdl, vi_get_view_left() + vi_get_view_width() - 70.0f * mult, vi_get_view_top() + 12,
string1, string2, string3, 0x00ff00a0, alpha);
}
return text_end(gdl);
}
#else
Gfx *fr_render_hud(Gfx *gdl)
{
char string1[128];
char string2[128];
bool red;
bool exists;
s32 alpha = 0xa0;
f32 mult;
if (vi_get_view_width() > (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 = text_begin(gdl);
// Time
red = fr_format_time(string1);
exists = fr_get_hud_middle_subtext(string2);
gdl = fr_render_hud_element(gdl, vi_get_view_width() >> 1, vi_get_view_top() + 12,
string1, exists ? string2 : NULL,
red ? 0xff0000a0 : 0x00ff00a0,
alpha);
// Score
fr_get_score_value(string1);
fr_get_goal_score_text(string2);
gdl = fr_render_hud_element(gdl, vi_get_view_left() + 65.0f * mult, vi_get_view_top() + 12,
string1, string2, 0x00ff00a0, alpha);
// Feedback
if (fr_get_feedback(string1, string2)) {
gdl = fr_render_hud_element(gdl,vi_get_view_left() + 65.0f * mult, vi_get_view_top() + 40,
string1, string2, 0x00ff00a0, alpha);
}
if (g_FrData.goalaccuracy > 0) {
red = fr_get_min_accuracy(string2, fr_get_accuracy(string1));
gdl = fr_render_hud_element(gdl, vi_get_view_left() + vi_get_view_width() - 70.0f * mult, vi_get_view_top() + 12,
string1, string2,
red ? 0xff0000a0 : 0x00ff00a0,
alpha);
} else if (g_FrData.goaltargets != 255) {
fr_get_targets_destroyed_value(string1);
fr_get_goal_targets_text(string2);
if (mult == 2) {
mult = 2.4;
}
gdl = fr_render_hud_element(gdl, vi_get_view_left() + vi_get_view_width() - 70.0f * mult, vi_get_view_top() + 12,
string1, string2, 0x00ff00a0, alpha);
}
return text_end(gdl);
}
#endif