Lephisto/src/pilot.c
2013-08-09 20:45:17 +01:00

1279 lines
38 KiB
C

#include <string.h>
#include <math.h>
#include <stdlib.h>
#include "lephisto.h"
#include "log.h"
#include "weapon.h"
#include "pack.h"
#include "xml.h"
#include "spfx.h"
#include "rng.h"
#include "hook.h"
#include "map.h"
#include "pilot.h"
#define XML_ID "Fleets" /* XML section identifier. */
#define XML_FLEET "fleet"
#define FLEET_DATA "../dat/fleet.xml"
#define PILOT_CHUNK 32 /* Chunks to increment pilot_stack by. */
/* Stack of pilot id's to assure uniqueness. */
static unsigned int pilot_id = PLAYER_ID;
/* id for special mission cargo. */
static unsigned int mission_cargo_id = 0;
/* Stack of pilots. */
Pilot** pilot_stack = NULL; /* Not static, it is used in player.c and weapon.c and ai.c */
int pilot_nstack = 0;
static int pilot_mstack = 0;
extern Pilot* player;
extern unsigned int player_crating;
/* Stack of fleets. */
static Fleet* fleet_stack = NULL;
static int nfleets = 0;
/* External. */
/* AI. */
extern void ai_destroy(Pilot* p);
extern void ai_think(Pilot* pilot);
extern void ai_create(Pilot* pilot);
/* Player. */
extern void player_think(Pilot* pilot);
extern void player_brokeHyperspace(void);
extern double player_faceHyperspace(void);
extern void player_dead(void);
extern void player_destroyed(void);
extern int gui_load(const char* name);
/* Internal. */
static void pilot_shootWeapon(Pilot* p, PilotOutfit* w, const unsigned int t);
static void pilot_update(Pilot* pilot, const double dt);
static void pilot_hyperspace(Pilot* pilot);
void pilot_render(Pilot* pilot);
static void pilot_calcCargo(Pilot* pilot);
void pilot_free(Pilot* p);
static Fleet* fleet_parse(const xmlNodePtr parent);
static void pilot_dead(Pilot* p);
static int pilot_oquantity(Pilot* p, PilotOutfit* w);
/* Get the next pilot based on id. */
unsigned int pilot_getNextID(const unsigned int id) {
/* Binary search. */
int l, m, h;
l = 0;
h = pilot_nstack-1;
while(l <= h) {
m = (l+h) >> 1; /* For impossible overflow returning negative value. */
if(pilot_stack[m]->id > id) h = m-1;
else if(pilot_stack[m]->id < id) l = m+1;
else break;
}
if(m == (pilot_nstack-1)) return PLAYER_ID;
else return pilot_stack[m+1]->id;
}
/* Get the nearest enemy to the pilot -- Tamir's (insightful) request. */
unsigned int pilot_getNearestEnemy(const Pilot* p) {
unsigned int tp;
int i;
double d, td;
for(tp = 0, d = 0., i = 0; i < pilot_nstack; i++)
if(areEnemies(p->faction, pilot_stack[i]->faction)) {
td = vect_dist(&pilot_stack[i]->solid->pos, &p->solid->pos);
if(!pilot_isDisabled(pilot_stack[i]) && ((!tp) || (td < d))) {
d = td;
tp = pilot_stack[i]->id;
}
}
return tp;
}
/* Get the nearest hostile enemy to the player. */
unsigned pilot_getNearestHostile(void) {
unsigned int tp;
int i;
double d, td;
tp = PLAYER_ID;
d = 0;
for(i = 0; i < pilot_nstack; i++)
if(pilot_isFlag(pilot_stack[i], PILOT_HOSTILE) ||
areEnemies(FACTION_PLAYER, pilot_stack[i]->faction)) {
td = vect_dist(&pilot_stack[i]->solid->pos, &player->solid->pos);
if(!pilot_isDisabled(pilot_stack[i]) && ((tp == PLAYER_ID) || (td < d))) {
d = td;
tp = pilot_stack[i]->id;
}
}
return tp;
}
/* Get the nearest pilot. */
unsigned int pilot_getNearestPilot(const Pilot* p) {
unsigned int tp;
int i;
double d, td;
tp = PLAYER_ID;
d = 0;
for(i = 0; i < pilot_nstack; i++)
if(pilot_stack[i] != p) {
td = vect_dist(&pilot_stack[i]->solid->pos, &player->solid->pos);
if(!pilot_isDisabled(pilot_stack[i]) && ((tp == PLAYER_ID) || (td < d))) {
d = td;
tp = pilot_stack[i]->id;
}
}
return tp;
}
/* Pull a pilot out of the pilot_stack based on id. */
Pilot* pilot_get(const unsigned int id) {
if(id == PLAYER_ID) return player; /* Special case player. */
/* Binary search. */
int l, m, h;
l = 0;
h = pilot_nstack-1;
while(l <= h) {
m = (l+h)>>1;
if(pilot_stack[m]->id > id) h = m-1;
else if(pilot_stack[m]->id < id) l = m+1;
else return pilot_stack[m];
}
return NULL;
}
/* Grab a fleet out of the stack. */
Fleet* fleet_get(const char* name) {
int i;
for(i = 0; i < nfleets; i++)
if(strcmp(fleet_stack[i].name, name)==0)
return &fleet_stack[i];
WARN("Fleet '%s' not found in stack", name);
return NULL;
}
/* Attempt to turn the pilot to face dir. */
double pilot_face(Pilot* p, const float dir) {
double diff, turn;
diff = angle_diff(p->solid->dir, dir);
turn = -10.*diff;
if(turn > 1.) turn = 1.;
else if(turn < -1.) turn = -1.;
p->solid->dir_vel = 0.;
if(turn)
p->solid->dir_vel -= p->turn * turn;
return diff;
}
/* Get the amount of jumps the pilot has left. */
int pilot_getJumps(const Pilot* p) {
return (int)(p->fuel) / HYPERSPACE_FUEL;
}
/* Return quantity of a pilot outfit. */
static int pilot_oquantity(Pilot* p, PilotOutfit* w) {
return (outfit_isAmmo(w->outfit) && p->secondary) ?
p->secondary->quantity : w->quantity;
}
/* Get pilot's free weapon space. */
int pilot_freeSpace(Pilot* p) {
int i, s;
s = p->ship->cap_weapon;
for(i = 0; i < p->noutfits; i++)
s -= p->outfits[i].quantity * p->outfits[i].outfit->mass;
return s;
}
/* Mkay, this is how we shoot. Listen up. */
void pilot_shoot(Pilot* p, const unsigned int target, const int secondary) {
int i;
if(!p->outfits) return; /* No outfits. */
if(!secondary) {
/* Primary weapons. */
for(i = 0; i < p->noutfits; i++)
/* Cycle through outfits to find primary weapons. */
if(!outfit_isProp(p->outfits[i].outfit, OUTFIT_PROP_WEAP_SECONDARY))
pilot_shootWeapon(p, &p->outfits[i], target);
} else {
if(!p->secondary) return; /* No secondary weapon. */
pilot_shootWeapon(p, p->secondary, target);
}
}
static void pilot_shootWeapon(Pilot* p, PilotOutfit* w, const unsigned int t) {
int quantity, delay;
/* WElll... Trying to shoot when you have no ammo?? FUUU */
quantity = pilot_oquantity(p,w);
delay = outfit_delay(w->outfit);
/* Check to see if weapon is ready. */
if((SDL_GetTicks() - w->timer) < (unsigned int)(delay/quantity)) return;
/* Regular weapons. */
if(outfit_isWeapon(w->outfit) || (outfit_isTurret(w->outfit))) {
/* Different weapons. */
switch(w->outfit->type) {
case OUTFIT_TYPE_TURRET_BOLT:
case OUTFIT_TYPE_BOLT:
/* Enough energy? */
if(outfit_energy(w->outfit) > p->energy) return;
p->energy -= outfit_energy(w->outfit);
weapon_add(w->outfit, p->solid->dir, &p->solid->pos,
&p->solid->vel, p->id, t);
/* Can't shoot for a while. */
w->timer = SDL_GetTicks();
break;
default:
break;
}
}
/* Missile launchers. */
/* Must be secondary weapon, Shooter can't be the target. */
else if(outfit_isLauncher(w->outfit) && (w == p->secondary) && (p->id != t)) {
if(p->ammo && (p->ammo->quantity > 0)) {
/* Enough energy? */
if(outfit_energy(w->outfit) > p->energy) return;
p->energy -= outfit_energy(w->outfit);
weapon_add(p->ammo->outfit, p->solid->dir, &p->solid->pos,
&p->solid->vel, p->id, t);
w->timer = SDL_GetTicks(); /* Can't shoot for a while. */
p->ammo->quantity -= 1; /* There's no getting this one back. */
}
}
}
/* Damage the pilot. */
void pilot_hit(Pilot* p, const Solid* w, const unsigned int shooter,
const DamageType dtype, const double damage) {
int mod;
double damage_shield, damage_armour, knockback, dam_mod;
/* Calculate the damage. */
outfit_calcDamage(&damage_shield, &damage_armour, &knockback, dtype, damage);
if(p->shield - damage_shield > 0.) { /* Shields take the whole blow. */
p->shield -= damage_shield;
dam_mod = damage_shield/p->shield_max;
}
else if(p->shield > 0.) {
/* Shields can take part of the blow. */
p->armour -= p->shield/damage_shield*damage_armour;
p->shield = 0.;
dam_mod = (damage_shield+damage_armour) / (p->shield_max + p->armour_max);
}
else if(p->armour-damage_armour > 0.) {
p->armour -= damage_armour;
dam_mod = damage_armour/p->armour_max;
/* Shake us up a bit. */
if(p->id == PLAYER_ID)
spfx_shake(dam_mod*100.);
}
else {
/* We are officially dead. */
p->armour = 0.;
dam_mod = 0.;
if(!pilot_isFlag(p, PILOT_DEAD)) {
pilot_dead(p);
/* Adjust the combat rating based on pilot mass and ditto faction. */
if(shooter == PLAYER_ID) {
mod = (int)sqrt(p->ship->mass/25.);
player_crating += MAX(1, mod);
faction_modPlayer(p->faction, -mod);
}
}
}
/* Knock back effect is dependent on both damage and mass of the weapon. */
/* should probably turn it into a partial conservative collision.. */
vect_cadd(&p->solid->vel,
knockback * (w->vel.x * (dam_mod/6. + w->mass/p->solid->mass/6.)),
knockback * (w->vel.y * (dam_mod/6. + w->mass/p->solid->mass/6.)));
}
void pilot_dead(Pilot* p) {
if(pilot_isFlag(p, PILOT_DEAD)) return; /* She's already dead. */
/* Basically just set the timers.. */
if(p->id == PLAYER_ID) player_dead();
p->timer[0] = SDL_GetTicks(); /* No need for AI anymore. */
p->ptimer = p->timer[0] + 1000 + (unsigned int)sqrt(10*p->armour_max*p->shield_max);
p->timer[1] = p->timer[0]; /* Explosion timer. */
/* Flag cleanup - fixes some issues. */
if(pilot_isFlag(p, PILOT_HYP_PREP)) pilot_rmFlag(p, PILOT_HYP_PREP);
if(pilot_isFlag(p, PILOT_HYP_BEGIN)) pilot_rmFlag(p, PILOT_HYP_BEGIN);
if(pilot_isFlag(p, PILOT_HYPERSPACE)) pilot_rmFlag(p, PILOT_HYPERSPACE);
/* Our pilot is now deadz. */
pilot_setFlag(p, PILOT_DEAD);
/* Run hook if pilot has a death hook. */
if(p->hook_type == PILOT_HOOK_DEATH)
hook_runID(p->hook);
}
void pilot_setSecondary(Pilot* p, const char* secondary) {
int i;
/* No need for ammo if there is no secondary. */
if(secondary == NULL) {
p->secondary = NULL;
p->ammo = NULL;
return;
}
/* Find the secondary and set ammo appropriately. */
for(i = 0; i < p->noutfits; i++) {
if(strcmp(secondary, p->outfits[i].outfit->name)==0) {
p->secondary = &p->outfits[i];;
pilot_setAmmo(p);
return;
}
}
WARN("Attempted to set pilot '%s' secondary weapon to non-existing '%s'",
p->name, secondary);
p->secondary = NULL;
p->ammo = NULL;
}
/* Set the pilot's ammo based on their secondary weapon. */
void pilot_setAmmo(Pilot* p) {
int i;
char* name;
/* Only launchers use ammo. */
if((p->secondary == NULL) || !outfit_isLauncher(p->secondary->outfit)) {
p->ammo = NULL;
return;
}
/* Find the ammo and set it. */
name = p->secondary->outfit->u.lau.ammo;
for(i = 0; i < p->noutfits; i++)
if(strcmp(p->outfits[i].outfit->name, name)==0) {
p->ammo = &p->outfits[i];
return;
}
/* None found, so we assume if doesn't need ammo. */
p->ammo = NULL;
}
/* Set the pilots afterburner. */
void pilot_setAfterburner(Pilot* p) {
int i;
for(i = 0; i < p->noutfits; i++)
if(outfit_isAfterburner(p->outfits[i].outfit)) {
p->afterburner = &p->outfits[i];
return;
}
p->afterburner = NULL;
}
/* Render the pilot. */
void pilot_render(Pilot* p) {
gl_blitSprite(p->ship->gfx_space,
p->solid->pos.x, p->solid->pos.y,
p->tsx, p->tsy, NULL);
}
/* Update the pilot. */
static void pilot_update(Pilot* pilot, const double dt) {
int i;
unsigned int t, l;
double a, px, py, vx, vy;
/* She's dead D: */
if(pilot_isFlag(pilot, PILOT_DEAD)) {
t = SDL_GetTicks();
if(t > pilot->ptimer) { /* Completely destroyed with final explosion. */
if(pilot->id == PLAYER_ID) /* Player handled differently. */
player_destroyed();
pilot_setFlag(pilot, PILOT_DELETE); /* It'll get deleted next frame. */
return;
}
/* Final explosion. */
if(!pilot_isFlag(pilot, PILOT_EXPLODED) && (t > pilot->ptimer - 200)) {
spfx_add(spfx_get("ExpL"),
VX(pilot->solid->pos), VY(pilot->solid->pos),
VX(pilot->solid->vel), VY(pilot->solid->vel), SPFX_LAYER_BACK);
pilot_setFlag(pilot, PILOT_EXPLODED);
/* Release cargo. */
for(i = 0; i < pilot->ncommodities; i++)
commodity_Jettison(pilot->id, pilot->commodities[i].commodity,
pilot->commodities[i].quantity);
}
/* Reset random explosion time. */
else if(t > pilot->timer[1]) {
pilot->timer[1] = t +
(unsigned int)(100*(double)(pilot->ptimer - pilot->timer[1]) /
(double)(pilot->ptimer - pilot->timer[0]));
/* Random position on ship. */
a = RNGF()*2.*M_PI;
px = VX(pilot->solid->pos) + cos(a)*RNGF()*pilot->ship->gfx_space->sw/2.;
py = VY(pilot->solid->pos) + sin(a)*RNGF()*pilot->ship->gfx_space->sh/2.;
vx = VX(pilot->solid->vel);
vy = VY(pilot->solid->vel);
/* Set explosions. */
l = (pilot->id == PLAYER_ID) ? SPFX_LAYER_FRONT : SPFX_LAYER_BACK;
if(RNGF() > 0.8) spfx_add(spfx_get("ExpM"), px, py, vx, vy, l);
else spfx_add(spfx_get("ExpS"), px, py, vx, vy, l);
}
}
else if(pilot->armour <= 0.) /* PWNED! */
pilot_dead(pilot); /* Start death stuff. */
/* Purpose fallthrough to get the movement similar to disabled. */
if((pilot != player) &&
(pilot->armour < PILOT_DISABLED_ARMOUR*pilot->armour_max)) { /* Disabled. */
/* First time pilot is disabled. */
if(!pilot_isFlag(pilot, PILOT_DISABLED)) {
pilot_setFlag(pilot, PILOT_DISABLED); /* Set as disabled. */
/* Run hook. */
if(pilot->hook_type == PILOT_HOOK_DISABLE)
hook_runID(pilot->hook);
}
/* Come to a halt slowly. */
vect_pset(&pilot->solid->vel,
VMOD(pilot->solid->vel) * (1. - dt*0.10), VANGLE(pilot->solid->vel));
vectnull(&pilot->solid->force); /* No more accel. */
pilot->solid->dir_vel = 0.; /* Stop it from turning. */
/* Update the solid. */
pilot->solid->update(pilot->solid, dt);
gl_getSpriteFromDir(&pilot->tsx, &pilot->tsy,
pilot->ship->gfx_space, pilot->solid->dir);
return;
}
/* We are still alive. */
else if(pilot->armour < pilot->armour_max)
/* Regen armour. */
pilot->armour += pilot->armour_regen*dt;
else
/* And shields. */
pilot->shield += pilot->shield_regen*dt;
/* Update energy. */
if((pilot->energy < 1.) && pilot_isFlag(pilot, PILOT_AFTERBURNER))
pilot_rmFlag(pilot, PILOT_AFTERBURNER); /* Break afterburner. */
pilot->energy += pilot->energy_regen * dt;
/* Check limits. */
if(pilot->armour > pilot->armour_max) pilot->armour = pilot->armour_max;
if(pilot->shield > pilot->shield_max) pilot->shield = pilot->shield_max;
if(pilot->energy > pilot->energy_max) pilot->energy = pilot->energy_max;
/* Update the solid. */
(*pilot->solid->update)(pilot->solid, dt);
gl_getSpriteFromDir(&pilot->tsx, &pilot->tsy,
pilot->ship->gfx_space, pilot->solid->dir);
if(!pilot_isFlag(pilot, PILOT_HYPERSPACE)) { /* Limit the speed. */
/* Pilot is afterburning. */
if(pilot_isFlag(pilot, PILOT_AFTERBURNER) && /* Must have enough energy. */
(player->energy > pilot->afterburner->outfit->u.afb.energy * dt)) {
limit_speed(&pilot->solid->vel, /* Limit is higher. */
pilot->speed * pilot->afterburner->outfit->u.afb.speed_perc +
pilot->afterburner->outfit->u.afb.speed_abs, dt);
spfx_shake(SHAKE_DECAY/2. * dt); /* Shake goes down at half speed. */
pilot->energy -= pilot->afterburner->outfit->u.afb.energy * dt; /* Energy loss. */
} else /* Normal limit. */
limit_speed(&pilot->solid->vel, pilot->speed, dt);
}
}
/* Pilot is getting ready or is in, hyperspace. */
static void pilot_hyperspace(Pilot* p) {
double diff;
/* Pilot is actually in hyperspace. */
if(pilot_isFlag(p, PILOT_HYPERSPACE)) {
/* Has the jump happened? */
if(SDL_GetTicks() > p->ptimer) {
if(p == player) {
player_brokeHyperspace();
} else
pilot_setFlag(p, PILOT_DELETE); /* Set flag to delete pilot. */
return;
}
/* Keep accelerating - hyperspace uses much bigger accel. */
vect_pset(&p->solid->force, p->thrust * 5., p->solid->dir);
}
/* Engines getting ready for the jump. */
else if(pilot_isFlag(p, PILOT_HYP_BEGIN)) {
if(SDL_GetTicks() > p->ptimer) {
/* Engines are ready. */
p->ptimer = SDL_GetTicks() + HYPERSPACE_FLY_DELAY;
pilot_setFlag(p, PILOT_HYPERSPACE);
}
} else {
/* Pilot is getting ready for hyperspace. */
/* Brake. */
if(VMOD(p->solid->vel) > MIN_VEL_ERR) {
diff = pilot_face(p, VANGLE(p->solid->vel) + M_PI);
if(ABS(diff) < MAX_DIR_ERR)
vect_pset(&p->solid->force, p->thrust, p->solid->dir);
} else {
/* Face target. */
vectnull(&p->solid->force); /* Stop accelerating. */
/* Player should actually face the system she's headed to. */
if(p == player) diff = player_faceHyperspace();
else diff = pilot_face(p, VANGLE(p->solid->pos));
if(ABS(diff) < MAX_DIR_ERR) {
/* We should prepare for the jump now. */
p->solid->dir_vel = 0.;
p->ptimer = SDL_GetTicks() + HYPERSPACE_ENGINE_DELAY;
pilot_setFlag(p, PILOT_HYP_BEGIN);
}
}
}
}
int pilot_addOutfit(Pilot* pilot, Outfit* outfit, int quantity) {
int i, q, free_space;
char* osec;
free_space = pilot_freeSpace(pilot);
q = quantity;
/* Special case if it's a map. */
if(outfit_isMap(outfit)) {
map_map(NULL, outfit->u.map.radius);
return 1; /* Must return 1 for paying purposes. */
}
/* Mod quantity down if it doesn't fit. */
if(q*outfit->mass > free_space)
q = free_space / outfit->mass;
/* Can we actually add any? */
if(q == 0)
return 0;
/* Does outfit already exist? */
for(i = 0; i < pilot->noutfits; i++)
if(strcmp(outfit->name, pilot->outfits[i].outfit->name)==0) {
pilot->outfits[i].quantity += q;
/* Can't be over max. */
if(pilot->outfits[i].quantity > outfit->max) {
q -= pilot->outfits[i].quantity - outfit->max;
pilot->outfits[i].quantity = outfit->max;
}
/* Recalculate the stats. */
pilot_calcStats(pilot);
return q;
}
/* Hacks in case it reallocs. */
osec = (pilot->secondary) ? pilot->secondary->outfit->name : NULL;
/* No need for ammo since it's already handled in setSecondary, */
/* since pilot has only one afterburner it's handled at the end. */
/* Grow the outfits. */
pilot->outfits = realloc(pilot->outfits, (pilot->noutfits+1)*sizeof(PilotOutfit));
pilot->outfits[pilot->noutfits].outfit = outfit;
pilot->outfits[pilot->noutfits].quantity = q;
/* Can't be over max. */
if(pilot->outfits[pilot->noutfits].quantity > outfit->max) {
q -= pilot->outfits[pilot->noutfits].quantity - outfit->max;
pilot->outfits[i].quantity = outfit->max;
}
pilot->outfits[pilot->noutfits].timer = 0; /* Reset time. */
(pilot->noutfits)++;
if(outfit_isTurret(outfit))
/* Used to speed up AI. */
pilot_setFlag(pilot, PILOT_HASTURRET);
/* Hack due to realloc possibility. */
pilot_setSecondary(pilot, osec);
pilot_setAfterburner(pilot);
pilot_calcStats(pilot);
return q;
}
/* Remove an outfit from the pilot. */
int pilot_rmOutfit(Pilot* pilot, Outfit* outfit, int quantity) {
int i, q, c;
char* osec;
c = (outfit_isMod(outfit)) ? outfit->u.mod.cargo : 0;
q = quantity;
for(i = 0; i < pilot->noutfits; i++)
if(strcmp(outfit->name, pilot->outfits[i].outfit->name)==0) {
pilot->outfits[i].quantity -= quantity;
if(pilot->outfits[i].quantity <= 0) {
/* We didn't actually remove the full amount. */
q += pilot->outfits[i].quantity;
/* Hack in case it reallocs - Can happen even when shrinking. */
osec = (pilot->secondary) ? pilot->secondary->outfit->name : NULL;
/* Remove the outfit. */
memmove(pilot->outfits+i, pilot->outfits+i+1,
sizeof(PilotOutfit)*(pilot->noutfits-i-1));
pilot->noutfits--;
pilot->outfits = realloc(pilot->outfits,
sizeof(PilotOutfit)*(pilot->noutfits));
/* Set secondary and afterburner. */
pilot_setSecondary(pilot, osec);
pilot_setAfterburner(pilot);
}
pilot_calcStats(pilot); /* Recalculate stats. */
pilot->cargo_free -= c;
return q;
}
WARN("Failure attempting to remove %d '%s' from pilot '%s'",
quantity, outfit->name, pilot->name);
return 0;
}
/* Return all the outfits in a nice text form. */
char* pilot_getOutfits(Pilot* pilot) {
int i;
char buf[64], *str;
str = malloc(sizeof(char)*1024);
buf[0] = '\0';
/* First outfit. */
if(pilot->noutfits > 0)
snprintf(str, 1024, "%dx %s",
pilot->outfits[0].quantity, pilot->outfits[0].outfit->name);
/* Rest of the outfits. */
for(i = 1; i < pilot->noutfits; i++) {
snprintf(buf, 64, ", %dx %s",
pilot->outfits[i].quantity, pilot->outfits[i].outfit->name);
strcat(str, buf);
}
return str;
}
/* Recalculate the pilot's stats based on her outfits. */
void pilot_calcStats(Pilot* pilot) {
int i;
double q;
Outfit* o;
double ac, sc, ec, fc; /* Temp health coeficients to set. */
/* -- Set up the basic stuff. */
/* Movement. */
pilot->thrust = pilot->ship->thrust;
pilot->turn = pilot->ship->turn;
pilot->speed = pilot->ship->speed;
/* Health. */
ac = pilot->armour / pilot->armour_max;
sc = pilot->shield / pilot->shield_max;
ec = pilot->energy / pilot->energy_max;
fc = pilot->fuel / pilot->fuel_max;
pilot->armour_max = pilot->ship->armour;
pilot->shield_max = pilot->ship->shield;
pilot->energy_max = pilot->ship->energy;
pilot->fuel_max = pilot->ship->fuel;
pilot->armour_regen = pilot->ship->armour_regen;
pilot->shield_regen = pilot->ship->shield_regen;
pilot->energy_regen = pilot->ship->energy_regen;
/* Jamming. */
pilot->jam_range = 0.;
pilot->jam_chance = 0.;
/* Cargo has to be reset. */
pilot_calcCargo(pilot);
/* Now add outfit changes. */
for(i = 0; i < pilot->noutfits; i++) {
o = pilot->outfits[i].outfit;
q = (double) pilot->outfits[i].quantity;
if(outfit_isMod(o)) { /* Modification. */
/* Movement. */
pilot->thrust += o->u.mod.thrust * q;
pilot->turn += o->u.mod.turn * q;
pilot->speed += o->u.mod.speed * q;
/* Health. */
pilot->armour_max += o->u.mod.armour * q;
pilot->armour_regen += o->u.mod.armour_regen * q;
pilot->shield_max += o->u.mod.shield * q;
pilot->shield_regen += o->u.mod.shield_regen * q;
pilot->energy_max += o->u.mod.energy * q;
pilot->energy_regen += o->u.mod.energy_regen * q;
/* Fuel. */
pilot->fuel_max += o->u.mod.fuel * q;
/* Misc. */
pilot->cargo_free += o->u.mod.cargo * q;
}
else if(outfit_isAfterburner(o)) /* Afterburner. */
pilot->afterburner = &pilot->outfits[i]; /* Set afterburner. */
else if(outfit_isJammer(o)) { /* Jammer. */
if(pilot->jam_chance < o->u.jam.chance) { /* Substitude. */
/* TODO: Make more jammers improve overall. */
pilot->jam_range = o->u.jam.range;
pilot->jam_chance = o->u.jam.chance;
}
pilot->energy_regen -= o->u.jam.energy;
}
}
/* Give the pilot her health proportion back. */
pilot->armour = ac * pilot->armour_max;
pilot->shield = sc * pilot->shield_max;
pilot->energy = ec * pilot->energy_max;
pilot->fuel = fc * pilot->fuel_max;
}
/* Pilot free cargo space. */
int pilot_cargoFree(Pilot* p) {
return p->cargo_free;
}
/* Try to add quantity of cargo to pilot, return quantity actually added. */
int pilot_addCargo(Pilot* pilot, Commodity* cargo, int quantity) {
int i, q;
/* Check if pilot has it first. */
q = quantity;
for(i = 0; i < pilot->ncommodities; i++)
if(!pilot->commodities[i].id && (pilot->commodities[i].commodity == cargo)) {
if(pilot_cargoFree(pilot) < quantity)
q = pilot_cargoFree(pilot);
pilot->commodities[i].quantity += q;
pilot->cargo_free -= q;
pilot->solid->mass += q;
return q;
}
/* Must add another one. */
pilot->commodities = realloc(pilot->commodities,
sizeof(PilotCommodity) * (pilot->ncommodities+1));
pilot->commodities[pilot->ncommodities].commodity = cargo;
if(pilot_cargoFree(pilot) < quantity)
q = pilot_cargoFree(pilot);
pilot->commodities[pilot->ncommodities].id = 0;
pilot->commodities[pilot->ncommodities].quantity = q;
pilot->cargo_free -= q;
pilot->ncommodities++;
return q;
}
/* Return the amount of cargo onboard the ship. */
int pilot_cargoUsed(Pilot* pilot) {
int i, q;
q = 0;
pilot->cargo_free = pilot->ship->cap_cargo;
for(i = 0; i < pilot->ncommodities; i++)
q += pilot->commodities[i].quantity;
return q;
}
/* Calculate how much cargo ship has left etc. */
static void pilot_calcCargo(Pilot* pilot) {
int q;
q = pilot_cargoUsed(pilot);
pilot->cargo_free -= q; /* Reduce space left. */
pilot->solid->mass = pilot->ship->mass + q; /* Cargo affects weight. */
}
unsigned int pilot_addMissionCargo(Pilot* pilot, Commodity* cargo, int quantity) {
int q;
q = quantity;
pilot->commodities = realloc(pilot->commodities,
sizeof(PilotCommodity) * (pilot->ncommodities+1));
pilot->commodities[pilot->ncommodities].commodity = cargo;
if(pilot_cargoFree(pilot) < quantity)
q = pilot_cargoFree(pilot);
pilot->commodities[pilot->ncommodities].id = ++mission_cargo_id;
pilot->commodities[pilot->ncommodities].quantity = q;
pilot->cargo_free -= q;
pilot->solid->mass += q;
pilot->ncommodities++;
return pilot->commodities[pilot->ncommodities-1].id;
}
/* Remove special misssion cargo based on id. */
int pilot_rmMissionCargo(Pilot* pilot, unsigned int cargo_id) {
int i;
/* Check if pilot has it. */
for(i = 0; i < pilot->ncommodities; i++)
if(pilot->commodities[i].id == cargo_id)
break;
if(i >= pilot->ncommodities)
return 1; /* Pilot doesn't have it. */
/* Remove cargo. */
pilot->cargo_free += pilot->commodities[i].quantity;
pilot->solid->mass -= pilot->commodities[i].quantity;
memmove(pilot->commodities+i, pilot->commodities+i+1,
sizeof(PilotCommodity) * (pilot->ncommodities-i-1));
pilot->ncommodities--;
pilot->commodities = realloc(pilot->commodities,
sizeof(PilotCommodity) * pilot->ncommodities);
return 0;
}
/* Try to get rid of quantity cargo from pilot, */
/* return quantity actually removed. */
int pilot_rmCargo(Pilot* pilot, Commodity* cargo, int quantity) {
int i, q;
/* Check if pilot has it. */
q = quantity;
for(i = 0; i < pilot->ncommodities; i++)
/* Doesn't remove mission cargo. */
if(!pilot->commodities[i].id && (pilot->commodities[i].commodity == cargo)) {
if(quantity >= pilot->commodities[i].quantity) {
q = pilot->commodities[i].quantity;
/* Remove cargo. */
memmove(pilot->commodities+i, pilot->commodities+i+1,
sizeof(PilotCommodity)*(pilot->ncommodities-i));
pilot->ncommodities--;
pilot->commodities = realloc(pilot->commodities,
sizeof(PilotCommodity)*pilot->ncommodities);
} else
pilot->commodities[i].quantity -= q;
pilot->cargo_free += q;
pilot->solid->mass -= q;
return q;
}
return 0; /* Pilot didn't have it. */
}
/* Add a hook to the pilot. */
void pilot_addHook(Pilot* pilot, int type, int hook) {
pilot->hook_type = type;
pilot->hook = hook;
}
/* ==Init pilot.=========================================== */
/* ship : Ship pilot is flying. */
/* name : Pilot's name, if NULL, ships name will be used. */
/* dir : Initial facing direction. (radians) */
/* vel : Initial velocity. */
/* pos : Initial position. */
/* flags : Tweaking the pilot. */
/* ======================================================== */
void pilot_init(Pilot* pilot, Ship* ship, char* name, int faction,
AI_Profile* ai, const double dir, const Vec2* pos,
const Vec2* vel, const int flags) {
ShipOutfit* so;
if(flags & PILOT_PLAYER) /* Player is ID 0 */
pilot->id = PLAYER_ID;
else
pilot->id = ++pilot_id; /* New unique pilot id based on pilot_id, Can't be 0. */
pilot->ship = ship;
pilot->name = strdup((name == NULL) ? ship->name : name);
/* Faction. */
pilot->faction = faction;
/* AI. */
pilot->ai = ai;
pilot->tcontrol = 0;
pilot->flags = 0;
pilot->lockons = 0;
/* Solid. */
pilot->solid = solid_create(ship->mass, dir, pos, vel);
/* Initially idle. */
pilot->task = NULL;
/* Outfits. */
pilot->outfits = NULL;
pilot->secondary = NULL;
pilot->ammo = NULL;
pilot->afterburner = NULL;
pilot->noutfits = 0;
if(!(flags & PILOT_NO_OUTFITS)) {
if(ship->outfit) {
pilot->noutfits = 0;
for(so = ship->outfit; so; so = so->next) {
pilot->outfits = realloc(pilot->outfits, (pilot->noutfits+1)*sizeof(PilotOutfit));
pilot->outfits[pilot->noutfits].outfit = so->data;
pilot->outfits[pilot->noutfits].quantity = so->quantity;
pilot->outfits[pilot->noutfits].timer = 0;
(pilot->noutfits)++;
if(outfit_isTurret(so->data)) /* Used to speed up AI a bit. */
pilot_setFlag(pilot, PILOT_HASTURRET);
}
}
}
/* Jamming - must be set before calcStats. */
pilot->jam_range = 0.;
pilot->jam_chance = 0.;
/* Cargo must be set before calcStats. */
pilot->credits = 0;
pilot->commodities = NULL;
pilot->ncommodities = 0;
pilot->cargo_free = pilot->ship->cap_cargo; /* should get redone with calcCargo. */
/* Set the pilot stats based on her ship and outfits. */
/* Hack to have full armour/shield/energy/fuel. */
pilot->armour = pilot->armour_max = 1.;
pilot->shield = pilot->shield_max = 1.;
pilot->energy = pilot->energy_max = 1.;
pilot->fuel = pilot->fuel_max = 1.;
pilot_calcStats(pilot);
/* Hooks. */
pilot->hook_type = PILOT_HOOK_NONE;
pilot->hook = 0;
/* Set flags and functions. */
if(flags & PILOT_PLAYER) {
pilot->think = player_think; /* Players don't need to thing! :P */
pilot->render = NULL; /* Render will be called from player_think */
pilot_setFlag(pilot, PILOT_PLAYER); /* It's a player! */
/* Bit of a hack. */
if(!(flags & PILOT_EMPTY)) {
player = pilot;
gui_load(pilot->ship->gui); /* Load the GUI. */
}
} else {
pilot->think = ai_think;
pilot->render = pilot_render;
ai_create(pilot); /* Will run the create function in ai. */
}
/* All update the same way. */
pilot->update = pilot_update;
}
/* Create a new pilot - Params are same as pilot_init. Return pilot's id. */
unsigned int pilot_create(Ship* ship, char* name, int faction,
AI_Profile* ai, const double dir, const Vec2* pos,
const Vec2* vel, const int flags) {
Pilot** tp, *dyn;
dyn = MALLOC_L(Pilot);
if(dyn == NULL) {
WARN("Unable to allocate memory.");
return 0;
}
pilot_init(dyn, ship, name, faction, ai, dir, pos, vel, flags);
/* See if memory needs to grow. */
if(pilot_nstack+1 > pilot_mstack) { /* Needs to grow. */
pilot_mstack += PILOT_CHUNK;
tp = pilot_stack;
pilot_stack = realloc(pilot_stack, pilot_mstack*sizeof(Pilot*));
}
/* Set the pilot in the stack. */
pilot_stack[pilot_nstack] = dyn;
pilot_nstack++; /* There's a new pilot. */
return dyn->id;
}
Pilot* pilot_createEmpty(Ship* ship, char* name,
int faction, AI_Profile* ai, const int flags) {
Pilot* dyn;
dyn = MALLOC_L(Pilot);
pilot_init(dyn, ship, name, faction, ai, 0., NULL, NULL, flags | PILOT_EMPTY);
return dyn;
}
/* Copy src pilot to dest. */
Pilot* pilot_copy(Pilot* src) {
Pilot* dest = malloc(sizeof(Pilot));
memcpy(dest, src, sizeof(Pilot));
if(src->name) dest->name = strdup(src->name);
/* Solid. */
dest->solid = malloc(sizeof(Solid));
memcpy(dest->solid, src->solid, sizeof(Solid));
/* Copy outfits. */
dest->outfits = malloc(sizeof(PilotOutfit)*src->noutfits);
memcpy(dest->outfits, src->outfits,
sizeof(PilotOutfit)*src->noutfits);
dest->secondary = NULL;
dest->ammo = NULL;
dest->afterburner = NULL;
/* Copy commodities. */
dest->commodities = malloc(sizeof(PilotCommodity)*src->ncommodities);
memcpy(dest->commodities, src->commodities,
sizeof(PilotCommodity)*src->ncommodities);
/* Ai is not copied. */
dest->task = NULL;
/* Will set afterburner and correct stats. */
pilot_calcStats(dest);
return dest;
}
/* Frees and cleans up a pilot. */
void pilot_free(Pilot* p) {
if(player == p) player = NULL;
solid_free(p->solid);
if(p->outfits) free(p->outfits);
free(p->name);
if(p->commodities) free(p->commodities);
ai_destroy(p);
free(p);
}
/* Destroy pilot from stack. */
void pilot_destroy(Pilot* p) {
int i;
/* Find the pilot. */
for(i = 0; i < pilot_nstack; i++)
if(pilot_stack[i] == p)
break;
/* Pilot is eliminated. */
pilot_free(p);
pilot_nstack--;
/* Copy other pilots down. */
memmove(&pilot_stack[i], &pilot_stack[i+1], (pilot_nstack-i)*sizeof(Pilot*));
}
/* Free the prisoned pilot! */
void pilots_free(void) {
int i;
for(i = 0; i < pilot_nstack; i++)
pilot_free(pilot_stack[i]);
free(pilot_stack);
pilot_stack = NULL;
player = NULL;
pilot_nstack = 0;
}
/* Clean up the pilots - Leaves the player. */
void pilots_clean(void) {
int i;
for(i = 0; i < pilot_nstack; i++)
/* We'll set player at priveleged position. */
if((player != NULL) && (pilot_stack[i] == player)) {
pilot_stack[0] = player;
pilot_stack[0]->lockons = 0; /* Clear lockons. */
}
else /* Rest get killed. */
pilot_free(pilot_stack[i]);
if(player != NULL) /* Set stack to 1 if pilot exists. */
pilot_nstack = 1;
}
void pilots_cleanAll(void) {
pilots_clean();
if(player != NULL) {
pilot_free(player);
player = NULL;
}
pilot_nstack = 0;
}
/* Update all pilots. */
void pilots_update(double dt) {
int i;
for(i = 0; i < pilot_nstack; i++) {
if(pilot_stack[i]->think && !pilot_isDisabled(pilot_stack[i])) {
/* Hyperspace gets special treatment. */
if(pilot_isFlag(pilot_stack[i], PILOT_HYP_PREP))
pilot_hyperspace(pilot_stack[i]);
else
pilot_stack[i]->think(pilot_stack[i]);
}
if(pilot_stack[i]->update) {
if(pilot_isFlag(pilot_stack[i], PILOT_DELETE))
pilot_destroy(pilot_stack[i]);
else
pilot_stack[i]->update(pilot_stack[i], dt);
}
}
}
/* Render all the pilots. */
void pilots_render(void) {
int i;
for(i = 0; i < pilot_nstack; i++) {
if(player == pilot_stack[i]) continue; /* Skip the player. */
if(pilot_stack[i]->render != NULL) /* Render. */
pilot_stack[i]->render(pilot_stack[i]);
}
}
/* Parse the fleet node. */
static Fleet* fleet_parse(const xmlNodePtr parent) {
xmlNodePtr cur, node;
FleetPilot* pilot;
char* c;
node = parent->xmlChildrenNode;
Fleet* tmp = CALLOC_L(Fleet);
tmp->faction = -1;
tmp->name = (char*)xmlGetProp(parent, (xmlChar*)"name"); /* Already mallocs. */
if(tmp->name == NULL) WARN("Fleet in "FLEET_DATA" has invalid or no name");
do {
/* Load all the data. */
if(strcmp((char*)node->name, "faction")==0)
tmp->faction = faction_get((char*)node->children->content);
else if(strcmp((char*)node->name, "ai")==0)
tmp->ai = ai_getProfile((char*)node->children->content);
else if(strcmp((char*)node->name, "pilots")==0) {
cur = node->children;
do {
if(strcmp((char*)cur->name, "pilot")==0) {
tmp->npilots++; /* Pilot count. */
pilot = MALLOC_L(FleetPilot);
/* Name is not obligatory. Will only override ship name. */
c = (char*)xmlGetProp(cur, (xmlChar*)"name"); /* Mallocs. */
pilot->name = c; /* No need to free here however. */
pilot->ship = ship_get((char*)cur->children->content);
if(pilot->ship == NULL)
WARN("Pilot %s in Fleet %s has null ship", pilot->name, tmp->name);
c = (char*)xmlGetProp(cur, (xmlChar*)"chance"); /* Mallocs. */
pilot->chance = atoi(c);
if(pilot->chance == 0)
WARN("Pilot %s in Fleet %s has 0%% chance of appearing",
pilot->name, tmp->name);
if(c) free(c); /* Free the external malloc. */
tmp->pilots = realloc(tmp->pilots, sizeof(FleetPilot)*tmp->npilots);
memcpy(tmp->pilots+(tmp->npilots-1), pilot, sizeof(FleetPilot));
free(pilot);
}
} while((cur = cur->next));
}
} while((node = node->next));
#define MELEMENT(o,s) if(o) WARN("Fleet '%s' missing '"s"' element", tmp->name)
MELEMENT(tmp->ai==NULL, "ai");
MELEMENT(tmp->faction==-1, "faction");
MELEMENT(tmp->pilots==NULL, "pilots");
#undef MELEMENT
return tmp;
}
/* Load the fleets. */
int fleet_load(void) {
uint32_t bufsize;
char* buf = pack_readfile(DATA, FLEET_DATA, &bufsize);
xmlNodePtr node;
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
Fleet* tmp = NULL;
node = doc->xmlChildrenNode; /* Ships node. */
if(strcmp((char*)node->name, XML_ID)) {
ERR("Malformed "FLEET_DATA" file: missing root element '"XML_ID"'");
return -1;
}
node = node->xmlChildrenNode; /* First ship node. */
if(node == NULL) {
ERR("Malformed "FLEET_DATA" file: does not contain elements");
return -1;
}
do {
if(xml_isNode(node, XML_FLEET)) {
tmp = fleet_parse(node);
fleet_stack = realloc(fleet_stack, sizeof(Fleet)*(++nfleets));
memcpy(fleet_stack+nfleets-1, tmp, sizeof(Fleet));
free(tmp);
}
} while((node = node->next));
xmlFreeDoc(doc);
free(buf);
xmlCleanupParser();
DEBUG("Loaded %d fleet%c", nfleets, (nfleets==1)?' ':'s');
return 0;
}
/* Free the fleets. */
void fleet_free(void) {
int i, j;
if(fleet_stack != NULL) {
for(i = 0; i < nfleets; i++) {
for(j = 0; j < fleet_stack[i].npilots; j++)
if(fleet_stack[i].pilots[j].name)
free(fleet_stack[i].pilots[j].name);
free(fleet_stack[i].name);
free(fleet_stack[i].pilots);
}
free(fleet_stack);
fleet_stack = NULL;
}
nfleets = 0;
}