dos-vga-arena-shooter-game/combat.c

543 lines
15 KiB
C
Raw Normal View History

#include <math.h>
#include <stdio.h>
2024-02-25 21:59:04 +00:00
#include <stdlib.h>
#include "system/vga.h"
#include "game.h"
#include "powerup.h"
#include "movement.h"
#include "const.h"
2024-02-27 13:16:16 +00:00
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);
2024-02-25 21:59:04 +00:00
bullet->isActive = 1;
bullet->willBeInactive = 0;
bullet->x = x;
bullet->y = y;
bullet->oldX = x;
bullet->oldY = y;
2024-02-26 23:12:39 +00:00
bullet->wallCooldown = 1;
2024-02-27 13:16:16 +00:00
bullet->angle = angle;
2024-02-25 21:59:04 +00:00
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;
}
2024-02-27 13:16:16 +00:00
int attemptToFireRabbitBullet(
struct RabbitPosition *rabbitPosition,
struct RabbitWeaponry *rabbitWeaponry,
struct BulletPosition rabbitBulletPosition[]
) {
2024-02-27 01:40:05 +00:00
int okToFire = 0, i, mouseAngle;
int availableBullets[3];
2024-02-27 13:16:16 +00:00
if (rabbitWeaponry->cooldown > 0) return 0;
2024-02-27 01:40:05 +00:00
mouseAngle = rabbitPosition->mouseAngle;
2024-02-26 23:12:39 +00:00
rabbitWeaponry->cooldown = RABBIT_BULLET_COOLDOWN;
2024-02-27 13:16:16 +00:00
if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SINGLE_SHOT_GUN) {
2024-02-26 23:12:39 +00:00
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
if (rabbitBulletPosition[i].isActive == 0) {
okToFire = 1;
2024-02-27 01:40:05 +00:00
availableBullets[0] = i;
2024-02-26 23:12:39 +00:00
break;
}
}
2024-02-27 13:16:16 +00:00
if (!okToFire) return 0;
2024-02-26 23:12:39 +00:00
setupBullet(
2024-02-27 01:40:05 +00:00
&rabbitBulletPosition[availableBullets[0]],
2024-02-26 23:12:39 +00:00
rabbitPosition->rabbitPosition[0],
rabbitPosition->rabbitPosition[1],
2024-02-27 13:16:16 +00:00
mouseAngle,
2024-02-26 23:12:39 +00:00
RABBIT_BULLET_VELOCITY
);
2024-02-27 13:16:16 +00:00
} else if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SPREAD_SHOT_GUN) {
2024-02-26 23:12:39 +00:00
// make sure three bullets are available
2024-02-27 01:40:05 +00:00
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
if (rabbitBulletPosition[i].isActive == 0) {
availableBullets[okToFire++] = i;
if (okToFire == 3) break;
}
}
2024-02-27 13:16:16 +00:00
if (okToFire < 3) return 0;
2024-02-27 01:40:05 +00:00
2024-02-26 23:12:39 +00:00
// if so, fire away
2024-02-27 01:40:05 +00:00
for (i = 0; i < 3; ++i) {
2024-02-26 23:12:39 +00:00
setupBullet(
2024-02-27 01:40:05 +00:00
&rabbitBulletPosition[availableBullets[i]],
2024-02-26 23:12:39 +00:00
rabbitPosition->rabbitPosition[0],
rabbitPosition->rabbitPosition[1],
2024-02-27 13:16:16 +00:00
mouseAngle + (i - 1) * RABBIT_BULLET_SHOTGUN_SPREAD,
2024-02-26 23:12:39 +00:00
RABBIT_BULLET_VELOCITY
);
}
}
2024-02-27 13:16:16 +00:00
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;
2024-02-27 13:16:16 +00:00
moveBullet(&rabbitBulletPosition[i]);
2024-02-26 23:12:39 +00:00
if (rabbitBulletPosition[i].wallCooldown == 0) {
2024-02-27 13:16:16 +00:00
limitBulletToArena(&rabbitBulletPosition[i]);
2024-02-26 23:12:39 +00:00
} else {
rabbitBulletPosition[i].wallCooldown--;
}
}
if (rabbitWeaponry->cooldown > 0) rabbitWeaponry->cooldown--;
}
2024-02-25 21:59:04 +00:00
void advanceEnemyBullets(
struct BulletPosition enemyBulletPosition[]
) {
int i;
for (i = 0; i < ENEMY_BULLET_LIMIT; ++i) {
if (!enemyBulletPosition[i].isActive) continue;
2024-02-27 13:16:16 +00:00
moveBullet(&enemyBulletPosition[i]);
limitBulletToArena(&enemyBulletPosition[i]);
2024-02-25 21:59:04 +00:00
}
}
void attemptToFireEnemyBullets(
struct EnemyPosition enemyPosition[],
struct BulletPosition enemyBulletPosition[],
2024-02-26 12:52:59 +00:00
struct RabbitPosition *rabbitPosition,
int difficulty
2024-02-25 21:59:04 +00:00
) {
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],
2024-02-27 13:16:16 +00:00
angle,
2024-02-25 21:59:04 +00:00
ENEMY_BULLET_VELOCITY
);
2024-02-26 12:52:59 +00:00
enemyPosition[i].enemyFireDelayStep = ENEMY_FIRE_MIN_DELAY - difficulty + rand() % ENEMY_FIRE_VARIABLE_DELAY;
2024-02-25 21:59:04 +00:00
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;
}
2024-02-25 20:48:19 +00:00
// We are hardcoding a 2x2 grid on the screen as our collision partitioning
// scheme.
2024-02-27 13:16:16 +00:00
int rabbitGrid[4], powerupGrid[4];
2024-02-25 20:48:19 +00:00
int rabbitBulletGrid[4][RABBIT_BULLET_LIMIT];
int enemyGrid[4][ENEMY_MAX_COUNT];
2024-02-26 12:52:59 +00:00
int enemyBulletGrid[4][ENEMY_BULLET_LIMIT];
int rabbitBulletGridIndex[4],
enemyGridIndex[4],
enemyBulletGridIndex[4];
2024-02-25 20:48:19 +00:00
int gridPosition[4];
2024-02-25 20:48:19 +00:00
#define CALCULATE_GRID_POS(x,y) ((x / COLLISION_GRID_SIZE) + (y / COLLISION_GRID_SIZE) * 2)
2024-02-25 20:48:19 +00:00
void determineGridPositionsForSprite(struct CompiledSpriteRender *sprite) {
int i;
2024-02-25 20:48:19 +00:00
getSpriteBounds(sprite, &bounds);
for (i = 0; i < 4; ++i) {
gridPosition[i] = 0;
}
2024-02-25 20:48:19 +00:00
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;
}
2024-02-25 20:48:19 +00:00
void buildCollisionGrids(
struct BulletPosition rabbitBulletPosition[],
2024-02-26 12:52:59 +00:00
struct BulletPosition enemyBulletPosition[],
2024-02-25 20:48:19 +00:00
struct RabbitPosition *rabbitPosition,
2024-02-27 13:16:16 +00:00
struct EnemyPosition enemyPosition[],
struct PlayerPowerup *playerPowerup
) {
2024-02-25 20:48:19 +00:00
int grid, i;
for (grid = 0; grid < 4; ++grid) {
2024-02-25 20:48:19 +00:00
rabbitBulletGridIndex[grid] = 0;
2024-02-26 12:52:59 +00:00
enemyBulletGridIndex[grid] = 0;
2024-02-25 20:48:19 +00:00
enemyGridIndex[grid] = 0;
}
2024-02-25 20:48:19 +00:00
rabbit.x = rabbitPosition->rabbitPosition[0];
rabbit.y = rabbitPosition->rabbitPosition[1];
determineGridPositionsForSprite(&rabbit);
2024-02-25 20:48:19 +00:00
for (grid = 0; grid < 4; ++grid) {
rabbitGrid[grid] = gridPosition[grid];
}
2024-02-27 13:16:16 +00:00
switch (playerPowerup->type) {
case POWERUP_TYPE_SHOTGUN:
shotgun.x = playerPowerup->x;
shotgun.y = playerPowerup->y;
determineGridPositionsForSprite(&shotgun);
break;
case POWERUP_TYPE_SHIELD_KILLER:
shieldKiller.x = playerPowerup->x;
shieldKiller.y = playerPowerup->y;
determineGridPositionsForSprite(&shieldKiller);
break;
}
for (grid = 0; grid < 4; ++grid) {
powerupGrid[grid] = gridPosition[grid];
}
2024-02-25 20:48:19 +00:00
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;
}
}
}
2024-02-26 12:52:59 +00:00
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;
}
}
}
2024-02-25 20:48:19 +00:00
for (i = 0; i < ENEMY_MAX_COUNT; ++i) {
if (!enemyPosition[i].isActive) continue;
if (enemyPosition[i].willBeInactive) continue;
2024-02-25 20:48:19 +00:00
enemy.x = enemyPosition[i].enemyPosition[0];
enemy.y = enemyPosition[i].enemyPosition[1];
determineGridPositionsForSprite(&enemy);
2024-02-25 20:48:19 +00:00
for (grid = 0; grid < 4; ++grid) {
if (gridPosition[grid]) {
enemyGrid[grid][enemyGridIndex[grid]++] = i;
}
}
}
2024-02-25 20:48:19 +00:00
}
2024-02-26 12:52:59 +00:00
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;
}
2024-02-27 13:16:16 +00:00
#define KNOCKBACK_DISTANCE (3)
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;
}
2024-02-25 20:48:19 +00:00
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;
2024-02-25 20:48:19 +00:00
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()) {
2024-02-26 23:12:39 +00:00
enemyPosition[resolvedEnemyIdx].hitPoints--;
if (enemyPosition[resolvedEnemyIdx].hitPoints < 1) {
enemyPosition[resolvedEnemyIdx].willBeInactive = 1;
2024-02-27 13:16:16 +00:00
enemyPosition[resolvedEnemyIdx].wasKilled = 1;
} else {
knockbackEnemy(&enemyPosition[resolvedEnemyIdx], rabbitBulletPosition[resolvedBulletIdx].angle);
2024-02-26 23:12:39 +00:00
}
rabbitBulletPosition[resolvedBulletIdx].willBeInactive = 1;
break;
}
}
}
}
}
2024-02-26 12:52:59 +00:00
int handleRabbitToEnemyCollisions(
2024-02-25 20:48:19 +00:00
struct RabbitPosition *rabbitPosition,
struct EnemyPosition enemyPosition[]
) {
2024-02-26 12:52:59 +00:00
int enemyIdx, grid, resolvedEnemyIdx, hitCount = 0;
2024-02-25 20:48:19 +00:00
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;
2024-02-26 12:52:59 +00:00
hitCount++;
2024-02-25 20:48:19 +00:00
}
}
}
2024-02-26 12:52:59 +00:00
return hitCount;
2024-02-25 20:48:19 +00:00
}
2024-02-27 13:16:16 +00:00
int handleRabbitToPowerupCollision(
struct RabbitPosition *rabbitPosition,
struct PlayerPowerup *playerPowerup
) {
if (!playerPowerup->isActive) return 0;
rabbit.x = rabbitPosition->rabbitPosition[0];
rabbit.y = rabbitPosition->rabbitPosition[1];
populateSourceCollision(&rabbit);
switch (playerPowerup->type) {
case POWERUP_TYPE_SHOTGUN:
shotgun.x = playerPowerup->x;
shotgun.y = playerPowerup->y;
populateTargetCollision(&shotgun);
break;
case POWERUP_TYPE_SHIELD_KILLER:
shieldKiller.x = playerPowerup->x;
shieldKiller.y = playerPowerup->y;
populateTargetCollision(&shieldKiller);
break;
}
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, hadKill, 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;
processPowerupCooldown(
playerPowerup,
globalGameState,
rabbitWeaponry,
currentKillCount
);
if (hadKill) {
for (i = 0; i < 10; ++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;
}
}
}