#include #include #include #include "system/vga.h" #include "game.h" #include "powerup.h" #include "movement.h" #include "const.h" void setupBullet( struct BulletPosition *bullet, int x, int y, int angle, int velocity ) { signed int doubleVelocityX, doubleVelocityY; doubleVelocityX = sin(angle * DEG2RAD) * (velocity * 2); doubleVelocityY = -cos(angle * DEG2RAD) * (velocity * 2); bullet->isActive = 1; bullet->willBeInactive = 0; bullet->x = x; bullet->y = y; bullet->oldX = x; bullet->oldY = y; bullet->wallCooldown = 1; bullet->angle = angle; bullet->velocityXSteps[0] = doubleVelocityX / velocity; bullet->velocityXSteps[1] = doubleVelocityX - bullet->velocityXSteps[0]; bullet->velocityYSteps[0] = doubleVelocityY / velocity; bullet->velocityYSteps[1] = doubleVelocityY - bullet->velocityYSteps[0]; bullet->velocityStep = 0; } int maybeFireShotCount( struct BulletPosition rabbitBulletPosition[], int availableBullets[], int count ) { int i, remaining = count; for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) { if (rabbitBulletPosition[i].isActive == 0) { availableBullets[count - remaining] = i; remaining--; if (remaining == 0) break; } } return remaining == 0; } int attemptToFireRabbitBullet( struct RabbitPosition *rabbitPosition, struct RabbitWeaponry *rabbitWeaponry, struct BulletPosition rabbitBulletPosition[] ) { int okToFire = 0, i, mouseAngle; int availableBullets[4]; float beamOffsetX, beamOffsetY; if (rabbitWeaponry->cooldown > 0) return 0; mouseAngle = rabbitPosition->mouseAngle; rabbitWeaponry->cooldown = RABBIT_BULLET_COOLDOWN; if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SINGLE_SHOT_GUN) { if (!maybeFireShotCount( rabbitBulletPosition, availableBullets, 1 )) return 0; setupBullet( &rabbitBulletPosition[availableBullets[0]], rabbitPosition->rabbitPosition[0], rabbitPosition->rabbitPosition[1], mouseAngle, RABBIT_BULLET_VELOCITY ); } else if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SPREAD_SHOT_GUN) { // make sure three bullets are available if (!maybeFireShotCount( rabbitBulletPosition, availableBullets, 3 )) return 0; // if so, fire away for (i = 0; i < 3; ++i) { setupBullet( &rabbitBulletPosition[availableBullets[i]], rabbitPosition->rabbitPosition[0], rabbitPosition->rabbitPosition[1], mouseAngle + (i - 1) * RABBIT_BULLET_SHOTGUN_SPREAD, RABBIT_BULLET_VELOCITY ); } } else if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_BEAM_SHOT_GUN) { // make sure three bullets are available if (!maybeFireShotCount( rabbitBulletPosition, availableBullets, 4 )) return 0; // if so, fire away for (i = 0; i < 4; ++i) { beamOffsetX = sin(mouseAngle * DEG2RAD) * i * 2; beamOffsetY = -cos(mouseAngle * DEG2RAD) * i * 2; setupBullet( &rabbitBulletPosition[availableBullets[i]], rabbitPosition->rabbitPosition[0] + beamOffsetX, rabbitPosition->rabbitPosition[1] + beamOffsetY, mouseAngle, RABBIT_BULLET_VELOCITY ); } } return 1; } void limitBulletToArena(struct BulletPosition *bullet) { if (bullet->x < MOUSE_LIMIT_LEFT) bullet->willBeInactive = 1; if (bullet->x >= MOUSE_LIMIT_RIGHT) bullet->willBeInactive = 1; if (bullet->y < MOUSE_LIMIT_TOP) bullet->willBeInactive = 1; if (bullet->y >= MOUSE_LIMIT_BOTTOM) bullet->willBeInactive = 1; } void moveBullet(struct BulletPosition *bullet) { bullet->oldX = bullet->x; bullet->oldY = bullet->y; bullet->x += bullet->velocityXSteps[bullet->velocityStep]; bullet->y += bullet->velocityYSteps[bullet->velocityStep]; bullet->velocityStep = 1 - bullet->velocityStep; } void advanceRabbitBullets( struct BulletPosition rabbitBulletPosition[], struct RabbitWeaponry *rabbitWeaponry ) { int i; for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) { if (!rabbitBulletPosition[i].isActive) continue; moveBullet(&rabbitBulletPosition[i]); if (rabbitBulletPosition[i].wallCooldown == 0) { limitBulletToArena(&rabbitBulletPosition[i]); } else { rabbitBulletPosition[i].wallCooldown--; } } if (rabbitWeaponry->cooldown > 0) rabbitWeaponry->cooldown--; } void advanceEnemyBullets( struct BulletPosition enemyBulletPosition[] ) { int i; for (i = 0; i < ENEMY_BULLET_LIMIT; ++i) { if (!enemyBulletPosition[i].isActive) continue; moveBullet(&enemyBulletPosition[i]); limitBulletToArena(&enemyBulletPosition[i]); } } void attemptToFireEnemyBullets( struct EnemyPosition enemyPosition[], struct BulletPosition enemyBulletPosition[], struct RabbitPosition *rabbitPosition, int difficulty ) { int availableBullets[ENEMY_BULLET_LIMIT]; int i, availableBullet, maxAvailableBulletIndex = 0; float distanceX, distanceY, angle, doubleVelocityX, doubleVelocityY; for (i = 0; i < ENEMY_BULLET_LIMIT; ++i) { if (!enemyBulletPosition[i].isActive) { availableBullets[maxAvailableBulletIndex++] = i; } } for (i = 0; i < ENEMY_MAX_COUNT; ++i) { if (!enemyPosition[i].isActive) continue; enemyPosition[i].enemyFireDelayStep--; if (enemyPosition[i].enemyFireDelayStep > 0) continue; distanceX = rabbitPosition->rabbitPosition[0] - enemyPosition[i].enemyPosition[0]; distanceY = rabbitPosition->rabbitPosition[1] - enemyPosition[i].enemyPosition[1]; angle = atan2(distanceY, distanceX) * RAD2DEG + 90; if (angle < 0) angle += 360; availableBullet = availableBullets[--maxAvailableBulletIndex]; setupBullet( &enemyBulletPosition[availableBullet], enemyPosition[i].enemyPosition[0], enemyPosition[i].enemyPosition[1], angle, ENEMY_BULLET_VELOCITY ); enemyPosition[i].enemyFireDelayStep = ENEMY_FIRE_MIN_DELAY - difficulty + rand() % ENEMY_FIRE_VARIABLE_DELAY; if (maxAvailableBulletIndex < 0) break; } } struct CollisionDetection { short int sourceSX, sourceSY, sourceEX, sourceEY; short int targetSX, targetSY, targetEX, targetEY; }; struct CollisionDetection collisionDetection; int isCollision() { if (collisionDetection.sourceEY < collisionDetection.targetSY) return 0; if (collisionDetection.sourceEX < collisionDetection.targetSX) return 0; if (collisionDetection.targetEY < collisionDetection.sourceSY) return 0; if (collisionDetection.targetEX < collisionDetection.sourceSX) return 0; return 1; } void populateSourceCollision(struct CompiledSpriteRender *sprite) { getSpriteBounds(sprite, &bounds); collisionDetection.sourceSX = bounds.left; collisionDetection.sourceSY = bounds.top; collisionDetection.sourceEX = bounds.right; collisionDetection.sourceEY = bounds.bottom; } void populateTargetCollision(struct CompiledSpriteRender *sprite) { getSpriteBounds(sprite, &bounds); collisionDetection.targetSX = bounds.left; collisionDetection.targetSY = bounds.top; collisionDetection.targetEX = bounds.right; collisionDetection.targetEY = bounds.bottom; } // We are hardcoding a 2x2 grid on the screen as our collision partitioning // scheme. int rabbitGrid[4], powerupGrid[4]; int rabbitBulletGrid[4][RABBIT_BULLET_LIMIT]; int enemyGrid[4][ENEMY_MAX_COUNT]; int enemyBulletGrid[4][ENEMY_BULLET_LIMIT]; int rabbitBulletGridIndex[4], enemyGridIndex[4], enemyBulletGridIndex[4]; int gridPosition[4]; #define CALCULATE_GRID_POS(x,y) ((x / COLLISION_GRID_SIZE) + (y / COLLISION_GRID_SIZE) * 2) void determineGridPositionsForSprite(struct CompiledSpriteRender *sprite) { int i; getSpriteBounds(sprite, &bounds); for (i = 0; i < 4; ++i) { gridPosition[i] = 0; } gridPosition[CALCULATE_GRID_POS(bounds.left, bounds.top)] = 1; gridPosition[CALCULATE_GRID_POS(bounds.right, bounds.top)] = 1; gridPosition[CALCULATE_GRID_POS(bounds.left, bounds.bottom)] = 1; gridPosition[CALCULATE_GRID_POS(bounds.right, bounds.bottom)] = 1; } void buildCollisionGrids( struct BulletPosition rabbitBulletPosition[], struct BulletPosition enemyBulletPosition[], struct RabbitPosition *rabbitPosition, struct EnemyPosition enemyPosition[], struct PlayerPowerup *playerPowerup ) { int grid, i; struct CompiledSpriteRender *which; for (grid = 0; grid < 4; ++grid) { rabbitBulletGridIndex[grid] = 0; enemyBulletGridIndex[grid] = 0; enemyGridIndex[grid] = 0; } rabbit.x = rabbitPosition->rabbitPosition[0]; rabbit.y = rabbitPosition->rabbitPosition[1]; determineGridPositionsForSprite(&rabbit); for (grid = 0; grid < 4; ++grid) { rabbitGrid[grid] = gridPosition[grid]; } switch (playerPowerup->type) { case POWERUP_TYPE_SHOTGUN: which = &shotgun; break; case POWERUP_TYPE_BEAM_WEAPON: which = &beam; break; } which->x = playerPowerup->x; which->y = playerPowerup->y; determineGridPositionsForSprite(which); for (grid = 0; grid < 4; ++grid) { powerupGrid[grid] = gridPosition[grid]; } for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) { if (!rabbitBulletPosition[i].isActive) continue; if (rabbitBulletPosition[i].willBeInactive) continue; bullet.x = rabbitBulletPosition[i].x; bullet.y = rabbitBulletPosition[i].y; determineGridPositionsForSprite(&bullet); for (grid = 0; grid < 4; ++grid) { if (gridPosition[grid]) { rabbitBulletGrid[grid][rabbitBulletGridIndex[grid]++] = i; } } } for (i = 0; i < ENEMY_BULLET_LIMIT; ++i) { if (!enemyBulletPosition[i].isActive) continue; if (enemyBulletPosition[i].willBeInactive) continue; enemyBullet.x = enemyBulletPosition[i].x; enemyBullet.y = enemyBulletPosition[i].y; determineGridPositionsForSprite(&enemyBullet); for (grid = 0; grid < 4; ++grid) { if (gridPosition[grid]) { enemyBulletGrid[grid][enemyBulletGridIndex[grid]++] = i; } } } for (i = 0; i < ENEMY_MAX_COUNT; ++i) { if (!enemyPosition[i].isActive) continue; if (enemyPosition[i].willBeInactive) continue; enemy.x = enemyPosition[i].enemyPosition[0]; enemy.y = enemyPosition[i].enemyPosition[1]; determineGridPositionsForSprite(&enemy); for (grid = 0; grid < 4; ++grid) { if (gridPosition[grid]) { enemyGrid[grid][enemyGridIndex[grid]++] = i; } } } } int handleEnemyBulletToRabbitCollisions( struct BulletPosition enemyBulletPosition[], struct RabbitPosition *rabbitPosition ) { int bulletIdx, grid; int resolvedBulletIdx; int hitCount = 0; rabbit.x = rabbitPosition->rabbitPosition[0]; rabbit.y = rabbitPosition->rabbitPosition[1]; populateTargetCollision(&rabbit); for (grid = 0; grid < 4; ++grid) { if (!rabbitGrid[grid]) continue; for (bulletIdx = 0; bulletIdx < enemyBulletGridIndex[grid]; ++bulletIdx) { resolvedBulletIdx = enemyBulletGrid[grid][bulletIdx]; enemyBullet.x = enemyBulletPosition[resolvedBulletIdx].x; enemyBullet.y = enemyBulletPosition[resolvedBulletIdx].y; populateSourceCollision(&enemyBullet); if (isCollision()) { enemyBulletPosition[resolvedBulletIdx].willBeInactive = 1; hitCount++; break; } } } return hitCount; } void knockbackEnemy( struct EnemyPosition *enemy, int angle ) { int distanceX, distanceY; distanceX = sin(angle * DEG2RAD) * (KNOCKBACK_DISTANCE * 2); distanceY = -cos(angle * DEG2RAD) * (KNOCKBACK_DISTANCE * 2); enemy->enemyPosition[0] += distanceX; enemy->enemyPosition[1] += distanceY; } void handleRabbitBulletToEnemyCollisions( struct BulletPosition rabbitBulletPosition[], struct EnemyPosition enemyPosition[] ) { int bulletIdx, enemyIdx, grid; int resolvedBulletIdx, resolvedEnemyIdx; for (grid = 0; grid < 4; ++grid) { if (enemyGridIndex[grid] == 0) continue; for (bulletIdx = 0; bulletIdx < rabbitBulletGridIndex[grid]; ++bulletIdx) { resolvedBulletIdx = rabbitBulletGrid[grid][bulletIdx]; bullet.x = rabbitBulletPosition[resolvedBulletIdx].x; bullet.y = rabbitBulletPosition[resolvedBulletIdx].y; populateSourceCollision(&bullet); for (enemyIdx = 0; enemyIdx < enemyGridIndex[grid]; ++enemyIdx) { resolvedEnemyIdx = enemyGrid[grid][enemyIdx]; enemy.x = enemyPosition[resolvedEnemyIdx].enemyPosition[0]; enemy.y = enemyPosition[resolvedEnemyIdx].enemyPosition[1]; populateTargetCollision(&enemy); if (isCollision()) { enemyPosition[resolvedEnemyIdx].hitPoints--; if (enemyPosition[resolvedEnemyIdx].hitPoints < 1) { enemyPosition[resolvedEnemyIdx].willBeInactive = 1; enemyPosition[resolvedEnemyIdx].wasKilled = 1; } else { knockbackEnemy(&enemyPosition[resolvedEnemyIdx], rabbitBulletPosition[resolvedBulletIdx].angle); } rabbitBulletPosition[resolvedBulletIdx].willBeInactive = 1; break; } } } } } int handleRabbitToEnemyCollisions( struct RabbitPosition *rabbitPosition, struct EnemyPosition enemyPosition[] ) { int enemyIdx, grid, resolvedEnemyIdx, hitCount = 0; rabbit.x = rabbitPosition->rabbitPosition[0]; rabbit.y = rabbitPosition->rabbitPosition[1]; populateSourceCollision(&rabbit); for (grid = 0; grid < 4; ++grid) { if (!rabbitGrid[grid]) continue; if (enemyGridIndex[grid] == 0) continue; for (enemyIdx = 0; enemyIdx < enemyGridIndex[grid]; ++enemyIdx) { resolvedEnemyIdx = enemyGrid[grid][enemyIdx]; enemy.x = enemyPosition[resolvedEnemyIdx].enemyPosition[0]; enemy.y = enemyPosition[resolvedEnemyIdx].enemyPosition[1]; populateTargetCollision(&enemy); if (isCollision()) { enemyPosition[resolvedEnemyIdx].willBeInactive = 1; hitCount++; } } } return hitCount; } int handleRabbitToPowerupCollision( struct RabbitPosition *rabbitPosition, struct PlayerPowerup *playerPowerup ) { struct CompiledSpriteRender *which; if (!playerPowerup->isActive) return 0; rabbit.x = rabbitPosition->rabbitPosition[0]; rabbit.y = rabbitPosition->rabbitPosition[1]; populateSourceCollision(&rabbit); switch (playerPowerup->type) { case POWERUP_TYPE_SHOTGUN: which = &shotgun; break; case POWERUP_TYPE_BEAM_WEAPON: which = &beam; break; } which->x = playerPowerup->x; which->y = playerPowerup->y; populateTargetCollision(which); return isCollision(); } int processEnemyKillStates( struct EnemyPosition enemyPosition[] ) { int i, currentKillCount = 0; for (i = 0; i < ENEMY_MAX_COUNT; ++i) { if (enemyPosition[i].wasKilled) { enemyPosition[i].wasKilled = 0; currentKillCount++; } } return currentKillCount; } void handleEnemyKills( struct EnemyPosition enemyPosition[], struct GlobalGameState *globalGameState, struct PlayerPowerup *playerPowerup, struct RabbitWeaponry *rabbitWeaponry ) { int i, currentKillCount; currentKillCount = processEnemyKillStates(enemyPosition); globalGameState->health += ENEMY_KILL_HEALTH_GAIN * currentKillCount; if (globalGameState->health > RABBIT_HEALTH_MAX) globalGameState->health = RABBIT_HEALTH_MAX; globalGameState->kills += currentKillCount; if (currentKillCount > 0) { processPowerupCooldown( playerPowerup, globalGameState, rabbitWeaponry, currentKillCount ); for (i = 0; i < MAX_DIFFICULTY; ++i) { if (globalGameState->kills > difficultyBands[i]) { globalGameState->difficulty = i + 1; } } } } void fireCurrentWeaponOnce(struct RabbitWeaponry *rabbitWeaponry) { if (rabbitWeaponry->currentWeaponRemainingRounds > 0) { rabbitWeaponry->currentWeaponRemainingRounds--; if (rabbitWeaponry->currentWeaponRemainingRounds == 0) { rabbitWeaponry->currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN; } } }