diff --git a/combat.c b/combat.c index d6032bd..41c1390 100644 --- a/combat.c +++ b/combat.c @@ -1,5 +1,6 @@ #include #include +#include #include "game.h" @@ -7,6 +8,23 @@ #include "const.h" #include "system/vga.h" +void setupBullet(struct BulletPosition *bullet, int x, int y, signed int doubleVelocityX, signed int doubleVelocityY, int velocity) { + bullet->isActive = 1; + bullet->willBeInactive = 0; + bullet->x = x; + bullet->y = y; + bullet->oldX = x; + bullet->oldY = y; + + 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; +} + void attemptToFireRabbitBullet( struct RabbitPosition *rabbitPosition, struct RabbitWeaponry *rabbitWeaponry, @@ -14,7 +32,6 @@ void attemptToFireRabbitBullet( ) { int okToFire = 0, availableBullet, i; signed int doubleVelocityX, doubleVelocityY; - char buffer[20]; if (rabbitWeaponry->cooldown > 0) return; @@ -33,20 +50,14 @@ void attemptToFireRabbitBullet( doubleVelocityX = sin(rabbitPosition->mouseAngle * DEG2RAD) * (RABBIT_BULLET_VELOCITY * 2); doubleVelocityY = -cos(rabbitPosition->mouseAngle * DEG2RAD) * (RABBIT_BULLET_VELOCITY * 2); - rabbitBulletPosition[availableBullet].isActive = 1; - rabbitBulletPosition[availableBullet].willBeInactive = 0; - rabbitBulletPosition[availableBullet].x = rabbitPosition->rabbitPosition[0]; - rabbitBulletPosition[availableBullet].y = rabbitPosition->rabbitPosition[1]; - rabbitBulletPosition[availableBullet].oldX = rabbitBulletPosition[availableBullet].x; - rabbitBulletPosition[availableBullet].oldY = rabbitBulletPosition[availableBullet].y; - - rabbitBulletPosition[availableBullet].velocityXSteps[0] = doubleVelocityX / RABBIT_BULLET_VELOCITY; - rabbitBulletPosition[availableBullet].velocityXSteps[1] = doubleVelocityX - rabbitBulletPosition[availableBullet].velocityXSteps[0]; - - rabbitBulletPosition[availableBullet].velocityYSteps[0] = doubleVelocityY / RABBIT_BULLET_VELOCITY; - rabbitBulletPosition[availableBullet].velocityYSteps[1] = doubleVelocityY - rabbitBulletPosition[availableBullet].velocityYSteps[0]; - - rabbitBulletPosition[availableBullet].velocityStep = 0; + setupBullet( + &rabbitBulletPosition[availableBullet], + rabbitPosition->rabbitPosition[0], + rabbitPosition->rabbitPosition[1], + doubleVelocityX, + doubleVelocityY, + RABBIT_BULLET_VELOCITY + ); } void advanceRabbitBullets( @@ -75,6 +86,77 @@ void advanceRabbitBullets( 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; + + enemyBulletPosition[i].oldX = enemyBulletPosition[i].x; + enemyBulletPosition[i].oldY = enemyBulletPosition[i].y; + + enemyBulletPosition[i].x += enemyBulletPosition[i].velocityXSteps[enemyBulletPosition[i].velocityStep]; + enemyBulletPosition[i].y += enemyBulletPosition[i].velocityYSteps[enemyBulletPosition[i].velocityStep]; + + if (enemyBulletPosition[i].x < MOUSE_LIMIT_LEFT) enemyBulletPosition[i].willBeInactive = 1; + if (enemyBulletPosition[i].x >= MOUSE_LIMIT_RIGHT) enemyBulletPosition[i].willBeInactive = 1; + if (enemyBulletPosition[i].y < MOUSE_LIMIT_TOP) enemyBulletPosition[i].willBeInactive = 1; + if (enemyBulletPosition[i].y >= MOUSE_LIMIT_BOTTOM) enemyBulletPosition[i].willBeInactive = 1; + + enemyBulletPosition[i].velocityStep = 1 - enemyBulletPosition[i].velocityStep; + } +} + +void attemptToFireEnemyBullets( + struct EnemyPosition enemyPosition[], + struct BulletPosition enemyBulletPosition[], + struct RabbitPosition *rabbitPosition +) { + 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; + + doubleVelocityX = sin(angle * DEG2RAD) * (ENEMY_BULLET_VELOCITY * 2); + doubleVelocityY = -cos(angle * DEG2RAD) * (ENEMY_BULLET_VELOCITY * 2); + + availableBullet = availableBullets[--maxAvailableBulletIndex]; + + setupBullet( + &enemyBulletPosition[availableBullet], + enemyPosition[i].enemyPosition[0], + enemyPosition[i].enemyPosition[1], + doubleVelocityX, + doubleVelocityY, + ENEMY_BULLET_VELOCITY + ); + + enemyPosition[i].enemyFireDelayStep = ENEMY_FIRE_MIN_DELAY + rand() % ENEMY_FIRE_VARIABLE_DELAY; + + if (maxAvailableBulletIndex < 0) break; + } +} + struct CollisionDetection { short int sourceSX, sourceSY, sourceEX, sourceEY; short int targetSX, targetSY, targetEX, targetEY; @@ -218,6 +300,7 @@ void handleRabbitBulletToEnemyCollisions( if (isCollision()) { enemyPosition[resolvedEnemyIdx].willBeInactive = 1; + enemyPosition[resolvedBulletIdx].wasKilled = 1; rabbitBulletPosition[resolvedBulletIdx].willBeInactive = 1; break; } diff --git a/combat.h b/combat.h index 978fb60..14d87ed 100644 --- a/combat.h +++ b/combat.h @@ -24,3 +24,13 @@ void buildCollisionGrids( struct RabbitPosition *rabbitPosition, struct EnemyPosition enemyPosition[] ); + +void attemptToFireEnemyBullets( + struct EnemyPosition[], + struct BulletPosition enemyBulletPosition[], + struct RabbitPosition* +); + +void advanceEnemyBullets( + struct BulletPosition[] +); diff --git a/const.h b/const.h index 2edc2e4..1dd2151 100644 --- a/const.h +++ b/const.h @@ -11,7 +11,7 @@ #define RABBIT_MOTION_VELOCITY_DECAY (5) #define RABBIT_BULLET_LIMIT (12) -#define RABBIT_BULLET_VELOCITY (RABBIT_MOTION_ACCELERATION + 1) +#define RABBIT_BULLET_VELOCITY ((RABBIT_MOTION_ACCELERATION / 2) + 1) #define RABBIT_BULLET_COOLDOWN (10) #define RABBIT_BULLET_HEIGHT_START (8) @@ -24,3 +24,15 @@ #define DEG2RAD (3.14159/180) #define ENEMY_MAX_COUNT (50) +#define ENEMY_BULLET_LIMIT (ENEMY_MAX_COUNT / 2) + +#define ENEMY_MOVE_SPEED (1) +#define ENEMY_MOVE_DELAY (3) +#define ENEMY_BULLET_VELOCITY (RABBIT_BULLET_VELOCITY - 2) + +#define ENEMY_FIRE_MIN_DELAY (30) +#define ENEMY_FIRE_VARIABLE_DELAY (45) + +#define BASE_ENEMY_SPAWN_COOLDOWN (30) +#define MINIMUM_ENEMY_SPAWN_COOLDOWN (3) +#define VARIABLE_ENEMY_SPAWN_COOLDOWN (10) diff --git a/game.c b/game.c index 1a52b79..c51a0a4 100644 --- a/game.c +++ b/game.c @@ -18,7 +18,7 @@ #include "combat.h" // TODO: centralize these outside of game.c -struct CompiledSpriteRender rabbit, mouse, bullet, enemy; +struct CompiledSpriteRender rabbit, mouse, bullet, enemy, enemyBullet; struct SpriteBounds bounds; struct BMPImage spritesheetImage; @@ -35,6 +35,7 @@ struct RabbitPosition rabbitPosition = { }; struct EnemyPosition enemyPosition[ENEMY_MAX_COUNT]; struct BulletPosition rabbitBulletPosition[RABBIT_BULLET_LIMIT]; +struct BulletPosition enemyBulletPosition[ENEMY_BULLET_LIMIT]; struct RabbitWeaponry rabbitWeaponry; struct SpawnPointRange { @@ -42,8 +43,11 @@ struct SpawnPointRange { int top, height; }; -struct SpawnPointRange spawnPointRanges[1] = { - { .left = 130, .width = 50, .top = 50, .height = 100 } +struct SpawnPointRange spawnPointRanges[4] = { + { .left = TILE_SIZE * 4, .width = TILE_SIZE * 2, .top = TILE_SIZE, .height = TILE_SIZE }, + { .left = TILE_SIZE * 4, .width = TILE_SIZE * 2, .top = (ARENA_HEIGHT_TILES - 2) * TILE_SIZE, .height = TILE_SIZE }, + { .left = TILE_SIZE, .width = TILE_SIZE, .top = TILE_SIZE * 4, .height = TILE_SIZE * 2 }, + { .left = (ARENA_WIDTH_TILES - 2) * TILE_SIZE, .width = TILE_SIZE, .top = TILE_SIZE * 4, .height = TILE_SIZE * 2 }, }; @@ -58,6 +62,15 @@ void setupRabbitBullets() { rabbitWeaponry.cooldown = 0; } +void setupEnemyBullets() { + int i; + + for (i = 0; i < ENEMY_BULLET_LIMIT; ++i) { + enemyBulletPosition[i].isActive = 0; + enemyBulletPosition[i].willBeInactive = 0; + } +} + void setupEnemies() { int i; @@ -67,11 +80,40 @@ void setupEnemies() { } } +int spawnCooldown = 0; +int difficulty = 0; +int kills = 0; + +int difficultyBands[10] = { 10, 20, 30, 50, 80, 130, 210, 340, 550, 890 }; + +void handleEnemyKills() { + int i, hadKill; + + for (i = 0; i < ENEMY_MAX_COUNT; ++i) { + if (enemyPosition[i].wasKilled) { + enemyPosition[i].wasKilled = 0; + kills++; + + hadKill = 1; + } + } + + if (hadKill) { + for (i = 0; i < 10; ++i) { + if (kills > difficultyBands[i]) { + difficulty = i + 1; + } + } + } +} + void maybeSpawnEnemy() { char canSpawn; int i, availableEnemy; int spawnX, spawnY; + if (spawnCooldown-- > 0) return; + for (i = 0; i < ENEMY_MAX_COUNT; ++i) { if (!enemyPosition[i].isActive) { availableEnemy = i; @@ -82,14 +124,22 @@ void maybeSpawnEnemy() { if (!canSpawn) return; - spawnX = spawnPointRanges[0].left + rand() % spawnPointRanges[0].width; - spawnY = spawnPointRanges[0].top + rand() % spawnPointRanges[0].height; + i = rand() % 4; + + spawnX = spawnPointRanges[i].left + rand() % spawnPointRanges[i].width; + spawnY = spawnPointRanges[i].top + rand() % spawnPointRanges[i].height; enemyPosition[availableEnemy].isActive = 1; + enemyPosition[availableEnemy].willBeInactive = 0; + enemyPosition[availableEnemy].wasKilled = 0; + enemyPosition[availableEnemy].enemyMoveDelayStep = 0; + enemyPosition[availableEnemy].enemyFireDelayStep = ENEMY_FIRE_MIN_DELAY + rand() % ENEMY_FIRE_VARIABLE_DELAY; enemyPosition[availableEnemy].enemyPosition[0] = spawnX; enemyPosition[availableEnemy].enemyPosition[1] = spawnY; enemyPosition[availableEnemy].oldEnemyPosition[0] = spawnX; enemyPosition[availableEnemy].oldEnemyPosition[1] = spawnY; + + spawnCooldown = (BASE_ENEMY_SPAWN_COOLDOWN - difficulty) + MINIMUM_ENEMY_SPAWN_COOLDOWN + rand() % VARIABLE_ENEMY_SPAWN_COOLDOWN; } void setupEnemySprites() { @@ -101,6 +151,15 @@ void setupEnemySprites() { SPRITE_ENEMY_OFFSET_X, SPRITE_ENEMY_OFFSET_Y ); + + buildCompiledSprite( + &sprite_bullet, + &enemyBullet, + SPRITE_BULLET_WIDTH, + SPRITE_BULLET_HEIGHT, + SPRITE_BULLET_OFFSET_X, + SPRITE_BULLET_OFFSET_Y + ); } void setupRabbitSprites() { @@ -173,6 +232,19 @@ void renderRabbitBullets() { } } +void renderEnemyBullets() { + char i; + + for (i = 0; i < ENEMY_BULLET_LIMIT; ++i) { + if (!enemyBulletPosition[i].isActive) continue; + + enemyBullet.x = enemyBulletPosition[i].x; + enemyBullet.y = enemyBulletPosition[i].y; + + drawCompiledSprite(&enemyBullet); + } +} + void drawOnlyMouseArena() { mouse.x = rabbitPosition.oldMousePosition[0]; mouse.y = rabbitPosition.oldMousePosition[1]; @@ -226,6 +298,22 @@ void drawOnlyRabbitBulletArena() { } } +void drawOnlyEnemyBulletArena() { + int i; + for (i = 0; i < ENEMY_BULLET_LIMIT; ++i) { + if (!enemyBulletPosition[i].isActive) continue; + + enemyBullet.x = enemyBulletPosition[i].oldX; + enemyBullet.y = enemyBulletPosition[i].oldY; + getSpriteBounds(&enemyBullet, &bounds); + drawOnlyArena(&bounds); + + if (enemyBulletPosition[i].willBeInactive) { + enemyBulletPosition[i].isActive = 0; + } + } +} + int setupGame() { FILE *fh; @@ -262,6 +350,10 @@ void handleMovement() { &rabbitPosition, &keyboardKeydownState ); + handleEnemyMovement( + enemyPosition, + &rabbitPosition + ); captureAndLimitMousePosition( &rabbitPosition, &mouseStatus @@ -281,11 +373,21 @@ void handleCombat() { ); } + attemptToFireEnemyBullets( + enemyPosition, + enemyBulletPosition, + &rabbitPosition + ); + advanceRabbitBullets( rabbitBulletPosition, &rabbitWeaponry ); + advanceEnemyBullets( + enemyBulletPosition + ); + buildCollisionGrids( rabbitBulletPosition, &rabbitPosition, @@ -308,6 +410,7 @@ void handleRedraw() { drawOnlyEnemiesArena(); drawOnlyMouseArena(); drawOnlyRabbitBulletArena(); + drawOnlyEnemyBulletArena(); redrawArena(); @@ -315,6 +418,7 @@ void handleRedraw() { renderEnemies(); renderMouse(); renderRabbitBullets(); + renderEnemyBullets(); } /* @@ -333,20 +437,19 @@ int main(void) { drawBuffer = getDrawBuffer(); - for (i = 0; i < ENEMY_MAX_COUNT; ++i) { - maybeSpawnEnemy(); - } - while (keepRunning) { readMouse(&mouseStatus); populateKeyboardKeydownState(); + maybeSpawnEnemy(); + handleMovement(); handleRedraw(); handleCombat(); waitStartVbl(); copyDrawBufferToDisplay(); + waitEndVbl(); if (keyboardKeydownState.KEY_ESC) { keepRunning = 0; } diff --git a/game.h b/game.h index f061979..1ceb6bf 100644 --- a/game.h +++ b/game.h @@ -1,4 +1,4 @@ #include "system/vga.h" -extern struct CompiledSpriteRender rabbit, mouse, bullet, enemy; +extern struct CompiledSpriteRender rabbit, mouse, bullet, enemy, enemyBullet; extern struct SpriteBounds bounds; diff --git a/movement.c b/movement.c index 16be776..3fc3e7e 100644 --- a/movement.c +++ b/movement.c @@ -1,4 +1,5 @@ #include +#include #include "const.h" #include "movement.h" @@ -45,16 +46,39 @@ void calculateTargetAngle( pos->mousePosition[0] = pos->rabbitPosition[0] + distanceX; pos->mousePosition[1] = pos->rabbitPosition[1] + distanceY; - // get the shot angle - distanceX = pos->mouseDotPosition[0] - pos->rabbitPosition[0]; - distanceY = pos->mouseDotPosition[1] - pos->rabbitPosition[1]; - - angle = atan2(distanceY, distanceX) * RAD2DEG + 90; - if (angle < 0) angle += 360; - pos->mouseAngle = angle; } +#define SIGN(x) ((x > 0) - (x < 0)) + +void handleEnemyMovement( + struct EnemyPosition enemyPosition[], + struct RabbitPosition *pos +) { + int i, xDistance, yDistance; + + for (i = 0; i < ENEMY_MAX_COUNT; ++i) { + if (!enemyPosition[i].isActive) continue; + + enemyPosition[i].enemyMoveDelayStep += 1; + if (enemyPosition[i].enemyMoveDelayStep < ENEMY_MOVE_DELAY) continue; + enemyPosition[i].enemyMoveDelayStep = 0; + + // which straight directon is the player closest to + xDistance = pos->rabbitPosition[0] - enemyPosition[i].enemyPosition[0]; + yDistance = pos->rabbitPosition[1] - enemyPosition[i].enemyPosition[1]; + + enemyPosition[i].oldEnemyPosition[0] = enemyPosition[i].enemyPosition[0]; + enemyPosition[i].oldEnemyPosition[1] = enemyPosition[i].enemyPosition[1]; + + if (abs(xDistance) > abs(yDistance)) { + enemyPosition[i].enemyPosition[0] += SIGN(xDistance) * ENEMY_MOVE_SPEED; + } else { + enemyPosition[i].enemyPosition[1] += SIGN(yDistance) * ENEMY_MOVE_SPEED; + } + } +} + void handleRabbitMovement( struct RabbitPosition *pos, struct KeyboardKeydownState *keyboardKeydownState diff --git a/movement.h b/movement.h index 5470287..214f5e9 100644 --- a/movement.h +++ b/movement.h @@ -27,19 +27,24 @@ struct BulletPosition { int x, y; int oldX, oldY; - signed char velocityXSteps[2], velocityYSteps[2]; - char velocityStep; + signed int velocityXSteps[2], velocityYSteps[2]; + int velocityStep; }; struct EnemyPosition { char isActive; char willBeInactive; + char wasKilled; int enemyPosition[2]; int oldEnemyPosition[2]; + + int enemyMoveDelayStep; + int enemyFireDelayStep; }; void calculateTargetAngle(struct RabbitPosition*, struct MouseStatus*); void captureAndLimitMousePosition(struct RabbitPosition*, struct MouseStatus*); void handleRabbitMovement(struct RabbitPosition*, struct KeyboardKeydownState*); +void handleEnemyMovement(struct EnemyPosition[], struct RabbitPosition*);