mirror of
https://github.com/WiIIiam278/breaking-bad-ds.git
synced 2025-06-19 01:05:33 -04:00

* 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
1598 lines
43 KiB
C++
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);
|
|
} |