// Copyright (c) 2014 Alex Diener. All rights reserved. #include "watertowerclassic/AudioManager.h" #include "watertowerclassic/LevelModel.h" #include "watertowerclassic/PhysicsFunctions.h" #include "utilities/AutoFreePool.h" #include #include #include #define SUPERCLASS StemObject LevelModel * LevelModel_create() { stemobject_create_implementation(LevelModel, init) } LevelModel * LevelModel_copy(LevelModel * self) { stemobject_copy_implementation(LevelModel, initCopy) } static void sharedInit(LevelModel * self) { call_super(init, self); self->dispose = LevelModel_dispose; } bool LevelModel_init(LevelModel * self) { unsigned int blockIndexY, blockIndexX; sharedInit(self); self->playerStartPosition.x = LEVEL_BLOCK_COUNT_X / 2; self->playerStartPosition.y = LEVEL_BLOCK_COUNT_Y - 2; for (blockIndexY = 0; blockIndexY < LEVEL_BLOCK_COUNT_Y - 1; blockIndexY++) { for (blockIndexX = 0; blockIndexX < LEVEL_BLOCK_COUNT_X; blockIndexX++) { self->blocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX].type = BLOCK_TYPE_EMPTY; } } for (blockIndexX = 0; blockIndexX < LEVEL_BLOCK_COUNT_X; blockIndexX++) { self->blocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX].type = BLOCK_TYPE_STONE; self->blocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX].tilePosition.x = blockIndexX; self->blocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX].tilePosition.y = blockIndexY; self->blocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX].markedForRemoval = false; } self->powerupCount = 0; self->waterLevel = -100.0f; self->waterSpeed = 0.3f; self->pointsToUnlockNextLevel = 0; self->lastBlockWaterLevel = self->waterLevel / LEVEL_BLOCK_SIZE; return true; } bool LevelModel_initCopy(LevelModel * self, LevelModel * original) { unsigned int blockIndex, powerupIndex; sharedInit(self); self->playerStartPosition = original->playerStartPosition; for (blockIndex = 0; blockIndex < LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_COUNT_Y; blockIndex++) { self->blocks[blockIndex] = original->blocks[blockIndex]; } self->powerupCount = original->powerupCount; for (powerupIndex = 0; powerupIndex < LEVEL_POWERUP_COUNT_MAX; powerupIndex++) { self->powerups[powerupIndex] = original->powerups[powerupIndex]; } self->waterLevel = original->waterLevel; self->waterSpeed = original->waterSpeed; self->pointsToUnlockNextLevel = original->pointsToUnlockNextLevel; self->lastBlockWaterLevel = original->lastBlockWaterLevel; return true; } void LevelModel_dispose(LevelModel * self) { call_super(dispose, self); } static bool switchableBlockRespondsToSwitchID(struct LevelBlock block, unsigned int id) { unsigned int switchIDIndex; for (switchIDIndex = 0; switchIDIndex < SWITCH_ID_COUNT_MAX; switchIDIndex++) { if (block.properties.switchable.switchIDs[switchIDIndex] == id) { return true; } } return false; } void LevelModel_switchBlocks(LevelModel * self, unsigned int id) { unsigned int blockIndex; for (blockIndex = 0; blockIndex < LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_COUNT_Y; blockIndex++) { if (self->blocks[blockIndex].type == BLOCK_TYPE_SWITCHABLE && switchableBlockRespondsToSwitchID(self->blocks[blockIndex], id)) { self->blocks[blockIndex].properties.switchable.on = !self->blocks[blockIndex].properties.switchable.on; } } } static void initWaterBlocks(LevelModel * self, short row) { short blockIndexX; bool playSound = false; if (row >= LEVEL_BLOCK_COUNT_Y || row < 0) { return; } for (blockIndexX = 0; blockIndexX < LEVEL_BLOCK_COUNT_X; blockIndexX++) { switch (self->blocks[LEVEL_BLOCK_COUNT_X * row + blockIndexX].type) { case BLOCK_TYPE_WET_SOLID: case BLOCK_TYPE_DRY_SOLID: playSound = true; break; default: break; } } if (playSound) { AudioManager_play("block_toggle"); } } void LevelModel_update(LevelModel * self, PlayerModel * playerModel, unsigned int frameIndex) { unsigned int blockIndex; bool wasSolidLastFrame, isSolidThisFrame; bool playPhaseSound = false; Vector2i lastTilePosition; float distance; unsigned int numberOfCollisionBlocks, numberOfCenterCollisionBlocks; struct LevelBlock * collisionBlock, * centerCollisionBlock; for (blockIndex = 0; blockIndex < LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_COUNT_Y; blockIndex++) { // Deviation from original code: alreadyCarried was originally left uninitialized until the block loop, // which only zeroed each one in turn self->blocks[blockIndex].alreadyCarried = 0; if (self->blocks[blockIndex].markedForRemoval) { self->blocks[blockIndex].type = BLOCK_TYPE_EMPTY; self->blocks[blockIndex].markedForRemoval = false; } } for (blockIndex = 0; blockIndex < LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_COUNT_Y; blockIndex++) { switch (self->blocks[blockIndex].type) { case BLOCK_TYPE_PHASING: wasSolidLastFrame = isPhasingBlockSolid(&self->blocks[blockIndex], frameIndex - 1); isSolidThisFrame = isPhasingBlockSolid(&self->blocks[blockIndex], frameIndex); if (isSolidThisFrame != wasSolidLastFrame) { playPhaseSound = true; } break; case BLOCK_TYPE_MOVING: distance = pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], self->blocks[blockIndex].properties.moving.direction, xtof(self->blocks[blockIndex].properties.moving.speed), true); if (distance > 0.0) { pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], self->blocks[blockIndex].properties.moving.direction, distance, false); } else { switch (self->blocks[blockIndex].properties.moving.direction) { case DIRECTION_LEFT: self->blocks[blockIndex].properties.moving.direction = DIRECTION_RIGHT; break; case DIRECTION_UP: self->blocks[blockIndex].properties.moving.direction = DIRECTION_DOWN; break; case DIRECTION_RIGHT: self->blocks[blockIndex].properties.moving.direction = DIRECTION_LEFT; break; case DIRECTION_DOWN: self->blocks[blockIndex].properties.moving.direction = DIRECTION_UP; break; } distance = pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], self->blocks[blockIndex].properties.moving.direction, xtof(self->blocks[blockIndex].properties.moving.speed), true); if (distance > 0.0) { pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], self->blocks[blockIndex].properties.moving.direction, distance, false); } } break; case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: lastTilePosition.x = getBlockPixelCenter(&self->blocks[blockIndex]).x / LEVEL_BLOCK_SIZE; lastTilePosition.y = getBlockPixelCenter(&self->blocks[blockIndex]).y / LEVEL_BLOCK_SIZE; if (self->blocks[blockIndex].type == BLOCK_TYPE_PUSHABLE || (int) self->waterLevel < 480 - (int) getBlockPixelRect(&self->blocks[blockIndex]).top) { if (self->blocks[blockIndex].properties.pushable.yVelocity < 0) { self->blocks[blockIndex].properties.pushable.yVelocity += 0x7F; if (self->blocks[blockIndex].properties.pushable.yVelocity > 0) self->blocks[blockIndex].properties.pushable.yVelocity = 0; } else { self->blocks[blockIndex].properties.pushable.yVelocity += 0x3F; } } else if ((int) self->waterLevel > 480 - (int) getBlockPixelRect(&self->blocks[blockIndex]).top) { if (self->blocks[blockIndex].properties.pushable.yVelocity > 0) { self->blocks[blockIndex].properties.pushable.yVelocity -= 0xBF; if (self->blocks[blockIndex].properties.pushable.yVelocity < 0) self->blocks[blockIndex].properties.pushable.yVelocity = 0; } else { self->blocks[blockIndex].properties.pushable.yVelocity -= 0x7F; } } else { if (self->blocks[blockIndex].properties.pushable.yVelocity < 0x100 && self->blocks[blockIndex].properties.pushable.yVelocity> -0x100) self->blocks[blockIndex].properties.pushable.yVelocity = 0; } if (self->waterLevel >= 480 - getBlockPixelRect(&self->blocks[blockIndex]).top) { if (((float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x80) > PLAYER_MAX_FALL_SPEED) self->blocks[blockIndex].properties.pushable.yVelocity = (PLAYER_MAX_FALL_SPEED * 0x80); else if (((float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x80) < (PLAYER_MAX_FALL_SPEED / -2)) self->blocks[blockIndex].properties.pushable.yVelocity = ((PLAYER_MAX_FALL_SPEED / -2) * 0x80); } else { if (((float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x100) > PLAYER_MAX_FALL_SPEED) self->blocks[blockIndex].properties.pushable.yVelocity = (PLAYER_MAX_FALL_SPEED * 0x100); else if (((float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x100) < (PLAYER_MAX_FALL_SPEED / -2)) self->blocks[blockIndex].properties.pushable.yVelocity = ((PLAYER_MAX_FALL_SPEED / -2) * 0x100); } numberOfCollisionBlocks = 0; if (lastTilePosition.y < LEVEL_BLOCK_COUNT_Y) { if (self->blocks[blockIndex].properties.pushable.yVelocity > 0) { numberOfCenterCollisionBlocks = detectCollision(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_DOWN, (float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x100, ¢erCollisionBlock, 1, 1, 0); numberOfCollisionBlocks = detectCollision(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_DOWN, (float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x100, &collisionBlock, 1, 0, 0); } else { numberOfCenterCollisionBlocks = detectCollision(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_UP, (float) self->blocks[blockIndex].properties.pushable.yVelocity / -0x100, ¢erCollisionBlock, 1, 1, 0); numberOfCollisionBlocks = detectCollision(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_UP, (float) self->blocks[blockIndex].properties.pushable.yVelocity / -0x100, &collisionBlock, 1, 0, 0); } } if (numberOfCollisionBlocks == 0) { /* No collision */ if (self->blocks[blockIndex].properties.pushable.yVelocity != 0) { self->blocks[blockIndex].properties.pushable.position.y += (float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x100; if (lastTilePosition.x >= 0 && lastTilePosition.y >= 0 && lastTilePosition.x < LEVEL_BLOCK_COUNT_X && lastTilePosition.y < LEVEL_BLOCK_COUNT_Y && self->blocks[blockIndex].properties.pushable.yVelocity > 0) { carryBlocks(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_DOWN, (float) self->blocks[blockIndex].properties.pushable.yVelocity / 0x100, 0); } } } else if (numberOfCollisionBlocks > 0 && collisionBlock != NULL) { /* Collision with another block */ if (self->blocks[blockIndex].type == BLOCK_TYPE_ICE && collisionBlock->type == BLOCK_TYPE_FIRE) { /* Collision with a Fire block; Remove both blocks */ self->blocks[blockIndex].markedForRemoval = true; collisionBlock->markedForRemoval = true; AudioManager_play("ice_block_melt"); break; } if (self->blocks[blockIndex].properties.pushable.yVelocity > 0) { distance = getBlockPixelCenter(collisionBlock).y - getBlockPixelCenter(&self->blocks[blockIndex]).y - LEVEL_BLOCK_SIZE; if (distance != 0.0) { self->blocks[blockIndex].properties.pushable.position.y += distance; } if (lastTilePosition.x >= 0 && lastTilePosition.y >= 0 && lastTilePosition.x < LEVEL_BLOCK_COUNT_X && lastTilePosition.y < LEVEL_BLOCK_COUNT_Y) { carryBlocks(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_DOWN, distance, 0); } } else { if (getBlockPixelCenter(collisionBlock).y - getBlockPixelCenter(&self->blocks[blockIndex]).y + LEVEL_BLOCK_SIZE != 0) { self->blocks[blockIndex].properties.pushable.position.y += getBlockPixelCenter(collisionBlock).y - getBlockPixelCenter(&self->blocks[blockIndex]).y + LEVEL_BLOCK_SIZE; } } /* See if I need to slide */ if (numberOfCenterCollisionBlocks == 0) { if (getBlockPixelCenter(collisionBlock).x > getBlockPixelCenter(&self->blocks[blockIndex]).x) { /* Sliding left */ distance = ((getBlockPixelCenter(&self->blocks[blockIndex]).x + LEVEL_BLOCK_SIZE) - getBlockPixelCenter(collisionBlock).x); if (distance > 1.0) distance = 1.0; distance = pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_LEFT, distance, true); if (distance > 0.0) { pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_LEFT, distance, false); } } else { /* Sliding right */ distance = ((getBlockPixelCenter(collisionBlock).x + LEVEL_BLOCK_SIZE) - getBlockPixelCenter(&self->blocks[blockIndex]).x); if (distance > 1.0) distance = 1.0; distance = pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_RIGHT, distance, true); if (distance > 0.0) { pushBlock(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_RIGHT, distance, false); } } } self->blocks[blockIndex].properties.pushable.yVelocity = 0; } else if (numberOfCollisionBlocks > 0) { /* Collision with the player */ if (self->blocks[blockIndex].properties.pushable.yVelocity > 0) { self->blocks[blockIndex].properties.pushable.position.y += (((playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)) - getBlockPixelRect(&self->blocks[blockIndex]).top) - LEVEL_BLOCK_SIZE); } else { distance = pushBlock(self, playerModel, frameIndex, NULL, DIRECTION_UP, ((float) self->blocks[blockIndex].properties.pushable.yVelocity / -0x100), true); if (distance > 0.0) { pushBlock(self, playerModel, frameIndex, NULL, DIRECTION_UP, distance, false); self->blocks[blockIndex].properties.pushable.position.y -= distance; } } self->blocks[blockIndex].properties.pushable.yVelocity = 0; } break; case BLOCK_TYPE_FALLING: if (self->blocks[blockIndex].properties.falling.isFalling) { if (getBlockPixelRect(&self->blocks[blockIndex]).top > LEVEL_BLOCK_COUNT_Y * LEVEL_BLOCK_SIZE) { self->blocks[blockIndex].markedForRemoval = true; if (playerModel->standingOn == &self->blocks[blockIndex]) { playerModel->standingOn = NULL; playerModel->vVel = self->blocks[blockIndex].properties.falling.yVelocity / (float) 0x100; } } else { fixed8_8 speedMax; if (self->waterLevel >= 480 - getBlockPixelRect(&self->blocks[blockIndex]).top) { speedMax = PLAYER_MAX_FALL_SPEED * 0x80; } else { speedMax = PLAYER_MAX_FALL_SPEED * 0x100; } self->blocks[blockIndex].properties.falling.yVelocity += 0x3F; if (self->blocks[blockIndex].properties.falling.yVelocity > speedMax) { self->blocks[blockIndex].properties.falling.yVelocity = speedMax; } self->blocks[blockIndex].properties.falling.position.y += self->blocks[blockIndex].properties.falling.yVelocity / (float) 0x100; carryBlocks(self, playerModel, frameIndex, &self->blocks[blockIndex], DIRECTION_DOWN, self->blocks[blockIndex].properties.falling.yVelocity / (float) 0x100, 0); } } else if (self->blocks[blockIndex].properties.falling.isUnsteady && --self->blocks[blockIndex].properties.falling.framesUntilFalling == 0) { self->blocks[blockIndex].properties.falling.isUnsteady = false; self->blocks[blockIndex].properties.falling.isFalling = true; } break; default: break; } } if (playPhaseSound) { AudioManager_play("phase"); } self->waterLevel += self->waterSpeed; if (self->waterLevel > 480.0f) { self->waterLevel = 480.0f; } if ((int) self->waterLevel / LEVEL_BLOCK_SIZE != self->lastBlockWaterLevel) { short blockIndexY, firstBlock, lastBlock; if ((int) (self->waterLevel / LEVEL_BLOCK_SIZE) > self->lastBlockWaterLevel) { firstBlock = ((LEVEL_BLOCK_COUNT_Y - 1) - self->lastBlockWaterLevel); lastBlock = ((LEVEL_BLOCK_COUNT_Y - 1) - (int) (self->waterLevel / LEVEL_BLOCK_SIZE)); } else { firstBlock = ((LEVEL_BLOCK_COUNT_Y - 1) - (int) (self->waterLevel / LEVEL_BLOCK_SIZE)); lastBlock = ((LEVEL_BLOCK_COUNT_Y - 1) - self->lastBlockWaterLevel); } for (blockIndexY = firstBlock; blockIndexY > lastBlock; blockIndexY--) { initWaterBlocks(self, blockIndexY); } self->lastBlockWaterLevel = self->waterLevel / LEVEL_BLOCK_SIZE; } } unsigned int LevelModel_getPowerupByLocation(LevelModel * self, int tileX, int tileY) { unsigned int powerupIndex; for (powerupIndex = 0; powerupIndex < self->powerupCount; powerupIndex++) { if (self->powerups[powerupIndex].tilePosition.x == tileX && self->powerups[powerupIndex].tilePosition.y == tileY) { return powerupIndex; } } return UINT_MAX; } void LevelModel_removePowerupAtIndex(LevelModel * self, unsigned int powerupIndex) { self->powerupCount--; for (; powerupIndex < self->powerupCount; powerupIndex++) { self->powerups[powerupIndex] = self->powerups[powerupIndex + 1]; } } #define BLOCK_REF_COUNT_MAX 16 blockRef * LevelModel_getBlockRefsOccupyingTilePosition(LevelModel * self, unsigned int tileX, unsigned int tileY) { blockRef * blockRefs; unsigned int blockIndex, blockRefCount; rect4f blockRect, tileRect; tileRect.left = tileX * LEVEL_BLOCK_SIZE; tileRect.top = tileY * LEVEL_BLOCK_SIZE; tileRect.right = tileRect.left + LEVEL_BLOCK_SIZE; tileRect.bottom = tileRect.top + LEVEL_BLOCK_SIZE; blockRefs = NULL; blockRefCount = 0; for (blockIndex = 0; blockIndex < LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_COUNT_Y; blockIndex++) { switch (self->blocks[blockIndex].type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: blockRect = getBlockPixelRect(&self->blocks[blockIndex]); if (rectsIntersect(blockRect, tileRect)) { if (blockRefs == NULL) { blockRefs = malloc(sizeof(blockRef) * BLOCK_REF_COUNT_MAX); AutoFreePool_add(blockRefs); } if (blockRefCount > 0) { blockRefs[blockRefCount - 1].nextBlock = blockRefs + blockRefCount; } blockRefs[blockRefCount].tilePosition.x = blockIndex % LEVEL_BLOCK_COUNT_X; blockRefs[blockRefCount].tilePosition.y = blockIndex / LEVEL_BLOCK_COUNT_X; blockRefs[blockRefCount].nextBlock = NULL; blockRefCount++; if (blockRefCount >= BLOCK_REF_COUNT_MAX) { printf("Warning: blockRefCount >= BLOCK_REF_COUNT_MAX!\n"); return blockRefs; } } break; default: break; } } return blockRefs; } unsigned int getPowerupPointValue(enum LevelPowerupType type) { switch (type) { case POWERUP_TYPE_KEY: return 10; case POWERUP_TYPE_HEART: return 50; case POWERUP_TYPE_GEM: return 200; } return 0; }