tmc/src/title.c

484 lines
15 KiB
C

/**
* @file title.c
* @ingroup Tasks
*
* @brief Title task
*/
#include "sound.h"
#include "main.h"
#include "entity.h"
#include "common.h"
#include "screen.h"
#include "object.h"
#include "message.h"
#include "functions.h"
#include "save.h"
#include "area.h"
#include "item.h"
#include "game.h"
typedef struct {
u8 filler0[0x4];
u8 language;
u8 state;
u8 subState;
u8 filler7[0x1];
u16 timer;
u8 fillerA[0x6];
u8 lightRaysPaletteGroup;
u8 lightRaysAlphaBlendIndex;
u8 counter;
u8 filler13[0x19];
int swordBgScaleRatio;
} IntroState;
// TODO: This occupies the same memory region as gMenu
extern IntroState gIntroState;
enum {
ADVANCE_NONE,
ADVANCE_TIMER_EXPIRED,
ADVANCE_KEY_PRESSED,
};
static void HandleNintendoCapcomLogos(void);
static void HandleTitlescreen(void);
static void UpdateSwordBgAffineData(void);
static void ExitTitlescreen(void);
static void HandleJapaneseTitlescreenAnimationIntro(void);
static void HandleTitlescreenAnimationIntro(void);
static u32 GetAdvanceState(void);
static void UpdateLightRays(void);
static void UpdatePressStartIcon(void);
static void (*const sIntroSequenceHandlers[])(void) = {
HandleNintendoCapcomLogos,
HandleTitlescreen,
ExitTitlescreen,
};
static const u16 sLightRaysAlphaBlends[] = {
BLDALPHA_BLEND(9, 9), BLDALPHA_BLEND(8, 10), BLDALPHA_BLEND(7, 11), BLDALPHA_BLEND(6, 12),
BLDALPHA_BLEND(5, 13), BLDALPHA_BLEND(6, 12), BLDALPHA_BLEND(7, 11), BLDALPHA_BLEND(8, 10),
};
#define FLAG_BYTE(bank, flag) (((bank) + (flag)) >> 3)
#define FLAG_X(flag) (1 << ((flag)&7))
#ifdef DEMO_JP
static const SaveFile gDemoSave = {
.initialized = 1,
.msg_speed = 1,
.brightness = 1,
.global_progress = 1,
.field_0x20 = 0x1F,
.windcrests = 0x00013780,
.unk50 = 7,
.areaVisitFlags = { 0x0114C300 },
.name = "\x97\x7f\xdd",
.saved_status = {
.area_next = AREA_DEEPWOOD_SHRINE,
.room_next = ROOM_DEEPWOOD_SHRINE_ENTRANCE,
.start_pos_x = 0xa8,
.start_pos_y = 0xc8,
.layer = 1,
.overworld_map_x = 0xd66,
.overworld_map_y = 0xae0,
},
.stats = {
.health = 40,
.maxHealth = 40,
.itemButtons = { ITEM_SHIELD, ITEM_SMITH_SWORD },
.rupees = 5,
},
.inventory = {
[0] = 5,
[3] = 4,
[13] = 6,
[17] = 0x40,
[22] = 0x40,
},
.flags = {
[FLAG_BYTE(FLAG_BANK_G, START)] = FLAG_X(START) | FLAG_X(EZERO_1ST) | FLAG_X(TABIDACHI),
[FLAG_BYTE(FLAG_BANK_G, OUTDOOR)] = FLAG_X(OUTDOOR),
[FLAG_BYTE(FLAG_BANK_G, ENTRANCE_0)] = FLAG_X(ENTRANCE_0),
[FLAG_BYTE(FLAG_BANK_1, MORI_00_KOBITO)] = FLAG_X(MORI_00_KOBITO) | FLAG_X(MORI_ENTRANCE_1ST),
[FLAG_BYTE(FLAG_BANK_1, SOUGEN_01_ZELDA)] = FLAG_X(SOUGEN_01_ZELDA),
[FLAG_BYTE(FLAG_BANK_1, SOUGEN_06_WAKAGI_1)] = FLAG_X(SOUGEN_06_WAKAGI_1) | FLAG_X(SOUGEN_06_WAKAGI_2) | FLAG_X(SOUGEN_06_WAKAGI_3),
[FLAG_BYTE(FLAG_BANK_1, SOUGEN_06_AKINDO)] = FLAG_X(SOUGEN_06_AKINDO),
[FLAG_BYTE(FLAG_BANK_1, CASTLE_04_MEZAME)] = FLAG_X(CASTLE_04_MEZAME),
[FLAG_BYTE(FLAG_BANK_1, MACHI_01_DEMO)] = FLAG_X(MACHI_01_DEMO),
[FLAG_BYTE(FLAG_BANK_2, MHOUSE15_OP1ST)] = FLAG_X(MHOUSE15_OP1ST),
[FLAG_BYTE(FLAG_BANK_2, M_PRIEST_TALK)] = FLAG_X(M_PRIEST_TALK) | FLAG_X(M_ELDER_TALK1ST) | FLAG_X(M_PRIEST_MOVE),
[FLAG_BYTE(FLAG_BANK_2, KOBITO_MORI_1ST)] = FLAG_X(KOBITO_MORI_1ST),
[FLAG_BYTE(FLAG_BANK_5, LV1_0B_WALK)] = FLAG_X(LV1_0B_WALK),
},
};
static const u8 unk[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0,
0xc2, 0xd1, 0xc2, 0xd2, 0xc2, 0xd3, 0xc2, 0xd4, 0xc2, 0xd5, 0xc2, 0xd6, 0xc2,
0xd7, 0xc2, 0xd8, 0xc2, 0x00, 0xf0, 0x11, 0x90, 0x11, 0x90, 0x05, 0xd3, 0x49,
0xd3, 0x26, 0xd3, 0x65, 0xdf, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90,
0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11,
0x90, 0x11, 0x90, 0x8a, 0xc2, 0x8b, 0xc2, 0x8c, 0xc2, 0x8d, 0xc2 };
#endif
static u32 AdvanceIntroSequence(u32 transition) {
gUI.lastState = transition;
gMain.state = GAMETASK_MAIN;
MemClear(&gIntroState, sizeof(gIntroState));
SetFade(FADE_IN_OUT | FADE_BLACK_WHITE | FADE_INSTANT, 8);
}
void TitleTask(void) {
FlushSprites();
switch (gMain.state) {
case 0:
MessageInitialize();
MemClear(&gUI, sizeof(gUI));
AdvanceIntroSequence(0);
break;
case 1:
sIntroSequenceHandlers[gUI.lastState]();
break;
case 2:
if (gFadeControl.active) {
return;
}
DispReset(1);
gMain.state = GAMETASK_INIT;
break;
}
CopyOAM();
}
static void HandleNintendoCapcomLogos(void) {
u32 advance;
u32 paletteGroup;
advance = GetAdvanceState();
if (gIntroState.state == 0) {
DispReset(1);
gIntroState.state = 1;
gIntroState.timer = 120;
LoadGfxGroup(16);
LoadGfxGroup(1);
if (gSaveHeader->language == 0) {
paletteGroup = 1;
} else {
paletteGroup = 2;
}
LoadPaletteGroup(paletteGroup);
gScreen.lcd.displayControl |= DISPCNT_BG2_ON;
gScreen.bg1.updated = 1;
SetFade(FADE_BLACK_WHITE | FADE_INSTANT, 8);
advance = ADVANCE_NONE;
#if defined(DEMO_USA)
if (gUnk_02000010.listenForKeyPresses == 0) {
if ((gInput.heldKeys & 0x204) == 0x204) { // TODO
gUnk_02000010.field_0x7 = 1;
SoundReq(SFX_SECRET_BIG);
} else {
if ((gInput.heldKeys & 0x104) == 0x104) { // TODO
gUnk_02000010.field_0x7 = 2;
SoundReq(SFX_TASK_COMPLETE);
} else {
gUnk_02000010.field_0x7 = 0;
}
}
}
#endif
} else {
if (advance == ADVANCE_TIMER_EXPIRED) {
advance = ADVANCE_KEY_PRESSED;
}
}
if (advance == ADVANCE_KEY_PRESSED) {
gUnk_02000010.listenForKeyPresses = 1;
AdvanceIntroSequence(1);
}
}
static void HandleTitlescreen(void) {
int advance;
u32 paletteGroup;
gIntroState.counter++;
switch (gIntroState.state) {
case 0:
gIntroState.state = 1;
gIntroState.subState = 0;
gIntroState.timer = 30;
gIntroState.language = 7;
EraseAllEntities();
ResetPaletteTable(0);
ResetPalettes();
gGFXSlots.unk0 = 1;
LoadGfxGroup(2);
if (gSaveHeader->language == 0) {
paletteGroup = 3;
} else {
paletteGroup = 4;
}
LoadPaletteGroup(paletteGroup);
if (gSaveHeader->language == 0) {
// Blend first and second layer
gScreen.controls.layerFXControl = BLDCNT_TGT1_BG2 | BLDCNT_TGT2_BG3 | BLDCNT_EFFECT_BLEND;
gScreen.controls.alphaBlend = BLDALPHA_BLEND(9, 9);
gScreen.bg1.control = BGCNT_SCREENBASE(28) | BGCNT_PRIORITY(1) | BGCNT_CHARBASE(2);
gScreen.bg2.control = BGCNT_SCREENBASE(29) | BGCNT_PRIORITY(2);
gScreen.bg3.control = BGCNT_SCREENBASE(30) | BGCNT_PRIORITY(3);
gScreen.lcd.displayControl |= DISPCNT_BG1_ON | DISPCNT_BG2_ON | DISPCNT_BG3_ON | DISPCNT_OBJ_ON;
gScreen.bg1.yOffset = -160;
} else {
gScreen.controls.layerFXControl = BLDCNT_TGT1_BG0 | BLDCNT_TGT2_BG1 | BLDCNT_EFFECT_BLEND;
gScreen.controls.alphaBlend = BLDALPHA_BLEND(9, 9);
gScreen.bg0.control = BGCNT_SCREENBASE(29) | BGCNT_PRIORITY(2);
gScreen.bg1.control = BGCNT_SCREENBASE(30) | BGCNT_PRIORITY(3);
gScreen.bg2.control = BGCNT_SCREENBASE(28) | BGCNT_PRIORITY(1) | BGCNT_CHARBASE(2) | BGCNT_256COLOR |
BGCNT_WRAP | BGCNT_TXT512x256;
gScreen.lcd.displayControl |= DISPCNT_MODE_1;
gScreen.lcd.displayControl |= DISPCNT_BG0_ON | DISPCNT_BG1_ON | DISPCNT_OBJ_ON;
gIntroState.swordBgScaleRatio = 0x10;
UpdateSwordBgAffineData();
}
InitSoundPlayingInfo();
SoundReq(BGM_TITLE_SCREEN);
SetFade(FADE_BLACK_WHITE | FADE_INSTANT, 8);
break;
case 1:
if (gFadeControl.active) {
return;
}
if (gSaveHeader->language == 0) {
HandleJapaneseTitlescreenAnimationIntro();
} else {
HandleTitlescreenAnimationIntro();
}
break;
case 2:
#if defined(JP) || defined(DEMO_JP) || defined(EU)
if (GetAdvanceState()) {
#else
if (--gIntroState.timer == 0) {
#endif
gIntroState.timer = 3600;
gIntroState.state++;
}
#if defined(USA) || defined(DEMO_USA)
UpdatePressStartIcon();
#endif
break;
default:
advance = GetAdvanceState();
if (advance != ADVANCE_NONE) {
if (advance == ADVANCE_KEY_PRESSED) {
SoundReq(SFX_TEXTBOX_SELECT);
} else {
advance = ADVANCE_NONE;
}
AdvanceIntroSequence(advance);
SoundReq(SONG_VOL_FADE_OUT);
}
#if defined(JP) || defined(DEMO_JP) || defined(DEMO_JP)
gOamCmd._4 = 0;
gOamCmd._6 = 0;
gOamCmd._8 = 0xE020;
gOamCmd.x = 120;
gOamCmd.y = 152;
DrawDirect(511, 1);
#elif defined(EU)
gOamCmd._4 = 0;
gOamCmd._6 = 0;
gOamCmd._8 = 0xE020;
gOamCmd.x = 120;
gOamCmd.y = 152;
DrawDirect(510, 1);
#else
UpdatePressStartIcon();
#endif
if ((gIntroState.timer & 0x20) == 0) {
gOamCmd._8 = 0xe000;
gOamCmd.y = 0x84;
#ifdef EU
DrawDirect(0x1fe, 0);
#else
DrawDirect(0x1ff, 0);
#endif
}
}
if (gIntroState.language != gSaveHeader->language) {
gIntroState.language = gSaveHeader->language;
LoadGfxGroup(3);
}
UpdateLightRays();
UpdateEntities();
DrawEntities();
}
#if defined(USA) || defined(DEMO_USA)
static void UpdatePressStartIcon(void) {
gOamCmd._4 = 0;
gOamCmd._6 = 0;
gOamCmd._8 = 0xE020;
gOamCmd.x = 120;
gOamCmd.y = 152;
DrawDirect(511, 1);
}
#endif
static void UpdateSwordBgAffineData(void) {
struct BgAffineSrcData aff;
aff.texY = 0x8000;
aff.texX = 0x8000;
aff.scrX = DISPLAY_WIDTH / 2;
aff.scrY = DISPLAY_HEIGHT / 2 - 8;
aff.alpha = 0;
aff.sy = aff.sx = gIntroState.swordBgScaleRatio;
BgAffineSet(&aff, (struct BgAffineDstData*)&gScreen.controls, 1);
}
static void HandleJapaneseTitlescreenAnimationIntro(void) {
Entity* pEVar2;
switch (gIntroState.subState) {
case 0:
if (!gFadeControl.active) {
if ((gIntroState.counter & 1) == 0) {
gScreen.bg1.yOffset++;
}
if (GetAdvanceState() == ADVANCE_KEY_PRESSED || gScreen.bg1.yOffset == 0) {
gIntroState.subState++;
gScreen.bg1.yOffset = 0;
gScreen.bg1.control = BGCNT_SCREENBASE(12) | BGCNT_PRIORITY(1) | BGCNT_CHARBASE(2);
gFadeControl.mask = 0x00000040;
SetFade(FADE_BLACK_WHITE | FADE_INSTANT, 0x10);
SoundReq(SFX_F8);
}
}
break;
case 1:
if (!gFadeControl.active) {
gFadeControl.mask = 0xFFFFFFFF;
gIntroState.subState++;
#if defined(JP) || defined(EU) || defined(DEMO_JP)
gIntroState.timer = 120;
#else
gIntroState.timer = 90;
#endif
pEVar2 = CreateObject(OBJECT_B4, 0, 0);
if (pEVar2 != NULL) {
pEVar2->x.HALF.HI = 0;
pEVar2->y.HALF.HI = DISPLAY_HEIGHT / 2 - 8;
}
}
break;
case 2:
if (GetAdvanceState() != ADVANCE_NONE) {
gIntroState.state++;
#if defined(JP) || defined(EU) || defined(DEMO_JP)
gIntroState.timer = 30;
#else
gIntroState.timer = 60;
#endif
}
}
}
static void HandleTitlescreenAnimationIntro(void) {
switch (gIntroState.subState) {
case 0:
if (!gFadeControl.active) {
gIntroState.subState = 1;
gScreen.lcd.displayControl |= DISPCNT_BG2_ON;
SoundReq(SFX_EVAPORATE);
}
break;
case 1:
gIntroState.swordBgScaleRatio += 0x10;
if (gIntroState.swordBgScaleRatio > 0x100) {
gIntroState.swordBgScaleRatio = 0x100;
gIntroState.timer = 40;
gIntroState.subState++;
SetFade(FADE_BLACK_WHITE | FADE_INSTANT, 16);
}
UpdateSwordBgAffineData();
break;
case 2:
if (--gIntroState.timer == 0) {
#if defined(JP) || defined(EU) || defined(DEMO_JP)
gIntroState.timer = 360;
#else
gIntroState.timer = 300;
#endif
gIntroState.subState++;
CreateObject(OBJECT_BD, 0, 0);
SetFade(FADE_BLACK_WHITE | FADE_INSTANT, 16);
SoundReq(SFX_F8);
}
break;
default:
if (!gFadeControl.active && GetAdvanceState() != ADVANCE_NONE) {
gIntroState.state++;
#if defined(JP) || defined(EU) || defined(DEMO_JP)
gIntroState.timer = 30;
#else
gIntroState.timer = 60;
#endif
}
break;
}
}
static void ExitTitlescreen(void) {
if (!gFadeControl.active) {
#ifdef DEMO_JP
MemCopy(&gDemoSave, &gSave, sizeof(gSave));
SetTask(TASK_GAME);
#else
SetTask(TASK_FILE_SELECT);
#endif
}
}
static u32 GetAdvanceState(void) {
u32 newKeys;
if (gFadeControl.active) {
return ADVANCE_NONE;
}
if (!gUnk_02000010.listenForKeyPresses) {
newKeys = 0;
} else {
newKeys = gInput.newKeys & (A_BUTTON | START_BUTTON);
}
if (--gIntroState.timer == 0) {
return ADVANCE_TIMER_EXPIRED;
}
if (newKeys) {
return ADVANCE_KEY_PRESSED;
}
return ADVANCE_NONE;
}
static void UpdateLightRays(void) {
// Periodically rotate the palette to give a shimmering effect.
if ((gIntroState.counter & 0x7) == 0) {
gIntroState.lightRaysPaletteGroup++;
gIntroState.lightRaysPaletteGroup &= 0x3;
LoadPaletteGroup(5 + gIntroState.lightRaysPaletteGroup);
}
// Periodically update the transparency of the light rays.
if ((gIntroState.counter & 0x1F) == 0) {
gIntroState.lightRaysAlphaBlendIndex = (gIntroState.lightRaysAlphaBlendIndex + 1) & 0x7;
gScreen.controls.alphaBlend = sLightRaysAlphaBlends[gIntroState.lightRaysAlphaBlendIndex];
}
}