1102 lines
21 KiB
C
1102 lines
21 KiB
C
/*
|
|
Copyright (C) 2015-2018 Parallel Realities
|
|
|
|
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 "fighters.h"
|
|
|
|
static void separate(void);
|
|
static void die(void);
|
|
static void immediateDie(void);
|
|
static void spinDie(void);
|
|
static void straightDie(void);
|
|
static void simpleDie(void);
|
|
static void randomizeDart(Entity *dart);
|
|
static void randomizeDartGuns(Entity *dart);
|
|
static void loadFighterDef(char *filename);
|
|
static void loadFighterDefList(char *filename);
|
|
static Entity *getFighterDef(char *name);
|
|
static void addFighterStat(char *name);
|
|
static void incFighterStat(char *key);
|
|
|
|
static Entity defHead, *defTail;
|
|
|
|
Entity *spawnFighter(char *name, int x, int y, int side)
|
|
{
|
|
Entity *e, *def;
|
|
|
|
e = spawnEntity();
|
|
|
|
def = getFighterDef(name);
|
|
|
|
memcpy(e, def, sizeof(Entity));
|
|
|
|
e->next = NULL;
|
|
|
|
e->x = x;
|
|
e->y = y;
|
|
e->side = side;
|
|
|
|
switch (side)
|
|
{
|
|
case SIDE_ALLIES:
|
|
e->aiAggression = 2 + rand() % 2;
|
|
if (!(e->aiFlags & AIF_FOLLOWS_PLAYER))
|
|
{
|
|
e->aiFlags |= AIF_MOVES_TO_PLAYER;
|
|
}
|
|
break;
|
|
|
|
case SIDE_PIRATE:
|
|
e->aiAggression = rand() % 3;
|
|
break;
|
|
|
|
case SIDE_PANDORAN:
|
|
e->aiAggression = 3 + rand() % 2;
|
|
break;
|
|
|
|
case SIDE_TZAC:
|
|
e->aiAggression = 1 + rand() % 3;
|
|
break;
|
|
}
|
|
|
|
if (strcmp(name, "ATAF") == 0)
|
|
{
|
|
e->aiAggression = 4;
|
|
}
|
|
|
|
if (strcmp(name, "Dart") == 0)
|
|
{
|
|
randomizeDart(e);
|
|
}
|
|
|
|
if (strcmp(name, "Civilian") == 0 && rand() % 2 == 0)
|
|
{
|
|
e->texture = getAtlasImage("gfx/craft/civilian02.png");
|
|
}
|
|
|
|
if (e->aiFlags & AIF_AGGRESSIVE)
|
|
{
|
|
e->aiAggression = 4;
|
|
}
|
|
|
|
e->action = doAI;
|
|
e->die = die;
|
|
|
|
if (game.currentMission->challengeData.isDeathMatch)
|
|
{
|
|
e->side = SDL_GetTicks();
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
static void randomizeDart(Entity *dart)
|
|
{
|
|
char texture[MAX_DESCRIPTION_LENGTH];
|
|
|
|
if (rand() % 5 == 0)
|
|
{
|
|
dart->health = dart->maxHealth = 5 + (rand() % 21);
|
|
}
|
|
|
|
if (rand() % 5 == 0)
|
|
{
|
|
dart->shield = dart->maxShield = 1 + (rand() % 16);
|
|
dart->shieldRechargeRate = 30 + (rand() % 90);
|
|
}
|
|
|
|
if (rand() % 5 == 0)
|
|
{
|
|
dart->speed = 2 + (rand() % 3);
|
|
}
|
|
|
|
if (rand() % 5 == 0)
|
|
{
|
|
dart->reloadTime = 24 + (rand() % 11);
|
|
}
|
|
|
|
randomizeDartGuns(dart);
|
|
|
|
dart->missiles = rand() % 3;
|
|
|
|
sprintf(texture, "gfx/fighters/dart0%d.png", 1 + rand() % 7);
|
|
|
|
dart->texture = getAtlasImage(texture);
|
|
}
|
|
|
|
static void randomizeDartGuns(Entity *dart)
|
|
{
|
|
int i;
|
|
|
|
switch (rand() % 4)
|
|
{
|
|
/* Single plasma gun */
|
|
case 0:
|
|
dart->guns[0].type = BT_PLASMA;
|
|
dart->guns[0].x = dart->guns[0].y = 0;
|
|
|
|
for (i = 1 ; i < MAX_FIGHTER_GUNS ; i++)
|
|
{
|
|
if (dart->guns[i].type)
|
|
{
|
|
dart->guns[i].type = BT_NONE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* Dual plasma guns */
|
|
case 1:
|
|
dart->guns[0].type = BT_PLASMA;
|
|
dart->guns[1].type = BT_PLASMA;
|
|
break;
|
|
|
|
/* Triple particle guns */
|
|
case 2:
|
|
dart->guns[2].type = BT_PARTICLE;
|
|
dart->guns[2].y = -10;
|
|
break;
|
|
|
|
|
|
/* Plasma / Laser cannons */
|
|
case 3:
|
|
dart->guns[0].type = BT_PLASMA;
|
|
dart->guns[0].x = dart->guns[0].y = 0;
|
|
|
|
dart->guns[1].type = BT_LASER;
|
|
dart->guns[1].x = dart->guns[1].y = 0;
|
|
break;
|
|
|
|
/* Dual Laser cannons */
|
|
case 4:
|
|
dart->guns[0].type = BT_LASER;
|
|
dart->guns[1].type = BT_LASER;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void resetFighter(Entity *fighter)
|
|
{
|
|
Entity *e;
|
|
|
|
e = spawnFighter(fighter->defName, fighter->x, fighter->y, fighter->side);
|
|
|
|
e->x += (rand() % 7500) - (rand() % 7500);
|
|
e->y += (rand() % 7500) - (rand() % 7500);
|
|
|
|
e->active = 0;
|
|
|
|
if (rand() % 4)
|
|
{
|
|
e->aiFlags |= AIF_TARGET_FOCUS;
|
|
}
|
|
}
|
|
|
|
void doFighter(void)
|
|
{
|
|
if (self->alive == ALIVE_ALIVE)
|
|
{
|
|
if (self != player)
|
|
{
|
|
separate();
|
|
}
|
|
|
|
if (!(self->flags & EF_DISABLED))
|
|
{
|
|
attachRope();
|
|
}
|
|
|
|
if (self->thrust > 0.25)
|
|
{
|
|
addEngineEffect();
|
|
}
|
|
|
|
if (self->health <= 0)
|
|
{
|
|
self->health = 0;
|
|
self->alive = ALIVE_DYING;
|
|
self->die();
|
|
|
|
if (self == battle.missionTarget)
|
|
{
|
|
battle.missionTarget = NULL;
|
|
}
|
|
}
|
|
else if (self->systemPower <= 0 || (self->flags & EF_DISABLED))
|
|
{
|
|
self->dx *= 0.99;
|
|
self->dy *= 0.99;
|
|
self->thrust = 0;
|
|
self->shield = self->maxShield = 0;
|
|
self->action = NULL;
|
|
|
|
if ((self->flags & EF_DISABLED) == 0)
|
|
{
|
|
playBattleSound(SND_POWER_DOWN, self->x, self->y);
|
|
|
|
self->flags |= EF_DISABLED;
|
|
self->flags |= EF_SECONDARY_TARGET;
|
|
|
|
if (self->aiFlags & AIF_SURRENDERING)
|
|
{
|
|
self->aiFlags |= AIF_SURRENDERED;
|
|
self->aiFlags &= ~AIF_SURRENDERING;
|
|
}
|
|
|
|
battle.stats[STAT_ENEMIES_DISABLED]++;
|
|
|
|
updateObjective(self->name, TT_DISABLE);
|
|
updateObjective(self->groupName, TT_DISABLE);
|
|
|
|
if (self->side != player->side)
|
|
{
|
|
runScriptFunction("ENEMIES_DISABLED %d", battle.stats[STAT_ENEMIES_DISABLED]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->target != NULL && self->target->alive != ALIVE_ALIVE)
|
|
{
|
|
self->target = NULL;
|
|
|
|
if (self != player)
|
|
{
|
|
self->action = doAI;
|
|
}
|
|
}
|
|
|
|
if (self->aiFlags & AIF_SUSPICIOUS)
|
|
{
|
|
checkSuspicionLevel();
|
|
}
|
|
|
|
if (self->aiFlags & AIF_ZAK_SUSPICIOUS)
|
|
{
|
|
checkZackariaSuspicionLevel();
|
|
}
|
|
}
|
|
|
|
if (self->alive == ALIVE_ESCAPED)
|
|
{
|
|
if (self == player && !game.currentMission->challengeData.isChallenge)
|
|
{
|
|
updateObjective("Player", TT_ESCAPED);
|
|
|
|
completeMission();
|
|
}
|
|
|
|
if (self->side != player->side && (!(self->flags & EF_DISABLED)))
|
|
{
|
|
addHudMessage(colors.red, _("Mission target has escaped."));
|
|
battle.stats[STAT_ENEMIES_ESCAPED]++;
|
|
}
|
|
|
|
if (strcmp(self->defName, "Civilian") == 0)
|
|
{
|
|
battle.stats[STAT_CIVILIANS_RESCUED]++;
|
|
}
|
|
|
|
/* if you did not escape under your own volition, or with the aid of a friend, you've been stolen */
|
|
if (!self->owner || self->side == self->owner->side)
|
|
{
|
|
updateObjective(self->name, TT_ESCAPED);
|
|
updateCondition(self->name, TT_ESCAPED);
|
|
}
|
|
else
|
|
{
|
|
updateObjective(self->name, TT_STOLEN);
|
|
updateCondition(self->name, TT_STOLEN);
|
|
}
|
|
}
|
|
|
|
if (self->alive == ALIVE_DEAD)
|
|
{
|
|
if (player->alive == ALIVE_ALIVE && self != player)
|
|
{
|
|
if (self->side != player->side)
|
|
{
|
|
if (!(self->flags & EF_NO_KILL_INC))
|
|
{
|
|
battle.stats[STAT_ENEMIES_KILLED]++;
|
|
|
|
runScriptFunction("ENEMIES_KILLED %d", battle.stats[STAT_ENEMIES_KILLED]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (strcmp(self->name, "Civilian") == 0)
|
|
{
|
|
battle.stats[STAT_CIVILIANS_KILLED]++;
|
|
if (!battle.isEpic || game.currentMission->challengeData.isChallenge)
|
|
{
|
|
addHudMessage(colors.red, _("Civilian has been killed"));
|
|
}
|
|
|
|
runScriptFunction("CIVILIANS_KILLED %d", battle.stats[STAT_CIVILIANS_KILLED]);
|
|
}
|
|
else
|
|
{
|
|
battle.stats[STAT_ALLIES_KILLED]++;
|
|
if (!battle.isEpic && !game.currentMission->challengeData.isChallenge)
|
|
{
|
|
addHudMessage(colors.red, _("Ally has been killed"));
|
|
}
|
|
|
|
runScriptFunction("ALLIES_KILLED %d", battle.stats[STAT_ALLIES_KILLED]);
|
|
}
|
|
}
|
|
|
|
updateObjective(self->name, TT_DESTROY);
|
|
updateObjective(self->groupName, TT_DESTROY);
|
|
|
|
if (battle.isEpic && self->killedBy == player)
|
|
{
|
|
updateObjective("EPIC_PLAYER_KILLS", TT_DESTROY);
|
|
}
|
|
|
|
adjustObjectiveTargetValue(self->name, TT_ESCAPED, -1);
|
|
|
|
updateCondition(self->name, TT_DESTROY);
|
|
updateCondition(self->groupName, TT_DESTROY);
|
|
|
|
/* don't fire if the opposing side is responsible */
|
|
if (self->aiFlags & AIF_SURRENDERED && self->killedBy->side == player->side)
|
|
{
|
|
updateCondition("SURRENDERED", TT_DESTROY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void separate(void)
|
|
{
|
|
int angle;
|
|
int distance;
|
|
float dx, dy, force;
|
|
int count;
|
|
Entity *e, **candidates;
|
|
int i;
|
|
|
|
dx = dy = 0;
|
|
count = 0;
|
|
force = 0;
|
|
|
|
candidates = getAllEntsInRadius(self->x, self->y, self->separationRadius, self);
|
|
|
|
for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i])
|
|
{
|
|
if ((e->flags & EF_TAKES_DAMAGE) && (!(e->flags & EF_NON_SOLID)))
|
|
{
|
|
distance = getDistance(e->x, e->y, self->x, self->y);
|
|
|
|
if (distance > 0 && distance < self->separationRadius)
|
|
{
|
|
angle = getAngle(self->x, self->y, e->x, e->y);
|
|
|
|
dx += sin(TO_RAIDANS(angle));
|
|
dy += -cos(TO_RAIDANS(angle));
|
|
force += (self->separationRadius - distance) * 0.005;
|
|
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count > 0)
|
|
{
|
|
dx /= count;
|
|
dy /= count;
|
|
|
|
dx *= force;
|
|
dy *= force;
|
|
|
|
self->dx -= dx;
|
|
self->dy -= dy;
|
|
}
|
|
}
|
|
|
|
void applyFighterThrust(void)
|
|
{
|
|
float v;
|
|
|
|
self->dx += sin(TO_RAIDANS(self->angle)) * 0.1;
|
|
self->dy += -cos(TO_RAIDANS(self->angle)) * 0.1;
|
|
self->thrust = sqrt((self->dx * self->dx) + (self->dy * self->dy));
|
|
|
|
if (self->thrust > self->speed * self->speed)
|
|
{
|
|
v = (self->speed / sqrt(self->thrust));
|
|
self->dx = v * self->dx;
|
|
self->dy = v * self->dy;
|
|
self->thrust = sqrt((self->dx * self->dx) + (self->dy * self->dy));
|
|
}
|
|
}
|
|
|
|
void applyFighterBrakes(void)
|
|
{
|
|
self->dx *= 0.95;
|
|
self->dy *= 0.95;
|
|
|
|
self->thrust = sqrt((self->dx * self->dx) + (self->dy * self->dy));
|
|
}
|
|
|
|
void damageFighter(Entity *e, int amount, long flags)
|
|
{
|
|
int prevShield = e->shield;
|
|
|
|
e->aiDamageTimer = FPS;
|
|
e->aiDamagePerSec += amount;
|
|
|
|
if (flags & BF_SYSTEM_DAMAGE)
|
|
{
|
|
if (e->shield > 0)
|
|
{
|
|
amount /= 2;
|
|
|
|
e->shield -= amount;
|
|
|
|
if (e->shield < 0)
|
|
{
|
|
amount = -e->shield;
|
|
}
|
|
}
|
|
|
|
if (amount >= 0)
|
|
{
|
|
e->systemPower = MAX(0, e->systemPower - amount);
|
|
|
|
e->systemHit = 255;
|
|
|
|
if (e->systemPower == 0)
|
|
{
|
|
e->shield = e->maxShield = 0;
|
|
e->action = NULL;
|
|
}
|
|
|
|
playBattleSound(SND_MAG_HIT, e->x, e->y);
|
|
}
|
|
}
|
|
else if (flags & BF_SHIELD_DAMAGE)
|
|
{
|
|
e->shield -= amount;
|
|
|
|
if (e->shield <= 0 && prevShield > 0)
|
|
{
|
|
playBattleSound(SND_SHIELD_BREAK, e->x, e->y);
|
|
addShieldSplinterEffect(e);
|
|
e->shield = -(FPS * 10);
|
|
}
|
|
|
|
e->shield = MAX(-(FPS * 10), e->shield);
|
|
}
|
|
else
|
|
{
|
|
if (e->shield > 0)
|
|
{
|
|
e->shield -= amount;
|
|
|
|
if (e->shield <= 0)
|
|
{
|
|
e->armourHit = 255;
|
|
e->health += e->shield;
|
|
e->shield = 0;
|
|
|
|
playBattleSound(SND_ARMOUR_HIT, e->x, e->y);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e->health -= amount;
|
|
e->armourHit = 255;
|
|
|
|
playBattleSound(SND_ARMOUR_HIT, e->x, e->y);
|
|
}
|
|
}
|
|
|
|
if (e->shield > 0)
|
|
{
|
|
e->shieldHit = 255;
|
|
|
|
playBattleSound(SND_SHIELD_HIT, e->x, e->y);
|
|
}
|
|
|
|
/* don't allow the shield to recharge immediately after taking a hit */
|
|
e->shieldRecharge = e->shieldRechargeRate;
|
|
|
|
/*
|
|
* Sometimes run away if you take too much damage in a short space of time
|
|
*/
|
|
if (e->type == ET_FIGHTER && e->alive == ALIVE_ALIVE && (!(e->aiFlags & AIF_EVADE)) && e != player && e->aiDamagePerSec >= (e->maxHealth + e->maxShield) * 0.1)
|
|
{
|
|
if ((rand() % 10) > 7)
|
|
{
|
|
e->action = doAI;
|
|
e->aiFlags |= AIF_EVADE;
|
|
e->aiActionTime = e->aiEvadeTimer = FPS * (1 + (rand() % 3));
|
|
}
|
|
else
|
|
{
|
|
e->aiDamagePerSec = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void die(void)
|
|
{
|
|
int n = rand() % 3;
|
|
|
|
switch (self->deathType)
|
|
{
|
|
case DT_ANY:
|
|
n = rand() % 3;
|
|
break;
|
|
case DT_NO_SPIN:
|
|
n = 1 + rand() % 2;
|
|
break;
|
|
case DT_INSTANT:
|
|
n = 2;
|
|
break;
|
|
case DT_SIMPLE:
|
|
n = 3;
|
|
break;
|
|
}
|
|
|
|
if (self == player && battle.isEpic)
|
|
{
|
|
n = 1;
|
|
}
|
|
|
|
switch (n)
|
|
{
|
|
case 0:
|
|
self->action = spinDie;
|
|
break;
|
|
case 1:
|
|
self->action = straightDie;
|
|
break;
|
|
case 2:
|
|
self->action = immediateDie;
|
|
break;
|
|
case 3:
|
|
self->action = simpleDie;
|
|
break;
|
|
}
|
|
|
|
if (self->killedBy == player && (!(self->flags & EF_NO_KILL_INC)))
|
|
{
|
|
battle.stats[STAT_ENEMIES_KILLED_PLAYER]++;
|
|
|
|
if (self->flags & EF_COMMON_FIGHTER)
|
|
{
|
|
incFighterStat(self->defName);
|
|
}
|
|
|
|
if (battle.isEpic && player->flags & EF_COMMON_FIGHTER)
|
|
{
|
|
battle.stats[STAT_EPIC_KILL_STREAK]++;
|
|
}
|
|
}
|
|
|
|
if (self->flags & EF_DROPS_ITEMS)
|
|
{
|
|
addRandomItem(self->x, self->y);
|
|
}
|
|
}
|
|
|
|
static void immediateDie(void)
|
|
{
|
|
self->alive = ALIVE_DEAD;
|
|
addSmallExplosion();
|
|
playBattleSound(SND_EXPLOSION_1 + rand() % 4, self->x, self->y);
|
|
addDebris(self->x, self->y, 3 + rand() % 6);
|
|
}
|
|
|
|
static void spinDie(void)
|
|
{
|
|
self->health--;
|
|
self->thinkTime = 0;
|
|
self->armourHit = 0;
|
|
self->shieldHit = 0;
|
|
self->systemHit = 0;
|
|
|
|
self->angle += 8;
|
|
|
|
if (rand() % 2 == 0)
|
|
{
|
|
addSmallFighterExplosion();
|
|
}
|
|
|
|
if (self->health <= -(FPS * 1.5))
|
|
{
|
|
self->alive = ALIVE_DEAD;
|
|
addSmallExplosion();
|
|
playBattleSound(SND_EXPLOSION_1 + rand() % 4, self->x, self->y);
|
|
addDebris(self->x, self->y, 3 + rand() % 6);
|
|
}
|
|
}
|
|
|
|
static void straightDie(void)
|
|
{
|
|
self->health--;
|
|
self->thinkTime = 0;
|
|
self->armourHit = 0;
|
|
self->shieldHit = 0;
|
|
self->systemHit = 0;
|
|
|
|
if (rand() % 2 == 0)
|
|
{
|
|
addSmallFighterExplosion();
|
|
}
|
|
|
|
if (self->health <= -(FPS * 1.5))
|
|
{
|
|
self->alive = ALIVE_DEAD;
|
|
addSmallExplosion();
|
|
playBattleSound(SND_EXPLOSION_1 + rand() % 4, self->x, self->y);
|
|
addDebris(self->x, self->y, 3 + rand() % 6);
|
|
}
|
|
}
|
|
|
|
static void simpleDie(void)
|
|
{
|
|
self->alive = ALIVE_DEAD;
|
|
addSmallExplosion();
|
|
playBattleSound(SND_EXPLOSION_1 + rand() % 4, self->x, self->y);
|
|
}
|
|
|
|
void retreatEnemies(void)
|
|
{
|
|
Entity *e;
|
|
|
|
for (e = battle.entityHead.next ; e != NULL ; e = e->next)
|
|
{
|
|
if (e->type == ET_FIGHTER && e->side != player->side)
|
|
{
|
|
e->flags |= EF_RETREATING;
|
|
|
|
e->aiFlags |= AIF_AVOIDS_COMBAT;
|
|
e->aiFlags |= AIF_UNLIMITED_RANGE;
|
|
e->aiFlags &= ~AIF_MOVES_TO_LEADER;
|
|
e->aiFlags &= ~AIF_WANDERS;
|
|
|
|
e->aiActionTime = MIN(e->aiActionTime, FPS);
|
|
|
|
if (!game.currentMission->challengeData.isChallenge)
|
|
{
|
|
e->aiFlags |= AIF_GOAL_JUMPGATE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void retreatAllies(void)
|
|
{
|
|
Entity *e;
|
|
|
|
for (e = battle.entityHead.next ; e != NULL ; e = e->next)
|
|
{
|
|
if (e->type == ET_FIGHTER && e->side == player->side)
|
|
{
|
|
e->flags |= EF_RETREATING;
|
|
|
|
e->aiFlags |= AIF_AVOIDS_COMBAT;
|
|
e->aiFlags |= AIF_UNLIMITED_RANGE;
|
|
e->aiFlags &= ~AIF_FOLLOWS_PLAYER;
|
|
e->aiFlags &= ~AIF_MOVES_TO_PLAYER;
|
|
e->aiFlags &= ~AIF_MOVES_TO_LEADER;
|
|
e->aiFlags &= ~AIF_WANDERS;
|
|
|
|
e->aiActionTime = MIN(e->aiActionTime, FPS);
|
|
|
|
if (!game.currentMission->challengeData.isChallenge)
|
|
{
|
|
e->aiFlags |= AIF_GOAL_JUMPGATE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static Entity *getFighterDef(char *name)
|
|
{
|
|
Entity *e;
|
|
|
|
for (e = defHead.next ; e != NULL ; e = e->next)
|
|
{
|
|
if (strcmp(e->name, name) == 0)
|
|
{
|
|
return e;
|
|
}
|
|
}
|
|
|
|
printf("Error: no such fighter '%s'\n", name);
|
|
exit(1);
|
|
}
|
|
|
|
Entity **getDBFighters(int *num)
|
|
{
|
|
Entity *e, **dbFighters;
|
|
int i;
|
|
|
|
i = *num = 0;
|
|
|
|
for (e = defHead.next ; e != NULL ; e = e->next)
|
|
{
|
|
if (e->description != NULL)
|
|
{
|
|
*num = *num + 1;
|
|
}
|
|
}
|
|
|
|
dbFighters = malloc(sizeof(Entity*) * *num);
|
|
|
|
for (e = defHead.next ; e != NULL ; e = e->next)
|
|
{
|
|
if (e->description != NULL)
|
|
{
|
|
dbFighters[i] = e;
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return dbFighters;
|
|
}
|
|
|
|
void loadFighterDefs(void)
|
|
{
|
|
memset(&defHead, 0, sizeof(Entity));
|
|
defTail = &defHead;
|
|
|
|
loadFighterDefList("data/fighters");
|
|
loadFighterDefList("data/craft");
|
|
loadFighterDefList("data/turrets");
|
|
}
|
|
|
|
static void loadFighterDefList(char *dir)
|
|
{
|
|
char **filenames;
|
|
char path[MAX_FILENAME_LENGTH];
|
|
int count, i;
|
|
|
|
filenames = getFileList(dir, &count);
|
|
|
|
for (i = 0 ; i < count ; i++)
|
|
{
|
|
sprintf(path, "%s/%s", dir, filenames[i]);
|
|
|
|
loadFighterDef(path);
|
|
|
|
free(filenames[i]);
|
|
}
|
|
|
|
free(filenames);
|
|
}
|
|
|
|
static void loadFighterDef(char *filename)
|
|
{
|
|
cJSON *root, *node;
|
|
char *text;
|
|
Entity *e;
|
|
int i, len;
|
|
|
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Loading %s", filename);
|
|
|
|
text = readFile(filename);
|
|
|
|
root = cJSON_Parse(text);
|
|
|
|
if (root)
|
|
{
|
|
e = malloc(sizeof(Entity));
|
|
memset(e, 0, sizeof(Entity));
|
|
defTail->next = e;
|
|
defTail = e;
|
|
|
|
e->type = ET_FIGHTER;
|
|
e->active = 1;
|
|
|
|
STRNCPY(e->name, cJSON_GetObjectItem(root, "name")->valuestring, MAX_NAME_LENGTH);
|
|
STRNCPY(e->defName, e->name, MAX_NAME_LENGTH);
|
|
STRNCPY(e->affiliation, cJSON_GetObjectItem(root, "affiliation")->valuestring, MAX_NAME_LENGTH);
|
|
e->health = e->maxHealth = cJSON_GetObjectItem(root, "health")->valueint;
|
|
e->shield = e->maxShield = getJSONValue(root, "shield", 0);
|
|
e->speed = cJSON_GetObjectItem(root, "speed")->valuedouble;
|
|
e->reloadTime = getJSONValue(root, "reloadTime", 0);
|
|
e->shieldRechargeRate = getJSONValue(root, "shieldRechargeRate", 0);
|
|
e->texture = getAtlasImage(cJSON_GetObjectItem(root, "texture")->valuestring);
|
|
|
|
if (strlen(cJSON_GetObjectItem(root, "description")->valuestring) > 0)
|
|
{
|
|
len = strlen(_(cJSON_GetObjectItem(root, "description")->valuestring)) + 1;
|
|
e->description = malloc(len);
|
|
strcpy(e->description, _(cJSON_GetObjectItem(root, "description")->valuestring));
|
|
}
|
|
|
|
e->w = e->texture->rect.w;
|
|
e->h = e->texture->rect.h;
|
|
|
|
if (cJSON_GetObjectItem(root, "guns"))
|
|
{
|
|
i = 0;
|
|
|
|
for (node = cJSON_GetObjectItem(root, "guns")->child ; node != NULL ; node = node->next)
|
|
{
|
|
e->guns[i].type = lookup(cJSON_GetObjectItem(node, "type")->valuestring);
|
|
e->guns[i].x = cJSON_GetObjectItem(node, "x")->valueint;
|
|
e->guns[i].y = cJSON_GetObjectItem(node, "y")->valueint;
|
|
|
|
i++;
|
|
|
|
if (i >= MAX_FIGHTER_GUNS)
|
|
{
|
|
printf("ERROR: cannot assign more than %d guns to a fighter\n", MAX_FIGHTER_GUNS);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
e->combinedGuns = getJSONValue(root, "combinedGuns", 0);
|
|
}
|
|
|
|
e->selectedGunType = e->guns[0].type;
|
|
|
|
e->missiles = getJSONValue(root, "missiles", 0);
|
|
|
|
if (cJSON_GetObjectItem(root, "flags"))
|
|
{
|
|
e->flags = flagsToLong(cJSON_GetObjectItem(root, "flags")->valuestring, NULL);
|
|
}
|
|
|
|
if (cJSON_GetObjectItem(root, "aiFlags"))
|
|
{
|
|
e->aiFlags = flagsToLong(cJSON_GetObjectItem(root, "aiFlags")->valuestring, NULL);
|
|
}
|
|
|
|
if (cJSON_GetObjectItem(root, "deathType"))
|
|
{
|
|
e->deathType = lookup(cJSON_GetObjectItem(root, "deathType")->valuestring);
|
|
}
|
|
|
|
if (e->flags & EF_COMMON_FIGHTER)
|
|
{
|
|
addFighterStat(e->name);
|
|
}
|
|
|
|
e->separationRadius = MAX(e->w, e->h) * 3;
|
|
|
|
e->systemPower = MAX_SYSTEM_POWER;
|
|
|
|
cJSON_Delete(root);
|
|
}
|
|
else
|
|
{
|
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, "Failed to load '%s'", filename);
|
|
}
|
|
|
|
free(text);
|
|
}
|
|
|
|
static void addFighterStat(char *key)
|
|
{
|
|
Tuple *t, *tail;
|
|
|
|
tail = &game.fighterStatHead;
|
|
|
|
for (t = game.fighterStatHead.next ; t != NULL ; t = t->next)
|
|
{
|
|
if (strcmp(t->key, key) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
tail = t;
|
|
}
|
|
|
|
t = malloc(sizeof(Tuple));
|
|
memset(t, 0, sizeof(Tuple));
|
|
tail->next = t;
|
|
|
|
STRNCPY(t->key, key, MAX_NAME_LENGTH);
|
|
t->value = 0;
|
|
|
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Added '%s' to fighter stats", key);
|
|
}
|
|
|
|
static void incFighterStat(char *key)
|
|
{
|
|
Tuple *t;
|
|
|
|
for (t = game.fighterStatHead.next ; t != NULL ; t = t->next)
|
|
{
|
|
if (strcmp(t->key, key) == 0)
|
|
{
|
|
t->value++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void loadFighters(cJSON *node)
|
|
{
|
|
Entity *e;
|
|
char **types, *name, *groupName, *type, *strpos;
|
|
int side, scatter, number, active;
|
|
int i, numTypes, addFlags, addAIFlags, id;
|
|
long flags, aiFlags;
|
|
float x, y;
|
|
|
|
if (node)
|
|
{
|
|
id = 0;
|
|
|
|
node = node->child;
|
|
|
|
while (node)
|
|
{
|
|
name = NULL;
|
|
groupName = NULL;
|
|
flags = -1;
|
|
aiFlags = -1;
|
|
|
|
types = toTypeArray(cJSON_GetObjectItem(node, "types")->valuestring, &numTypes);
|
|
side = lookup(cJSON_GetObjectItem(node, "side")->valuestring);
|
|
x = (cJSON_GetObjectItem(node, "x")->valuedouble / BATTLE_AREA_CELLS) * BATTLE_AREA_WIDTH;
|
|
y = (cJSON_GetObjectItem(node, "y")->valuedouble / BATTLE_AREA_CELLS) * BATTLE_AREA_HEIGHT;
|
|
name = getJSONValueStr(node, "name", NULL);
|
|
groupName = getJSONValueStr(node, "groupName", NULL);
|
|
number = getJSONValue(node, "number", 1);
|
|
scatter = getJSONValue(node, "scatter", 1);
|
|
active = getJSONValue(node, "active", 1);
|
|
|
|
if (cJSON_GetObjectItem(node, "flags"))
|
|
{
|
|
flags = flagsToLong(cJSON_GetObjectItem(node, "flags")->valuestring, &addFlags);
|
|
}
|
|
|
|
if (cJSON_GetObjectItem(node, "aiFlags"))
|
|
{
|
|
aiFlags = flagsToLong(cJSON_GetObjectItem(node, "aiFlags")->valuestring, &addAIFlags);
|
|
}
|
|
|
|
for (i = 0 ; i < number ; i++)
|
|
{
|
|
type = types[rand() % numTypes];
|
|
|
|
e = spawnFighter(type, x, y, side);
|
|
|
|
if (scatter > 1)
|
|
{
|
|
e->x += (rand() % scatter) - (rand() % scatter);
|
|
e->y += (rand() % scatter) - (rand() % scatter);
|
|
}
|
|
|
|
e->active = active;
|
|
|
|
if (flags != -1)
|
|
{
|
|
if (addFlags)
|
|
{
|
|
e->flags |= flags;
|
|
}
|
|
else
|
|
{
|
|
e->flags = flags;
|
|
|
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, "Flags for '%s' (%s) replaced", e->name, e->defName);
|
|
}
|
|
|
|
if (e->flags & EF_DISABLED)
|
|
{
|
|
e->speed = 0;
|
|
}
|
|
}
|
|
|
|
if (aiFlags != -1)
|
|
{
|
|
if (addAIFlags)
|
|
{
|
|
e->aiFlags |= aiFlags;
|
|
}
|
|
else
|
|
{
|
|
e->aiFlags = aiFlags;
|
|
|
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, "AI Flags for '%s' (%s) replaced", e->name, e->defName);
|
|
}
|
|
}
|
|
|
|
if (name)
|
|
{
|
|
STRNCPY(e->name, name, MAX_NAME_LENGTH);
|
|
|
|
/* update 'name #?' to 'name #1', etc. */
|
|
strpos = strstr(e->name, "#?");
|
|
|
|
if (strpos)
|
|
{
|
|
*(++strpos) = ('0' + ++id);
|
|
}
|
|
}
|
|
|
|
if (groupName)
|
|
{
|
|
STRNCPY(e->groupName, groupName, MAX_NAME_LENGTH);
|
|
}
|
|
}
|
|
|
|
node = node->next;
|
|
|
|
for (i = 0 ; i < numTypes ; i++)
|
|
{
|
|
free(types[i]);
|
|
}
|
|
|
|
free(types);
|
|
}
|
|
}
|
|
}
|
|
|
|
void destroyFighterDefs(void)
|
|
{
|
|
Entity *e;
|
|
|
|
while (defHead.next)
|
|
{
|
|
e = defHead.next;
|
|
defHead.next = e->next;
|
|
|
|
if (e->description)
|
|
{
|
|
free(e->description);
|
|
}
|
|
|
|
free(e);
|
|
}
|
|
}
|
|
|
|
void destroyFighterStats(void)
|
|
{
|
|
Tuple *t;
|
|
|
|
while (game.fighterStatHead.next)
|
|
{
|
|
t = game.fighterStatHead.next;
|
|
game.fighterStatHead.next = t->next;
|
|
free(t);
|
|
}
|
|
}
|