// Copyright (c) 2014 Alex Diener. All rights reserved. #include "watertowerclassic/AudioManager.h" #include "watertowerclassic/GameplayScreen.h" #include "watertowerclassic/LevelFileIO.h" #include "watertowerclassic/PhysicsFunctions.h" #include "watertowerclassic/SharedDefinitions.h" #include "watertowerclassic/SharedEvents.h" #include "utilities/AutoFreePool.h" #include "shell/Shell.h" #include "shell/ShellKeyCodes.h" #include #include #define SUPERCLASS Screen enum { MENU_ITEM_RESUME, MENU_ITEM_SETTINGS, MENU_ITEM_RETRY, MENU_ITEM_END_GAME, MENU_ITEM_COUNT }; GameplayScreen * GameplayScreen_create(GameSession * gameSession) { stemobject_create_implementation(GameplayScreen, init, gameSession) } bool GameplayScreen_init(GameplayScreen * self, GameSession * gameSession) { call_super(init, self); self->activate = GameplayScreen_activate; self->deactivate = GameplayScreen_deactivate; self->dispose = GameplayScreen_dispose; self->gameSession = gameSession; self->inputController = InputController_create(gameSession->inputMap, "left", "right", "down", "up", "jump", "action", "pause", NULL); self->backgrounded = false; glGenBuffersARB(1, &self->vertexBufferID); glGenBuffersARB(1, &self->indexBufferID); self->bitmapFont = ResourceManager_referenceResource(self->gameSession->resourceManager, "bitmap_font", "pixelfont.json"); return true; } void GameplayScreen_dispose(GameplayScreen * self) { glDeleteBuffersARB(1, &self->vertexBufferID); glDeleteBuffersARB(1, &self->indexBufferID); if (self->runLoop != NULL) { FixedIntervalRunLoop_dispose(self->runLoop); } InputController_dispose(self->inputController); ResourceManager_releaseResource(self->gameSession->resourceManager, "bitmap_font", "pixelfont.json"); call_super(dispose, self); } static void initLevel(GameplayScreen * self) { short numberOfLives = PLAYER_INITIAL_LIVES; long totalPoints = 0; if (self->currentLevel != NULL) { self->currentLevel->dispose(self->currentLevel); } self->currentLevel = LevelSetModel_copyLevelAtIndex(self->levelSet, self->levelIndex); self->frameIndex = 0; if (self->playerModel != NULL) { numberOfLives = self->playerModel->numberOfLives; totalPoints = self->playerModel->totalPoints; self->playerModel->dispose(self->playerModel); } self->playerModel = PlayerModel_create(self->currentLevel); self->playerModel->opacity = 0x00; self->playerModel->fadingIn = true; self->playerModel->numberOfLives = numberOfLives; self->playerModel->totalPoints = totalPoints; if (self->playerModel->tileLocation.y < LEVEL_BLOCK_COUNT_Y && isBlockSolid(&self->currentLevel->blocks[LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1) + self->playerModel->tileLocation.x], self->currentLevel->waterLevel, self->frameIndex)) { self->playerModel->standingOn = &self->currentLevel->blocks[LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1) + self->playerModel->tileLocation.x]; } else { self->playerModel->standingOn = NULL; } numberOfTouchedBlocks = 0; } static void beginLevelTransition(GameplayScreen * self) { GameplayView_beginLevelTransition(self->view); self->levelIndex++; initLevel(self); self->gameState = GAME_STATE_LEVEL_TRANSITION; self->levelTransitionFramesRemaining = 43; } static void finishLevel(GameplayScreen * self) { if (self->gameSession->testingLevel) { AudioManager_stopMusic(); if (self->levelIndex + 1 >= self->levelSet->levelCount) { AudioManager_play("finish_level_set"); } else { AudioManager_play("finish_level"); } InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "level_test_ended"); } else { if (self->levelIndex + 1 >= self->levelSet->levelCount) { AudioManager_stopMusic(); AudioManager_play("finish_level_set"); } else { AudioManager_play("finish_level"); } self->gameState = GAME_STATE_FINISHED_LEVEL; } } static void playerDie(GameplayScreen * self) { AudioManager_stopMusic(); if (self->gameSession->testingLevel) { AudioManager_play("die"); InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "level_test_ended"); } else { self->playerModel->numberOfLives--; AudioManager_play(self->playerModel->numberOfLives < 0 ? "game_over" : "die"); self->deathFramesRemaining = 108; self->gameState = GAME_STATE_DYING; } } static void runGamePhysics(GameplayScreen * self) { struct LevelBlock * blockFound; short found; bool overlapLeft, overlapRight, overlapDown, overlapUp; short leftCollisionLimit = -1; blockRef * theBlockRef; short blockIndexX, blockIndexY; rect4f playerRect, blockRect; struct LevelBlock * oldStandingOn; float newAmount; /* Edge of screen boundaries */ if (self->playerModel->pixelLocation.x < PLAYER_WIDTH / 2) { self->playerModel->pixelLocation.x = PLAYER_WIDTH / 2; } if (self->playerModel->pixelLocation.x > LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_SIZE - PLAYER_WIDTH / 2) { self->playerModel->pixelLocation.x = LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_SIZE - PLAYER_WIDTH / 2; } /* Check to see if the player has finished the level */ if (self->playerModel->pixelLocation.y < LEVEL_BLOCK_SIZE / 2) { finishLevel(self); return; } /* Check to see if the player has fallen off the bottom of the level */ if (self->playerModel->pixelLocation.y > LEVEL_BLOCK_COUNT_Y * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE / 2) { playerDie(self); return; } self->playerModel->update(self->playerModel); /* Remember what block the player was on */ oldStandingOn = self->playerModel->standingOn; /* Constrain the player's movement */ self->playerModel->tileLocation.x = (self->playerModel->pixelLocation.x / LEVEL_BLOCK_SIZE); self->playerModel->tileLocation.y = (self->playerModel->pixelLocation.y / LEVEL_BLOCK_SIZE); if (self->playerModel->tileLocation.x < 0) self->playerModel->tileLocation.x = 0; if (self->playerModel->tileLocation.x >= LEVEL_BLOCK_COUNT_X) self->playerModel->tileLocation.x = (LEVEL_BLOCK_COUNT_X - 1); if (self->playerModel->tileLocation.y < 0) self->playerModel->tileLocation.y = 0; if (self->playerModel->tileLocation.y >= LEVEL_BLOCK_COUNT_Y) self->playerModel->tileLocation.y = (LEVEL_BLOCK_COUNT_Y - 1); overlapLeft = (self->playerModel->tileLocation.x > 0 && (self->playerModel->pixelLocation.x - (self->playerModel->tileLocation.x * LEVEL_BLOCK_SIZE)) < (PLAYER_WIDTH / 2)); overlapRight = (self->playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1) && (self->playerModel->pixelLocation.x - (self->playerModel->tileLocation.x * LEVEL_BLOCK_SIZE)) > (LEVEL_BLOCK_SIZE - (PLAYER_WIDTH / 2))); overlapDown = (self->playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1) && (self->playerModel->pixelLocation.y - (self->playerModel->tileLocation.y * LEVEL_BLOCK_SIZE)) > (LEVEL_BLOCK_SIZE / 2)); overlapUp = (self->playerModel->tileLocation.y > 0 && (self->playerModel->pixelLocation.y - (self->playerModel->tileLocation.y * LEVEL_BLOCK_SIZE)) < (LEVEL_BLOCK_SIZE / 2)); if (self->playerModel->vVel >= 0.0) { if (self->playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1)) { blockFound = NULL; if (overlapDown) { /* Check regular blocks */ if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex) || (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)].type == BLOCK_TYPE_LADDER && !self->playerModel->onLadder && self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + self->playerModel->tileLocation.x)].type != BLOCK_TYPE_LADDER)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)]; else if (overlapLeft && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex) && !isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))]; else if (overlapRight && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex) && !isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))]; } playerRect.left = (self->playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); playerRect.top = (self->playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); playerRect.right = (playerRect.left + PLAYER_WIDTH); playerRect.bottom = (playerRect.top + LEVEL_BLOCK_SIZE); for (blockIndexY = self->playerModel->tileLocation.y; blockIndexY <= (self->playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1) ? (self->playerModel->tileLocation.y + 1) : self->playerModel->tileLocation.y); blockIndexY++) { for (blockIndexX = (self->playerModel->tileLocation.x > 0 ? (self->playerModel->tileLocation.x - 1) : self->playerModel->tileLocation.x); blockIndexX <= (self->playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1) ? (self->playerModel->tileLocation.x + 1) : self->playerModel->tileLocation.x); blockIndexX++) { for (theBlockRef = LevelModel_getBlockRefsOccupyingTilePosition(self->currentLevel, blockIndexX, blockIndexY); theBlockRef != NULL; theBlockRef = theBlockRef->nextBlock) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)], self->currentLevel->waterLevel, self->frameIndex)) continue; blockRect = getBlockPixelRect(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]); switch (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)].type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: if (rectTouch(&playerRect, &blockRect, DIRECTION_DOWN)) { bool replaceBlockFound = 1; if (blockFound != NULL && getBlockPixelRect(blockFound).top <= blockRect.top) replaceBlockFound = 0; if (replaceBlockFound) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]; } break; default: break; } } } } if (blockFound != NULL) { self->playerModel->pixelLocation.y = getBlockPixelRect(blockFound).top - PLAYER_HEIGHT / 2; self->playerModel->vVel = 0.0; if (self->playerModel->standingOn == NULL) AudioManager_play("land"); self->playerModel->standingOn = blockFound; touchedBlocks[numberOfTouchedBlocks].x = blockFound->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = blockFound->tilePosition.y; numberOfTouchedBlocks++; overlapDown = 0; } else { if (self->playerModel->standingOn != NULL && !overlapUp) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex) || (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)].type == BLOCK_TYPE_LADDER && !self->playerModel->onLadder && self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + self->playerModel->tileLocation.x)].type != BLOCK_TYPE_LADDER)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)]; else if (overlapLeft && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))]; else if (overlapRight && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))]; } if (blockFound == NULL) { if (self->playerModel->vVel < PLAYER_MAX_FALL_SPEED && !self->playerModel->onLadder) self->playerModel->vVel += (self->playerModel->underwater ? 0.125 : 0.25); } else { if (self->playerModel->standingOn == NULL) AudioManager_play("land"); } self->playerModel->standingOn = blockFound; } } else { if (self->playerModel->vVel < PLAYER_MAX_FALL_SPEED && !self->playerModel->onLadder) self->playerModel->vVel += (self->playerModel->underwater ? 0.125 : 0.25); } if (self->playerModel->tileLocation.y > 0) { blockFound = NULL; if (overlapUp) { /* Check regular blocks */ if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + self->playerModel->tileLocation.x)]; else if (overlapLeft && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x - 1))]; else if (overlapRight && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x + 1))]; } playerRect.left = (self->playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); playerRect.top = (self->playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); playerRect.right = (playerRect.left + PLAYER_WIDTH); playerRect.bottom = (playerRect.top + LEVEL_BLOCK_SIZE); for (blockIndexY = (self->playerModel->tileLocation.y > 0 ? (self->playerModel->tileLocation.y - 1) : self->playerModel->tileLocation.y); blockIndexY <= self->playerModel->tileLocation.y; blockIndexY++) { for (blockIndexX = (self->playerModel->tileLocation.x > 0 ? (self->playerModel->tileLocation.x - 1) : self->playerModel->tileLocation.x); blockIndexX <= (self->playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1) ? (self->playerModel->tileLocation.x + 1) : self->playerModel->tileLocation.x); blockIndexX++) { for (theBlockRef = LevelModel_getBlockRefsOccupyingTilePosition(self->currentLevel, blockIndexX, blockIndexY); theBlockRef != NULL; theBlockRef = theBlockRef->nextBlock) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)], self->currentLevel->waterLevel, self->frameIndex)) continue; blockRect = getBlockPixelRect(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]); switch (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)].type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: if (rectTouch(&playerRect, &blockRect, DIRECTION_UP)) { bool replaceBlockFound = 1; if (blockFound != NULL && getBlockPixelRect(blockFound).bottom >= blockRect.bottom) replaceBlockFound = 0; if (replaceBlockFound) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]; } break; default: break; } } } } if (blockFound != NULL && self->playerModel->vVel < 0.0) { self->playerModel->pixelLocation.y = getBlockPixelRect(blockFound).bottom + PLAYER_HEIGHT / 2; self->playerModel->vVel = 0.0; touchedBlocks[numberOfTouchedBlocks].x = blockFound->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = blockFound->tilePosition.y; numberOfTouchedBlocks++; overlapUp = 0; } } } if (self->playerModel->tileLocation.x > 0) { blockFound = NULL; if (overlapLeft) { /* Check regular blocks */ if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + (self->playerModel->tileLocation.x - 1))]; else if (overlapUp && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) { if (self->playerModel->tileLocation.y <= 0 || !isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x - 1))]; } else if (overlapDown && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))]; } playerRect.left = (self->playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); playerRect.top = (self->playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); playerRect.right = (playerRect.left + PLAYER_WIDTH); playerRect.bottom = (playerRect.top + LEVEL_BLOCK_SIZE); for (blockIndexY = (self->playerModel->tileLocation.y > 0 ? (self->playerModel->tileLocation.y - 1) : self->playerModel->tileLocation.y); blockIndexY <= (self->playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1) ? (self->playerModel->tileLocation.y + 1) : self->playerModel->tileLocation.y); blockIndexY++) { for (blockIndexX = (self->playerModel->tileLocation.x > 0 ? (self->playerModel->tileLocation.x - 1) : self->playerModel->tileLocation.x); blockIndexX <= self->playerModel->tileLocation.x; blockIndexX++) { for (theBlockRef = LevelModel_getBlockRefsOccupyingTilePosition(self->currentLevel, blockIndexX, blockIndexY); theBlockRef != NULL; theBlockRef = theBlockRef->nextBlock) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)], self->currentLevel->waterLevel, self->frameIndex)) continue; blockRect = getBlockPixelRect(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]); switch (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)].type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: if (rectCollision(&playerRect, &blockRect, DIRECTION_LEFT)) { blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]; } break; case BLOCK_TYPE_FIRE: if (rectCollision(&playerRect, &blockRect, DIRECTION_LEFT)) { touchedBlocks[numberOfTouchedBlocks].x = theBlockRef->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = theBlockRef->tilePosition.y; numberOfTouchedBlocks++; } break; default: break; } } } } if (blockFound != NULL) { switch (blockFound->type) { case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: blockRect = getBlockPixelRect(blockFound); /* Move half speed when pushing a block */ newAmount = pushBlock(self->currentLevel, self->playerModel, self->frameIndex, blockFound, DIRECTION_LEFT, (self->playerModel->hVel / -2), 0); if (newAmount > 0.0) { pushBlock(self->currentLevel, self->playerModel, self->frameIndex, blockFound, DIRECTION_LEFT, newAmount, 1); } break; default: break; } } if (blockFound != NULL) { self->playerModel->pixelLocation.x = getBlockPixelRect(blockFound).right + PLAYER_WIDTH / 2; leftCollisionLimit = self->playerModel->pixelLocation.x; overlapLeft = 0; touchedBlocks[numberOfTouchedBlocks].x = blockFound->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = blockFound->tilePosition.y; numberOfTouchedBlocks++; } } if (self->playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1)) { blockFound = NULL; if (overlapRight) { /* Check regular blocks */ if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + (self->playerModel->tileLocation.x + 1))]; else if (overlapUp && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) { if (self->playerModel->tileLocation.y <= 0 || !isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x + 1))]; } else if (overlapDown && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))]; } playerRect.left = (self->playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); playerRect.top = (self->playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); playerRect.right = (playerRect.left + PLAYER_WIDTH); playerRect.bottom = (playerRect.top + LEVEL_BLOCK_SIZE); for (blockIndexY = (self->playerModel->tileLocation.y > 0 ? (self->playerModel->tileLocation.y - 1) : self->playerModel->tileLocation.y); blockIndexY <= (self->playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1) ? (self->playerModel->tileLocation.y + 1) : self->playerModel->tileLocation.y); blockIndexY++) { for (blockIndexX = self->playerModel->tileLocation.x; blockIndexX <= (self->playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1) ? (self->playerModel->tileLocation.x + 1) : self->playerModel->tileLocation.x); blockIndexX++) { for (theBlockRef = LevelModel_getBlockRefsOccupyingTilePosition(self->currentLevel, blockIndexX, blockIndexY); theBlockRef != NULL; theBlockRef = theBlockRef->nextBlock) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)], self->currentLevel->waterLevel, self->frameIndex)) continue; blockRect = getBlockPixelRect(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]); switch (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)].type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: if (rectCollision(&playerRect, &blockRect, DIRECTION_RIGHT)) { blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]; } break; case BLOCK_TYPE_FIRE: if (rectCollision(&playerRect, &blockRect, DIRECTION_RIGHT)) { touchedBlocks[numberOfTouchedBlocks].x = theBlockRef->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = theBlockRef->tilePosition.y; numberOfTouchedBlocks++; } break; default: break; } } } } if (blockFound != NULL) { switch (blockFound->type) { case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: blockRect = getBlockPixelRect(blockFound); /* Move half speed when pushing a block */ newAmount = pushBlock(self->currentLevel, self->playerModel, self->frameIndex, blockFound, DIRECTION_RIGHT, (self->playerModel->hVel / 2), 0); if (newAmount > 0.0) { pushBlock(self->currentLevel, self->playerModel, self->frameIndex, blockFound, DIRECTION_RIGHT, newAmount, 1); } break; default: break; } } if (blockFound != NULL) { self->playerModel->pixelLocation.x = getBlockPixelRect(blockFound).left - PLAYER_WIDTH / 2; if (self->playerModel->pixelLocation.x < leftCollisionLimit) self->playerModel->pixelLocation.x = leftCollisionLimit; touchedBlocks[numberOfTouchedBlocks].x = blockFound->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = blockFound->tilePosition.y; numberOfTouchedBlocks++; overlapRight = 0; } } if (self->playerModel->vVel < 0.0) { if (self->playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1)) { blockFound = NULL; if (overlapDown) { /* Check regular blocks */ if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)]; else if (overlapLeft && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))]; else if (overlapRight && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))]; } playerRect.left = (self->playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); playerRect.top = (self->playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); playerRect.right = (playerRect.left + PLAYER_WIDTH); playerRect.bottom = (playerRect.top + LEVEL_BLOCK_SIZE); for (blockIndexY = self->playerModel->tileLocation.y; blockIndexY <= (self->playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1) ? (self->playerModel->tileLocation.y + 1) : self->playerModel->tileLocation.y); blockIndexY++) { for (blockIndexX = (self->playerModel->tileLocation.x > 0 ? (self->playerModel->tileLocation.x - 1) : self->playerModel->tileLocation.x); blockIndexX <= (self->playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1) ? (self->playerModel->tileLocation.x + 1) : self->playerModel->tileLocation.x); blockIndexX++) { for (theBlockRef = LevelModel_getBlockRefsOccupyingTilePosition(self->currentLevel, blockIndexX, blockIndexY); theBlockRef != NULL; theBlockRef = theBlockRef->nextBlock) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)], self->currentLevel->waterLevel, self->frameIndex)) continue; blockRect = getBlockPixelRect(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]); switch (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)].type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: if (rectTouch(&playerRect, &blockRect, DIRECTION_DOWN)) { bool replaceBlockFound = 1; if (blockFound != NULL && getBlockPixelRect(blockFound).top <= blockRect.top) replaceBlockFound = 0; if (replaceBlockFound) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]; } break; default: break; } } } } if (blockFound != NULL && self->playerModel->vVel >= 0.0) { self->playerModel->pixelLocation.y = getBlockPixelRect(blockFound).top + PLAYER_HEIGHT / 2; self->playerModel->vVel = 0.0; if (self->playerModel->standingOn == NULL) AudioManager_play("land"); self->playerModel->standingOn = blockFound; touchedBlocks[numberOfTouchedBlocks].x = blockFound->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = blockFound->tilePosition.y; numberOfTouchedBlocks++; overlapDown = 0; } else { if (self->playerModel->standingOn != NULL) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)]; else if (overlapLeft && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x - 1))]; else if (overlapRight && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + (self->playerModel->tileLocation.x + 1))]; } if (blockFound == NULL) { if (self->playerModel->vVel < PLAYER_MAX_FALL_SPEED && !self->playerModel->onLadder) self->playerModel->vVel += (self->playerModel->underwater ? 0.125 : 0.25); } else { if (self->playerModel->standingOn == NULL) AudioManager_play("land"); } self->playerModel->standingOn = blockFound; } } else { if (self->playerModel->vVel < PLAYER_MAX_FALL_SPEED && !self->playerModel->onLadder) self->playerModel->vVel += (self->playerModel->underwater ? 0.125 : 0.25); } if (self->playerModel->tileLocation.y > 0) { blockFound = NULL; if (overlapUp) { /* Check regular blocks */ if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + self->playerModel->tileLocation.x)], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + self->playerModel->tileLocation.x)]; else if (overlapLeft && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x - 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x - 1))]; else if (overlapRight && isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x + 1))], self->currentLevel->waterLevel, self->frameIndex)) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y - 1)) + (self->playerModel->tileLocation.x + 1))]; } playerRect.left = (self->playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); playerRect.top = (self->playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); playerRect.right = (playerRect.left + PLAYER_WIDTH); playerRect.bottom = (playerRect.top + LEVEL_BLOCK_SIZE); for (blockIndexY = (self->playerModel->tileLocation.y > 0 ? (self->playerModel->tileLocation.y - 1) : self->playerModel->tileLocation.y); blockIndexY <= self->playerModel->tileLocation.y; blockIndexY++) { for (blockIndexX = (self->playerModel->tileLocation.x > 0 ? (self->playerModel->tileLocation.x - 1) : self->playerModel->tileLocation.x); blockIndexX <= (self->playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1) ? (self->playerModel->tileLocation.x + 1) : self->playerModel->tileLocation.x); blockIndexX++) { for (theBlockRef = LevelModel_getBlockRefsOccupyingTilePosition(self->currentLevel, blockIndexX, blockIndexY); theBlockRef != NULL; theBlockRef = theBlockRef->nextBlock) { if (isBlockSolid(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)], self->currentLevel->waterLevel, self->frameIndex)) continue; blockRect = getBlockPixelRect(&self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]); switch (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)].type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: if (rectTouch(&playerRect, &blockRect, DIRECTION_UP)) { bool replaceBlockFound = 1; if (blockFound != NULL && getBlockPixelRect(blockFound).bottom >= blockRect.bottom) replaceBlockFound = 0; if (replaceBlockFound) blockFound = &self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * theBlockRef->tilePosition.y) + theBlockRef->tilePosition.x)]; } break; default: break; } } } } if (blockFound != NULL && self->playerModel->vVel < 0.0) { self->playerModel->pixelLocation.y = getBlockPixelRect(blockFound).bottom + PLAYER_HEIGHT / 2; self->playerModel->vVel = 0.0; touchedBlocks[numberOfTouchedBlocks].x = blockFound->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = blockFound->tilePosition.y; numberOfTouchedBlocks++; overlapUp = 0; } } } self->playerModel->tileLocation.x = (self->playerModel->pixelLocation.x / LEVEL_BLOCK_SIZE); self->playerModel->tileLocation.y = (self->playerModel->pixelLocation.y / LEVEL_BLOCK_SIZE); if (self->playerModel->tileLocation.x < 0) self->playerModel->tileLocation.x = 0; if (self->playerModel->tileLocation.x >= LEVEL_BLOCK_COUNT_X) self->playerModel->tileLocation.x = (LEVEL_BLOCK_COUNT_X - 1); if (self->playerModel->tileLocation.y < 0) self->playerModel->tileLocation.y = 0; if (self->playerModel->tileLocation.y >= LEVEL_BLOCK_COUNT_Y) self->playerModel->tileLocation.y = (LEVEL_BLOCK_COUNT_Y - 1); /* See if the player is climbing a ladder */ if (self->playerModel->onLadder) { if (!overlapDown && self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + self->playerModel->tileLocation.x)].type != BLOCK_TYPE_LADDER) { self->playerModel->onLadder = 0; self->playerModel->pixelLocation.y = ((self->playerModel->tileLocation.y * LEVEL_BLOCK_SIZE) + (LEVEL_BLOCK_SIZE / 2)); self->playerModel->vVel = 0; } } else if (self->playerModel->ladderIntention != 0) { found = 0; if (self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * self->playerModel->tileLocation.y) + self->playerModel->tileLocation.x)].type == BLOCK_TYPE_LADDER) found = 1; if ((overlapDown || self->playerModel->standingOn != NULL) && self->currentLevel->blocks[((LEVEL_BLOCK_COUNT_X * (self->playerModel->tileLocation.y + 1)) + self->playerModel->tileLocation.x)].type == BLOCK_TYPE_LADDER) found = 1; if (found || (self->playerModel->standingOn != NULL && self->playerModel->standingOn->type == BLOCK_TYPE_LADDER)) { self->playerModel->onLadder = 1; self->playerModel->pixelLocation.x = ((self->playerModel->tileLocation.x * LEVEL_BLOCK_SIZE) + (LEVEL_BLOCK_SIZE / 2)); self->playerModel->hVel = 0.0; if (self->playerModel->standingOn != NULL) { leaveBlock(self->playerModel->standingOn); } if (self->playerModel->ladderIntention < 0) self->playerModel->vVel = -PLAYER_MAX_WALK_SPEED; else self->playerModel->vVel = PLAYER_MAX_WALK_SPEED; self->playerModel->ladderIntention = 0; } } /* Check to see if the player is picking anything up */ if (self->currentLevel->powerupCount > 0) { short left, right, top, bottom; short blockIndexX, blockIndexY; const char * soundToPlay; /* Calculate what tiles the player is touching */ left = (overlapLeft * -1); top = (overlapUp * -1); right = overlapRight; bottom = overlapDown; for (blockIndexY = top; blockIndexY <= bottom; blockIndexY++) { for (blockIndexX = left; blockIndexX <= right; blockIndexX++) { if ((found = LevelModel_getPowerupByLocation(self->currentLevel, self->playerModel->tileLocation.x + blockIndexX, self->playerModel->tileLocation.y + blockIndexY)) != -1) { soundToPlay = NULL; switch (self->currentLevel->powerups[found].type) { case POWERUP_TYPE_KEY: soundToPlay = "key"; self->playerModel->numberOfKeys++; break; case POWERUP_TYPE_HEART: soundToPlay = "heart"; if (self->playerModel->numberOfLives < PLAYER_MAX_LIVES) { self->playerModel->numberOfLives++; } break; case POWERUP_TYPE_GEM: soundToPlay = "gem"; break; } self->playerModel->numberOfPoints += getPowerupPointValue(self->currentLevel->powerups[found].type); if (soundToPlay != NULL) { AudioManager_play(soundToPlay); } LevelModel_removePowerupAtIndex(self->currentLevel, found); } } } } /* See if the player is standing on a new block */ if (self->playerModel->standingOn != oldStandingOn) { leaveBlock(oldStandingOn); enterBlock(self->playerModel, self->playerModel->standingOn); if (self->playerModel->standingOn != NULL) { if (touchBlock(self->playerModel, self->playerModel->standingOn) == BLOCK_TYPE_DEATH) { playerDie(self); return; } } } /* Process touchedBlocks. Has to be done last, in case they touched a blockClassDeadly and I have to call playerDie(). */ while (numberOfTouchedBlocks > 0) { numberOfTouchedBlocks--; if (touchBlock(self->playerModel, &self->currentLevel->blocks[touchedBlocks[numberOfTouchedBlocks].y * LEVEL_BLOCK_COUNT_X + touchedBlocks[numberOfTouchedBlocks].x]) == BLOCK_TYPE_DEATH) { playerDie(self); return; } } } static void run(void * context) { GameplayScreen * self = context; if (self->paused) { return; } switch (self->gameState) { case GAME_STATE_STARTING_LEVEL: if (self->playerModel->fadingIn) { self->playerModel->opacity += 8; if (self->playerModel->opacity > 0xFF) { self->playerModel->opacity = 0xFF; self->playerModel->fadingIn = false; } } else { self->playerModel->opacity -= 8; if (self->playerModel->opacity < 0x00) { self->playerModel->opacity = 0x00; self->playerModel->fadingIn = true; } } break; case GAME_STATE_PLAYING: self->playerModel->pixelLocation.x += (self->playerModel->underwater ? self->playerModel->hVel / 2 : self->playerModel->hVel) / (self->playerModel->standingOn != NULL && self->playerModel->standingOn->type == BLOCK_TYPE_STICKY ? 2 : 1); self->playerModel->pixelLocation.y += self->playerModel->underwater ? self->playerModel->vVel / 2 : self->playerModel->vVel; self->frameIndex++; LevelModel_update(self->currentLevel, self->playerModel, self->frameIndex); runGamePhysics(self); if (self->gameState != GAME_STATE_PLAYING) return; /* Game state can be changed if player finishes level in LEVELTESTING mode */ self->playerModel->animationCounter++; self->playerModel->underwater = (self->playerModel->pixelLocation.y > (WATER_LEVEL_MAX - self->currentLevel->waterLevel)); if (self->playerModel->underwater) { self->playerModel->airLeft--; if (self->playerModel->airLeft <= 0) { playerDie(self); return; } } else { if (self->playerModel->airLeft < 600) { AudioManager_play("breath"); } self->playerModel->airLeft = PLAYER_BREATH_DURATION; } if (self->playerModel->onLadder) self->playerModel->setAnimationState(self->playerModel, abstAnimClimb); else if (self->playerModel->standingOn == NULL) self->playerModel->setAnimationState(self->playerModel, abstAnimJump); else if (self->playerModel->hVel < 0.0) self->playerModel->setAnimationState(self->playerModel, abstAnimRunLeft); else if (self->playerModel->hVel > 0.0) self->playerModel->setAnimationState(self->playerModel, abstAnimRunRight); else self->playerModel->setAnimationState(self->playerModel, abstAnimStand); break; case GAME_STATE_DYING: if (self->deathFramesRemaining > 0) { self->deathFramesRemaining--; self->playerModel->opacity -= 8; if (self->playerModel->opacity < 0) { self->playerModel->opacity = 0; } } else if (self->playerModel->numberOfLives < 0) { InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "game_over"); } else { initLevel(self); self->gameState = GAME_STATE_STARTING_LEVEL; } break; case GAME_STATE_FINISHED_LEVEL: if (self->currentLevel->waterSpeed <= 0.0f || self->currentLevel->waterLevel >= WATER_LEVEL_MAX) { self->playerModel->totalPoints += self->playerModel->numberOfPoints; if (self->levelIndex + 1 >= self->levelSet->levelCount) { InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "game_over"); } else { beginLevelTransition(self); } } else { short waterValue; self->currentLevel->waterLevel += 2.0; if (self->currentLevel->waterLevel > WATER_LEVEL_MAX) { self->currentLevel->waterLevel = WATER_LEVEL_MAX; } if (self->currentLevel->waterSpeed < 1.0) { waterValue = 1; } else { waterValue = (self->currentLevel->waterSpeed + 0.5); } self->playerModel->totalPoints += waterValue * 2; } break; case GAME_STATE_LEVEL_TRANSITION: if (self->levelTransitionFramesRemaining > 0) { self->levelTransitionFramesRemaining--; } else { self->gameState = GAME_STATE_STARTING_LEVEL; } break; } AutoFreePool_empty(); } static void pauseGame(GameplayScreen * self) { self->paused = true; self->menuItemIndex = MENU_ITEM_RESUME; FixedIntervalRunLoop_pause(self->runLoop); AudioManager_pauseMusic(); Shell_setCursorVisible(true); } static void unpauseGame(GameplayScreen * self) { InputController_releaseAction(self->inputController, ATOM("jump")); InputController_releaseAction(self->inputController, ATOM("action")); self->paused = false; self->lastDrawTime = Shell_getCurrentTime(); FixedIntervalRunLoop_resume(self->runLoop); AudioManager_resumeMusic(); Shell_redisplay(); if (Shell_isFullScreen()) { Shell_setCursorVisible(false); } } static void startLevel(GameplayScreen * self) { if (self->gameState == GAME_STATE_STARTING_LEVEL && !self->paused) { self->gameState = GAME_STATE_PLAYING; self->playerModel->opacity = 0xFF; if (valueGetBoolean(Preferences_get(self->gameSession->preferences, "play_music")) && !AudioManager_isMusicPlaying()) { AudioManager_startMusic("music.ogg"); } } } static void menuAction(GameplayScreen * self, unsigned int menuItemIndex) { switch (menuItemIndex) { case MENU_ITEM_RESUME: unpauseGame(self); break; case MENU_ITEM_RETRY: if (self->gameState == GAME_STATE_PLAYING || self->gameState == GAME_STATE_STARTING_LEVEL) { unpauseGame(self); startLevel(self); playerDie(self); } break; case MENU_ITEM_SETTINGS: self->gameSession->viewingSettingsFromGameplay = true; InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "settings"); break; case MENU_ITEM_END_GAME: if (self->gameState == GAME_STATE_PLAYING || self->gameState == GAME_STATE_STARTING_LEVEL) { unpauseGame(self); startLevel(self); self->playerModel->numberOfLives = 0; playerDie(self); } break; } } static void menuDown(GameplayScreen * self) { unsigned int enabledItemCount = (self->gameState == GAME_STATE_PLAYING || self->gameState == GAME_STATE_STARTING_LEVEL) ? MENU_ITEM_COUNT : MENU_ITEM_COUNT - 2; self->menuItemIndex += 1; self->menuItemIndex %= enabledItemCount; Shell_redisplay(); } static void menuUp(GameplayScreen * self) { unsigned int enabledItemCount = (self->gameState == GAME_STATE_PLAYING || self->gameState == GAME_STATE_STARTING_LEVEL) ? MENU_ITEM_COUNT : MENU_ITEM_COUNT - 2; self->menuItemIndex += enabledItemCount - 1; self->menuItemIndex %= enabledItemCount; Shell_redisplay(); } static bool mouseDown(Atom eventID, void * eventData, void * context) { struct mouseEvent * event = eventData; GameplayScreen * self = context; if (event->position.y >= 192.0f) { menuAction(self, (event->position.y - 192.0f) / 32.0f); } return true; } static bool keyDown(Atom eventID, void * eventData, void * context) { struct keyEvent * event = eventData; GameplayScreen * self = context; bool handled = false; if (event->isRepeat) { return false; } if (event->keyCode == KEYBOARD_ESCAPE) { if (self->paused) { unpauseGame(self); } else { pauseGame(self); } return true; } if (self->paused) { switch (event->keyCode) { case KEYBOARD_RETURN_OR_ENTER: case KEYBOARD_SPACEBAR: InputController_triggerAction(self->inputController, ATOM("jump")); return true; case KEYBOARD_DOWN_ARROW: InputController_triggerAction(self->inputController, ATOM("down")); return true; case KEYBOARD_UP_ARROW: InputController_triggerAction(self->inputController, ATOM("up")); return true; } } handled = InputController_keyDown(self->inputController, event->keyCode); if (handled) { FixedIntervalRunLoop_resume(self->runLoop); Shell_redisplay(); } return handled; } static bool keyUp(Atom eventID, void * eventData, void * context) { struct keyEvent * event = eventData; GameplayScreen * self = context; if (self->paused) { switch (event->keyCode) { case KEYBOARD_RETURN_OR_ENTER: case KEYBOARD_SPACEBAR: InputController_releaseAction(self->inputController, ATOM("jump")); return true; case KEYBOARD_DOWN_ARROW: InputController_releaseAction(self->inputController, ATOM("down")); return true; case KEYBOARD_UP_ARROW: InputController_releaseAction(self->inputController, ATOM("up")); return true; } } return InputController_keyUp(self->inputController, event->keyCode); } static bool keyModifiersChanged(Atom eventID, void * eventData, void * context) { struct keyEvent * event = eventData; GameplayScreen * self = context; return InputController_keyModifiersChanged(self->inputController, event->modifiers); } static bool gamepadButtonDown(Atom eventID, void * eventData, void * context) { struct gamepadButtonEvent * event = eventData; GameplayScreen * self = context; return InputController_gamepadButtonDown(self->inputController, event->device->vendorID, event->device->productID, event->buttonID); } static bool gamepadButtonUp(Atom eventID, void * eventData, void * context) { struct gamepadButtonEvent * event = eventData; GameplayScreen * self = context; return InputController_gamepadButtonUp(self->inputController, event->device->vendorID, event->device->productID, event->buttonID); } static bool gamepadAxisMove(Atom eventID, void * eventData, void * context) { struct gamepadAxisEvent * event = eventData; GameplayScreen * self = context; return InputController_gamepadAxisMoved(self->inputController, event->device->vendorID, event->device->productID, event->axisID, event->value, event->lastValue); } static bool actionDown(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; Atom actionID = eventData; if (actionID == ATOM("pause")) { if (self->paused) { unpauseGame(self); } else { pauseGame(self); } return true; } if (actionID == ATOM("left")) { if (!self->paused) { startLevel(self); self->playerModel->startMovingLeft(self->playerModel); } return true; } if (actionID == ATOM("right")) { if (!self->paused) { startLevel(self); self->playerModel->startMovingRight(self->playerModel); } return true; } if (actionID == ATOM("down")) { if (self->paused) { menuDown(self); } else { startLevel(self); self->playerModel->startMovingDown(self->playerModel); } return true; } if (actionID == ATOM("up")) { if (self->paused) { menuUp(self); } else { startLevel(self); self->playerModel->startMovingUp(self->playerModel); } return true; } if (actionID == ATOM("jump")) { if (self->paused) { menuAction(self, self->menuItemIndex); } else if (self->gameState == GAME_STATE_FINISHED_LEVEL && self->currentLevel->waterSpeed > 0.0f && self->currentLevel->waterLevel < WATER_LEVEL_MAX) { short waterValue; if (self->currentLevel->waterSpeed < 1.0) { waterValue = 1; } else { waterValue = (self->currentLevel->waterSpeed + 0.5); } self->playerModel->totalPoints += waterValue * (WATER_LEVEL_MAX - self->currentLevel->waterLevel); self->currentLevel->waterLevel = WATER_LEVEL_MAX; } else { startLevel(self); self->playerModel->jump(self->playerModel); } return true; } if (actionID == ATOM("action")) { if (self->paused) { unpauseGame(self); } else { startLevel(self); self->playerModel->action(self->playerModel); } return true; } return false; } static bool actionUp(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; Atom actionID = eventData; if (actionID == ATOM("left")) { self->playerModel->stopMovingLeft(self->playerModel); return true; } if (actionID == ATOM("right")) { self->playerModel->stopMovingRight(self->playerModel); return true; } if (actionID == ATOM("down")) { self->playerModel->stopMovingDown(self->playerModel); return true; } if (actionID == ATOM("up")) { self->playerModel->stopMovingUp(self->playerModel); return true; } return false; } static bool resized(Atom eventID, void * eventData, void * context) { Shell_redisplay(); return true; } static bool backgrounded(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; self->backgrounded = true; FixedIntervalRunLoop_pause(self->runLoop); AudioManager_pauseMusic(); return true; } static bool foregrounded(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; self->backgrounded = false; if (!self->paused) { self->lastDrawTime = Shell_getCurrentTime(); FixedIntervalRunLoop_resume(self->runLoop); AudioManager_resumeMusic(); Shell_redisplay(); } return true; } static bool fullscreened(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; if (Shell_isFullScreen() && !self->paused) { Shell_setCursorVisible(false); } return true; } static bool windowed(Atom eventID, void * eventData, void * context) { Shell_setCursorVisible(true); return true; } static void getPauseMenuVertices(GameplayScreen * self, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { Color4f colors[MENU_ITEM_COUNT]; unsigned int colorIndex; for (colorIndex = 0; colorIndex < MENU_ITEM_COUNT; colorIndex++) { if (colorIndex == self->menuItemIndex) { colors[colorIndex] = MENU_COLOR_HIGHLIGHTED; } else if (self->gameState != GAME_STATE_PLAYING && self->gameState != GAME_STATE_STARTING_LEVEL && (colorIndex == MENU_ITEM_RETRY || colorIndex == MENU_ITEM_END_GAME)) { colors[colorIndex] = MENU_COLOR_DISABLED; } else { colors[colorIndex] = MENU_COLOR_ENABLED; } } GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "Paused", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 144.0f), VECTOR2f(0.5f, 0.5f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "RESUME", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 208.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_RESUME], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "SETTINGS", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 240.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_SETTINGS], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "RETRY LEVEL", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 272.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_RETRY], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "END GAME", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 304.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_END_GAME], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } static void drawPauseScreen(GameplayScreen * self) { struct vertex_p2f_t2f_c4f * vertices; GLushort * indexes; unsigned int vertexCount, indexCount; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glBindBufferARB(GL_ARRAY_BUFFER, self->vertexBufferID); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, self->indexBufferID); vertexCount = indexCount = 0; getPauseMenuVertices(self, NULL, NULL, &vertexCount, &indexCount); glBufferDataARB(GL_ARRAY_BUFFER, sizeof(struct vertex_p2f_t2f_c4f) * vertexCount, NULL, GL_STREAM_DRAW); glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * indexCount, NULL, GL_STREAM_DRAW); vertices = glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY); indexes = glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); vertexCount = indexCount = 0; getPauseMenuVertices(self, vertices, indexes, &vertexCount, &indexCount); glUnmapBufferARB(GL_ARRAY_BUFFER); glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER); glVertexPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, position)); glTexCoordPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, texCoords)); glColorPointer(4, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, color)); GLTexture_activate(self->bitmapFont->atlas->texture); glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0); GLTexture_deactivate(self->bitmapFont->atlas->texture); glBindBufferARB(GL_ARRAY_BUFFER, 0); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); } static bool draw(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; double interval, currentTime; FixedIntervalRunLoop_run(self->runLoop); if (self->runLoop == NULL) { return true; } AudioManager_singleThreadStreamMusic(); currentTime = Shell_getCurrentTime(); interval = currentTime - self->lastDrawTime; self->lastDrawTime = currentTime; GameplayView_draw(self->view, interval); if (self->paused) { drawPauseScreen(self); } else if (!self->backgrounded) { Shell_redisplay(); } return true; } static bool confirmQuit(Atom eventID, void * eventData, void * context) { GameplayScreen * self = context; if (self->gameSession->testingLevel && self->gameSession->hasUnsavedLevelChanges) { AudioManager_stopMusic(); InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "level_test_ended"); return true; } return false; } void GameplayScreen_activate(GameplayScreen * self) { EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), keyUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_MODIFIERS_CHANGED), keyModifiersChanged, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_DOWN), gamepadButtonDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_UP), gamepadButtonUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_AXIS_MOVE), gamepadAxisMove, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_RESIZED), resized, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_BACKGROUNDED), backgrounded, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_FOREGROUNDED), foregrounded, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_FULLSCREENED), fullscreened, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_WINDOWED), windowed, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_CONFIRM_QUIT), confirmQuit, self); EventDispatcher_registerForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); EventDispatcher_registerForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_UP), actionUp, self); if (self->gameSession->viewingSettingsFromGameplay) { self->gameSession->viewingSettingsFromGameplay = false; if (valueGetBoolean(Preferences_get(self->gameSession->preferences, "play_music")) && !AudioManager_isMusicPlaying() && self->gameState == GAME_STATE_PLAYING) { AudioManager_startMusic("music.ogg"); } else if (!valueGetBoolean(Preferences_get(self->gameSession->preferences, "play_music")) && AudioManager_isMusicPlaying()) { AudioManager_stopMusic(); } } else { self->runLoop = FixedIntervalRunLoop_create(Shell_getCurrentTime, FRAME_INTERVAL, run, self); FixedIntervalRunLoop_setTolerance(self->runLoop, FRAME_INTERVAL * 0.25f); self->levelSet = self->gameSession->levelSet; self->paused = false; self->lastDrawTime = Shell_getCurrentTime(); self->view = GameplayView_create(self, self->gameSession->resourceManager); if (Shell_isFullScreen()) { Shell_setCursorVisible(false); } self->currentLevel = NULL; self->playerModel = NULL; self->levelIndex = self->gameSession->startLevelIndex; initLevel(self); self->gameState = GAME_STATE_STARTING_LEVEL; } Shell_redisplay(); } void GameplayScreen_deactivate(GameplayScreen * self) { EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), keyUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_MODIFIERS_CHANGED), keyModifiersChanged, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_DOWN), gamepadButtonDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_UP), gamepadButtonUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_AXIS_MOVE), gamepadAxisMove, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_RESIZED), resized, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_BACKGROUNDED), backgrounded, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_FOREGROUNDED), foregrounded, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_FULLSCREENED), fullscreened, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_WINDOWED), windowed, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_CONFIRM_QUIT), confirmQuit, self); EventDispatcher_unregisterForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); EventDispatcher_unregisterForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_UP), actionUp, self); if (!self->gameSession->viewingSettingsFromGameplay) { AudioManager_stopMusic(); FixedIntervalRunLoop_dispose(self->runLoop); self->runLoop = NULL; self->view->dispose(self->view); } Shell_setCursorVisible(true); }