1626 lines
41 KiB
C
1626 lines
41 KiB
C
#include <ultra64.h>
|
|
#include "constants.h"
|
|
#include "constants.h"
|
|
#include "game/propsnd.h"
|
|
#include "game/game_0b0fd0.h"
|
|
#include "game/player.h"
|
|
#include "game/savebuffer.h"
|
|
#include "game/hudmsg.h"
|
|
#include "game/menugfx.h"
|
|
#include "game/playermgr.h"
|
|
#include "game/game_1531a0.h"
|
|
#include "game/lv.h"
|
|
#include "game/mplayer/mplayer.h"
|
|
#include "game/options.h"
|
|
#include "game/propobj.h"
|
|
#include "bss.h"
|
|
#include "lib/lib_317f0.h"
|
|
#include "lib/memp.h"
|
|
#include "lib/mtx.h"
|
|
#include "lib/snd.h"
|
|
#include "lib/str.h"
|
|
#include "lib/vi.h"
|
|
#include "data.h"
|
|
#include "types.h"
|
|
#include "string.h"
|
|
|
|
u32 g_NextHudMessageId;
|
|
|
|
u8 g_HudmsgsActive = 0;
|
|
|
|
u32 g_HudmsgColours[] = {
|
|
/* 0*/ 0x00ff0000, // green
|
|
/* 1*/ 0x9999ff00, // pastel blue
|
|
/* 2*/ 0xffffff00, // white
|
|
/* 3*/ 0xff777700, // pastel red
|
|
/* 4*/ 0xffff5500, // yellow
|
|
/* 5*/ 0x00ff0000, // green
|
|
/* 6*/ 0xcccccc00, // gray
|
|
/* 7*/ 0xff888800, // pastel red
|
|
/* 8*/ 0xffaa5500, // orange
|
|
/* 9*/ 0x55aaff00, // sky blue
|
|
/*10*/ 0xaa55ff00, // purple
|
|
};
|
|
|
|
s32 g_HudPaddingY = 10;
|
|
s32 g_HudPaddingX = 24;
|
|
s32 g_NumHudMessages = 0;
|
|
struct hudmessage *g_HudMessages = NULL;
|
|
|
|
#if VERSION < VERSION_NTSC_1_0
|
|
struct sndstate *var800736b0nb = NULL;
|
|
#endif
|
|
|
|
struct hudmsgtype g_HudmsgTypes[] = {
|
|
/* 0*/ { 1, 1, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ff0000, 0x000000a0, HUDMSGALIGN_LEFT, HUDMSGALIGN_BOTTOM, 0, 0, 80 },
|
|
/* 1*/ { 0, 1, 0, &g_CharsHandelGothicMd, &g_FontHandelGothicMd, 0x00ff0000, 0x000000a0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_YMIDDLE, 0, 0, 120 },
|
|
#if VERSION == VERSION_JPN_FINAL
|
|
/* 2*/ { 0, 0, 1, &g_CharsHandelGothicMd, &g_FontHandelGothicMd, 0xff999900, 0xffffffa0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_YMIDDLE, 0, 0, 120 },
|
|
#else
|
|
/* 2*/ { 0, 0, 1, &g_CharsHandelGothicMd, &g_FontHandelGothicMd, 0xff000000, 0xffffffa0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_YMIDDLE, 0, 0, 120 },
|
|
#endif
|
|
/* 3*/ { 0, 1, 0, &g_CharsHandelGothicMd, &g_FontHandelGothicMd, 0x00ff0000, 0x000000a0, HUDMSGALIGN_LEFT, HUDMSGALIGN_BOTTOM, 0, 0, 120 },
|
|
/* 4*/ { 1, 1, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ffc000, 0x000000a0, HUDMSGALIGN_LEFT, HUDMSGALIGN_BOTTOM, 0, 0, 40 },
|
|
/* 5*/ { 0, 0, 0, &g_CharsHandelGothicMd, &g_FontHandelGothicMd, 0x00ff0000, 0x000000a0, HUDMSGALIGN_LEFT, HUDMSGALIGN_TOP, 0, 0, 120 },
|
|
/* 6*/ { 1, 0, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ff0000, 0x000000a0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_TOP, 0, 0, 120 },
|
|
/* 7*/ { 1, 1, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ff0000, 0x000000a0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_TOP, 0, 0, -1 },
|
|
/* 8*/ { 1, 1, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ffc000, 0x000000a0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_BOTTOM, 0, 0, 500 },
|
|
#if VERSION == VERSION_JPN_FINAL
|
|
/* 9*/ { 1, 1, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ff0000, 0x000000a0, HUDMSGALIGN_LEFT, HUDMSGALIGN_BOTTOM, 0, 0, 120 },
|
|
#else
|
|
/* 9*/ { 1, 1, 0, &g_CharsHandelGothicXs, &g_FontHandelGothicXs, 0x00ff0000, 0x000000a0, HUDMSGALIGN_LEFT, HUDMSGALIGN_BOTTOM, 0, 0, 120 },
|
|
#endif
|
|
/*10*/ { 1, 1, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ff0000, 0x000000a0, HUDMSGALIGN_LEFT, HUDMSGALIGN_BOTTOM, 0, 0, 240 },
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
/*11*/ { 0, 0, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ff0000, 0x000000a0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_BELOWVIEWPORT, 0, 0, 120 },
|
|
#else
|
|
/*11*/ { 1, 0, 0, &g_CharsHandelGothicSm, &g_FontHandelGothicSm, 0x00ff0000, 0x000000a0, HUDMSGALIGN_XMIDDLE, HUDMSGALIGN_BELOWVIEWPORT, 0, 0, 120 },
|
|
#endif
|
|
};
|
|
|
|
u8 hudmsgsAreActive(void)
|
|
{
|
|
return g_HudmsgsActive;
|
|
}
|
|
|
|
s32 hudmsgIsZoomRangeVisible(void)
|
|
{
|
|
return optionsGetShowZoomRange(g_Vars.currentplayerstats->mpindex)
|
|
&& (PLAYERCOUNT() == 1
|
|
|| !g_Vars.mplayerisrunning
|
|
|| g_Vars.coopplayernum >= 0
|
|
|| g_Vars.antiplayernum >= 0)
|
|
&& var80075d60 == 2
|
|
&& currentPlayerGetSight() == SIGHT_ZOOM
|
|
&& g_Vars.currentplayer->cameramode != CAMERAMODE_EYESPY
|
|
&& g_Vars.currentplayer->cameramode != CAMERAMODE_THIRDPERSON;
|
|
}
|
|
|
|
/**
|
|
* hudmsgRenderMissionTimer calls viGetWidth (which returns an s16), then stores
|
|
* the width in sp42 while it calls viGetHeight. However, when we do this it
|
|
* stores the width to sp40 instead.
|
|
*
|
|
* Changing the definition of viGetHeight to return an s32 fixes this, but is
|
|
* surely wrong and creates mismatches elsewhere. So we declare a new function
|
|
* with the return type we need, and link it to the same address as viGetHeight
|
|
* via the linker config.
|
|
*/
|
|
extern s32 viGetHeight_hack(void);
|
|
|
|
Gfx *hudmsgRenderMissionTimer(Gfx *gdl, u32 alpha)
|
|
{
|
|
s32 x;
|
|
s32 y;
|
|
s32 viewleft;
|
|
s32 timery;
|
|
char buffer[24];
|
|
u32 textcolour;
|
|
s32 is4mb;
|
|
s32 playercount;
|
|
s32 playernum;
|
|
s16 viewtop;
|
|
s16 viewheight;
|
|
|
|
textcolour = alpha;
|
|
|
|
viewleft = viGetViewLeft() / g_ScaleX;
|
|
viewtop = viGetViewTop();
|
|
viewheight = viGetViewHeight();
|
|
playercount = PLAYERCOUNT();
|
|
playernum = g_Vars.currentplayernum;
|
|
|
|
timery = viewheight;
|
|
timery += viewtop;
|
|
timery -= g_HudPaddingY;
|
|
timery -= 8;
|
|
|
|
is4mb = IS4MB();
|
|
|
|
// @bug: There is no check for playercount >= 2 in the next two statements.
|
|
// Because of this, in 1 player the timer is drawn out of place when the
|
|
// screen split option is vertical and either the countdown timer is visible
|
|
// or a zoomable weapon is in use.
|
|
if ((is4mb || optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) && countdownTimerIsVisible()) {
|
|
timery -= 8;
|
|
}
|
|
|
|
if ((IS4MB() || optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || playercount >= 3) && hudmsgIsZoomRangeVisible()) {
|
|
timery -= 8;
|
|
}
|
|
|
|
if (playercount == 2) {
|
|
if (IS4MB() || (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL && playernum == 0)) {
|
|
timery += 10;
|
|
} else {
|
|
timery += 2;
|
|
}
|
|
} else if (playercount >= 3) {
|
|
if (playernum < 2) {
|
|
timery += 10;
|
|
} else {
|
|
timery += 2;
|
|
}
|
|
} else {
|
|
if (optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) {
|
|
timery += 8;
|
|
}
|
|
}
|
|
|
|
// If this is a second player with their viewport on the right side of the
|
|
// screen, move the timer left a bit as the safe zone doesn't need to be
|
|
// considered.
|
|
if (playercount == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB()) && playernum == 1) {
|
|
viewleft -= 14;
|
|
} else if (playercount >= 3 && (playernum & 1) == 1) {
|
|
viewleft -= 14;
|
|
}
|
|
|
|
textcolour = textcolour * 160 / 255;
|
|
if (g_Is4Mb);
|
|
textcolour |= 0x00ff0000;
|
|
|
|
formatTime(buffer, playerGetMissionTime(), TIMEPRECISION_HUNDREDTHS);
|
|
|
|
x = viewleft + g_HudPaddingX + 3;
|
|
y = timery;
|
|
|
|
gdl = textRender(gdl, &x, &y, buffer, g_CharsNumeric, g_FontNumeric, textcolour, 0x000000a0, viGetWidth(), viGetHeight_hack(), 0, 0);
|
|
|
|
return gdl;
|
|
}
|
|
|
|
Gfx *hudmsgRenderZoomRange(Gfx *gdl, u32 alpha)
|
|
{
|
|
s32 viewtop;
|
|
s32 viewleft;
|
|
s32 viewhalfwidth;
|
|
s32 viewheight;
|
|
f32 zoominfovy;
|
|
f32 zoomfov;
|
|
s32 playercount;
|
|
f32 curzoom;
|
|
f32 maxzoom;
|
|
char text[24];
|
|
s32 weaponnum;
|
|
s32 texty;
|
|
s32 x;
|
|
s32 y;
|
|
s32 textwidth;
|
|
s32 textheight;
|
|
s32 x2;
|
|
s32 y2;
|
|
u32 colour;
|
|
|
|
colour = (alpha * 0xa0 / 255) | 0x00ff0000;
|
|
viewtop = viGetViewTop();
|
|
viewleft = viGetViewLeft() / g_ScaleX;
|
|
viewhalfwidth = (viGetViewWidth() / g_ScaleX) >> 1;
|
|
viewheight = viGetViewHeight();
|
|
texty = viewheight + viewtop - 1;
|
|
maxzoom = 1.0f;
|
|
weaponnum = g_Vars.currentplayer->hands[0].gset.weaponnum;
|
|
playercount = PLAYERCOUNT();
|
|
|
|
texty -= 17;
|
|
|
|
if (countdownTimerIsVisible()) {
|
|
texty -= 8;
|
|
}
|
|
|
|
if (playercount == 2) {
|
|
if (IS4MB() || (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL && g_Vars.currentplayernum == 0)) {
|
|
texty += 10;
|
|
} else {
|
|
texty += 2;
|
|
}
|
|
} else if (playercount >= 3) {
|
|
if (g_Vars.currentplayernum < 2) {
|
|
texty += 10;
|
|
} else {
|
|
texty += 2;
|
|
}
|
|
} else if (optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) {
|
|
texty += 8;
|
|
}
|
|
|
|
// Left side - current zoom level
|
|
zoomfov = currentPlayerGetGunZoomFov();
|
|
zoominfovy = g_Vars.currentplayer->zoominfovy;
|
|
|
|
if (zoomfov == 0.0f || zoomfov == 60.0f) {
|
|
if (weaponnum == WEAPON_SNIPERRIFLE) {
|
|
curzoom = 1.0f;
|
|
} else {
|
|
return gdl;
|
|
}
|
|
} else {
|
|
maxzoom = 60.0f / zoomfov;
|
|
curzoom = maxzoom - 1.0f / (zoomfov / zoominfovy) + 1;
|
|
}
|
|
|
|
sprintf(text, "%s%s%4.2fX", "", "", curzoom);
|
|
textMeasure(&textheight, &textwidth, text, g_CharsNumeric, g_FontNumeric, 0);
|
|
|
|
x = viewleft + viewhalfwidth - textwidth - 5;
|
|
y = texty;
|
|
x2 = x + textwidth;
|
|
y2 = y + textheight;
|
|
|
|
gdl = text0f1538e4(gdl, &x, &y, &x2, &y2);
|
|
gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, colour, 0x000000a0, viGetWidth(), viGetHeight(), 0, 0);
|
|
|
|
// Divider
|
|
sprintf(text, "/");
|
|
textMeasure(&textheight, &textwidth, text, g_CharsNumeric, g_FontNumeric, 0);
|
|
|
|
x = viewleft + viewhalfwidth - (textwidth >> 1);
|
|
y = texty;
|
|
x2 = x + textwidth;
|
|
y2 = y + textheight;
|
|
|
|
gdl = text0f1538e4(gdl, &x, &y, &x2, &y2);
|
|
gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, colour, 0x000000a0, viGetWidth(), viGetHeight(), 0, 0);
|
|
|
|
// Right side - max zoom level
|
|
sprintf(text, "%s%s%4.2fX", "", "", maxzoom);
|
|
textMeasure(&textheight, &textwidth, text, g_CharsNumeric, g_FontNumeric, 0);
|
|
|
|
x = viewleft + viewhalfwidth + 5;
|
|
y = texty;
|
|
x2 = x + textwidth;
|
|
y2 = y + textheight;
|
|
|
|
gdl = text0f1538e4(gdl, &x, &y, &x2, &y2);
|
|
gdl = textRender(gdl, &x, &y, text, g_CharsNumeric, g_FontNumeric, colour, 0x000000a0, viGetWidth(), viGetHeight(), 0, 0);
|
|
|
|
return gdl;
|
|
}
|
|
|
|
Gfx *hudmsgRenderBox(Gfx *gdl, s32 x1, s32 y1, s32 x2, s32 y2, f32 bgopacity, u32 bordercolour, f32 textopacity)
|
|
{
|
|
f32 f0;
|
|
f32 f20;
|
|
f32 f22;
|
|
|
|
if (x1);
|
|
|
|
g_HudmsgsActive = true;
|
|
|
|
f0 = sinf(90 * bgopacity * M_PI / 180.0f);
|
|
f22 = (x2 - x1) * 0.5f;
|
|
f20 = (y2 - y1) * 0.5f;
|
|
|
|
if (f0 < 0.5f) {
|
|
f20 = 0.0f;
|
|
f22 *= f0 + f0;
|
|
} else {
|
|
f20 *= (f0 - 0.5f) + (f0 - 0.5f);
|
|
}
|
|
|
|
gdl = func0f0d479c(gdl);
|
|
|
|
gdl = menugfxDrawFilledRect(gdl, x1, y1, x2, y1 + 1, bordercolour, bordercolour);
|
|
gdl = menugfxDrawFilledRect(gdl, x1, y2, x2, y2 + 1, bordercolour, bordercolour);
|
|
gdl = menugfxDrawFilledRect(gdl, x1, y1 + 1, x1 + 1, y2, bordercolour, bordercolour);
|
|
gdl = menugfxDrawFilledRect(gdl, x2, y1, x2 + 1, y2 + 1, bordercolour, bordercolour);
|
|
|
|
gdl = func0f0d49c8(gdl);
|
|
|
|
if (textopacity > 0.0f) {
|
|
f32 width = (x1 + x2) * 0.5f;
|
|
f32 height = (y1 + y2) * 0.5f;
|
|
|
|
gdl = text0f153a34(gdl,
|
|
(s32)((width - f22) + 1.0f) * g_ScaleX,
|
|
(height - f20) + 1.0f,
|
|
(s32)(width + f22) * g_ScaleX,
|
|
height + f20,
|
|
128.0f * textopacity);
|
|
}
|
|
|
|
return gdl;
|
|
}
|
|
|
|
s32 hudmsg0f0ddb1c(s32 *arg0, s32 arg1)
|
|
{
|
|
#if VERSION >= VERSION_PAL_FINAL
|
|
s32 viewwidth = viGetViewWidth() / g_ScaleX;
|
|
s32 result = 0;
|
|
|
|
*arg0 = 24;
|
|
|
|
if (PLAYERCOUNT() == 2
|
|
&& optionsGetScreenSplit() == SCREENSPLIT_VERTICAL
|
|
&& (!g_InCutscene || g_MainIsEndscreen)) {
|
|
result -= *arg0 * 2 / 3;
|
|
|
|
if (g_Vars.currentplayernum == 0) {
|
|
*arg0 /= 3;
|
|
} else {
|
|
*arg0 /= 6;
|
|
}
|
|
}
|
|
|
|
result = result + viewwidth - *arg0 - arg1 - 11;
|
|
|
|
if (PLAYERCOUNT() == 1 || (PLAYERCOUNT() == 2 && g_InCutscene && !g_MainIsEndscreen)) {
|
|
result -= 16;
|
|
|
|
#if VERSION < VERSION_JPN_FINAL
|
|
if (g_ViRes == VIRES_HI) {
|
|
result -= 16;
|
|
}
|
|
#endif
|
|
}
|
|
#else
|
|
s32 viewwidth = g_Vars.currentplayer->viewwidth / g_ScaleX;
|
|
s32 result = 0;
|
|
|
|
*arg0 = 24;
|
|
|
|
if (PLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) {
|
|
result -= *arg0 * 2 / 3;
|
|
|
|
if (g_Vars.currentplayernum == 0) {
|
|
*arg0 /= 3;
|
|
} else {
|
|
*arg0 /= 6;
|
|
}
|
|
}
|
|
|
|
result = result + viewwidth - *arg0 - arg1 - 11;
|
|
|
|
if (PLAYERCOUNT() == 1) {
|
|
result -= 16;
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
void hudmsgsHideByChannel(s32 channelnum)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
if (g_HudMessages[i].state != HUDMSGSTATE_FREE && g_HudMessages[i].channelnum == channelnum) {
|
|
g_HudMessages[i].flags |= HUDMSGFLAG_FORCEOFF;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void hudmsgsReset(void)
|
|
{
|
|
s32 i;
|
|
|
|
g_NumHudMessages = g_Vars.mplayerisrunning ? 20 : 8;
|
|
g_HudMessages = mempAlloc(ALIGN64(sizeof(struct hudmessage) * g_NumHudMessages), MEMPOOL_STAGE);
|
|
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
g_HudMessages[i].state = HUDMSGSTATE_FREE;
|
|
}
|
|
|
|
g_NextHudMessageId = 0;
|
|
|
|
#if VERSION < VERSION_NTSC_1_0
|
|
var800736b0nb = NULL;
|
|
#endif
|
|
}
|
|
|
|
void hudmsgRemoveAll(void)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
g_HudMessages[i].state = HUDMSGSTATE_FREE;
|
|
}
|
|
}
|
|
|
|
s32 hudmsgGetNext(s32 refid)
|
|
{
|
|
s32 bestid = -1;
|
|
s32 bestindex = -1;
|
|
s32 i;
|
|
|
|
// Finding the smallest ID that is greater than refid
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
if (g_HudMessages[i].state && g_HudMessages[i].id > refid) {
|
|
if (bestid < 0 || g_HudMessages[i].id < bestid) {
|
|
bestindex = i;
|
|
bestid = g_HudMessages[i].id;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestindex;
|
|
}
|
|
|
|
void hudmsgCreate(char *text, s32 type)
|
|
{
|
|
hudmsgCreateFromArgs(text, type,
|
|
g_HudmsgTypes[type].unk00,
|
|
g_HudmsgTypes[type].unk01,
|
|
g_HudmsgTypes[type].unk02,
|
|
g_HudmsgTypes[type].unk04,
|
|
g_HudmsgTypes[type].unk08,
|
|
g_HudmsgTypes[type].colour,
|
|
g_HudmsgTypes[type].unk10,
|
|
g_HudmsgTypes[type].alignh,
|
|
g_HudmsgTypes[type].unk16,
|
|
g_HudmsgTypes[type].alignv,
|
|
g_HudmsgTypes[type].unk18,
|
|
-1, 0);
|
|
}
|
|
|
|
void hudmsgCreateWithFlags(char *text, s32 type, u32 flags)
|
|
{
|
|
hudmsgCreateFromArgs(text, type,
|
|
g_HudmsgTypes[type].unk00,
|
|
g_HudmsgTypes[type].unk01,
|
|
g_HudmsgTypes[type].unk02,
|
|
g_HudmsgTypes[type].unk04,
|
|
g_HudmsgTypes[type].unk08,
|
|
g_HudmsgTypes[type].colour,
|
|
g_HudmsgTypes[type].unk10,
|
|
g_HudmsgTypes[type].alignh,
|
|
g_HudmsgTypes[type].unk16,
|
|
g_HudmsgTypes[type].alignv,
|
|
g_HudmsgTypes[type].unk18,
|
|
-1, flags);
|
|
}
|
|
|
|
void hudmsgCreateWithColour(char *text, s32 type, u8 colournum)
|
|
{
|
|
g_HudmsgTypes[type].colour = g_HudmsgColours[colournum];
|
|
|
|
hudmsgCreateFromArgs(text, type,
|
|
g_HudmsgTypes[type].unk00,
|
|
g_HudmsgTypes[type].unk01,
|
|
g_HudmsgTypes[type].unk02,
|
|
g_HudmsgTypes[type].unk04,
|
|
g_HudmsgTypes[type].unk08,
|
|
g_HudmsgTypes[type].colour,
|
|
g_HudmsgTypes[type].unk10,
|
|
g_HudmsgTypes[type].alignh,
|
|
g_HudmsgTypes[type].unk16,
|
|
g_HudmsgTypes[type].alignv,
|
|
g_HudmsgTypes[type].unk18,
|
|
-1, 0);
|
|
}
|
|
|
|
void hudmsgCreateWithDuration(char *text, s32 type, struct hudmsgtype *config, s32 duration60)
|
|
{
|
|
hudmsgCreateFromArgs(text, type,
|
|
config->unk00,
|
|
config->unk01,
|
|
config->unk02,
|
|
config->unk04,
|
|
config->unk08,
|
|
config->colour,
|
|
config->unk10,
|
|
config->alignh,
|
|
config->unk16,
|
|
config->alignv,
|
|
config->unk18,
|
|
duration60, HUDMSGFLAG_NOCHANNEL);
|
|
}
|
|
|
|
/**
|
|
* Create a hudmsg that is tied to the given audio channel. When the audio
|
|
* finishes the hudmsg is removed.
|
|
*
|
|
* This function is used for both in-game subtitles and cutscene subtitles.
|
|
* If a cutscene is in progress, the function forces the type to cutscene.
|
|
* This allows the caller to specify the type as in-game unconditionally
|
|
* and it will do the right thing.
|
|
*
|
|
* For cutscene subtitles, a dynamic width is used which means the source text
|
|
* has to be re-wrapped. There is also a limit of two lines at a time.
|
|
*
|
|
* The source text is split into individual messages. These splits are
|
|
* usually at the end of each sentence, but they also occur after commas and
|
|
* semi-colons.
|
|
*
|
|
* Each message is wrapped and appended to an accumulator. Every time the
|
|
* accumulator would exceed two lines, the accumulator is queued as a hudmsg
|
|
* and then cleared prior to appending the new message.
|
|
*
|
|
* Each hudmsg is assigned a duration according to its character length relative
|
|
* to the entire string and the audio duration.
|
|
*/
|
|
void hudmsgCreateAsSubtitle(char *srctext, s32 type, u8 colourindex, s32 audiochannelnum)
|
|
{
|
|
s32 audioduration60;
|
|
struct hudmsgtype *config;
|
|
|
|
audioduration60 = propsndGetDuration60(audiochannelnum);
|
|
|
|
if (type == HUDMSGTYPE_INGAMESUBTITLE) {
|
|
if (g_Vars.tickmode == TICKMODE_CUTSCENE) {
|
|
if (!optionsGetCutsceneSubtitles()) {
|
|
return;
|
|
}
|
|
|
|
type = HUDMSGTYPE_CUTSCENESUBTITLE;
|
|
} else if (!optionsGetInGameSubtitles()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
config = &g_HudmsgTypes[type];
|
|
config->colour = g_HudmsgColours[colourindex];
|
|
|
|
if (g_Vars.tickmode == TICKMODE_CUTSCENE && audioduration60 >= 0) {
|
|
#if VERSION == VERSION_JPN_FINAL
|
|
u16 totallen = strlen(srctext);
|
|
bool alldone = false;
|
|
f32 time60perchar = audioduration60 / (f32) totallen;
|
|
char buffer[300];
|
|
char *ptr = srctext;
|
|
|
|
while (!alldone) {
|
|
u8 len = 0;
|
|
bool paragraphdone = false;
|
|
|
|
while (!paragraphdone) {
|
|
if (*ptr == '\0') {
|
|
alldone = true;
|
|
paragraphdone = true;
|
|
} else {
|
|
if (*ptr == 'P' || *ptr == 'p') {
|
|
paragraphdone = true;
|
|
} else {
|
|
buffer[len] = *ptr;
|
|
len++;
|
|
}
|
|
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
buffer[len] = '\0';
|
|
|
|
if (len != 0) {
|
|
hudmsgCreateWithDuration(buffer, type, config, len * time60perchar);
|
|
}
|
|
}
|
|
#else
|
|
char puncchars[] = { '.', ';', '!', '?', ',' };
|
|
u16 srclen;
|
|
s32 sp4a8;
|
|
s32 wrapwidth;
|
|
char accum[250];
|
|
char prewrap[250];
|
|
char postwrap[250];
|
|
char msg[250];
|
|
s32 msglen;
|
|
bool split;
|
|
s32 accumlen;
|
|
s32 linecount;
|
|
f32 time60perchar;
|
|
s32 i;
|
|
s32 j;
|
|
bool append;
|
|
bool foundpunctuation;
|
|
|
|
srclen = strlen(srctext);
|
|
wrapwidth = hudmsg0f0ddb1c(&sp4a8, config->unk16);
|
|
|
|
accumlen = 0;
|
|
i = 0;
|
|
time60perchar = (f32)audioduration60 / srclen;
|
|
|
|
// These two loops both work with the i iterator.
|
|
// The inner loop increments i and is looking for places to split the
|
|
// text, while the outer loop iterates once per split until the srctext
|
|
// has been completely scanned.
|
|
while (srctext[i] != '\0') {
|
|
msglen = 0;
|
|
foundpunctuation = false;
|
|
split = false;
|
|
|
|
while (srctext[i] != '\0' && (!foundpunctuation || !split || i > srclen - 10)) {
|
|
// Check if the current char is punctuation
|
|
for (j = 0; j < ARRAYCOUNT(puncchars); j++) {
|
|
if (puncchars[j] == srctext[i]) {
|
|
foundpunctuation = true;
|
|
}
|
|
}
|
|
|
|
// Avoid splitting in the middle of trailing dots,
|
|
// and also avoid splitting after "Dr." or "Mr."
|
|
if (foundpunctuation && srctext[i] == '.') {
|
|
if (srctext[i + 1] == '.') {
|
|
foundpunctuation = false;
|
|
}
|
|
|
|
if (i >= 2) {
|
|
if ((srctext[i - 2] == 'D' || srctext[i - 2] == 'd')
|
|
&& (srctext[i - 1] == 'r' || srctext[i - 1] == 'R')) {
|
|
foundpunctuation = false;
|
|
}
|
|
|
|
if ((srctext[i - 2] == 'M' || srctext[i - 2] == 'm')
|
|
&& (srctext[i - 1] == 'r' || srctext[i - 1] == 'R')) {
|
|
foundpunctuation = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the character from srctext to msg, except:
|
|
// - if it's a space at the start of the string
|
|
// - if it's a consecutive space
|
|
// - if it's a line break (sometimes copy a space instead)
|
|
if (msglen < 249) {
|
|
bool ignore = false;
|
|
|
|
if (srctext[i] == ' ') {
|
|
if (msglen == 0) {
|
|
ignore = true;
|
|
} else if (msg[msglen - 1] == ' ') {
|
|
ignore = true;
|
|
}
|
|
}
|
|
|
|
if (srctext[i] == '\n') {
|
|
ignore = true;
|
|
|
|
if (msglen != 0 && msg[msglen - 1] != ' ' && srctext[i + 1] != ' ') {
|
|
msg[msglen] = ' ';
|
|
msglen++;
|
|
}
|
|
}
|
|
|
|
if (foundpunctuation && srctext[i] == ' ') {
|
|
split = true;
|
|
}
|
|
|
|
if (!ignore) {
|
|
msg[msglen] = srctext[i];
|
|
msglen++;
|
|
}
|
|
}
|
|
|
|
if (1);
|
|
|
|
i++;
|
|
} // end of inner loop
|
|
|
|
// At this point the string in msg is a single sentence,
|
|
// free of line breaks. It still needs to be wrapped.
|
|
|
|
// Make sure msg ends in a space
|
|
if (msglen > 0 && msg[msglen - 1] != ' ') {
|
|
msg[msglen] = ' ';
|
|
msglen++;
|
|
}
|
|
|
|
// Rebuild prewrap by concatenating the accumulator and msg.
|
|
// prewrap will be everything that's been read so far and has yet to
|
|
// be queued.
|
|
for (j = 0; j < accumlen; j++) {
|
|
prewrap[j] = accum[j];
|
|
}
|
|
|
|
for (j = 0; j < msglen; j++) {
|
|
prewrap[j + accumlen] = msg[j];
|
|
}
|
|
|
|
prewrap[accumlen + msglen] = '\n';
|
|
prewrap[accumlen + msglen + 1] = '\0';
|
|
|
|
// Apply text wrapping to prewrap
|
|
textWrap(wrapwidth, prewrap, postwrap, g_CharsHandelGothicSm, g_FontHandelGothicSm);
|
|
|
|
// Next, count the number of lines in the wrapped message.
|
|
// If it's more than two, send the accumulator out as a hudmsg and
|
|
// then put msg in the accumulator. Otherwise, just append msg to
|
|
// the accumulator.
|
|
|
|
// Note that these strings always end in a line break, so counting
|
|
// the line breaks is the same as counting visual lines
|
|
linecount = 0;
|
|
|
|
for (j = 0; postwrap[j] != '\0'; j++) {
|
|
if (postwrap[j] == '\n') {
|
|
linecount++;
|
|
}
|
|
}
|
|
|
|
append = true;
|
|
|
|
if (linecount >= 3) {
|
|
if (accumlen == 0) {
|
|
// Nothing is in the accumulator, so just queue the message
|
|
msg[msglen] = '\n';
|
|
msglen++;
|
|
|
|
msg[msglen] = '\0';
|
|
|
|
hudmsgCreateWithDuration(msg, type, config, msglen * time60perchar);
|
|
append = false;
|
|
} else {
|
|
// Queue the accumulator and then clear it.
|
|
// The current message will be copied into the accumulator
|
|
// for the next iteration.
|
|
accum[accumlen] = '\n';
|
|
accumlen++;
|
|
|
|
accum[accumlen] = '\0';
|
|
|
|
hudmsgCreateWithDuration(accum, type, config, accumlen * time60perchar);
|
|
accumlen = 0;
|
|
}
|
|
}
|
|
|
|
if (append) {
|
|
for (j = 0; j < msglen; j++) {
|
|
accum[accumlen + j] = msg[j];
|
|
}
|
|
|
|
accumlen += msglen;
|
|
}
|
|
|
|
msg[msglen] = '\0';
|
|
} // end of outer loop
|
|
|
|
// If there's anything remaining in the accumulator, queue it
|
|
if (accumlen != 0) {
|
|
accum[accumlen] = '\n';
|
|
accumlen++;
|
|
|
|
accum[accumlen] = '\0';
|
|
|
|
hudmsgCreateWithDuration(accum, type, config, accumlen * time60perchar);
|
|
}
|
|
#endif
|
|
} else {
|
|
hudmsgCreateFromArgs(srctext, type, config->unk00, config->unk01, config->unk02,
|
|
config->unk04, config->unk08, config->colour, config->unk10, config->alignh,
|
|
config->unk16, config->alignv, config->unk18, audiochannelnum, 0);
|
|
}
|
|
}
|
|
|
|
void hudmsgCreateFromArgsWithoutFlags(char *text, s32 type, s32 conf00, s32 conf01, s32 conf02, struct fontchar **conf04, struct font **conf08, u32 textcolour, u32 shadowcolour, u32 alignh, s32 conf16, u32 alignv, s32 conf18, s32 arg14)
|
|
{
|
|
hudmsgCreateFromArgs(text, type,
|
|
conf00,
|
|
conf01,
|
|
conf02,
|
|
conf04,
|
|
conf08,
|
|
textcolour,
|
|
shadowcolour,
|
|
alignh,
|
|
conf16,
|
|
alignv,
|
|
conf18,
|
|
arg14, 0);
|
|
}
|
|
|
|
void hudmsgCalculatePosition(struct hudmessage *msg)
|
|
{
|
|
s32 x;
|
|
s32 y;
|
|
s32 viewleft = g_Vars.players[msg->playernum]->viewleft / g_ScaleX;
|
|
s32 viewtop = g_Vars.players[msg->playernum]->viewtop;
|
|
s32 viewwidth = g_Vars.players[msg->playernum]->viewwidth / g_ScaleX;
|
|
s32 viewheight = g_Vars.players[msg->playernum]->viewheight;
|
|
s32 v0;
|
|
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
s32 offset = (msg->alignh == HUDMSGALIGN_XMIDDLE) ? 10 : 0;
|
|
|
|
if (PLAYERCOUNT() >= 3) {
|
|
viewwidth -= offset;
|
|
|
|
if (g_Vars.currentplayernum == 0 || g_Vars.currentplayernum == 2) {
|
|
viewleft += offset;
|
|
}
|
|
}
|
|
|
|
if (PLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())) {
|
|
#if VERSION >= VERSION_PAL_FINAL
|
|
if (!g_InCutscene || g_MainIsEndscreen)
|
|
#endif
|
|
{
|
|
viewwidth -= offset;
|
|
|
|
if (g_Vars.currentplayernum == 0) {
|
|
viewleft += offset;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch (msg->alignh) {
|
|
case HUDMSGALIGN_SCREENLEFT:
|
|
x = msg->xmargin;
|
|
break;
|
|
case HUDMSGALIGN_LEFT:
|
|
v0 = (g_InCutscene && !g_MainIsEndscreen) ? 24 : msg->xmarginextra;
|
|
|
|
x = viewleft + v0 + msg->xmargin + 3;
|
|
|
|
if (PLAYERCOUNT() == 2
|
|
&& (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())
|
|
&& (!g_InCutscene || g_MainIsEndscreen)) {
|
|
if (IS4MB()) {
|
|
if (msg->playernum == 0) {
|
|
x--;
|
|
} else if (msg->playernum == 1) {
|
|
x -= 16;
|
|
}
|
|
} else {
|
|
if (msg->playernum == 0) {
|
|
x += 15;
|
|
} else if (msg->playernum == 1) {
|
|
x += 4;
|
|
}
|
|
}
|
|
} else if (PLAYERCOUNT() >= 3) {
|
|
if ((msg->playernum % 2) == 0) {
|
|
x--;
|
|
} else {
|
|
x -= 16;
|
|
}
|
|
}
|
|
break;
|
|
case HUDMSGALIGN_RIGHT:
|
|
x = viewleft + viewwidth - msg->width - msg->xmargin - 57;
|
|
break;
|
|
case HUDMSGALIGN_XMIDDLE:
|
|
x = (viewwidth - msg->width) / 2 + viewleft + msg->xmargin;
|
|
break;
|
|
default:
|
|
x = msg->xmargin;
|
|
break;
|
|
}
|
|
|
|
switch (msg->alignv) {
|
|
case HUDMSGALIGN_SCREENTOP:
|
|
y = msg->ymargin;
|
|
break;
|
|
case HUDMSGALIGN_TOP:
|
|
y = viewtop + msg->ymargin + 13;
|
|
break;
|
|
case HUDMSGALIGN_BOTTOM:
|
|
y = viewtop + viewheight - msg->height - msg->ymargin - 14;
|
|
|
|
if (PLAYERCOUNT() == 2 && (g_InCutscene == 0 || g_MainIsEndscreen)) {
|
|
if (IS4MB() || (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL && msg->playernum == 0)) {
|
|
y += 8;
|
|
} else {
|
|
y += 3;
|
|
}
|
|
} else if (PLAYERCOUNT() >= 3) {
|
|
if (msg->playernum <= 1) {
|
|
y += 8;
|
|
} else {
|
|
y += 3;
|
|
}
|
|
} else {
|
|
if (optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) {
|
|
y += 8;
|
|
}
|
|
}
|
|
break;
|
|
case HUDMSGALIGN_YMIDDLE:
|
|
y = (viewheight - msg->height) / 2 + viewtop + msg->ymargin;
|
|
break;
|
|
case HUDMSGALIGN_BELOWVIEWPORT:
|
|
y = viewtop + viewheight - (msg->height / 2) + 18;
|
|
break;
|
|
default:
|
|
y = msg->ymargin;
|
|
break;
|
|
}
|
|
|
|
msg->x = x;
|
|
msg->y = y;
|
|
}
|
|
|
|
void hudmsgCreateFromArgs(char *text, s32 type, s32 conf00, s32 conf01, s32 conf02,
|
|
struct fontchar **conf04, struct font **conf08,
|
|
u32 textcolour, u32 glowcolour,
|
|
u32 alignh, s32 conf16, u32 alignv, s32 conf18, s32 arg14, u32 flags)
|
|
{
|
|
s32 j;
|
|
struct hudmessage *msg;
|
|
s32 hash = 0;
|
|
s32 i;
|
|
s32 index;
|
|
s32 textwidth;
|
|
s32 textheight;
|
|
s32 xmarginaextra;
|
|
s32 wrapwidth;
|
|
char stacktext[400];
|
|
s32 writeindex;
|
|
|
|
if (type == HUDMSGTYPE_INGAMESUBTITLE && !optionsGetInGameSubtitles()) {
|
|
return;
|
|
}
|
|
|
|
for (j = 0; text[j] != '\0'; j++) {
|
|
hash = hash + text[j];
|
|
}
|
|
|
|
if ((flags & HUDMSGFLAG_ONLYIFALIVE) == 0 || !g_Vars.currentplayer->isdead) {
|
|
if ((flags & HUDMSGFLAG_ALLOWDUPES) == 0) {
|
|
// Check for duplicate messages
|
|
s32 dupeofindex = -1;
|
|
|
|
for (index = 0; index < g_NumHudMessages; index++) {
|
|
if (g_HudMessages[index].state != HUDMSGSTATE_FREE
|
|
&& g_HudMessages[index].state != HUDMSGSTATE_FADINGOUT
|
|
&& g_HudMessages[index].playernum == g_Vars.currentplayernum
|
|
&& g_HudMessages[index].hash == hash) {
|
|
dupeofindex = index;
|
|
}
|
|
}
|
|
|
|
if (dupeofindex >= 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if PAL
|
|
g_ScaleX = 1;
|
|
#else
|
|
g_ScaleX = g_ViRes == VIRES_HI ? 2 : 1;
|
|
#endif
|
|
|
|
// Find an unused index for the new message
|
|
for (index = 0; index < g_NumHudMessages; index++) {
|
|
if (g_HudMessages[index].state == HUDMSGSTATE_FREE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index >= g_NumHudMessages
|
|
&& (type == HUDMSGTYPE_OBJECTIVECOMPLETE
|
|
|| type == HUDMSGTYPE_OBJECTIVEFAILED
|
|
|| type == HUDMSGTYPE_INGAMESUBTITLE)) {
|
|
// Out of space - Check if an existing message can be replaced
|
|
index = hudmsgGetNext(-1);
|
|
|
|
while (index >= 0) {
|
|
if (g_HudMessages[index].state == HUDMSGSTATE_QUEUED) {
|
|
if (g_HudMessages[index].type == HUDMSGTYPE_DEFAULT
|
|
|| g_HudMessages[index].type == HUDMSGTYPE_3
|
|
|| g_HudMessages[index].type == HUDMSGTYPE_4) {
|
|
// Good to replace this one
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Can't replace - try and find another
|
|
index = hudmsgGetNext(g_HudMessages[index].id);
|
|
}
|
|
}
|
|
|
|
if (index >= 0 && index < g_NumHudMessages) {
|
|
xmarginaextra = 0;
|
|
msg = &g_HudMessages[index];
|
|
wrapwidth = hudmsg0f0ddb1c(&xmarginaextra, conf16);
|
|
textMeasure(&textheight, &textwidth, text, *conf04, *conf08, 0);
|
|
|
|
#if VERSION >= VERSION_JPN_FINAL
|
|
if (textwidth > wrapwidth && (flags & HUDMSGFLAG_NOWRAP) == 0)
|
|
#else
|
|
if (textwidth > wrapwidth)
|
|
#endif
|
|
{
|
|
i = 0;
|
|
writeindex = 0;
|
|
|
|
while (i < 400 && text[i] != '\0') {
|
|
if (text[i] != '\n') {
|
|
stacktext[writeindex++] = text[i];
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
stacktext[writeindex++] = '\n';
|
|
stacktext[writeindex++] = '\0';
|
|
|
|
textWrap(wrapwidth, stacktext, msg->text, *conf04, *conf08);
|
|
textMeasure(&textheight, &textwidth, msg->text, *conf04, *conf08, 0);
|
|
} else {
|
|
strncpy(msg->text, text, 399);
|
|
msg->text[399] = '\0';
|
|
}
|
|
|
|
msg->flags = flags;
|
|
msg->playernum = g_Vars.currentplayernum;
|
|
msg->type = type;
|
|
msg->id = g_NextHudMessageId++;
|
|
msg->state = HUDMSGSTATE_QUEUED;
|
|
msg->timer = 0;
|
|
msg->boxed = conf00;
|
|
msg->allowfadein = conf01;
|
|
msg->flash = conf02;
|
|
msg->font1 = *conf04;
|
|
msg->font2 = *conf08;
|
|
msg->textcolour = textcolour;
|
|
msg->glowcolour = glowcolour;
|
|
msg->alignh = alignh;
|
|
msg->alignv = alignv;
|
|
msg->width = textwidth;
|
|
msg->height = textheight;
|
|
msg->xmarginextra = xmarginaextra;
|
|
msg->xmargin = conf16;
|
|
msg->ymargin = conf18;
|
|
msg->hash = hash;
|
|
|
|
hudmsgCalculatePosition(msg);
|
|
|
|
if (flags & HUDMSGFLAG_NOCHANNEL) {
|
|
msg->showduration = TICKS(arg14);
|
|
msg->channelnum = -1;
|
|
} else {
|
|
msg->showduration = TICKS(g_HudmsgTypes[type].duration);
|
|
msg->channelnum = arg14;
|
|
}
|
|
}
|
|
|
|
g_ScaleX = 1;
|
|
}
|
|
}
|
|
|
|
void hudmsgsTick(void)
|
|
{
|
|
s32 k;
|
|
s32 previd;
|
|
bool show;
|
|
struct hudmessage *msg;
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
s32 prevplayernum;
|
|
#endif
|
|
s32 i;
|
|
s32 j;
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
s32 index;
|
|
bool hide;
|
|
#else
|
|
bool hide;
|
|
s32 index;
|
|
#endif
|
|
f32 fadeintime;
|
|
f32 fadeouttime;
|
|
|
|
g_HudmsgsActive = false;
|
|
|
|
#if PAL
|
|
g_ScaleX = 1;
|
|
#else
|
|
g_ScaleX = (g_ViRes == VIRES_HI) ? 2 : 1;
|
|
#endif
|
|
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
prevplayernum = g_Vars.currentplayernum;
|
|
|
|
for (k = 0; k < g_NumHudMessages; k++) {
|
|
if (g_HudMessages[k].state != HUDMSGSTATE_FREE) {
|
|
if (g_Vars.tickmode == TICKMODE_CUTSCENE) {
|
|
for (j = 0; j < g_NumHudMessages; j++) {
|
|
if (k != j
|
|
&& g_HudMessages[j].state != HUDMSGSTATE_FREE
|
|
&& g_HudMessages[j].hash == g_HudMessages[k].hash) {
|
|
g_HudMessages[j].state = HUDMSGSTATE_FREE;
|
|
}
|
|
}
|
|
}
|
|
|
|
setCurrentPlayerNum(g_HudMessages[k].playernum);
|
|
hudmsgCalculatePosition(&g_HudMessages[k]);
|
|
}
|
|
}
|
|
|
|
setCurrentPlayerNum(prevplayernum);
|
|
#else
|
|
for (k = 0; k < g_NumHudMessages; k++) {
|
|
if (g_HudMessages[k].state != HUDMSGSTATE_FREE) {
|
|
hudmsgCalculatePosition(&g_HudMessages[k]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
previd = -1; \
|
|
while (true) {
|
|
index = hudmsgGetNext(previd);
|
|
|
|
if (index < 0) {
|
|
break;
|
|
}
|
|
|
|
msg = &g_HudMessages[index];
|
|
previd = msg->id;
|
|
|
|
if (msg->channelnum >= 0) {
|
|
msg->opacity = propsndGetSubtitleOpacity(msg->channelnum);
|
|
} else {
|
|
msg->opacity = 0xff;
|
|
}
|
|
|
|
if (msg->type == HUDMSGTYPE_CUTSCENESUBTITLE && g_Vars.tickmode != TICKMODE_CUTSCENE) {
|
|
msg->state = HUDMSGSTATE_FREE;
|
|
msg->timer = 0;
|
|
}
|
|
|
|
switch (msg->state) {
|
|
case HUDMSGSTATE_QUEUED:
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
if (msg->flags & HUDMSGFLAG_DELAY) {
|
|
msg->timer++;
|
|
|
|
if (msg->timer > 3) {
|
|
msg->flags &= ~HUDMSGFLAG_DELAY;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
show = true;
|
|
|
|
if (g_Vars.players[msg->playernum]->isdead) {
|
|
show = false;
|
|
}
|
|
|
|
if (show) {
|
|
// Check if any other message is occupying our space
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
if (g_HudMessages[i].state != HUDMSGSTATE_FREE
|
|
&& g_HudMessages[i].state != HUDMSGSTATE_QUEUED
|
|
&& g_HudMessages[i].x + g_HudMessages[i].width >= msg->x
|
|
&& g_HudMessages[i].x <= msg->x + msg->width
|
|
&& g_HudMessages[i].y + g_HudMessages[i].height >= msg->y
|
|
&& g_HudMessages[i].y <= msg->y + msg->height) {
|
|
show = false;
|
|
|
|
// Consider booting the previous message out earlier
|
|
if (g_HudMessages[i].type == msg->type
|
|
&& msg->boxed
|
|
&& g_HudMessages[i].boxed
|
|
&& g_HudMessages[i].state == HUDMSGSTATE_FADINGOUT) {
|
|
g_HudMessages[i].state = HUDMSGSTATE_FREE;
|
|
g_HudMessages[i].timer = 0;
|
|
msg->state = HUDMSGSTATE_FADINGIN;
|
|
msg->timer = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (show) {
|
|
if (msg->boxed) {
|
|
msg->state = HUDMSGSTATE_CHOOSETRANSITION;
|
|
} else if (msg->allowfadein) {
|
|
msg->state = HUDMSGSTATE_FADINGIN;
|
|
} else {
|
|
msg->state = HUDMSGSTATE_ONSCREEN;
|
|
}
|
|
|
|
msg->timer = 0;
|
|
|
|
if (msg->type == HUDMSGTYPE_CUTSCENESUBTITLE) {
|
|
msg->state = HUDMSGSTATE_ONSCREEN;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case HUDMSGSTATE_CHOOSETRANSITION:
|
|
if (msg->boxed && msg->allowfadein) {
|
|
msg->state = HUDMSGSTATE_FADINGIN;
|
|
} else {
|
|
msg->state = HUDMSGSTATE_ONSCREEN;
|
|
}
|
|
|
|
if (msg->type == HUDMSGTYPE_CUTSCENESUBTITLE) {
|
|
msg->state = HUDMSGSTATE_ONSCREEN;
|
|
}
|
|
|
|
msg->timer = 0;
|
|
break;
|
|
case HUDMSGSTATE_FADINGIN:
|
|
if (msg->type == HUDMSGTYPE_CUTSCENESUBTITLE) {
|
|
// Cutscene subtitles appear immediately
|
|
msg->state = HUDMSGSTATE_ONSCREEN;
|
|
msg->timer = 0;
|
|
} else {
|
|
// Most HUD messages play a swish sound effect
|
|
if (msg->timer == 0
|
|
&& !lvIsPaused()
|
|
&& !mpIsPaused()
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
&& msg->type != HUDMSGTYPE_CUTSCENESUBTITLE
|
|
&& msg->type != HUDMSGTYPE_INGAMESUBTITLE
|
|
#endif
|
|
&& PLAYERCOUNT() == 1) {
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
sndStart(var80095200, SFX_HUDMSG, NULL, -1, -1, -1, -1, -1);
|
|
#else
|
|
sndStart(var80095200, SFX_HUDMSG, &var800736b0nb, -1, -1, -1, -1, -1);
|
|
#endif
|
|
}
|
|
|
|
fadeintime = (sqrtf(msg->width * msg->width + msg->height * msg->height) + 132) / PALUPF(7.0f);
|
|
|
|
msg->timer += g_Vars.lvupdate60;
|
|
|
|
if (msg->timer >= (s32)fadeintime || msg->type == HUDMSGTYPE_CUTSCENESUBTITLE) {
|
|
msg->state = HUDMSGSTATE_ONSCREEN;
|
|
msg->timer = 0;
|
|
}
|
|
}
|
|
break;
|
|
case HUDMSGSTATE_ONSCREEN:
|
|
msg->timer += g_Vars.lvupdate60;
|
|
|
|
hide = false;
|
|
|
|
// Subtitles have an audio channel number and are hidden when the audio stops
|
|
if (msg->channelnum >= 0) {
|
|
if (audioIsChannelIdle(msg->channelnum)) {
|
|
hide = true;
|
|
} else if (msg->flags & HUDMSGFLAG_FORCEOFF) {
|
|
msg->flags &= ~HUDMSGFLAG_FORCEOFF;
|
|
hide = true;
|
|
}
|
|
} else if (msg->timer >= msg->showduration && msg->showduration != -1) {
|
|
hide = true;
|
|
}
|
|
|
|
if (hide) {
|
|
if (msg->boxed) {
|
|
msg->state = HUDMSGSTATE_FADINGOUT;
|
|
} else {
|
|
msg->state = HUDMSGSTATE_FREE;
|
|
}
|
|
|
|
msg->timer = 0;
|
|
}
|
|
break;
|
|
case HUDMSGSTATE_FADINGOUT:
|
|
fadeouttime = (sqrtf(msg->width * msg->width + msg->height * msg->height) + 92) / PALUPF(7.0f);
|
|
|
|
msg->timer += g_Vars.lvupdate60;
|
|
|
|
if (msg->timer >= (s32)fadeouttime) {
|
|
msg->state = HUDMSGSTATE_FREE;
|
|
msg->timer = 0;
|
|
}
|
|
break;
|
|
case HUDMSGSTATE_FREE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_ScaleX = 1;
|
|
}
|
|
|
|
void hudmsgsSetOn(u32 reason)
|
|
{
|
|
g_Vars.currentplayer->hudmessoff &= ~reason;
|
|
}
|
|
|
|
void hudmsgsSetOff(u32 reason)
|
|
{
|
|
g_Vars.currentplayer->hudmessoff |= reason;
|
|
}
|
|
|
|
void hudmsgsRemoveForDeadPlayer(s32 playernum)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
if (g_HudMessages[i].state
|
|
&& g_HudMessages[i].playernum == playernum
|
|
&& (g_HudMessages[i].flags & HUDMSGFLAG_ONLYIFALIVE)) {
|
|
g_HudMessages[i].state = HUDMSGSTATE_FREE;
|
|
g_HudMessages[i].timer = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
Gfx *hudmsgsRender(Gfx *gdl)
|
|
{
|
|
struct hudmessage *msg;
|
|
s32 i;
|
|
u32 textcolour;
|
|
u32 glowcolour;
|
|
f32 sin;
|
|
s32 x;
|
|
s32 y;
|
|
s32 timerthing = 255;
|
|
s32 spdc = true;
|
|
|
|
#if PAL
|
|
g_ScaleX = 1;
|
|
#else
|
|
g_ScaleX = g_ViRes == VIRES_HI ? 2 : 1;
|
|
#endif
|
|
|
|
gdl = text0f153628(gdl);
|
|
|
|
if ((g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0)
|
|
&& g_InCutscene
|
|
&& g_MainIsEndscreen == 0
|
|
&& g_Vars.currentplayernum == 0) {
|
|
spdc = false;
|
|
}
|
|
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
msg = &g_HudMessages[i];
|
|
|
|
if (!msg->opacity) {
|
|
continue;
|
|
}
|
|
|
|
if (msg->state == HUDMSGSTATE_FREE
|
|
|| msg->state == HUDMSGSTATE_QUEUED
|
|
|| (spdc && g_Vars.currentplayernum != msg->playernum)) {
|
|
continue;
|
|
}
|
|
|
|
if (msg->flash) {
|
|
s32 alpha;
|
|
sin = sinf((msg->timer * M_PI) / 60.0f);
|
|
|
|
if (sin < 0.0f) {
|
|
sin = -sin;
|
|
}
|
|
|
|
alpha = 192.0f * sin;
|
|
|
|
textcolour = (msg->textcolour & 0xffffff00) + alpha;
|
|
glowcolour = msg->glowcolour;
|
|
} else {
|
|
textcolour = msg->textcolour | 0xa0;
|
|
glowcolour = msg->glowcolour;
|
|
}
|
|
|
|
if (msg->opacity != 255) {
|
|
u32 textalpha = textcolour & 0xff;
|
|
u32 glowalpha = glowcolour & 0xff;
|
|
|
|
textalpha = (msg->opacity * textalpha) / 255;
|
|
glowalpha = (msg->opacity * glowalpha) / 255;
|
|
|
|
textcolour = (textcolour & 0xffffff00) + (textalpha & 0xff);
|
|
glowcolour = (glowcolour & 0xffffff00) + (glowalpha & 0xff);
|
|
}
|
|
|
|
x = msg->x;
|
|
y = msg->y;
|
|
|
|
if (msg->type == HUDMSGTYPE_INGAMESUBTITLE && playerIsHealthVisible()) {
|
|
y += (s32)(16.0f * playerGetHealthBarHeightFrac());
|
|
}
|
|
|
|
if (msg->type == HUDMSGTYPE_CUTSCENESUBTITLE) {
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
gDPSetScissor(gdl++, 0,
|
|
(x - 4) * g_ScaleX, 0,
|
|
(x + msg->width + 3) * g_ScaleX, viGetBufHeight());
|
|
#else
|
|
gDPSetScissor(gdl++, 0,
|
|
(x - 4) * g_ScaleX, y - 4,
|
|
(x + msg->width + 3) * g_ScaleX, y + msg->height + 3);
|
|
#endif
|
|
}
|
|
|
|
switch (msg->state) {
|
|
case HUDMSGSTATE_FREE:
|
|
case HUDMSGSTATE_QUEUED:
|
|
break;
|
|
case HUDMSGSTATE_FADINGIN:
|
|
{
|
|
u32 bordercolour = msg->textcolour | 0x40;
|
|
f32 tmp;
|
|
f32 spc0;
|
|
|
|
if (msg->opacity != 255) {
|
|
u32 alpha = (msg->opacity * (bordercolour & 0xff)) / 255;
|
|
bordercolour = (bordercolour & 0xffffff00) + (alpha & 0xff);
|
|
}
|
|
|
|
spc0 = (sqrtf(msg->width * msg->width + msg->height * msg->height) + 132.0f) / PALUPF(7.0f);
|
|
|
|
if (spc0 > 30.0f) {
|
|
spc0 = 30.0f;
|
|
}
|
|
|
|
spc0 = msg->timer / spc0;
|
|
|
|
if (spc0 > 1.0f) {
|
|
spc0 = 1.0f;
|
|
}
|
|
|
|
if (spc0 < 0.0f) {
|
|
spc0 = 0.0f;
|
|
}
|
|
|
|
tmp = msg->timer * PALUPF(7.0f);
|
|
|
|
textSetDiagonalBlend(x, y, tmp, DIAGMODE_FADEIN);
|
|
|
|
if (msg->boxed) {
|
|
#if VERSION >= VERSION_JPN_FINAL
|
|
gdl = hudmsgRenderBox(gdl, x - 3, y - 3, x + msg->width + 2, y + msg->height - 1, 1.0f, bordercolour, spc0);
|
|
#else
|
|
gdl = hudmsgRenderBox(gdl, x - 3, y - 3, x + msg->width + 2, y + msg->height + 2, 1.0f, bordercolour, spc0);
|
|
#endif
|
|
|
|
gdl = textRenderProjected(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
} else {
|
|
gdl = text0f153a34(gdl, x, y, x + msg->width, y + msg->height, 0);
|
|
|
|
#if VERSION >= VERSION_JPN_FINAL
|
|
gdl = func0f1574d0jf(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, glowcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
#else
|
|
gdl = textRender(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, glowcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
#endif
|
|
}
|
|
|
|
if (msg->alignv == 6) {
|
|
timerthing = 0;
|
|
}
|
|
|
|
textResetBlends();
|
|
}
|
|
break;
|
|
case HUDMSGSTATE_ONSCREEN:
|
|
if (msg->boxed) {
|
|
u32 bordercolour = msg->textcolour | 0x40;
|
|
|
|
if (msg->opacity != 255) {
|
|
u32 alpha = (msg->opacity * (bordercolour & 0xff)) / 255;
|
|
bordercolour = (bordercolour & 0xffffff00) + (alpha & 0xff);
|
|
}
|
|
|
|
#if VERSION >= VERSION_JPN_FINAL
|
|
gdl = hudmsgRenderBox(gdl, x - 3, y - 3, x + msg->width + 2, y + msg->height - 1, 1.0f, bordercolour, 1.0f);
|
|
#else
|
|
gdl = hudmsgRenderBox(gdl, x - 3, y - 3, x + msg->width + 2, y + msg->height + 2, 1.0f, bordercolour, 1.0f);
|
|
#endif
|
|
|
|
gdl = textRenderProjected(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
} else {
|
|
gdl = text0f153a34(gdl, x, y, x + msg->width, y + msg->height, 0);
|
|
|
|
#if VERSION >= VERSION_JPN_FINAL
|
|
gdl = func0f1574d0jf(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, glowcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
#else
|
|
gdl = textRender(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, glowcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
#endif
|
|
}
|
|
if (msg->alignv == 6) {
|
|
timerthing = 0;
|
|
}
|
|
break;
|
|
case HUDMSGSTATE_FADINGOUT:
|
|
{
|
|
u32 bordercolour;
|
|
u32 stack;
|
|
f32 spa8 = (sqrtf(msg->width * msg->width + msg->height * msg->height) + 92.0f) / PALUPF(7.0f);
|
|
f32 tmp;
|
|
|
|
bordercolour = msg->textcolour | 0x40;
|
|
|
|
if (msg->opacity != 255) {
|
|
u32 alpha = (msg->opacity * (bordercolour & 0xff)) / 255;
|
|
bordercolour = (bordercolour & 0xffffff00) + (alpha & 0xff);
|
|
}
|
|
|
|
tmp = (spa8 - msg->timer) * PALUPF(7.0f);
|
|
|
|
textSetDiagonalBlend(x + msg->width, y + msg->height, tmp, DIAGMODE_FADEOUT);
|
|
|
|
if (spa8 > 30.0f) {
|
|
spa8 = 30.0f;
|
|
}
|
|
|
|
spa8 = msg->timer / spa8;
|
|
|
|
if (spa8 > 1.0f) {
|
|
spa8 = 1.0f;
|
|
}
|
|
|
|
if (msg->boxed) {
|
|
#if VERSION >= VERSION_JPN_FINAL
|
|
gdl = hudmsgRenderBox(gdl, x - 3, y - 3, x + msg->width + 2, y + msg->height - 1, 1.0f, bordercolour, 1.0f - spa8);
|
|
#else
|
|
gdl = hudmsgRenderBox(gdl, x - 3, y - 3, x + msg->width + 2, y + msg->height + 2, 1.0f, bordercolour, 1.0f - spa8);
|
|
#endif
|
|
|
|
gdl = textRenderProjected(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
} else {
|
|
gdl = text0f153a34(gdl, x, y, x + msg->width, y + msg->height, 0);
|
|
|
|
#if VERSION >= VERSION_JPN_FINAL
|
|
gdl = func0f1574d0jf(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, glowcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
#else
|
|
gdl = textRender(gdl, &x, &y, msg->text, msg->font1, msg->font2, textcolour, glowcolour, viGetWidth(), viGetHeight(), 0, 0);
|
|
#endif
|
|
}
|
|
|
|
if (msg->alignv == 6) {
|
|
timerthing = 0;
|
|
}
|
|
|
|
textResetBlends();
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (msg->type == HUDMSGTYPE_CUTSCENESUBTITLE) {
|
|
gDPSetScissor(gdl++, 0,
|
|
viGetViewLeft(), viGetViewTop(),
|
|
viGetViewLeft() + viGetViewWidth(), viGetViewTop() + viGetViewHeight());
|
|
}
|
|
}
|
|
|
|
if (timerthing) {
|
|
if (optionsGetShowMissionTime(g_Vars.currentplayerstats->mpindex)
|
|
&& var80075d60 == 2
|
|
&& g_Vars.normmplayerisrunning == false
|
|
&& g_Vars.stagenum != STAGE_CITRAINING
|
|
&& g_Vars.currentplayer->cameramode != CAMERAMODE_EYESPY
|
|
&& g_Vars.currentplayer->cameramode != CAMERAMODE_THIRDPERSON) {
|
|
gdl = hudmsgRenderMissionTimer(gdl, timerthing);
|
|
}
|
|
|
|
if (hudmsgIsZoomRangeVisible()) {
|
|
gdl = hudmsgRenderZoomRange(gdl, timerthing);
|
|
}
|
|
|
|
gdl = countdownTimerRender(gdl);
|
|
}
|
|
|
|
gdl = text0f153780(gdl);
|
|
|
|
g_ScaleX = 1;
|
|
|
|
return gdl;
|
|
}
|
|
|
|
void hudmsgsStop(void)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < g_NumHudMessages; i++) {
|
|
g_HudMessages[i].state = HUDMSGSTATE_FREE;
|
|
}
|
|
|
|
#if VERSION < VERSION_NTSC_1_0
|
|
if (var800736b0nb && sndGetState(var800736b0nb) != AL_STOPPED) {
|
|
audioStop(var800736b0nb);
|
|
}
|
|
#endif
|
|
}
|