mirror of
https://github.com/perfect-dark-pc-port/perfect_dark.git
synced 2026-10-24 00:02:43 +02:00
435 lines
12 KiB
C++
435 lines
12 KiB
C++
/**
|
|
* Hold the Briefcase
|
|
*
|
|
* A briefcase is spawned into a random location in the arena. Players must pick
|
|
* up the briefcase and then stay alive for as long as possible. The player
|
|
* scores a point every 30 seconds while the briefcase is held.
|
|
*
|
|
* While holding the briefcase, the player cannot pick up shields. The move
|
|
* slightly slower but still have full access to their weaponry.
|
|
*/
|
|
|
|
struct menuitem g_HtbOptionsMenuItems[] = {
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_222, MPOPTION_ONEHITKILLS, menuhandlerMpOneHitKills }, // "One-Hit Kills"
|
|
{ MENUITEMTYPE_DROPDOWN, 0, 0x00020000, L_MPMENU_223, 0x00000000, menuhandlerMpSlowMotion }, // "Slow Motion"
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_224, MPOPTION_FASTMOVEMENT, menuhandlerMpCheckboxOption }, // "Fast Movement"
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_225, MPOPTION_DISPLAYTEAM, menuhandlerMpDisplayTeam }, // "Display Team"
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_226, MPOPTION_NORADAR, menuhandlerMpCheckboxOption }, // "No Radar"
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_227, MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption }, // "No Auto-Aim"
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_OPTIONS_493, MPOPTION_KILLSSCORE, menuhandlerMpCheckboxOption }, // "Kills Score"
|
|
{ MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL },
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_237, MPOPTION_HTB_HIGHLIGHTBRIEFCASE, menuhandlerMpCheckboxOption }, // "Highlight Briefcase"
|
|
{ MENUITEMTYPE_CHECKBOX, 0, 0x00020000, L_MPMENU_238, MPOPTION_HTB_SHOWONRADAR, menuhandlerMpCheckboxOption }, // "Show on Radar"
|
|
{ MENUITEMTYPE_SEPARATOR, 0, 0x00000000, 0x00000000, 0x00000000, NULL },
|
|
{ MENUITEMTYPE_SELECTABLE, 0, 0x00000008, L_MPMENU_239, 0x00000000, NULL }, // "Back"
|
|
{ MENUITEMTYPE_END, 0, 0x00000000, 0x00000000, 0x00000000, NULL },
|
|
};
|
|
|
|
struct menudialogdef g_HtbOptionsMenuDialog = {
|
|
MENUDIALOGTYPE_DEFAULT,
|
|
L_MPMENU_216, // "Briefcase Options"
|
|
g_HtbOptionsMenuItems,
|
|
mpOptionsMenuDialog,
|
|
MENUDIALOGFLAG_MPLOCKABLE,
|
|
NULL,
|
|
};
|
|
|
|
struct defaultobj *var800869ec = NULL;
|
|
|
|
struct weaponobj g_HtbTokenObj;
|
|
|
|
void htbInit(void)
|
|
{
|
|
g_ScenarioData.htb.token = NULL;
|
|
}
|
|
|
|
void htbAddPad(s16 padnum)
|
|
{
|
|
struct scenariodata_htb *data = &g_ScenarioData.htb;
|
|
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
if (data->nextindex < ARRAYCOUNT(data->padnums))
|
|
#endif
|
|
{
|
|
osSyncPrintf("CaptureTheBriefcaseAddBankPad -> Adding New Pad %d - Pad Id = %d-> Saving Pad\n", data->nextindex, padnum);
|
|
|
|
data->padnums[data->nextindex] = padnum;
|
|
data->nextindex++;
|
|
}
|
|
}
|
|
|
|
s32 htbNumProps(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void htbRemoveAmmoCrateAtPad(s16 padnum)
|
|
{
|
|
struct prop *prop = g_Vars.activeprops;
|
|
|
|
while (prop) {
|
|
if (prop->type == PROPTYPE_OBJ) {
|
|
struct defaultobj *obj = prop->obj;
|
|
|
|
if (obj->pad == padnum
|
|
&& (obj->type == OBJTYPE_AMMOCRATE || obj->type == OBJTYPE_MULTIAMMOCRATE)
|
|
&& obj->modelnum == MODEL_MULTI_AMMO_CRATE) {
|
|
obj->hidden |= OBJHFLAG_REAPABLE;
|
|
obj->hidden2 &= ~OBJH2FLAG_CANREGEN;
|
|
return;
|
|
}
|
|
}
|
|
|
|
prop = prop->next;
|
|
}
|
|
}
|
|
|
|
void htbReset(void)
|
|
{
|
|
s32 i;
|
|
|
|
g_ScenarioData.htb.nextindex = 0;
|
|
|
|
for (i = 0; i < ARRAYCOUNT(g_ScenarioData.htb.padnums); i++) {
|
|
g_ScenarioData.htb.padnums[i] = -1;
|
|
}
|
|
}
|
|
|
|
void htbCreateToken(void)
|
|
{
|
|
struct weaponobj template = {
|
|
256, // extrascale
|
|
0, // hidden2
|
|
OBJTYPE_WEAPON, // type
|
|
MODEL_CHRBRIEFCASE, // modelnum
|
|
0, // pad
|
|
OBJFLAG_00000001 | OBJFLAG_INVINCIBLE | OBJFLAG_00400000,
|
|
OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000,
|
|
0, // flags3
|
|
NULL, // prop
|
|
NULL, // model
|
|
1, 0, 0, // realrot
|
|
0, 1, 0,
|
|
0, 0, 1,
|
|
0, // hidden
|
|
NULL, // geo
|
|
NULL, // projectile
|
|
0, // damage
|
|
1000, // maxdamage
|
|
0xff, 0xff, 0xff, 0x00, // shadecol
|
|
0xff, 0xff, 0xff, 0x00, // nextcol
|
|
0x0fff, // floorcol
|
|
0, // tiles
|
|
WEAPON_BRIEFCASE2, // weaponnum
|
|
0, // unk5d
|
|
0, // unk5e
|
|
FUNC_PRIMARY, // gunfunc
|
|
0, // fadeouttimer60
|
|
-1, // dualweaponnum
|
|
-1, // timer240
|
|
NULL, // dualweapon
|
|
};
|
|
|
|
struct prop *prop = g_Vars.activeprops;
|
|
struct defaultobj *obj;
|
|
s32 count = 0;
|
|
struct defaultobj *candidates[20];
|
|
|
|
// Build a list of candidate objects to replace. Consider only ammocrates.
|
|
// NTSC beta doesn't check the prop type, so it could potentially replace a
|
|
// player, bot, explosion or smoke.
|
|
while (prop && count < 20) {
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
if (prop->type == PROPTYPE_OBJ)
|
|
#endif
|
|
{
|
|
obj = prop->obj;
|
|
|
|
if (obj->type == OBJTYPE_MULTIAMMOCRATE) {
|
|
candidates[count] = obj;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
prop = prop->next;
|
|
}
|
|
|
|
// Choose the candidate and remove it
|
|
if (count > 0) {
|
|
count = random() % count;
|
|
var800869ec = candidates[count];
|
|
g_ScenarioData.htb.tokenpad = var800869ec->pad;
|
|
var800869ec->hidden |= OBJHFLAG_REAPABLE;
|
|
var800869ec->hidden2 |= OBJH2FLAG_CANREGEN;
|
|
} else if (g_ScenarioData.htb.nextindex > 0) {
|
|
g_ScenarioData.htb.tokenpad = g_ScenarioData.htb.padnums[random() % g_ScenarioData.htb.nextindex];
|
|
} else {
|
|
g_ScenarioData.htb.tokenpad = 0;
|
|
}
|
|
|
|
// Set up the token
|
|
g_HtbTokenObj = template;
|
|
g_HtbTokenObj.base.pad = g_ScenarioData.htb.tokenpad;
|
|
|
|
weaponAssignToHome(&g_HtbTokenObj, 999);
|
|
|
|
g_HtbTokenObj.base.hidden2 &= ~OBJH2FLAG_CANREGEN;
|
|
|
|
g_ScenarioData.htb.token = g_HtbTokenObj.base.prop;
|
|
|
|
if (g_ScenarioData.htb.token) {
|
|
g_ScenarioData.htb.token->forcetick = true;
|
|
}
|
|
}
|
|
|
|
void htbInitProps(void)
|
|
{
|
|
var800869ec = NULL;
|
|
htbCreateToken();
|
|
}
|
|
|
|
void htbTick(void)
|
|
{
|
|
s32 i;
|
|
u32 prevplayernum = g_Vars.currentplayernum;
|
|
struct prop *prop;
|
|
|
|
if (var800869ec && var800869ec->prop) {
|
|
if (g_ScenarioData.htb.token == NULL || g_ScenarioData.htb.token->type != PROPTYPE_WEAPON) {
|
|
var800869ec = NULL;
|
|
} else {
|
|
var800869ec->prop->timetoregen = TICKS(1200);
|
|
}
|
|
}
|
|
|
|
g_ScenarioData.htb.token = NULL;
|
|
|
|
// Check if briefcase is on the ground
|
|
prop = g_Vars.activeprops;
|
|
|
|
while (prop) {
|
|
if (prop->type == PROPTYPE_WEAPON) {
|
|
struct weaponobj *weapon = prop->weapon;
|
|
|
|
if (weapon->weaponnum == WEAPON_BRIEFCASE2) {
|
|
g_ScenarioData.htb.token = prop;
|
|
}
|
|
}
|
|
|
|
prop = prop->next;
|
|
}
|
|
|
|
// Check if a player is holding it
|
|
if (g_ScenarioData.htb.token == NULL) {
|
|
for (i = 0; i < PLAYERCOUNT(); i++) {
|
|
setCurrentPlayerNum(i);
|
|
|
|
if (invHasBriefcase()) {
|
|
g_ScenarioData.htb.token = g_Vars.currentplayer->prop;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
setCurrentPlayerNum(prevplayernum);
|
|
|
|
// Check if a simulant is holding it
|
|
if (g_ScenarioData.htb.token == NULL) {
|
|
for (i = PLAYERCOUNT(); i < g_MpNumChrs; i++) {
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
if (g_MpAllChrPtrs[i]->prop && g_MpAllChrPtrs[i]->aibot->hasbriefcase)
|
|
#else
|
|
if (g_MpAllChrPtrs[i]->aibot->hasbriefcase)
|
|
#endif
|
|
{
|
|
g_ScenarioData.htb.token = g_MpAllChrPtrs[i]->prop;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_ScenarioData.htb.token == NULL) {
|
|
htbCreateToken();
|
|
}
|
|
|
|
if (g_ScenarioData.htb.token == NULL) {
|
|
g_ScenarioData.htb.pos.x = 0;
|
|
g_ScenarioData.htb.pos.y = 0;
|
|
g_ScenarioData.htb.pos.z = 0;
|
|
} else {
|
|
struct coord *pos = &g_ScenarioData.htb.pos;
|
|
pos->x = g_ScenarioData.htb.token->pos.x;
|
|
pos->y = g_ScenarioData.htb.token->pos.y;
|
|
pos->z = g_ScenarioData.htb.token->pos.z;
|
|
}
|
|
}
|
|
|
|
void htbTickChr(struct chrdata *chr)
|
|
{
|
|
if (chr) {
|
|
if (chr->aibot->hasbriefcase) {
|
|
chr->aibot->unk0a0 += g_Vars.lvupdate240;
|
|
|
|
if (chr->aibot->unk0a0 >= TICKS(7200)) {
|
|
sndStart(var80095200, SFX_MP_SCOREPOINT, NULL, -1, -1, -1, -1, -1);
|
|
g_MpAllChrConfigPtrs[mpPlayerGetIndex(chr)]->numpoints++;
|
|
chr->aibot->unk0a0 = 0;
|
|
}
|
|
} else {
|
|
chr->aibot->unk0a0 = 0;
|
|
}
|
|
} else {
|
|
if (invHasBriefcase()) {
|
|
g_Vars.currentplayerstats->tokenheldtime += g_Vars.lvupdate240;
|
|
|
|
if (g_Vars.currentplayerstats->tokenheldtime >= TICKS(7200)) {
|
|
sndStart(var80095200, SFX_MP_SCOREPOINT, NULL, -1, -1, -1, -1, -1);
|
|
g_MpAllChrConfigPtrs[g_Vars.currentplayernum]->numpoints++;
|
|
hudmsgCreateWithFlags(langGet(L_MPWEAPONS_024), HUDMSGTYPE_MPSCENARIO, HUDMSGFLAG_ONLYIFALIVE); // "1 Point!"
|
|
g_Vars.currentplayerstats->tokenheldtime = 0;
|
|
}
|
|
} else {
|
|
g_Vars.currentplayerstats->tokenheldtime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @bug: In NTSC Final, the calculation of mins and subsequent subtraction from
|
|
* time240 should use 60 * 240 instead of 30 * 240. This has no noticeable
|
|
* effect unless the score duration is increased to above 30 seconds.
|
|
*
|
|
* PAL recognises that mins will always be 0 and simplifies the calculation.
|
|
*/
|
|
Gfx *htbRenderHud(Gfx *gdl)
|
|
{
|
|
s32 time240;
|
|
s32 mins;
|
|
s32 secs;
|
|
s32 textwidth;
|
|
s32 textheight;
|
|
s32 x;
|
|
s32 y;
|
|
char text[64];
|
|
|
|
if (invHasBriefcase()) {
|
|
x = viGetViewLeft() + viGetViewWidth() / 2;
|
|
y = viGetViewTop() + 10;
|
|
|
|
#if VERSION >= VERSION_PAL_BETA
|
|
time240 = (30 * 200) - g_Vars.currentplayerstats->tokenheldtime;
|
|
secs = (time240 + 199) / 200;
|
|
sprintf(text, "%d:%02d", 0, secs);
|
|
#else
|
|
time240 = (30 * 240) - g_Vars.currentplayerstats->tokenheldtime;
|
|
mins = time240 / (30 * 240);
|
|
time240 -= (30 * 240) * mins;
|
|
secs = (time240 + 239) / 240;
|
|
sprintf(text, "%d:%02d", mins, secs);
|
|
#endif
|
|
|
|
gdl = func0f153628(gdl);
|
|
textMeasure(&textheight, &textwidth, text, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0);
|
|
|
|
x -= textwidth / 2;
|
|
textwidth += x;
|
|
textheight += y;
|
|
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
gdl = func0f153990(gdl, x, y, textwidth, textheight);
|
|
gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0xa0, viGetWidth(), viGetHeight(), 0, 0);
|
|
#else
|
|
gdl = func0f153858(gdl, &x, &y, &textwidth, &textheight);
|
|
gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, 0x00ff00a0, 0x88, viGetWidth(), viGetHeight(), 0, 0);
|
|
#endif
|
|
|
|
gdl = func0f153780(gdl);
|
|
}
|
|
|
|
return gdl;
|
|
}
|
|
|
|
void htbCalculatePlayerScore(struct mpchrconfig *mpchr, s32 mpchrnum, s32 *score, s32 *deaths)
|
|
{
|
|
struct mpchrconfig *loopmpchr;
|
|
s32 i;
|
|
|
|
*score = 0;
|
|
*score += mpchr->numpoints;
|
|
|
|
if (g_MpSetup.options & MPOPTION_KILLSSCORE) {
|
|
for (i = 0; i != MAX_MPCHRS; i++) {
|
|
if (i == mpchrnum) {
|
|
*score -= mpchr->killcounts[i];
|
|
} else if (g_MpSetup.options & MPOPTION_TEAMSENABLED) {
|
|
loopmpchr = MPCHR(i);
|
|
|
|
if (loopmpchr->team == mpchr->team) {
|
|
*score -= mpchr->killcounts[i];
|
|
} else {
|
|
*score += mpchr->killcounts[i];
|
|
}
|
|
} else {
|
|
*score += mpchr->killcounts[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
*deaths = mpchr->numdeaths;
|
|
}
|
|
|
|
Gfx *htbRadarExtra(Gfx *gdl)
|
|
{
|
|
if ((g_MpSetup.options & MPOPTION_HTB_SHOWONRADAR) &&
|
|
g_ScenarioData.htb.token != NULL &&
|
|
g_ScenarioData.htb.token->type != PROPTYPE_PLAYER &&
|
|
g_ScenarioData.htb.token->type != PROPTYPE_CHR) {
|
|
struct coord dist;
|
|
dist.x = g_ScenarioData.htb.pos.x - g_Vars.currentplayer->prop->pos.x;
|
|
dist.y = g_ScenarioData.htb.pos.y - g_Vars.currentplayer->prop->pos.y;
|
|
dist.z = g_ScenarioData.htb.pos.z - g_Vars.currentplayer->prop->pos.z;
|
|
gdl = radarDrawDot(gdl, g_ScenarioData.htb.token, &dist, 0x00ff0000, 0, 1);
|
|
}
|
|
|
|
return gdl;
|
|
}
|
|
|
|
bool htbRadarChr(Gfx **gdl, struct prop *prop)
|
|
{
|
|
if ((g_MpSetup.options & MPOPTION_HTB_SHOWONRADAR) &&
|
|
g_ScenarioData.htb.token &&
|
|
prop == g_ScenarioData.htb.token) {
|
|
if (prop->type == PROPTYPE_PLAYER || prop->type == PROPTYPE_CHR) {
|
|
struct coord dist;
|
|
dist.x = prop->pos.x - g_Vars.currentplayer->prop->pos.x;
|
|
dist.y = prop->pos.y - g_Vars.currentplayer->prop->pos.y;
|
|
dist.z = prop->pos.z - g_Vars.currentplayer->prop->pos.z;
|
|
|
|
if (g_MpSetup.options & MPOPTION_TEAMSENABLED) {
|
|
u32 colour = g_TeamColours[radarGetTeamIndex(prop->chr->team)];
|
|
*gdl = radarDrawDot(*gdl, g_ScenarioData.htb.token, &dist, colour, 0, 1);
|
|
} else {
|
|
*gdl = radarDrawDot(*gdl, g_ScenarioData.htb.token, &dist, 0x00ff0000, 0, 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool htbHighlightProp(struct prop *prop, s32 *colour)
|
|
{
|
|
if ((g_MpSetup.options & MPOPTION_HTB_HIGHLIGHTBRIEFCASE) && prop == g_ScenarioData.htb.token) {
|
|
colour[0] = 0;
|
|
colour[1] = 0xff;
|
|
colour[2] = 0;
|
|
colour[3] = 0x40;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|