tbftss/src/battle/bullets.c

523 lines
10 KiB
C
Raw Normal View History

2015-10-20 13:51:49 +02:00
/*
2018-04-29 11:01:09 +02:00
Copyright (C) 2015-2018 Parallel Realities
2015-10-20 13:51:49 +02:00
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "bullets.h"
static void huntTarget(Bullet *b);
static void checkCollisions(Bullet *b);
static void resizeDrawList(void);
2016-05-15 09:19:26 +02:00
static void selectNewTarget(Bullet *b);
static void doBulletHitEffect(Bullet *b);
2015-10-20 13:51:49 +02:00
static Bullet bulletDef[BT_MAX];
static Bullet **bulletsToDraw;
static int drawCapacity;
2015-10-20 13:51:49 +02:00
2015-12-11 20:06:16 +01:00
void initBullets(void)
{
drawCapacity = INITIAL_BULLET_DRAW_CAPACITY;
bulletsToDraw = malloc(sizeof(Bullet*) * drawCapacity);
memset(bulletsToDraw, 0, sizeof(Bullet*) * drawCapacity);
2015-12-11 20:06:16 +01:00
}
2015-10-20 13:51:49 +02:00
void initBulletDefs(void)
{
cJSON *root, *node;
char *text;
int type;
Bullet *def;
2015-10-20 13:51:49 +02:00
memset(&bulletDef, 0, sizeof(Bullet) * BT_MAX);
text = readFile("data/battle/bullets.json");
2015-10-20 13:51:49 +02:00
root = cJSON_Parse(text);
2015-10-20 13:51:49 +02:00
for (node = root->child ; node != NULL ; node = node->next)
{
type = lookup(cJSON_GetObjectItem(node, "type")->valuestring);
2015-10-20 13:51:49 +02:00
def = &bulletDef[type];
def->type = type;
def->damage = cJSON_GetObjectItem(node, "damage")->valueint;
def->texture = getTexture(cJSON_GetObjectItem(node, "texture")->valuestring);
2015-10-20 13:51:49 +02:00
def->sound = lookup(cJSON_GetObjectItem(node, "sound")->valuestring);
def->flags = flagsToLong(cJSON_GetObjectItem(node, "flags")->valuestring, NULL);
SDL_QueryTexture(def->texture, NULL, NULL, &def->w, &def->h);
2015-10-20 13:51:49 +02:00
}
2015-10-20 13:51:49 +02:00
cJSON_Delete(root);
free(text);
}
void doBullets(void)
{
2015-11-13 23:51:22 +01:00
int i = 0;
2015-10-20 13:51:49 +02:00
Bullet *b;
Bullet *prev = &battle.bulletHead;
battle.incomingMissile = 0;
2016-02-25 00:12:13 +01:00
memset(bulletsToDraw, 0, sizeof(Bullet*) * drawCapacity);
2015-10-20 13:51:49 +02:00
for (b = battle.bulletHead.next ; b != NULL ; b = b->next)
{
b->x += b->dx;
b->y += b->dy;
if (b->type == BT_ROCKET)
{
addMissileEngineEffect(b);
}
else if (b->type == BT_MISSILE)
2015-10-20 13:51:49 +02:00
{
addMissileEngineEffect(b);
2016-05-15 09:19:26 +02:00
if (b->life < MISSILE_LIFE - (FPS / 4))
{
huntTarget(b);
}
2016-05-15 09:19:26 +02:00
if (b->target == player && player->alive == ALIVE_ALIVE && player->health > 0)
{
battle.incomingMissile = 1;
}
2015-10-20 13:51:49 +02:00
}
2015-10-20 13:51:49 +02:00
checkCollisions(b);
2015-10-20 13:51:49 +02:00
if (--b->life <= 0)
{
if (b == battle.bulletTail)
{
battle.bulletTail = prev;
}
2015-10-20 13:51:49 +02:00
prev->next = b->next;
free(b);
b = prev;
}
2015-11-13 23:51:22 +01:00
else
{
2016-04-02 17:37:49 +02:00
if (isOnBattleScreen(b->x, b->y, b->w, b->h))
2015-11-13 23:51:22 +01:00
{
bulletsToDraw[i++] = b;
if (i == drawCapacity)
2015-11-13 23:51:22 +01:00
{
resizeDrawList();
2015-11-13 23:51:22 +01:00
}
}
}
2015-10-20 13:51:49 +02:00
prev = b;
}
}
static void resizeDrawList(void)
{
int n;
n = drawCapacity + INITIAL_BULLET_DRAW_CAPACITY;
2016-04-04 12:25:09 +02:00
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, "Resizing bullet draw capacity: %d -> %d", drawCapacity, n);
bulletsToDraw = resize(bulletsToDraw, sizeof(Bullet*) * drawCapacity, sizeof(Bullet*) * n);
drawCapacity = n;
}
2015-10-20 13:51:49 +02:00
static void checkCollisions(Bullet *b)
{
Entity *e, **candidates;
int i;
candidates = getAllEntsWithin(b->x - (b->w / 2), b->y - (b->h / 2), b->w, b->h, NULL);
2015-11-13 23:51:22 +01:00
for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i])
2015-10-20 13:51:49 +02:00
{
if (e->flags & EF_TAKES_DAMAGE)
2015-10-20 13:51:49 +02:00
{
if (b->owner->owner != NULL && b->owner->owner == e->owner)
{
continue;
}
if (b->owner != e && e->health > 0 && collision(b->x - b->w / 2, b->y - b->h / 2, b->w, b->h, e->x - e->w / 2, e->y - e->h / 2, e->w, e->h))
{
2016-05-15 09:19:26 +02:00
if (b->owner->side == e->side && !app.gameplay.friendlyFire && (!(e->flags & EF_DISABLED)) && e->type != ET_MINE)
{
b->damage = 0;
}
else if (b->owner == player)
{
if (b->type != BT_ROCKET)
{
battle.stats[STAT_SHOTS_HIT]++;
}
else
{
battle.stats[STAT_ROCKETS_HIT]++;
}
if (battle.hasSuspicionLevel)
{
if (e->aiFlags & (AIF_AVOIDS_COMBAT|AIF_DEFENSIVE))
{
battle.suspicionLevel -= (MAX_SUSPICION_LEVEL * 0.1);
}
else
{
battle.suspicionLevel -= (MAX_SUSPICION_LEVEL * 0.001);
}
}
}
if (e->flags & EF_IMMORTAL)
2015-11-21 18:36:55 +01:00
{
b->damage = 0;
2015-11-21 18:36:55 +01:00
}
damageFighter(e, b->damage, b->flags);
doBulletHitEffect(b);
b->life = 0;
2015-11-17 08:23:50 +01:00
b->damage = 0;
if (b->flags & BF_EXPLODES)
{
addMissileExplosion(b);
2015-11-22 09:17:16 +01:00
playBattleSound(SND_EXPLOSION_1, b->x, b->y);
2015-11-17 08:23:50 +01:00
if (e == player)
{
battle.stats[STAT_MISSILES_STRUCK]++;
}
}
2016-05-15 09:19:26 +02:00
/* missile was targetting player, but hit something else */
if (b->type == BT_MISSILE && b->target == player && e != player)
{
battle.stats[STAT_MISSILES_EVADED]++;
}
if (b->type == BT_MISSILE && b->target != e)
{
if (e == player)
{
awardTrophy("TEAM_PLAYER");
}
else if (b->owner == player && (e->aiFlags & AIF_MOVES_TO_LEADER) && (b->target->flags & EF_AI_LEADER))
{
awardTrophy("BODYGUARD");
}
}
/* assuming that health <= 0 will always mean killed */
2016-04-10 10:35:30 +02:00
if (e->health <= 0)
{
e->killedBy = b->owner;
2016-04-08 12:03:50 +02:00
2016-05-24 17:16:34 +02:00
if (e == player)
{
battle.lastKilledPlayer = b->owner;
2016-05-24 17:16:34 +02:00
}
if (b->owner == player && e == battle.lastKilledPlayer)
2016-05-24 17:16:34 +02:00
{
awardTrophy("REVENGE");
}
}
if (b->owner == player && b->type == BT_MISSILE)
{
battle.stats[STAT_MISSILES_HIT]++;
}
return;
}
2015-10-20 13:51:49 +02:00
}
}
}
void doBulletHitEffect(Bullet *b)
{
switch (b->type)
{
case BT_PARTICLE:
addBulletHitEffect(b->x, b->y, 255, 0, 255);
break;
case BT_PLASMA:
addBulletHitEffect(b->x, b->y, 0, 255, 0);
break;
case BT_LASER:
addBulletHitEffect(b->x, b->y, 255, 0, 0);
break;
case BT_MAG:
addBulletHitEffect(b->x, b->y, 196, 196, 255);
break;
default:
addBulletHitEffect(b->x, b->y, 255, 255, 255);
break;
}
}
2015-10-20 13:51:49 +02:00
void drawBullets(void)
{
2015-11-13 23:51:22 +01:00
int i;
2015-10-20 13:51:49 +02:00
Bullet *b;
2015-11-13 23:51:22 +01:00
for (i = 0, b = bulletsToDraw[i] ; b != NULL ; b = bulletsToDraw[++i])
2015-10-20 13:51:49 +02:00
{
blitRotated(b->texture, b->x - battle.camera.x, b->y - battle.camera.y, b->angle);
2015-10-20 13:51:49 +02:00
}
}
static void faceTarget(Bullet *b)
{
2016-05-15 09:19:26 +02:00
int dir, wantedAngle, dist;
wantedAngle = (int)getAngle(b->x, b->y, b->target->x, b->target->y) % 360;
2018-04-30 19:16:52 +02:00
if (abs(wantedAngle - b->angle) > TURN_THRESHOLD)
2015-10-20 13:51:49 +02:00
{
dir = (wantedAngle - b->angle + 360) % 360 > 180 ? -1 : 1;
2015-10-20 13:51:49 +02:00
b->angle += dir * TURN_SPEED;
2016-05-15 09:19:26 +02:00
dist = getDistance(b->x, b->y, b->target->x, b->target->y);
if (dist < 250)
{
dist = 250 - dist;
while (dist > 0)
{
b->angle += dir;
dist -= 50;
}
}
2015-10-20 13:51:49 +02:00
b->angle = mod(b->angle, 360);
2016-05-15 09:19:26 +02:00
b->dx *= 0.5;
b->dy *= 0.5;
2015-10-20 13:51:49 +02:00
}
}
static void applyMissileThrust(Bullet *b)
{
int maxSpeed;
float v, thrust;
b->dx += sin(TO_RAIDANS(b->angle));
b->dy += -cos(TO_RAIDANS(b->angle));
2015-10-20 13:51:49 +02:00
maxSpeed = MAX(MIN(b->target->speed + 1, 999), 3);
2015-10-20 13:51:49 +02:00
thrust = sqrt((b->dx * b->dx) + (b->dy * b->dy));
2015-10-20 13:51:49 +02:00
if (thrust > maxSpeed)
{
v = (maxSpeed / sqrt(thrust));
b->dx = v * b->dx;
b->dy = v * b->dy;
}
}
static void huntTarget(Bullet *b)
{
if (b->target != NULL && b->target->health > 0)
{
faceTarget(b);
2015-10-20 13:51:49 +02:00
applyMissileThrust(b);
2015-11-16 17:25:08 +01:00
if (b->target == player && battle.ecmTimer < FPS)
{
b->life = 0;
addMissileExplosion(b);
2015-11-22 09:17:16 +01:00
playBattleSound(SND_EXPLOSION_1, b->x, b->y);
}
2015-10-20 13:51:49 +02:00
}
else
2016-05-15 09:19:26 +02:00
{
selectNewTarget(b);
}
}
static void selectNewTarget(Bullet *b)
{
int i;
Entity *e, **candidates;
if (app.gameplay.missileReTarget)
2015-10-20 13:51:49 +02:00
{
b->target = NULL;
2016-05-15 09:19:26 +02:00
candidates = getAllEntsInRadius(b->x, b->y, SCREEN_HEIGHT, NULL);
for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i])
{
if (e->type == ET_FIGHTER && e->side != b->owner->side && e->health > 0)
{
b->target = e;
if (b->target == player)
{
playSound(SND_INCOMING);
}
return;
}
}
2015-10-20 13:51:49 +02:00
}
2016-05-15 09:19:26 +02:00
/* no target, just explode */
b->life = 0;
addMissileExplosion(b);
playBattleSound(SND_EXPLOSION_1, b->x, b->y);
2015-10-20 13:51:49 +02:00
}
static Bullet *createBullet(int type, int x, int y, Entity *owner)
2015-10-20 13:51:49 +02:00
{
Bullet *b;
2015-10-20 13:51:49 +02:00
b = malloc(sizeof(Bullet));
memset(b, 0, sizeof(Bullet));
battle.bulletTail->next = b;
battle.bulletTail = b;
2015-10-20 13:51:49 +02:00
memcpy(b, &bulletDef[type], sizeof(Bullet));
2015-10-20 13:51:49 +02:00
b->next = NULL;
2015-10-20 13:51:49 +02:00
b->x = x;
b->y = y;
b->dx += sin(TO_RAIDANS(owner->angle)) * 16;
b->dy += -cos(TO_RAIDANS(owner->angle)) * 16;
b->life = FPS * 2;
b->angle = owner->angle;
b->owner = owner;
b->target = owner->target;
2015-10-20 13:51:49 +02:00
return b;
}
void fireGuns(Entity *owner)
2015-10-20 13:51:49 +02:00
{
Bullet *b;
int i;
float x, y;
float c, s;
2016-05-15 09:19:26 +02:00
b = NULL;
2015-10-20 13:51:49 +02:00
for (i = 0 ; i < MAX_FIGHTER_GUNS ; i++)
{
2016-05-15 09:19:26 +02:00
if (owner->guns[i].type != BT_NONE && (owner->guns[i].type == owner->selectedGunType || owner->combinedGuns))
2015-10-20 13:51:49 +02:00
{
s = sin(TO_RAIDANS(owner->angle));
c = cos(TO_RAIDANS(owner->angle));
2015-10-20 13:51:49 +02:00
x = (owner->guns[i].x * c) - (owner->guns[i].y * s);
y = (owner->guns[i].x * s) + (owner->guns[i].y * c);
2015-10-20 13:51:49 +02:00
x += owner->x;
y += owner->y;
2015-10-20 13:51:49 +02:00
b = createBullet(owner->guns[i].type, x, y, owner);
2015-10-20 13:51:49 +02:00
if (owner == player)
{
battle.stats[STAT_SHOTS_FIRED]++;
2015-10-20 13:51:49 +02:00
}
}
}
2015-10-20 13:51:49 +02:00
owner->reload = owner->reloadTime;
2016-05-15 09:19:26 +02:00
if (b)
{
playBattleSound(b->sound, owner->x, owner->y);
}
2015-10-20 13:51:49 +02:00
}
void fireRocket(Entity *owner)
{
Bullet *b;
b = createBullet(BT_ROCKET, owner->x, owner->y, owner);
playBattleSound(b->sound, owner->x, owner->y);
owner->reload = FPS;
if (owner == player)
{
battle.stats[STAT_ROCKETS_FIRED]++;
}
}
void fireMissile(Entity *owner)
2015-10-20 13:51:49 +02:00
{
Bullet *b;
b = createBullet(BT_MISSILE, owner->x, owner->y, owner);
2016-05-15 09:19:26 +02:00
b->dx *= 0.5;
b->dy *= 0.5;
2016-05-15 09:19:26 +02:00
b->life = MISSILE_LIFE;
owner->missiles--;
if (owner == player)
{
battle.stats[STAT_MISSILES_FIRED]++;
}
playBattleSound(b->sound, owner->x, owner->y);
2016-05-15 09:19:26 +02:00
if (b->target == player)
{
playSound(SND_INCOMING);
}
2015-10-20 13:51:49 +02:00
}
void destroyBulletDefs(void)
2016-02-25 00:12:13 +01:00
{
}
void destroyBullets(void)
2015-10-20 13:51:49 +02:00
{
free(bulletsToDraw);
bulletsToDraw = NULL;
2015-10-20 13:51:49 +02:00
}