// Copyright (c) 2014 Alex Diener. All rights reserved. #include "watertowerclassic/AudioManager.h" #include "watertowerclassic/PhysicsFunctions.h" #include #include Vector2i touchedBlocks[50]; unsigned int numberOfTouchedBlocks = 0; bool isBlockSolid(struct LevelBlock * block, float waterLevel, unsigned int frameIndex) { switch (block->type) { case BLOCK_TYPE_EMPTY: case BLOCK_TYPE_LADDER: case BLOCK_TYPE_LEVER: case BLOCK_TYPE_TELEPORT: case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: return false; case BLOCK_TYPE_STONE: case BLOCK_TYPE_CRUMBLE: case BLOCK_TYPE_DEATH: case BLOCK_TYPE_LOCK: case BLOCK_TYPE_TRAMPOLINE: case BLOCK_TYPE_FIRE: case BLOCK_TYPE_SAND: case BLOCK_TYPE_STICKY: return true; case BLOCK_TYPE_DRY_SOLID: case BLOCK_TYPE_WET_SOLID: return isSpongeBlockSolid(block, waterLevel); case BLOCK_TYPE_PHASING: return isPhasingBlockSolid(block, frameIndex); case BLOCK_TYPE_SWITCHABLE: return block->properties.switchable.on; } return false; } bool isSpongeBlockSolid(struct LevelBlock * block, float waterLevel) { bool immersed = waterLevel >= (LEVEL_BLOCK_COUNT_Y - block->tilePosition.y) * LEVEL_BLOCK_SIZE; return block->type == BLOCK_TYPE_WET_SOLID ? immersed : !immersed; } bool isPhasingBlockSolid(struct LevelBlock * block, unsigned int frameIndex) { if (block->properties.phasing.initialDelay * 60 >= frameIndex) { return block->properties.phasing.initiallyOn; } return !((frameIndex - block->properties.phasing.initialDelay * 60) % ((block->properties.phasing.onPeriod + block->properties.phasing.offPeriod) * 60) > block->properties.phasing.onPeriod * 60); } Vector2f getBlockPixelCenter(struct LevelBlock * block) { switch (block->type) { case BLOCK_TYPE_MOVING: return block->properties.moving.position; case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: return block->properties.pushable.position; case BLOCK_TYPE_FALLING: return block->properties.falling.position; default: return VECTOR2f(block->tilePosition.x * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE * 0.5f, block->tilePosition.y * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE * 0.5f); } } rect4f getBlockPixelRect(struct LevelBlock * block) { Vector2f center = getBlockPixelCenter(block); return RECT4f(center.x - LEVEL_BLOCK_SIZE * 0.5f, center.x + LEVEL_BLOCK_SIZE * 0.5f, center.y - LEVEL_BLOCK_SIZE * 0.5f, center.y + LEVEL_BLOCK_SIZE * 0.5f); } Vector2f getPowerupPixelCenter(struct LevelPowerup * powerup) { return VECTOR2f(powerup->tilePosition.x * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE * 0.5f, powerup->tilePosition.y * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE * 0.5f); } rect4f getPowerupPixelRect(struct LevelPowerup * powerup) { Vector2f center = getPowerupPixelCenter(powerup); return RECT4f(center.x - LEVEL_BLOCK_SIZE * 0.5f, center.x + LEVEL_BLOCK_SIZE * 0.5f, center.y - LEVEL_BLOCK_SIZE * 0.5f, center.y + LEVEL_BLOCK_SIZE * 0.5f); } float pushBlock(LevelModel * levelModel, PlayerModel * playerModel, unsigned int frameIndex, struct LevelBlock * block, direction4 direction, float amount, bool peek) { struct LevelBlock * collisionBlocks[3]; short numberOfCollisionBlocks; bool canMove = true, hitSomething = false; float newAmount, newAmount2 = 0; newAmount = amount; if (block == NULL) { /* Pushing the player */ numberOfCollisionBlocks = detectCollision(levelModel, playerModel, frameIndex, NULL, direction, amount, collisionBlocks, 3, false, false); if (numberOfCollisionBlocks == -1) { canMove = false; } while (numberOfCollisionBlocks > 0) { numberOfCollisionBlocks--; touchedBlocks[numberOfTouchedBlocks].x = collisionBlocks[numberOfCollisionBlocks]->tilePosition.x; touchedBlocks[numberOfTouchedBlocks].y = collisionBlocks[numberOfCollisionBlocks]->tilePosition.y; numberOfTouchedBlocks++; switch (direction) { case DIRECTION_LEFT: newAmount2 = playerModel->pixelLocation.x - PLAYER_WIDTH / 2 - getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).right; break; case DIRECTION_UP: newAmount2 = 0.0f; break; case DIRECTION_RIGHT: newAmount2 = getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).left - (playerModel->pixelLocation.x + PLAYER_WIDTH / 2); break; case DIRECTION_DOWN: newAmount2 = getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).top - (playerModel->pixelLocation.y + LEVEL_BLOCK_SIZE / 2); break; } newAmount = pushBlock(levelModel, playerModel, frameIndex, collisionBlocks[numberOfCollisionBlocks], direction, (newAmount - newAmount2), peek); newAmount += newAmount2; if (newAmount <= 0.0f) { canMove = false; } } if (!canMove) { return 0.0f; } if (!peek) { switch (direction) { case DIRECTION_LEFT: playerModel->pixelLocation.x -= newAmount; break; case DIRECTION_UP: playerModel->pixelLocation.y -= newAmount; break; case DIRECTION_RIGHT: playerModel->pixelLocation.x += newAmount; break; case DIRECTION_DOWN: playerModel->pixelLocation.y += newAmount; break; } } return newAmount; } switch (block->type) { case BLOCK_TYPE_MOVING: numberOfCollisionBlocks = detectCollision(levelModel, playerModel, frameIndex, block, direction, amount, collisionBlocks, 3, false, false); if (numberOfCollisionBlocks == -1) hitSomething = 1; while (numberOfCollisionBlocks > 0) { numberOfCollisionBlocks--; switch (direction) { case DIRECTION_LEFT: if (collisionBlocks[numberOfCollisionBlocks] == NULL) { newAmount2 = (getBlockPixelRect(block).left - (playerModel->pixelLocation.x + (PLAYER_WIDTH / 2))); } else { newAmount2 = (getBlockPixelRect(block).left - (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).left + LEVEL_BLOCK_SIZE)); } break; case DIRECTION_UP: if (collisionBlocks[numberOfCollisionBlocks] == NULL) { newAmount2 = (getBlockPixelRect(block).top - (playerModel->pixelLocation.y + (LEVEL_BLOCK_SIZE / 2))); } else { newAmount2 = (getBlockPixelRect(block).top - (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).top + LEVEL_BLOCK_SIZE)); } break; case DIRECTION_RIGHT: if (collisionBlocks[numberOfCollisionBlocks] == NULL) { newAmount2 = ((playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)) - (getBlockPixelRect(block).left + LEVEL_BLOCK_SIZE)); } else { newAmount2 = (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).left - (getBlockPixelRect(block).left + LEVEL_BLOCK_SIZE)); } break; case DIRECTION_DOWN: if (collisionBlocks[numberOfCollisionBlocks] == NULL) { newAmount2 = ((playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)) - (getBlockPixelRect(block).top + LEVEL_BLOCK_SIZE)); } else { newAmount2 = (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).top - (getBlockPixelRect(block).top + LEVEL_BLOCK_SIZE)); } break; } /*if (newAmount2 < 0.0) newAmount2 = 0.0;*/ newAmount = pushBlock(levelModel, playerModel, frameIndex, collisionBlocks[numberOfCollisionBlocks], direction, (newAmount - newAmount2), peek); newAmount += newAmount2; /* If colliding with player or moving up, don't turn around */ /*if (direction == DIRECTION_DOWN) hitSomething = 1; else */if (newAmount < amount || newAmount <= 0.0) hitSomething = 1; else if (direction != DIRECTION_UP && collisionBlocks[numberOfCollisionBlocks] != NULL) hitSomething = 1; } if (newAmount <= 0.0) canMove = 0; if (!canMove) return 0.0; switch (direction) { case DIRECTION_LEFT: if (block->properties.moving.direction != DIRECTION_LEFT && block->properties.moving.direction != DIRECTION_RIGHT) return 0.0; /* Not movable on X plane */ if (!peek && hitSomething) block->properties.moving.direction = DIRECTION_RIGHT; else block->properties.moving.direction = DIRECTION_LEFT; if ((getBlockPixelRect(block).left - newAmount) < 0) return 0.0; if (!peek) { carryBlocks(levelModel, playerModel, frameIndex, block, block->properties.moving.direction, newAmount, 4); block->properties.moving.position.x -= newAmount; } break; case DIRECTION_UP: if (block->properties.moving.direction != DIRECTION_UP && block->properties.moving.direction != DIRECTION_DOWN) return 0.0; /* Not movable on Y plane */ if (!peek && hitSomething) block->properties.moving.direction = DIRECTION_DOWN; else block->properties.moving.direction = DIRECTION_UP; if ((getBlockPixelRect(block).top - newAmount) < 0) return 0.0; if (!peek) { block->properties.moving.position.y -= newAmount; } break; case DIRECTION_RIGHT: if (block->properties.moving.direction != DIRECTION_LEFT && block->properties.moving.direction != DIRECTION_RIGHT) return 0.0; /* Not movable on X plane */ if (!peek && hitSomething) block->properties.moving.direction = DIRECTION_LEFT; else block->properties.moving.direction = DIRECTION_RIGHT; if ((getBlockPixelRect(block).left + newAmount) > ((LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_SIZE) - LEVEL_BLOCK_SIZE)) return 0.0; if (!peek) { carryBlocks(levelModel, playerModel, frameIndex, block, block->properties.moving.direction, newAmount, 4); block->properties.moving.position.x += newAmount; } break; case DIRECTION_DOWN: if (block->properties.moving.direction != DIRECTION_UP && block->properties.moving.direction != DIRECTION_DOWN) return 0.0; /* Not movable on Y plane */ if (!peek && hitSomething) block->properties.moving.direction = DIRECTION_UP; else block->properties.moving.direction = DIRECTION_DOWN; if ((getBlockPixelRect(block).top + newAmount) > ((LEVEL_BLOCK_COUNT_Y * LEVEL_BLOCK_SIZE) - LEVEL_BLOCK_SIZE)) return 0.0; if (!peek) { block->properties.moving.position.y += newAmount; carryBlocks(levelModel, playerModel, frameIndex, block, block->properties.moving.direction, newAmount, 4); } break; } return newAmount; break; case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: numberOfCollisionBlocks = detectCollision(levelModel, playerModel, frameIndex, block, direction, amount, collisionBlocks, 3, false, false); if (numberOfCollisionBlocks == -1) { switch (direction) { case DIRECTION_LEFT: newAmount = getBlockPixelRect(block).left; break; case DIRECTION_UP: newAmount = getBlockPixelRect(block).top; break; case DIRECTION_RIGHT: newAmount = ((LEVEL_BLOCK_SIZE * LEVEL_BLOCK_COUNT_X) - (getBlockPixelRect(block).left + LEVEL_BLOCK_SIZE)); break; case DIRECTION_DOWN: newAmount = ((LEVEL_BLOCK_SIZE * LEVEL_BLOCK_COUNT_Y) - (getBlockPixelRect(block).top + LEVEL_BLOCK_SIZE)); break; } if (newAmount <= 0.0) canMove = 0; } while (numberOfCollisionBlocks > 0) { numberOfCollisionBlocks--; switch (direction) { case DIRECTION_LEFT: if (collisionBlocks[numberOfCollisionBlocks] == NULL) newAmount2 = (getBlockPixelRect(block).left - (playerModel->pixelLocation.x + (PLAYER_WIDTH / 2))); else newAmount2 = (getBlockPixelRect(block).left - (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).left + LEVEL_BLOCK_SIZE)); break; case DIRECTION_UP: if (collisionBlocks[numberOfCollisionBlocks] == NULL) newAmount2 = (getBlockPixelRect(block).top - (playerModel->pixelLocation.y + (LEVEL_BLOCK_SIZE / 2))); else newAmount2 = (getBlockPixelRect(block).top - (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).top + LEVEL_BLOCK_SIZE)); break; case DIRECTION_RIGHT: if (collisionBlocks[numberOfCollisionBlocks] == NULL) newAmount2 = ((playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)) - (getBlockPixelRect(block).left + LEVEL_BLOCK_SIZE)); else newAmount2 = (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).left - (getBlockPixelRect(block).left + LEVEL_BLOCK_SIZE)); break; case DIRECTION_DOWN: if (collisionBlocks[numberOfCollisionBlocks] == NULL) newAmount2 = ((playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)) - (getBlockPixelRect(block).top + LEVEL_BLOCK_SIZE)); else newAmount2 = (getBlockPixelRect(collisionBlocks[numberOfCollisionBlocks]).top - (getBlockPixelRect(block).top + LEVEL_BLOCK_SIZE)); break; } if (collisionBlocks[numberOfCollisionBlocks] != NULL && block->type == BLOCK_TYPE_ICE && collisionBlocks[numberOfCollisionBlocks]->type == BLOCK_TYPE_FIRE) { /* Remove both blocks */ block->markedForRemoval = true; collisionBlocks[numberOfCollisionBlocks]->markedForRemoval = true; AudioManager_play("ice_block_melt"); break; } else { if (direction == DIRECTION_DOWN) { /* Don't push down */ newAmount = newAmount2; } else { newAmount = pushBlock(levelModel, playerModel, frameIndex, collisionBlocks[numberOfCollisionBlocks], direction, (newAmount - newAmount2), peek); newAmount += newAmount2; } if (newAmount <= 0.0) canMove = 0; } } if (block->type == BLOCK_TYPE_EMPTY) break; /* If ice block melted */ if (canMove) { if (!peek) { switch (direction) { case DIRECTION_LEFT: carryBlocks(levelModel, playerModel, frameIndex, block, DIRECTION_LEFT, newAmount, 4); block->properties.pushable.position.x -= newAmount; break; case DIRECTION_UP: block->properties.pushable.position.y -= newAmount; break; case DIRECTION_RIGHT: carryBlocks(levelModel, playerModel, frameIndex, block, DIRECTION_RIGHT, newAmount, 4); block->properties.pushable.position.x += newAmount; break; case DIRECTION_DOWN: block->properties.pushable.position.y += newAmount; carryBlocks(levelModel, playerModel, frameIndex, block, DIRECTION_DOWN, newAmount, 4); break; } } return newAmount; } else { return 0.0; } break; default: break; } return 0.0f; } short detectCollision(LevelModel * levelModel, PlayerModel * playerModel, unsigned int frameIndex, struct LevelBlock * srcBlock, char direction, float amount, struct LevelBlock ** blockList, short blockListLength, bool useSrcCenter, bool useDestCenter) { struct LevelBlock * residentBlock; blockRef * specialBlockRef; bool overlapLeft, overlapUp, overlapRight, overlapDown, found; Vector2f oldPosition, newPosition; short blockIndexX, blockIndexY, leftBlock, rightBlock, topBlock, bottomBlock; rect4f myRect, blockRect, playerRect; short currentBlock = 0, wBlock; if (srcBlock == NULL) { oldPosition = playerModel->pixelLocation; if (direction == DIRECTION_UP) { newPosition.x = (playerModel->pixelLocation.x - playerModel->hVel); /* Hack */ } else { newPosition.x = (playerModel->pixelLocation.x + (direction == DIRECTION_LEFT || direction == DIRECTION_RIGHT ? (direction == DIRECTION_LEFT ? (amount * -1) : amount) : 0)); } newPosition.y = (playerModel->pixelLocation.y + (direction == DIRECTION_UP || direction == DIRECTION_DOWN ? (direction == DIRECTION_UP ? (amount * -1) : amount) : 0)); myRect.left = (playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); myRect.top = (playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); myRect.right = (myRect.left + PLAYER_WIDTH); myRect.bottom = (myRect.top + LEVEL_BLOCK_SIZE); if (myRect.left < 0 || myRect.right > (LEVEL_BLOCK_SIZE * LEVEL_BLOCK_COUNT_X) || myRect.top < 0 || myRect.bottom > (LEVEL_BLOCK_SIZE * LEVEL_BLOCK_COUNT_Y)) return -1; /* Off the screen */ overlapLeft = (playerModel->tileLocation.x > 0 && (newPosition.x - (playerModel->tileLocation.x * LEVEL_BLOCK_SIZE)) < (PLAYER_WIDTH / 2)); overlapRight = (playerModel->tileLocation.x < (LEVEL_BLOCK_COUNT_X - 1) && (newPosition.x - (playerModel->tileLocation.x * LEVEL_BLOCK_SIZE)) > (LEVEL_BLOCK_SIZE - (PLAYER_WIDTH / 2))); overlapDown = (playerModel->tileLocation.y < (LEVEL_BLOCK_COUNT_Y - 1) && (newPosition.y - (playerModel->tileLocation.y * LEVEL_BLOCK_SIZE)) > (LEVEL_BLOCK_SIZE / 2)); overlapUp = (playerModel->tileLocation.y > 0 && (newPosition.y - (playerModel->tileLocation.y * LEVEL_BLOCK_SIZE)) < (LEVEL_BLOCK_SIZE / 2)); if ((unsigned int) playerModel->tileLocation.x >= LEVEL_BLOCK_COUNT_X || (unsigned int) playerModel->tileLocation.y >= LEVEL_BLOCK_COUNT_Y) { return 0; } residentBlock = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * playerModel->tileLocation.y) + playerModel->tileLocation.x)]; } else { unsigned int residentBlockIndexX, residentBlockIndexY; oldPosition.x = getBlockPixelRect(srcBlock).left; oldPosition.y = getBlockPixelRect(srcBlock).top; newPosition.x = (oldPosition.x + (direction == DIRECTION_LEFT || direction == DIRECTION_RIGHT ? (direction == DIRECTION_LEFT ? (amount * -1) : amount) : 0)); newPosition.y = (oldPosition.y + (direction == DIRECTION_UP || direction == DIRECTION_DOWN ? (direction == DIRECTION_UP ? (amount * -1) : amount) : 0)); if (useSrcCenter) { myRect.left = myRect.right = newPosition.x + LEVEL_BLOCK_SIZE / 2; myRect.left -= 0.1; myRect.right += 0.1; } else { myRect.left = newPosition.x; myRect.right = myRect.left + LEVEL_BLOCK_SIZE; } myRect.top = newPosition.y; myRect.bottom = myRect.top + LEVEL_BLOCK_SIZE; if (myRect.left < 0 || myRect.right > (LEVEL_BLOCK_SIZE * LEVEL_BLOCK_COUNT_X)) return -1; /* Off the edge of the screen */ playerRect.left = (playerModel->pixelLocation.x - (PLAYER_WIDTH / 2)); playerRect.top = (playerModel->pixelLocation.y - (LEVEL_BLOCK_SIZE / 2)); playerRect.right = (playerRect.left + PLAYER_WIDTH); playerRect.bottom = (playerRect.top + LEVEL_BLOCK_SIZE); if (rectTouch(&myRect, &playerRect, direction)) { if ((direction != DIRECTION_LEFT && direction != DIRECTION_RIGHT) || (myRect.top < (playerRect.top + ((playerRect.bottom - playerRect.top) / 2)))) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == NULL) found = 1; } if (!found) { blockList[currentBlock] = NULL; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } overlapLeft = ((((short) (newPosition.x * 4)) % 64) >= 32); overlapRight = ((((short) (newPosition.x * 4)) % 64) <= 32); overlapUp = ((((short) (newPosition.y * 4)) % 64) >= 32); overlapDown = ((((short) (newPosition.y * 4)) % 64) <= 32); if ((newPosition.x + LEVEL_BLOCK_SIZE / 2) / LEVEL_BLOCK_SIZE <= 0) { overlapLeft = false; } if ((newPosition.x + LEVEL_BLOCK_SIZE / 2) / LEVEL_BLOCK_SIZE >= (LEVEL_BLOCK_COUNT_X - 1)) { overlapRight = false; } if ((newPosition.y + LEVEL_BLOCK_SIZE / 2) / LEVEL_BLOCK_SIZE <= 0) { overlapUp = false; } if ((newPosition.y + LEVEL_BLOCK_SIZE / 2) / LEVEL_BLOCK_SIZE >= (LEVEL_BLOCK_COUNT_Y - 1)) { overlapDown = false; } residentBlockIndexX = (newPosition.x + LEVEL_BLOCK_SIZE / 2) / LEVEL_BLOCK_SIZE; residentBlockIndexY = (newPosition.y + LEVEL_BLOCK_SIZE / 2) / LEVEL_BLOCK_SIZE; if (residentBlockIndexX >= LEVEL_BLOCK_COUNT_X || residentBlockIndexY >= LEVEL_BLOCK_COUNT_Y) { return 0; } residentBlock = &levelModel->blocks[LEVEL_BLOCK_COUNT_X * residentBlockIndexY + residentBlockIndexX]; } switch (direction) { case DIRECTION_LEFT: if (!overlapLeft) break; if (isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x - 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x - 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x - 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x - 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x - 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapUp && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapDown && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } break; case DIRECTION_RIGHT: if (!overlapRight) break; if (isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x + 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x + 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x + 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x + 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * residentBlock->tilePosition.y) + (residentBlock->tilePosition.x + 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapUp && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapDown && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } break; case DIRECTION_UP: if (!overlapUp) break; if (isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + residentBlock->tilePosition.x)], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + residentBlock->tilePosition.x)]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + residentBlock->tilePosition.x)]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + residentBlock->tilePosition.x)]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + residentBlock->tilePosition.x)]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapLeft && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x - 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapRight && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y - 1)) + (residentBlock->tilePosition.x + 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } break; case DIRECTION_DOWN: if (!overlapDown) break; if (isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + residentBlock->tilePosition.x)], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + residentBlock->tilePosition.x)]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + residentBlock->tilePosition.x)]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + residentBlock->tilePosition.x)]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + residentBlock->tilePosition.x)]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapLeft && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x - 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } else if (overlapRight && isBlockSolid(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))], levelModel->waterLevel, frameIndex) && srcBlock != &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]) { blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]); if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * (residentBlock->tilePosition.y + 1)) + (residentBlock->tilePosition.x + 1))]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } break; } leftBlock = (direction == DIRECTION_RIGHT ? (overlapRight ? (residentBlock->tilePosition.x + 1) : residentBlock->tilePosition.x) : (residentBlock->tilePosition.x - 1)); rightBlock = (direction == DIRECTION_LEFT ? (overlapLeft ? (residentBlock->tilePosition.x - 1) : residentBlock->tilePosition.x) : (residentBlock->tilePosition.x + 1)); topBlock = (direction == DIRECTION_DOWN ? (overlapDown ? (residentBlock->tilePosition.y + 1) : residentBlock->tilePosition.y) : (residentBlock->tilePosition.y - 1)); bottomBlock = (direction == DIRECTION_UP ? (overlapUp ? (residentBlock->tilePosition.y - 1) : residentBlock->tilePosition.y) : (residentBlock->tilePosition.y + 1)); for (blockIndexY = topBlock; blockIndexY <= bottomBlock; blockIndexY++) { if (blockIndexY < 0 || blockIndexY >= LEVEL_BLOCK_COUNT_Y) continue; for (blockIndexX = leftBlock; blockIndexX <= rightBlock; blockIndexX++) { if (blockIndexX < 0 || blockIndexX >= LEVEL_BLOCK_COUNT_X) continue; for (specialBlockRef = LevelModel_getBlockRefsOccupyingTilePosition(levelModel, blockIndexX, blockIndexY); specialBlockRef != NULL; specialBlockRef = specialBlockRef->nextBlock) { if (srcBlock != NULL && specialBlockRef->tilePosition.x == srcBlock->tilePosition.x && specialBlockRef->tilePosition.y == srcBlock->tilePosition.y) continue; /* Don't collide with self */ blockRect = getBlockPixelRect(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * specialBlockRef->tilePosition.y) + specialBlockRef->tilePosition.x)]); if (useDestCenter && (levelModel->blocks[((LEVEL_BLOCK_COUNT_X * specialBlockRef->tilePosition.y) + specialBlockRef->tilePosition.x)].type == BLOCK_TYPE_PUSHABLE || levelModel->blocks[((LEVEL_BLOCK_COUNT_X * specialBlockRef->tilePosition.y) + specialBlockRef->tilePosition.x)].type == BLOCK_TYPE_FLOATING || levelModel->blocks[((LEVEL_BLOCK_COUNT_X * specialBlockRef->tilePosition.y) + specialBlockRef->tilePosition.x)].type == BLOCK_TYPE_ICE)) { blockRect.left = blockRect.right = getBlockPixelCenter(&levelModel->blocks[((LEVEL_BLOCK_COUNT_X * specialBlockRef->tilePosition.y) + specialBlockRef->tilePosition.x)]).x; } if ((srcBlock == NULL && rectTouch(&myRect, &blockRect, direction)) || rectCollision(&myRect, &blockRect, direction)) { found = 0; for (wBlock = 0; wBlock < currentBlock; wBlock++) { if (blockList[wBlock] == &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * specialBlockRef->tilePosition.y) + specialBlockRef->tilePosition.x)]) found = 1; } if (!found) { blockList[currentBlock] = &levelModel->blocks[((LEVEL_BLOCK_COUNT_X * specialBlockRef->tilePosition.y) + specialBlockRef->tilePosition.x)]; currentBlock++; if (currentBlock >= blockListLength) return currentBlock; } } } } } return currentBlock; } void carryBlocks(LevelModel * levelModel, PlayerModel * playerModel, unsigned int frameIndex, struct LevelBlock * srcBlock, char direction, float amount, char modifier) { struct LevelBlock * collisionBlocks[3]; short numberOfCollisionBlocks; float newAmount; newAmount = amount; numberOfCollisionBlocks = detectCollision(levelModel, playerModel, frameIndex, srcBlock, DIRECTION_UP, (direction == DIRECTION_DOWN ? (amount + 0.01) : 0.01), collisionBlocks, 3, false, true); /* Anybody sitting directly on top of me? */ while (numberOfCollisionBlocks > 0) { numberOfCollisionBlocks--; if (collisionBlocks[numberOfCollisionBlocks] == NULL) { /* The player */ if (direction == DIRECTION_LEFT) playerModel->pixelLocation.x -= amount; else if (direction == DIRECTION_RIGHT) playerModel->pixelLocation.x += amount; else if (direction == DIRECTION_UP) playerModel->pixelLocation.y -= amount; else playerModel->pixelLocation.y += amount; } else if (collisionBlocks[numberOfCollisionBlocks] != srcBlock) { if (collisionBlocks[numberOfCollisionBlocks]->alreadyCarried & (1 << (direction + modifier))) continue; /* Hack hack hack hack */ switch (collisionBlocks[numberOfCollisionBlocks]->type) { case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: newAmount = pushBlock(levelModel, playerModel, frameIndex, collisionBlocks[numberOfCollisionBlocks], direction, newAmount, true); if (newAmount > 0.0) { pushBlock(levelModel, playerModel, frameIndex, collisionBlocks[numberOfCollisionBlocks], direction, newAmount, false); } collisionBlocks[numberOfCollisionBlocks]->alreadyCarried |= (1 << direction); /* Hack hack hack hack */ break; default: break; } } } } bool rectCollision(rect4f * rect1, rect4f * rect2, char direction) { switch (direction) { case DIRECTION_LEFT: if (rect1->left < rect2->right && rect1->left > (rect2->right - (LEVEL_BLOCK_SIZE / 2)) && rect1->top < rect2->bottom && rect1->bottom > rect2->top) return 1; break; case DIRECTION_RIGHT: if (rect1->right > rect2->left && rect1->right < (rect2->left + (LEVEL_BLOCK_SIZE / 2)) && rect1->top < rect2->bottom && rect1->bottom > rect2->top) return 1; break; case DIRECTION_UP: if (rect1->top < rect2->bottom && rect1->top > (rect2->bottom - (LEVEL_BLOCK_SIZE / 2)) && rect1->left < rect2->right && rect1->right > rect2->left) return 1; break; case DIRECTION_DOWN: if (rect1->bottom > rect2->top && rect1->bottom < (rect2->top + (LEVEL_BLOCK_SIZE / 2)) && rect1->left < rect2->right && rect1->right > rect2->left) return 1; break; } return 0; } bool rectTouch(rect4f * rect1, rect4f * rect2, char direction) { switch (direction) { case DIRECTION_LEFT: if (rect1->left <= rect2->right && rect1->left > (rect2->right - (LEVEL_BLOCK_SIZE / 2)) && rect1->top < rect2->bottom && rect1->bottom > rect2->top) return 1; break; case DIRECTION_RIGHT: if (rect1->right >= rect2->left && rect1->right < (rect2->left + (LEVEL_BLOCK_SIZE / 2)) && rect1->top < rect2->bottom && rect1->bottom > rect2->top) return 1; break; case DIRECTION_UP: if (rect1->top <= rect2->bottom && rect1->top > (rect2->bottom - (LEVEL_BLOCK_SIZE / 2)) && rect1->left < rect2->right && rect1->right > rect2->left) return 1; break; case DIRECTION_DOWN: if (rect1->bottom >= rect2->top && rect1->bottom < (rect2->top + (LEVEL_BLOCK_SIZE / 2)) && rect1->left < rect2->right && rect1->right > rect2->left) return 1; break; } return 0; } void leaveBlock(struct LevelBlock * block) { if (block == NULL) return; switch (block->type) { case BLOCK_TYPE_CRUMBLE: block->markedForRemoval = true; AudioManager_play("crumble"); break; case BLOCK_TYPE_FALLING: block->properties.falling.isUnsteady = false; break; default: break; } } void enterBlock(PlayerModel * playerModel, struct LevelBlock * block) { if (block == NULL) return; switch (block->type) { case BLOCK_TYPE_TRAMPOLINE: AudioManager_play("trampoline"); playerModel->vVel = PLAYER_JUMP_VELOCITY * 1.5f; break; case BLOCK_TYPE_FALLING: block->properties.falling.isUnsteady = true; block->properties.falling.framesUntilFalling = FALLING_BLOCK_UNSTEADY_FRAME_COUNT_MAX; break; default: break; } } enum LevelBlockType touchBlock(PlayerModel * playerModel, struct LevelBlock * block) { switch (block->type) { case BLOCK_TYPE_DEATH: case BLOCK_TYPE_FIRE: return BLOCK_TYPE_DEATH; case BLOCK_TYPE_LOCK: if (!block->markedForRemoval && playerModel->numberOfKeys > 0) { AudioManager_play("open_door"); playerModel->numberOfKeys--; block->markedForRemoval = true; } return BLOCK_TYPE_LOCK; default: break; } return BLOCK_TYPE_EMPTY; }