breaking-bad-ds/source/game.cpp
William cd9753f5f6
Fix line endings, bug, text & sound fixes (#8)
* Fix wrong line endings

* Fixup launch tasks

* Fixup nflib lib dir

* Fixup debugger, improve dialogue save tracking

* Fixup typo

* Improve stability of game end logic

* Dialogue text fixes

* Fix SFX bug on Hank's mineral screen

* Bump to 1.0.6

* Add SFX to cracking minigame
2023-11-22 01:04:19 +00:00

1598 lines
43 KiB
C++

#include "game.h"
Game::Game()
{
consoleDemoInit();
consoleClear();
printf("\n\n\n\n\n\n\n\n\n\n\n Getting Ready to Cook...\n");
// Initialize nitroFS
if (!nitroFSInit(NULL))
{
printf("\x1B[31mFailed to start nitroFS\n");
printf("\x1B[31mPlease reset the system\n");
while (1)
{
swiWaitForVBlank();
}
}
swiWaitForVBlank();
// Set the root folder to the nitroFS filesystem
NF_SetRootFolder("NITROFS");
// Setup interrupt handlers
irqEnable(IRQ_HBLANK);
irqSet(IRQ_VBLANK, NE_VBLFunc);
irqSet(IRQ_HBLANK, NE_HBLFunc);
// Prepare NiFi
nifiPrepare();
// Setup sound
sound.LoadSound();
// Load global save
globalSave.loadData();
}
void Game::WaitLoop()
{
while (1)
{
swiWaitForVBlank();
}
}
void Game::Prepare2DGraphics()
{
// Initialize sub 2D engine
NF_Set2D(1, 0);
// Initialize sprites for sub screen (it uses VRAM D and I)
NF_InitSpriteBuffers();
NF_InitSpriteSys(1);
// Initialize tiled backgrounds and text systems for the 2D sub engine (it
// uses VRAM C and H)
NF_InitTiledBgBuffers();
NF_InitTiledBgSys(1);
NF_InitTextSys(1);
// Load assets from filesystem to RAM
NF_LoadTextFont("font/default", "normal", 256, 256, 0);
// Create text layer
NF_CreateTextLayer(1, 0, 0, "normal");
}
void Game::Prepare3DGraphics()
{
// When using nflib for the 2D sub screen engine, banks C and H are used for
// backgrounds and banks D and I are used for sprites. Nitro Engine only
// uses bank E for palettes, so the only thing we need to do is to tell
// Nitro Engine to only use banks A and B and leave C and D unused.
NE_Init3D();
// Reset the texture system
NE_TextureSystemReset(0, 0, NE_VRAM_AB);
// Setup lighting
NE_LightSet(0, NE_White, -0.5, -0.5, -0.5);
// Setup background color
NE_ClearColorSet(CLEAR_COLOR, 31, 63);
// Enable shading and outlining
NE_ShadingEnable(true);
NE_OutliningEnable(true);
NE_OutliningSetColor(1, NE_Black); // Singleplayer player outline
NE_OutliningSetColor(2, NE_Green); // Multiplayer Player 1 outline
NE_OutliningSetColor(3, NE_LightBlue); // Multiplayer: Player 2 outline
// Create camera
camera = NE_CameraCreate();
}
void Game::UpdateCamera()
{
// Update the camera position based on mode
switch (mode)
{
case MAIN_MENU:
cameraTx = BASE_TITLE_CAMERA_POS[0];
cameraTy = BASE_TITLE_CAMERA_POS[1];
cameraTz = BASE_TITLE_CAMERA_POS[2];
break;
case DIALOGUE:
case PAUSED:
if (enabledCheats[CHEAT_POV]) {
cameraTx = player.x;
cameraTy = 2.5 + (player.y);
cameraTz = player.z;
break;
}
cameraTx = player.x;
cameraTy = 7.5;
cameraTz = -2.5 - 2.5 + (player.z / 6);
break;
case GAME_OVER:
if (gameOverFrame < -10)
{
return;
}
if (!levelClear)
{
cameraTx = player.x - 0.5f;
cameraTy = 5 + (((float)gameOverFrame / 500.0f) * 13.0f);
cameraTz = player.z + 1.5f;
NE_CameraRotate(camera, 0, frame % 2 == 0 ? 1 : 0, 0);
}
else
{
cameraTx = player.x;
cameraTy = 13 - (((float)gameOverFrame / 500.0f) * 5.75f);
cameraTz = player.z - 4.5f;
}
break;
default:
if (enabledCheats[CHEAT_POV]) {
cameraTx = player.x;
cameraTy = 2.5 + (player.y);
cameraTz = player.z;
// POV Camera pan controls (L/R)
if (keysHeld() & KEY_R)
{
NE_CameraRotate(camera, 0, 5, 0);
}
else if (keysHeld() & KEY_L)
{
NE_CameraRotate(camera, 0, -5, 0);
}
break;
}
cameraTx = player.x;
cameraTy = BASE_MOVE_CAMERA_POS[1] + (player.z / 6);
cameraTz = BASE_MOVE_CAMERA_POS[2] + (player.z / 6);
break;
}
// Move camera towards target
float camXDiff = cameraTx - cameraX;
float camYDiff = cameraTy - cameraY;
float camZDiff = cameraTz - cameraZ;
float camXSpeed = camXDiff / 10;
float camYSpeed = camYDiff / 10;
float camZSpeed = camZDiff / 10;
NE_CameraMove(camera, camXSpeed, camYSpeed, camZSpeed);
cameraX += camXSpeed;
cameraY += camYSpeed;
cameraZ += camZSpeed;
}
void Game::TranslateCamera(float x, float y, float z)
{
cameraTx += x;
cameraTy += y;
cameraTz += z;
}
void Game::AwardMineral(MineralType mineralType, bool silent)
{
if (this->customFlag)
{
return;
}
if (!globalSave.minerals[mineralType])
{
globalSave.minerals[mineralType] = true;
if (!silent)
{
sound.PlaySFX(SFX_MINERALS);
}
}
}
void Game::TogglePauseMenu()
{
if (!(mode == MOVE || mode == PAUSED))
{
return;
}
bool pausing = mode == MOVE;
Transition(false, 0, TS_BOTTOM, frame);
sound.PlaySFX(SFX_MENU_SELECT);
if (pausing)
{
player.canMove = false;
player.walking = false;
ToggleHud(false);
NF_LoadTiledBg(PAUSED_BG_NAME, PAUSED_BG_NAME, 256, 256);
NF_CreateTiledBg(1, PAUSED_END_BG, PAUSED_BG_NAME);
mode = PAUSED;
}
else
{
NF_DeleteTiledBg(1, PAUSED_END_BG);
NF_UnloadTiledBg(PAUSED_BG_NAME);
ToggleHud(true);
player.canMove = true;
mode = MOVE;
}
Transition(true, 15, TS_BOTTOM, frame);
}
// Show/hide bottom screen HUD
void Game::ToggleHud(bool show)
{
bool isMultiplayer = gameType == GAME_MULTIPLAYER_VS;
if (show && !hudVisible)
{
NF_LoadTiledBg(isMultiplayer ? MP_HUD_BG_NAME : SP_HUD_BG_NAME, isMultiplayer ? MP_HUD_BG_NAME : SP_HUD_BG_NAME, 256, 256);
NF_CreateTiledBg(1, HUD_BG, isMultiplayer ? MP_HUD_BG_NAME : SP_HUD_BG_NAME);
// Player markers
int playerSpriteFrame = isMultiplayer ? isHostClient() ? 1 : 2 : 0;
NF_CreateSprite(1, HUD_MAP_PLAYER_SPRITE, HUD_MAP_ICONS + 1, HUD_MAP_ICONS + 1, HUD_MAP_ORIGIN_COORDS[0], HUD_MAP_ORIGIN_COORDS[1]);
NF_SpriteFrame(1, HUD_MAP_PLAYER_SPRITE, playerSpriteFrame);
if (playerSpriteFrame > 0)
{
NF_CreateSprite(1, HUD_MAP_PLAYER2_SPRITE, HUD_MAP_ICONS + 1, HUD_MAP_ICONS + 1, HUD_MAP_ORIGIN_COORDS[0], HUD_MAP_ORIGIN_COORDS[1]);
NF_SpriteFrame(1, HUD_MAP_PLAYER2_SPRITE, playerSpriteFrame == 1 ? 2 : 1);
}
// Map marker
NF_CreateSprite(1, HUD_MAP_MARKER_SPRITE, HUD_MAP_ICONS + 1, HUD_MAP_ICONS + 1, HUD_MAP_ORIGIN_COORDS[0], HUD_MAP_ORIGIN_COORDS[1]);
NF_SpriteFrame(1, HUD_MAP_MARKER_SPRITE, 3);
// Checkboxes
for (int i = 0; i < HUD_CHECKBOX_COUNT; i++)
{
NF_CreateSprite(1, HUD_CHECKBOX_SPRITES[i], HUD_CHECKBOXES + 1, HUD_CHECKBOXES + 1, HUD_CHECKBOXES_COORDS[0] + (i / 2), HUD_CHECKBOXES_COORDS[1] + (i * HUD_CHECKBOX_Y_SPACING));
NF_SpriteFrame(1, HUD_CHECKBOX_SPRITES[i], 0);
}
// Timer and purity
for (int i = 0; i < 3; i++)
{
NF_CreateSprite(1, HUD_TIMER_SPRITES[i], HUD_NUMBERS + 1, HUD_NUMBERS + 1, HUD_TIMER_COORDS[0] + (i * 16), HUD_TIMER_COORDS[1]);
NF_SpriteFrame(1, HUD_TIMER_SPRITES[i], 0);
NF_CreateSprite(1, HUD_PURITY_SPRITES[i], HUD_NUMBERS + 1, HUD_NUMBERS + 1, HUD_PURITY_COORDS[0] + (i * 16), HUD_PURITY_COORDS[1]);
NF_SpriteFrame(1, HUD_PURITY_SPRITES[i], 0);
}
// Quota / Multiplayer Vs. Score
for (int i = 0; i < 4; i++)
{
NF_CreateSprite(1, HUD_QUOTA_SPRITES[i], HUD_NUMBERS + 1, HUD_NUMBERS + 1, (HUD_QUOTA_COORDS[0] + (i >= 2 ? 12 : 0)) + (i * 16), HUD_QUOTA_COORDS[1]);
NF_SpriteFrame(1, HUD_QUOTA_SPRITES[i], 0);
}
}
else if (hudVisible)
{
NF_DeleteTiledBg(1, HUD_BG);
NF_UnloadTiledBg(isMultiplayer ? MP_HUD_BG_NAME : SP_HUD_BG_NAME);
NF_DeleteSprite(1, HUD_MAP_PLAYER_SPRITE);
if (isMultiplayer)
{
NF_DeleteSprite(1, HUD_MAP_PLAYER2_SPRITE);
}
NF_DeleteSprite(1, HUD_MAP_MARKER_SPRITE);
for (int i = 0; i < HUD_CHECKBOX_COUNT; i++)
{
NF_DeleteSprite(1, HUD_CHECKBOX_SPRITES[i]);
}
for (int i = 0; i < 3; i++)
{
NF_DeleteSprite(1, HUD_TIMER_SPRITES[i]);
NF_DeleteSprite(1, HUD_PURITY_SPRITES[i]);
}
for (int i = 0; i < 4; i++)
{
NF_DeleteSprite(1, HUD_QUOTA_SPRITES[i]);
}
}
hudVisible = show;
}
void Game::UpdateHud()
{
bool isMultiplayer = gameType == GAME_MULTIPLAYER_VS;
if (mode == MOVE)
{
NF_MoveSprite(1, HUD_MAP_PLAYER_SPRITE, HUD_MAP_ORIGIN_COORDS[0] - (player.x * 4.1), HUD_MAP_ORIGIN_COORDS[1] - (player.z * 4.4));
if (isMultiplayer)
{
NF_MoveSprite(1, HUD_MAP_PLAYER2_SPRITE, HUD_MAP_ORIGIN_COORDS[0] - (player2->x * 4.1), HUD_MAP_ORIGIN_COORDS[1] - (player2->z * 4.4));
}
NF_MoveSprite(1, HUD_MAP_MARKER_SPRITE, HUD_MAP_ORIGIN_COORDS[0] - (HUD_MAP_MARKER_COORDS[currentBatchProgress][0] * 4.1), HUD_MAP_ORIGIN_COORDS[1] - (HUD_MAP_MARKER_COORDS[currentBatchProgress][1] * 4.4));
for (int i = 0; i < HUD_CHECKBOX_COUNT; i++)
{
if (isMultiplayer)
{
bool isHost = isHostClient();
bool p1Completed = isHost ? (currentBatchProgress > i) : (getOpponent()->currentBatchStep > i);
bool p2Completed = isHost ? (getOpponent()->currentBatchStep > i) : (currentBatchProgress > i);
int totalProgress = (p1Completed && p2Completed) ? 4 : p1Completed && !p2Completed ? 2
: !p1Completed && p2Completed ? 3
: 0;
NF_SpriteFrame(1, HUD_CHECKBOX_SPRITES[i], totalProgress);
}
else
{
NF_SpriteFrame(1, HUD_CHECKBOX_SPRITES[i], currentBatchProgress > i ? 1 : 0);
}
}
for (int i = 0; i < 3; i++)
{
if (timeLimit > -1)
{
NF_SpriteFrame(1, HUD_TIMER_SPRITES[i], (int)(timeLimit / pow(10, 2 - i)) % 10);
}
NF_SpriteFrame(1, HUD_PURITY_SPRITES[i], (int)(batchPurity / pow(10, 2 - i)) % 10);
}
for (int i = 0; i < 4; i++)
{
if (i < 2)
{
NF_SpriteFrame(1, HUD_QUOTA_SPRITES[i], (int)((!isMultiplayer ? batchesComplete : (isHostClient() ? batchesComplete : player2BatchesComplete)) / pow(10, 1 - i)) % 10);
}
else
{
NF_SpriteFrame(1, HUD_QUOTA_SPRITES[i], (int)((!isMultiplayer ? batchQuota : (isHostClient() ? player2BatchesComplete : batchesComplete)) / pow(10, 3 - i)) % 10);
}
}
}
}
void Game::StartDay(uint16 day)
{
if (gameType != GAME_STORY_MODE)
{
return;
}
ToggleHud(false);
Transition(false, 0, TS_TOP, frame);
globalSave.currentDay = day;
if (day < LEVEL_COUNT)
{
mode = STORY_TRANSITION;
sound.StopBGM();
frame = 0;
showingDayNumber = true;
return;
}
StartLevel(&LEVEL_STORY_MODE[day]);
Transition(true, 60, TS_BOTH, frame);
mode = MOVE;
}
void Game::StartNextDay()
{
StartDay(globalSave.currentDay + 1);
if (!customFlag) {
globalSave.saveData();
}
}
void Game::OpenShopMenu()
{
if (gameType != GAME_STORY_MODE)
{
return;
}
mode = STORY_SHOP;
ToggleHud(false);
Transition(false, 0, TS_TOP, frame);
frame = 0;
shop.Load(frame, &sound);
}
void Game::UpdateDayTransition()
{
if (!(gameType == GAME_STORY_MODE && mode == STORY_TRANSITION))
{
showingDayNumber = false;
return;
}
const Level *level = &LEVEL_STORY_MODE[globalSave.currentDay];
if (frame <= 1)
{
Transition(true, 60, TS_BOTTOM, frame);
}
if (showingDayNumber)
{
// Day no
char dayText[12];
sprintf(dayText, "Day %i", globalSave.currentDay + 1);
NF_WriteText(1, 0, 13, 9, dayText);
// Level details
int levelTime = level->time + (((int)(((float)level->time) * WATCH_BATTERY_TIME_MULTIPLIER)) * globalSave.powerUps[PWR_WATCH_BATTERY] ? 1 : 0);
char levelText[32];
if (!enabledCheats[CHEAT_TIMER])
{
sprintf(levelText, "Time: %is", levelTime);
NF_WriteText(1, 0, 10, 11, levelText);
}
sprintf(levelText, "Quota: %i", level->quota);
NF_WriteText(1, 0, 10, 12, levelText);
sprintf(levelText, "Money: $%i", globalSave.currentMoney);
NF_WriteText(1, 0, 10, 13, levelText);
}
if (frame == 240)
{
Transition(false, 0, TS_BOTH, frame);
showingDayNumber = false;
NF_ClearTextLayer(1, 0);
StartLevel(level);
}
if (frame > 270)
{
Transition(true, 60, TS_BOTH, frame);
mode = MOVE;
}
}
void Game::UpdateShopMenu()
{
if (!(gameType == GAME_STORY_MODE && mode == STORY_SHOP))
{
return;
}
if (shop.Update(frame, &sound))
{
shop.Unload(frame, &sound);
Transition(false, 0, TS_BOTH, frame);
if (!customFlag) {
globalSave.saveData();
}
if (globalSave.powerUps[PWR_EXPLOSIVES])
{
AwardMineral(MINERAL_CERIUM, false);
}
StartNextDay();
}
}
void Game::ShowEndScreen(bool winScreen)
{
if (gameType != GAME_STORY_MODE)
{
return;
}
mode = STORY_END_SCREEN;
showingWinScreen = winScreen;
frame = 0;
// Award "clear story mode" mineral
AwardMineral(MINERAL_EMERALD, true);
if (!customFlag) {
globalSave.saveData();
}
Transition(false, 0, TS_BOTH, frame);
ToggleHud(false);
NF_ClearTextLayer(1, 0);
NF_VramSpriteGfxDefrag(1);
sound.PlayBGM(BGM_TITLE_LOOP, true);
if (winScreen)
{
NF_LoadTiledBg(GOOD_ENDING_BG, GOOD_ENDING_BG, 256, 256);
NF_CreateTiledBg(1, PAUSED_END_BG, GOOD_ENDING_BG);
}
else
{
NF_LoadTiledBg(BAD_ENDING_BG, BAD_ENDING_BG, 256, 256);
NF_CreateTiledBg(1, PAUSED_END_BG, BAD_ENDING_BG);
}
}
void Game::UpdateEndScreen()
{
if (gameType != GAME_STORY_MODE || mode != STORY_END_SCREEN)
{
return;
}
if (frame == 10)
{
Transition(true, 60, TS_BOTTOM, frame);
}
if (frame == 1080)
{
Transition(false, 120, TS_BOTTOM, frame);
}
if (frame > 1200)
{
NF_DeleteTiledBg(1, PAUSED_END_BG);
if (showingWinScreen)
{
NF_UnloadTiledBg(GOOD_ENDING_BG);
}
else
{
NF_UnloadTiledBg(BAD_ENDING_BG);
}
globalSave.currentDay = 0;
globalSave.currentDialogue = 0;
globalSave.currentMoney = 0;
for (int p = 0; p < POWER_UP_COUNT; p++)
{
globalSave.powerUps[p] = false;
}
levelClear = true;
QuitToTitle();
}
}
void Game::QuitToTitle()
{
if (isQuitting)
{
return;
}
isQuitting = true;
if (gameType == GAME_MULTIPLAYER_VS)
{
disableMultiplayer();
}
if (showingIndicatorFor > 0)
{
NF_DeleteSprite(1, QUALITY_INDICATOR_SPRITE);
showingIndicatorFor = 0;
}
sound.StopBGM();
UnLoadLabScene();
NE_SpecialEffectSet(NE_NONE);
if (!customFlag) {
globalSave.saveData();
}
else
{
globalSave.loadData();
}
NF_VramSpriteGfxDefrag(1);
StartMenuScreen(false);
if (!levelClear)
{
sound.PlaySFX(SFX_MENU_SELECT);
}
}
void Game::StartDialogue(ScriptId script)
{
if (mode == DIALOGUE)
{
return;
}
Transition(false, 0, TS_BOTTOM, frame);
if (mode == MINIGAME)
{
currentMinigame->Unload(&map);
}
mode = DIALOGUE;
ToggleHud(false);
player.facing = DOWN;
player.inDialogue = true;
dialogue.Load(script, frame);
Transition(true, 30, TS_BOTTOM, frame);
}
void Game::EndDialogue()
{
if (mode != DIALOGUE)
{
return;
}
Transition(false, 0, TS_BOTTOM, frame);
dialogue.Unload();
ToggleHud(true);
mode = MOVE;
Transition(true, 30, TS_BOTTOM, frame);
player.inDialogue = false;
}
void Game::CheckDialogue()
{
if (gameType == GAME_TUTORIAL || gameType == GAME_STORY_MODE)
{
if (mode == DIALOGUE)
{
return;
}
int dialogueId = (gameType == GAME_TUTORIAL ? dialogue.GetTutorialDialogue(dialogueProgress, currentBatchProgress, player.GetTile(map))
: dialogue.GetStoryDialogue(dialogueProgress, currentBatchProgress, batchesComplete, player.GetTile(map)));
if (dialogueId != -1)
{
if (!enabledCheats[CHEAT_DIALOGUE]) {
StartDialogue((ScriptId)dialogueId);
}
// Increment dialogue progress
dialogueProgress++;
if (gameType == GAME_STORY_MODE)
{
globalSave.currentDialogue = dialogueProgress;
}
}
}
}
void Game::StartGame(GameType gameType)
{
Transition(false, 0, TS_BOTH, frame);
this->isQuitting = false;
// Reset enabled cheats
for (int i = 0; i < CHEAT_COUNT; i++) {
enabledCheats[i] = false;
}
// Handle custom games
this->customFlag = false;
if (gameType == GAME_CUSTOM) {
gameType = GAME_STORY_MODE;
this->customFlag = true;
}
// Set params
this->gameType = gameType;
this->dialogueProgress = gameType == GAME_STORY_MODE ? globalSave.currentDialogue : 0;
this->frame = 0;
this->mode = MOVE;
// Load lab
LoadLabScene();
// Start game
if (this->customFlag) {
int* values = menu.GetCustomGameValues();
globalSave.currentDay = values[0];
this->dialogueProgress = values[0];
globalSave.currentMoney = values[1];
enabledCheats[CHEAT_TIMER] = !values[3];
enabledCheats[CHEAT_DIALOGUE] = !values[4];
enabledCheats[CHEAT_NOCLIP] = values[5];
enabledCheats[CHEAT_POV] = values[6];
}
if (gameType == GAME_STORY_MODE)
{
StartDay(globalSave.currentDay);
}
else
{
StartLevel(gameType == GAME_TUTORIAL ? &LEVEL_TUTORIAL : &LEVEL_MULTIPLAYER_VS);
Transition(true, 60, TS_BOTH, frame);
}
}
void Game::LoadLabScene()
{
bool isMultiplayer = gameType == GAME_MULTIPLAYER_VS;
// Load map and player
if (map.Load() == -1)
{
WaitLoop();
}
// Load player animations
playerAnimations[0] = NE_AnimationCreate();
playerAnimations[1] = NE_AnimationCreate();
if (NE_AnimationLoadFAT(playerAnimations[0], "model/player_idle.dsa") == 0 || NE_AnimationLoadFAT(playerAnimations[1], "model/player_walk.dsa") == 0)
{
consoleDemoInit();
printf("Couldn't load player animations...");
WaitLoop();
return;
}
// Determine player 1 character
Character p1Char = (isMultiplayer ? (!isHostClient() ? CHAR_JESSE : CHAR_WALT) : ((keysHeld() & KEY_Y) && (keysHeld() & KEY_SELECT) ? CHAR_YEPPERS : CHAR_WALT));
// If this is a custom game, use selected character
if (!isMultiplayer && this->customFlag)
{
p1Char = static_cast<Character>(menu.GetCustomGameValues()[2]);
}
// Load player 1
if (player.Load(p1Char, playerAnimations) == -1)
{
WaitLoop();
}
// Load player 2 (multiplayer vs.)
if (isMultiplayer)
{
player2 = new Player();
player2->isPlayer2 = true;
if (player2->Load(isHostClient() ? CHAR_JESSE : CHAR_WALT, playerAnimations) == -1)
{
WaitLoop();
}
}
// Award achievements as neccessary
switch (p1Char)
{
case CHAR_JESSE:
AwardMineral(MINERAL_YTTRIUM, true);
break;
case CHAR_YEPPERS:
AwardMineral(MINERAL_RUBY, false);
break;
}
// Set fog color
NE_FogEnable(3, RGB15(15, 0, 0), 31, 3, 0x7c00);
// Load sprites
NF_LoadSpriteGfx("sprite/quality", QUALITY_INDICATOR_SPRITE, 64, 64);
NF_LoadSpritePal("sprite/quality", QUALITY_INDICATOR_SPRITE);
NF_VramSpriteGfx(1, QUALITY_INDICATOR_SPRITE, QUALITY_INDICATOR_SPRITE + 1, false);
NF_VramSpritePal(1, QUALITY_INDICATOR_SPRITE, QUALITY_INDICATOR_SPRITE + 1);
NF_LoadSpriteGfx("sprite/map_icons", HUD_MAP_ICONS, 16, 16);
NF_LoadSpritePal("sprite/map_icons", HUD_MAP_ICONS);
NF_VramSpriteGfx(1, HUD_MAP_ICONS, HUD_MAP_ICONS + 1, false);
NF_VramSpritePal(1, HUD_MAP_ICONS, HUD_MAP_ICONS + 1);
NF_LoadSpriteGfx("sprite/menu_checkboxes", HUD_CHECKBOXES, 16, 16);
NF_LoadSpritePal("sprite/menu_checkboxes", HUD_CHECKBOXES);
NF_VramSpriteGfx(1, HUD_CHECKBOXES, HUD_CHECKBOXES + 1, false);
NF_VramSpritePal(1, HUD_CHECKBOXES, HUD_CHECKBOXES + 1);
NF_LoadSpriteGfx("sprite/segment_numbers", HUD_NUMBERS, 16, 16);
NF_LoadSpritePal("sprite/segment_numbers", HUD_NUMBERS);
NF_VramSpriteGfx(1, HUD_NUMBERS, HUD_NUMBERS + 1, false);
NF_VramSpritePal(1, HUD_NUMBERS, HUD_NUMBERS + 1);
}
void Game::StartLevel(const Level *level)
{
// Set level params
this->timeLimit = level->time + (((int)(((float)level->time) * WATCH_BATTERY_TIME_MULTIPLIER)) * globalSave.powerUps[PWR_WATCH_BATTERY] ? 1 : 0);
this->batchQuota = level->quota;
// Reset game state
levelClear = false;
currentBatchProgress = 0;
batchPurity = 100;
batchesComplete = 0;
// Player setup
player.ResetPosition((gameType == GAME_STORY_MODE && globalSave.powerUps[PWR_AIR_JORDANS]));
if (gameType == GAME_MULTIPLAYER_VS)
{
bool isHost = isHostClient();
player.Translate(isHost ? -11.5 : -9.0, 0, 4.5);
player.targetX = player.tileX = isHost ? 4 : 3;
player2->ResetPosition(false);
player2->Translate(!isHost ? -11.5 : -9.0, 0, 4.5);
player2->targetX = player2->tileX = !isHost ? 4 : 3;
player2->targetZ = player2->tileZ = 1;
}
else
{
player.Translate(-11.5, 0, 4.5);
player.targetX = player.tileX = 4;
}
player.targetZ = player.tileZ = 1;
// Setup camera
if (!enabledCheats[CHEAT_POV]) {
cameraX = BASE_MOVE_CAMERA_POS[0];
cameraY = BASE_MOVE_CAMERA_POS[1];
cameraZ = BASE_MOVE_CAMERA_POS[2];
NE_CameraSet(camera,
cameraX, cameraY, cameraZ,
-11.5, 2, 8.5,
0, 1, 0);
}
else
{
cameraX = player.x;
cameraY = 2.5 + (player.y);
cameraZ = player.z;
NE_CameraSet(camera,
cameraX, cameraY, cameraZ,
-11.5, cameraY, 8.5,
0, 1, 0);
}
// Enable HUD, start sound effects, start dialogue if needed
ToggleHud(true);
switch (gameType)
{
case GAME_STORY_MODE:
sound.PlayBGM(globalSave.currentDay < LEVEL_COUNT ? BGM_THE_COUSINS : BGM_FINAL_COOK, true);
break;
case GAME_MULTIPLAYER_VS:
sound.PlayBGM(BGM_THE_COUSINS, true);
break;
case GAME_TUTORIAL:
sound.PlayBGM(BGM_CLEAR_WATERS, true);
break;
}
CheckDialogue();
// Fade in (story mode)
if (gameType == GAME_STORY_MODE && mode == DIALOGUE)
{
Transition(true, 60, TS_TOP, frame);
}
}
// Unload map and player
void Game::UnLoadLabScene()
{
Transition(false, 0, TS_BOTH, frame);
ToggleHud(false);
map.Unload();
player.Unload();
NE_AnimationDelete(playerAnimations[0]);
NE_AnimationDelete(playerAnimations[1]);
// Unload multiplayer
if (mode == GAME_MULTIPLAYER_VS)
{
player2->Unload();
}
// Remove fog
NE_FogDisable();
// Free sprites
NF_FreeSpriteGfx(1, QUALITY_INDICATOR_SPRITE + 1);
NF_UnloadSpriteGfx(QUALITY_INDICATOR_SPRITE);
NF_UnloadSpritePal(QUALITY_INDICATOR_SPRITE);
NF_FreeSpriteGfx(1, HUD_MAP_ICONS + 1);
NF_UnloadSpriteGfx(HUD_MAP_ICONS);
NF_UnloadSpritePal(HUD_MAP_ICONS);
NF_FreeSpriteGfx(1, HUD_CHECKBOXES + 1);
NF_UnloadSpriteGfx(HUD_CHECKBOXES);
NF_UnloadSpritePal(HUD_CHECKBOXES);
NF_FreeSpriteGfx(1, HUD_NUMBERS + 1);
NF_UnloadSpriteGfx(HUD_NUMBERS);
NF_UnloadSpritePal(HUD_NUMBERS);
NF_VramSpriteGfxDefrag(1);
}
void Game::StartMenuScreen(bool debugMode)
{
Transition(false, 0, TS_BOTH, frame);
debugFlag = debugMode;
frame = 0;
if (debugFlag)
{
// StartGame(GAME_STORY_MODE);
// OpenShopMenu();
// return;
}
mode = MAIN_MENU;
LoadLogoScene();
Transition(true, 120, TS_BOTH, frame);
}
void Game::LoadLogoScene()
{
// Position camera
cameraX = BASE_TITLE_CAMERA_POS[0];
cameraY = BASE_TITLE_CAMERA_POS[1];
cameraZ = BASE_TITLE_CAMERA_POS[2];
NE_CameraSet(camera,
cameraX, cameraY + 1.5f, cameraZ,
0, BASE_TITLE_CAMERA_POS[1], 0,
0, 1, 0);
// Load logo
if (menu.Load() == -1)
{
WaitLoop();
}
menu.SetState(MENU_LOGO, frame, &sound);
}
void Game::UnLoadLogoScene()
{
Transition(false, 0, TS_BOTH, frame);
menu.Unload(frame, &sound);
}
void Game::UpdateMenuScreen()
{
menu.Update(frame, &sound);
switch (menu.HandleInput(frame, &sound))
{
case SKIP_LOGO:
frame = 689;
menu.PositionLogo();
break;
case START_TUTORIAL:
UnLoadLogoScene();
StartGame(GAME_TUTORIAL);
break;
case START_STORY_MODE:
UnLoadLogoScene();
StartGame(GAME_STORY_MODE);
break;
case START_MP_GAME:
UnLoadLogoScene();
StartGame(GAME_MULTIPLAYER_VS);
break;
case START_CUSTOM_GAME:
NF_ClearTextLayer(1, 0);
UnLoadLogoScene();
StartGame(GAME_CUSTOM);
break;
}
}
void Game::StartMinigame(Tile tile)
{
CheckDialogue();
if (mode == MINIGAME || mode == DIALOGUE)
{
return;
}
if (tile > 2 && currentBatchProgress != ((int)tile - 3))
{
currentMinigame = NULL;
delete currentMinigame;
return;
}
switch (tile)
{
case MINIGAME_VALVE:
currentMinigame = new ValveMinigame();
break;
case MINIGAME_PESTLE:
currentMinigame = new PestleMinigame();
break;
case MINIGAME_PIPETTE:
currentMinigame = new PipetteMinigame();
break;
case MINIGAME_MIX:
currentMinigame = new MixMinigame();
break;
case MINIGAME_POUR:
currentMinigame = new PourMinigame();
break;
case MINIGAME_CRACK:
currentMinigame = new CrackMinigame();
break;
default:
currentMinigame = NULL;
delete currentMinigame;
return;
}
Transition(false, 0, TS_BOTTOM, frame);
mode = MINIGAME;
ToggleHud(false);
currentMinigame->Load();
Transition(true, 30, TS_BOTTOM, frame);
}
void Game::DeleteMinigame(bool doTransition)
{
if (currentMinigame == NULL)
{
return;
}
if (!doTransition)
{
currentMinigame->Unload(&map);
currentMinigame = NULL;
return;
}
Transition(false, 0, TS_BOTTOM, frame);
currentMinigame->Unload(&map);
currentMinigame = NULL;
ToggleHud(true);
Transition(true, 30, TS_BOTTOM, frame);
}
void Game::ShowMinigameResult(MinigameResult indicator, int frames)
{
int animation = indicator;
showingIndicatorFor = frames;
NF_CreateSprite(1, QUALITY_INDICATOR_SPRITE, QUALITY_INDICATOR_SPRITE + 1, QUALITY_INDICATOR_SPRITE + 1, 64, 32);
NF_SpriteFrame(1, QUALITY_INDICATOR_SPRITE, animation);
NF_EnableSpriteRotScale(1, QUALITY_INDICATOR_SPRITE, QUALITY_INDICATOR_SPRITE, true);
NF_SpriteRotScale(1, QUALITY_INDICATOR_SPRITE, 0, 300, 300);
if (indicator == RESULT_GOOD)
{
sound.PlaySFX(SFX_SUCCESS_BELL);
}
}
void Game::StartGameOver(bool hasWon)
{
if (!(mode == MOVE || mode == MINIGAME))
{
return;
}
levelClear = hasWon;
player.walking = false;
Transition(false, 0, TS_BOTH, frame);
// Clear indicator
if (showingIndicatorFor > 0)
{
NF_DeleteSprite(1, QUALITY_INDICATOR_SPRITE);
showingIndicatorFor = 0;
}
// Clear other elements
if (mode == MINIGAME)
{
DeleteMinigame(false);
}
else
{
ToggleHud(false);
}
// Set mode
sound.StopBGM();
NE_SpecialEffectSet(NE_NONE);
mode = GAME_OVER;
gameOverFrame = -170;
// Prepare baby blue stuff
if (!hasWon)
{
player.SetLyingDown(true);
NE_CameraRotate(camera, 90, 0, 0);
}
else
{
player.facing = DOWN;
}
// Achievements
if (gameType == GAME_TUTORIAL)
{
AwardMineral(MIENRAL_ANALCIME, true);
}
}
void Game::UpdateGameOver()
{
gameOverFrame++;
NF_WriteText(1, 0, 11, 11, (gameType == GAME_MULTIPLAYER_VS ? (levelClear ? "YOU WIN!" : "YOU LOSE!") : (levelClear ? "DAY CLEAR!" : "GAME OVER")));
switch (gameOverFrame)
{
case -150:
if (!levelClear)
{
sound.PlaySFX(SFX_GOODBYE_WALTER);
}
break;
case -55:
case -56:
case -57:
case -58:
case -59:
case -60:
if (!levelClear)
{
rumble(gameOverFrame % 2 == 0);
}
break;
case -10:
sound.StopSFX();
break;
case 0:
if (!levelClear)
{
sound.PlayBGM(BGM_BABY_BLUE, false);
}
else
{
sound.PlayBGM(BGM_RODRIGO_Y_GABRIELA, false);
}
Transition(true, 60, TS_BOTH, frame);
break;
case 520:
if (!levelClear)
{
Transition(false, 0, TS_BOTH, frame);
}
break;
case 575:
if (levelClear)
{
Transition(false, 0, TS_BOTH, frame);
sound.StopBGM();
}
break;
case 620:
if (!levelClear)
{
NF_ClearTextLayer(1, 0);
if (gameType == GAME_STORY_MODE)
{
if (globalSave.currentDay == LEVEL_COUNT - 1 && dialogueProgress == LEVEL_COUNT + 1)
{
ShowEndScreen(false);
return;
}
StartDay(globalSave.currentDay);
return;
}
QuitToTitle();
}
break;
case 660:
if (levelClear)
{
NF_ClearTextLayer(1, 0);
if (gameType == GAME_STORY_MODE)
{
if (globalSave.currentDay == LEVEL_COUNT - 1)
{
dialogueProgress = globalSave.currentDialogue = (globalSave.currentDay + 1);
CheckDialogue();
return;
}
globalSave.currentMoney += ((batchQuota * 30) + (timeLimit * 3) + (perfectClear ? 150 : 0));
bool allPowerups = true;
for (int i = 0; i < POWER_UP_COUNT; i++)
{
allPowerups &= globalSave.powerUps[i];
}
if (!allPowerups)
{
OpenShopMenu();
return;
}
StartNextDay();
return;
}
QuitToTitle();
}
break;
}
}
void Game::Tick()
{
frame++;
}
void Game::Render()
{
// Set camera
NE_CameraUse(camera);
// Set poly format
NE_PolyFormat(31, 0, NE_LIGHT_0, NE_CULL_NONE, NE_FOG_ENABLE);
// Render title screen
if (mode == MAIN_MENU)
{
menu.Draw(frame);
return;
}
// Update objects
if (mode != PAUSED)
{
player.Update(frame);
player.Move(map, enabledCheats[CHEAT_NOCLIP]);
if (gameType == GAME_MULTIPLAYER_VS)
{
player2->Update(frame);
player2->Move(map, false);
}
map.RotateSecurityCamera(player.x, player.z);
}
// Draw objects
map.Draw(player.tileX > 2 && player.tileX < 7 && player.tileZ > 4);
if (!enabledCheats[CHEAT_POV]) {
player.Draw(gameType == GAME_MULTIPLAYER_VS ? (isHostClient() ? 1 : 2) : 0);
}
if (gameType == GAME_MULTIPLAYER_VS && (mode != GAME_OVER))
{
player2->Draw(!isHostClient() ? 1 : 2);
}
}
void Game::Update()
{
if (mode != MAIN_MENU && gameType == GAME_MULTIPLAYER_VS && !isQuitting)
{
if (getMultiplayerStatus() == MP_CONNECTION_LOST)
{
QuitToTitle();
}
else
{
Client *local = getLocalClient();
local->playerTargetX = player.targetX;
local->playerTargetZ = player.targetZ;
local->playerTileX = player.tileX;
local->playerTileZ = player.tileZ;
local->playerFacing = player.rotation;
local->batchesComplete = batchesComplete;
local->currentBatchStep = currentBatchProgress;
if (isHostClient())
{
local->timeLeft = timeLimit;
}
Client *opponent = getOpponent();
if (opponent != NULL)
{
player2->targetX = opponent->playerTargetX;
player2->targetZ = opponent->playerTargetZ;
player2->tileX = opponent->playerTileX;
player2->tileZ = opponent->playerTileZ;
player2->rotation = opponent->playerFacing;
player2BatchesComplete = opponent->batchesComplete;
player2BatchProgress = opponent->currentBatchStep;
if (!isHostClient())
{
timeLimit = opponent->timeLeft;
}
}
tickMultiplayer();
}
}
// Wait for the VBlank interrupt
if (mode != PAUSED && !(mode == GAME_OVER && !levelClear))
{
NE_WaitForVBL(NE_UPDATE_ANIMATIONS);
}
// Copy shadow OAM copy to the OAM of the 2D sub engine
oamUpdate(&oamSub);
// Debug - print debug info
if (debugFlag)
{
char debugText[100];
sprintf(debugText, "Mem: %d (%d)", getMemUsed(), getMemFree());
NF_WriteText(1, 0, 1, 1, debugText);
sprintf(debugText, "Dialogue: %i", dialogueProgress);
NF_WriteText(1, 0, 1, 2, debugText);
}
// Update transition
UpdateTransitions(frame);
// Handle story mode stuff
if (gameType == GAME_STORY_MODE)
{
UpdateDayTransition();
UpdateShopMenu();
UpdateEndScreen();
}
// Update text layers
NF_UpdateTextLayers();
// Handle input
scanKeys();
if (mode != MAIN_MENU && mode != DIALOGUE && mode != GAME_OVER)
{
if (mode == MOVE || mode == MINIGAME)
{
player.HandleInput(keysHeld());
}
if (gameType != GAME_MULTIPLAYER_VS &&
(mode == PAUSED) && (keysDown() & KEY_B))
{
NF_DeleteTiledBg(1, PAUSED_END_BG);
NF_UnloadTiledBg(PAUSED_BG_NAME);
QuitToTitle();
return;
}
else if (gameType != GAME_MULTIPLAYER_VS &&
(mode == MOVE || mode == PAUSED) && (keysDown() & KEY_START))
{
TogglePauseMenu();
}
else if (mode == MOVE && !player.walking && keysHeld() == 0)
{
idleFrames++;
if (idleFrames == 1800)
{
player.facing = DOWN;
if (gameType == GAME_STORY_MODE && player.GetTile(map) == MINIGAME_CRACK)
{
AwardMineral(MINERAL_ALGODONITE, false);
}
}
if (idleFrames > 3600)
{
idleFrames = 0;
if (gameType == GAME_TUTORIAL)
{
AwardMineral(MINERAL_AMBER, false);
StartDialogue(SCRIPT_GALE_TUTORIAL_IDLE);
}
else if (gameType == GAME_STORY_MODE && globalSave.currentDay < LEVEL_COUNT)
{
StartDialogue(SCRIPT_GUS_IDLE);
}
}
}
else
{
idleFrames = 0;
}
// Update status indicators
if (showingIndicatorFor > 0)
{
showingIndicatorFor--;
if (showingIndicatorFor == 0)
{
NF_DeleteSprite(1, QUALITY_INDICATOR_SPRITE);
CheckDialogue();
}
}
}
// Update dialogue
if (mode == DIALOGUE)
{
if (dialogue.Update(frame, keysDown(), &sound))
{
sound.PlaySFX(SFX_MENU_DRUM);
EndDialogue();
if (gameType == GAME_STORY_MODE)
{
if (globalSave.currentDay == LEVEL_COUNT - 1 && dialogueProgress == LEVEL_COUNT + 1)
{
if (globalSave.powerUps[PWR_EXPLOSIVES])
{
StartNextDay();
}
else
{
player.ResetPosition(false);
player.Translate(-6.5, 0, 8.5);
StartGameOver(false);
}
}
else if (globalSave.currentDay == LEVEL_COUNT && dialogueProgress == LEVEL_COUNT + 2)
{
ShowEndScreen(true);
}
}
}
}
// Update camera
UpdateCamera();
// Update timer
if (!(mode == MAIN_MENU || mode == GAME_OVER || mode == PAUSED || mode == STORY_TRANSITION))
{
// Update timer
if (frame % 60 == 0 && timeLimit > -1 && mode != DIALOGUE)
{
if ((gameType != GAME_MULTIPLAYER_VS && !enabledCheats[CHEAT_TIMER]) || isHostClient())
{
timeLimit--;
}
if (batchesComplete < batchQuota)
{
if (timeLimit < 30 || (gameType == GAME_STORY_MODE && globalSave.currentDay == LEVEL_COUNT))
{
NE_SpecialEffectNoiseConfig((31 - (timeLimit <= 20 ? timeLimit : 20)) / 4);
NE_SpecialEffectSet(NE_NOISE);
}
if (timeLimit <= 10)
{
sound.PlaySFX(SFX_MENU_DRUM);
}
if (timeLimit == 0)
{
if (gameType == GAME_STORY_MODE)
{
StartGameOver(false);
AwardMineral(MINERAL_QUARTZ, true);
}
else if (gameType == GAME_MULTIPLAYER_VS)
{
const bool isDraw = batchesComplete == player2BatchesComplete && currentBatchProgress == player2BatchProgress;
const bool localWin = (batchesComplete == player2BatchesComplete ? currentBatchProgress > player2BatchProgress : batchesComplete > player2BatchesComplete);
if (localWin)
{
AwardMineral(MINERAL_AQUAMARINE, true);
}
StartGameOver(!isDraw && localWin);
}
return;
}
}
}
// Update batch progress
if (currentBatchProgress >= 6)
{
if (!(gameType == GAME_STORY_MODE && globalSave.currentDay == LEVEL_COUNT))
{
sound.PlaySFX(SFX_ACCEPTABLE);
}
batchesComplete += (BASE_BATCH_YIELD * ((float)batchPurity / 100.0f));
currentBatchProgress = 0;
perfectClear &= batchPurity == 100;
if (batchPurity < 40)
{
AwardMineral(MINERAL_FLUORITE, true);
}
else if (batchPurity == 100)
{
AwardMineral(MINERAL_TOPAZ, true);
}
batchPurity = 100;
if (gameType != GAME_MULTIPLAYER_VS && batchesComplete >= batchQuota)
{
if (perfectClear)
{
AwardMineral(MINERAL_DIAMOND, true);
}
if (gameType == GAME_STORY_MODE && globalSave.currentDay == LEVEL_COUNT)
{
dialogueProgress = LEVEL_COUNT + 2;
Transition(false, 0, TS_BOTH, frame);
if (mode == MINIGAME)
{
DeleteMinigame(false);
}
else
{
ToggleHud(false);
}
if (showingIndicatorFor > 0)
{
NF_DeleteSprite(1, QUALITY_INDICATOR_SPRITE);
showingIndicatorFor = 0;
}
StartDialogue(SCRIPT_GUS_ANGUISH);
return;
}
StartGameOver(true);
return;
}
}
Tile tile = map.GetTileAt(player.tileX, player.tileZ);
switch (tile)
{
case MINIGAME_VALVE:
case MINIGAME_PESTLE:
case MINIGAME_PIPETTE:
case MINIGAME_MIX:
case MINIGAME_POUR:
case MINIGAME_CRACK:
if (mode == MOVE)
{
StartMinigame(tile);
}
break;
default:
if (mode == MINIGAME)
{
mode = MOVE;
DeleteMinigame(true);
}
break;
}
if (hudVisible)
{
UpdateHud();
}
}
else
{
UpdateMenuScreen();
}
// Check for minigame
if (mode == MINIGAME)
{
inMinigameFor++;
currentMinigame->Update(frame, keysHeld(), &sound, &map);
if (currentMinigame->IsComplete() && currentBatchProgress == ((int)player.GetTile(map) - 3))
{
MinigameResult result = currentMinigame->GetResult(inMinigameFor);
ShowMinigameResult(result, 90);
currentBatchProgress++;
if (result == RESULT_BAD)
{
batchPurity -= (16 + (frame % 5));
}
else if (result == RESULT_OKAY)
{
batchPurity -= (6 + (frame % 3));
}
if (batchPurity < 5)
{
batchPurity = 5;
}
}
}
else
{
inMinigameFor = 0;
}
if (mode == GAME_OVER)
{
UpdateGameOver();
}
// Update sounds
sound.Update(frame);
// Refresh shadow OAM copy
NF_SpriteOamSet(1);
}