1952 lines
54 KiB
C
1952 lines
54 KiB
C
/**
|
|
* @file pilot.c
|
|
*
|
|
* @brief Handles the pilot stuff.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
|
|
#include "lephisto.h"
|
|
#include "log.h"
|
|
#include "weapon.h"
|
|
#include "ldata.h"
|
|
#include "lxml.h"
|
|
#include "spfx.h"
|
|
#include "rng.h"
|
|
#include "hook.h"
|
|
#include "map.h"
|
|
#include "explosion.h"
|
|
#include "escort.h"
|
|
#include "pilot.h"
|
|
|
|
#define XML_ID "Fleets" /**< XML document identifier. */
|
|
#define XML_FLEET "fleet" /**< XML individual fleet identifier. */
|
|
|
|
#define FLEET_DATA "../dat/fleet.xml" /**< Where to find fleet data. */
|
|
|
|
#define PILOT_CHUNK 32 /**< Chunks to increment pilot_stack by. */
|
|
|
|
#define CHUNK_SIZE 32 /**< Size to allocate memory by. */
|
|
|
|
/* ID generators. */
|
|
static unsigned int pilot_id = PLAYER_ID; /**< Stack of pilod ids to assure uniqueness. */
|
|
static unsigned int mission_cargo_id = 0; /**< ID generator for special mission cargo.
|
|
Not garanteed to be absolutely unique,
|
|
only unique for each pilot. */
|
|
|
|
/* 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; /**< Same. */
|
|
static int pilot_mstack = 0; /** Memory allocated for pilot_stack. */
|
|
|
|
extern Pilot* player;
|
|
extern double player_crating; /**< Players combat rating. */
|
|
extern void player_abortAutonav(char* reason);
|
|
|
|
/* Stack of fleets. */
|
|
static Fleet* fleet_stack = NULL; /** Fleet stack. */
|
|
static int nfleets = 0; /** Number of fleets. */
|
|
|
|
/* External. */
|
|
/* AI. */
|
|
extern AI_Profile* ai_pinit(Pilot* p, char* ai);
|
|
extern void ai_destroy(Pilot* p);
|
|
extern void ai_think(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);
|
|
extern void player_addLicense(char* license);
|
|
/* Internal. */
|
|
static int pilot_getStackPos(const unsigned int id);
|
|
static void pilot_shootWeapon(Pilot* p, PilotOutfit* w);
|
|
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); /* Externed in player.c */
|
|
static int fleet_parse(Fleet* tmp, const xmlNodePtr parent);
|
|
static void pilot_dead(Pilot* p);
|
|
static int pilot_setOutfitMounts(Pilot* p, PilotOutfit* po, int o, int q);
|
|
|
|
/**
|
|
* @brief Get the pilots position in the stack.
|
|
* @param id ID of the pilot to get.
|
|
* @return Position of pilot in stack or -1 if not found.
|
|
*/
|
|
static int pilot_getStackPos(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 return m;
|
|
}
|
|
|
|
/* Not found. */
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the next pilot on id.
|
|
* @param id ID of current pilot.
|
|
* @return ID of next pilot of PLAYER_ID if no next pilot.
|
|
*/
|
|
unsigned int pilot_getNextID(const unsigned int id) {
|
|
int m;
|
|
m = pilot_getStackPos(id);
|
|
|
|
if((m == (pilot_nstack-1)) || (m == -1)) return PLAYER_ID;
|
|
else return pilot_stack[m+1]->id;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the previous pilot based on ID.
|
|
* @param id ID of the current pilot.
|
|
* @return ID of previous pilot or PLAYER_ID if no previous pilot.
|
|
*/
|
|
unsigned int pilot_getPrevID(const unsigned int id) {
|
|
int m;
|
|
m = pilot_getStackPos(id);
|
|
|
|
if(m == -1) return PLAYER_ID;
|
|
else if(m == 0) return pilot_stack[pilot_nstack-1]->id;
|
|
else return pilot_stack[m-1]->id;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the nearest enemy to the pilot.
|
|
* @param p Pilot to get his nearest enemy.
|
|
* @param ID of her nearest enemy.
|
|
*/
|
|
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++) {
|
|
/* Must not be bribed. */
|
|
if((pilot_stack[i]->id == PLAYER_ID) && pilot_isFlag(p, PILOT_BRIBED))
|
|
continue;
|
|
|
|
if(areEnemies(p->faction, pilot_stack[i]->faction) ||
|
|
((pilot_stack[i]->id == PLAYER_ID) && (pilot_isFlag(p, PILOT_HOSTILE)))) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the nearest pilot to a pilot.
|
|
* @param p Pilot to get her nearest pilot.
|
|
* @return 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) {
|
|
int m;
|
|
|
|
if(id == PLAYER_ID) return player; /* Special case player. */
|
|
|
|
m = pilot_getStackPos(id);
|
|
|
|
if(m == -1)
|
|
return NULL;
|
|
else
|
|
return pilot_stack[m];
|
|
}
|
|
|
|
/* 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 double 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. */
|
|
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 int secondary) {
|
|
int i;
|
|
Outfit* o;
|
|
|
|
if(!p->outfits) return; /* No outfits. */
|
|
|
|
if(!secondary) {
|
|
/* Primary weapons. */
|
|
for(i = 0; i < p->noutfits; i++) {
|
|
o = p->outfits[i].outfit;
|
|
if(!outfit_isProp(o, OUTFIT_PROP_WEAP_SECONDARY) &&
|
|
(outfit_isBolt(o) || outfit_isBeam(o) || outfit_isFighterBay(o)))
|
|
/** @todo Possibly make this neater. */
|
|
pilot_shootWeapon(p, &p->outfits[i]);
|
|
}
|
|
} else {
|
|
if(!p->secondary) return; /* No secondary weapon. */
|
|
pilot_shootWeapon(p, p->secondary);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Has pilot stopped shooting her weapon.
|
|
*
|
|
* Only really deals with beam weapons.
|
|
* @param p Pilot that was shooting.
|
|
* @param secondary If weapon is secondary.
|
|
*/
|
|
void pilot_shootStop(Pilot* p, const int secondary) {
|
|
int i;
|
|
Outfit* o;
|
|
|
|
if(!p->outfits) return; /* No outfits. */
|
|
|
|
if(!secondary) { /* Primary weapons. */
|
|
for(i = 0; i < p->noutfits; i++) {
|
|
/* cycle through outfits to find primary weapons. */
|
|
o = p->outfits[i].outfit;
|
|
if(!outfit_isProp(o, OUTFIT_PROP_WEAP_SECONDARY) &&
|
|
outfit_isBeam(o)) /** @todo Possibly make this neater. */
|
|
if(p->outfits[i].beamid > 0) {
|
|
p->outfits[i].beamid = 0;
|
|
}
|
|
}
|
|
} else { /* Secondary weapons. */
|
|
if(p->secondary == NULL) return; /* No secondary weapon. */
|
|
|
|
o = p->secondary->outfit;
|
|
|
|
if(outfit_isBeam(o) && (p->secondary->beamid > 0)) {
|
|
beam_end(p->id, p->secondary->beamid);
|
|
p->secondary->beamid = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get the mount position of a pilot.
|
|
* @param p Pilot to get mount position of.
|
|
* @param id ID of the mount.
|
|
* @param[out] v Position of the mount.
|
|
* @return 0 on success.
|
|
*/
|
|
int pilot_getMount(Pilot* p, int id, Vec2* v) {
|
|
double a;
|
|
|
|
/* Calculate the sprite angle. */
|
|
a = (double)(p->tsy * p->ship->gfx_space->sx + p->tsx);
|
|
a *= p->ship->mangle;
|
|
|
|
/* Get the mount and add the player offset. */
|
|
ship_getMount(p->ship, a, id, v);
|
|
vect_cadd(v, p->solid->pos.x, p->solid->pos.y);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Actually handles the shooting, how often the player can shoot and such.
|
|
* @param p Pilot that is shooting.
|
|
* @param w Pilot's outfit to shoot.
|
|
* @param t Pilot's target.
|
|
*/
|
|
static void pilot_shootWeapon(Pilot* p, PilotOutfit* w) {
|
|
int id;
|
|
Vec2 v;
|
|
|
|
/* Check to see if weapon is ready. */
|
|
if(w->timer > 0.)
|
|
return;
|
|
|
|
/* Get weapon to see if weapon is ready. */
|
|
if(w->mounts == NULL)
|
|
id = 0;
|
|
else if(outfit_isTurret(w->outfit))
|
|
id = w->mounts[w->lastshot];
|
|
pilot_getMount(p, id, &v);
|
|
|
|
/* Regular bolt weapons. */
|
|
if(outfit_isBolt(w->outfit)) {
|
|
/* Enough energy? */
|
|
if(outfit_energy(w->outfit) > p->energy) return;
|
|
|
|
p->energy -= outfit_energy(w->outfit);
|
|
weapon_add(w->outfit, p->solid->dir,
|
|
&v, &p->solid->vel, p->id, p->target);
|
|
}
|
|
|
|
/* Beam Weapons. */
|
|
else if(outfit_isBeam(w->outfit)) {
|
|
if(outfit_energy(w->outfit) > p->energy) return;
|
|
|
|
/** @todo Handle warmup stage. */
|
|
w->state = PILOT_OUTFIT_ON;
|
|
w->beamid = beam_start(w->outfit, p->solid->dir,
|
|
&v, &p->solid->vel, p->id, p->target, id);
|
|
}
|
|
|
|
/*
|
|
* Missile Launchers.
|
|
*
|
|
* Must be a secondary weapon.
|
|
*/
|
|
else if(outfit_isLauncher(w->outfit) && (w == p->secondary)) {
|
|
/* Shooter can't be the target - Sanity check for the player. */
|
|
if((w->outfit->type != OUTFIT_TYPE_MISSILE_DUMB) && (p->id == p->target))
|
|
return;
|
|
|
|
/* Must have ammo left. */
|
|
if(p->ammo && (p->ammo->quantity <= 0))
|
|
return;
|
|
|
|
/* Enough energy? */
|
|
if(outfit_energy(w->outfit) > p->energy)
|
|
return;
|
|
|
|
p->energy -= outfit_energy(w->outfit);
|
|
weapon_add(p->ammo->outfit, p->solid->dir,
|
|
&v, &p->solid->vel, p->id, p->target);
|
|
|
|
p->ammo->quantity -= 1; /* There's no getting this one back. */
|
|
if(p->ammo->quantity <= 0) /* Out of ammo. */
|
|
pilot_rmOutfit(p, p->ammo->outfit, 0); /* It'll set p->ammo to NULL. */
|
|
}
|
|
/*
|
|
* Fighter bays.
|
|
*
|
|
* Must be secondary weapon.
|
|
*/
|
|
else if(outfit_isFighterBay(w->outfit) && (w == p->secondary)) {
|
|
/* Must have ammo left. */
|
|
if((p->ammo == NULL) || (p->ammo->quantity <= 0))
|
|
return;
|
|
|
|
/* Create escort. */
|
|
escort_create(p->id, p->ammo->outfit->u.fig.ship,
|
|
&v, &p->solid->vel, 1);
|
|
|
|
p->ammo->quantity -= 1; /* We just shot it. */
|
|
if(p->ammo->quantity <= 0) /* Out of ammo. */
|
|
pilot_rmOutfit(p, p->ammo->outfit, 0); /* It'll set p->ammo to NULL. */
|
|
} else {
|
|
WARN("Shooting unknown weapon type: %s", w->outfit->name);
|
|
}
|
|
|
|
/* Reset timer. */
|
|
w->timer += ((double)outfit_delay(w->outfit) / (double)w->quantity)/1000.;
|
|
|
|
/* Mark last updated. */
|
|
w->lastshot++;
|
|
if(w->lastshot >= w->quantity)
|
|
w->lastshot = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the pilots secondary weapon.
|
|
* @param p Pilot to set secondary weapon.
|
|
* @param i Index of the weapon to set.
|
|
*/
|
|
void pilot_switchSecondary(Pilot* p, int i) {
|
|
PilotOutfit* cur;
|
|
|
|
cur = player->secondary;
|
|
|
|
if((i < 0) || (i >= player->noutfits))
|
|
player->secondary = NULL;
|
|
else
|
|
player->secondary = &player->outfits[i];
|
|
|
|
/* Check for weapon change. */
|
|
if((cur != NULL) && (player->secondary != cur)) {
|
|
if(outfit_isBeam(cur->outfit) && (cur->beamid > 0)) {
|
|
beam_end(p->id, cur->beamid);
|
|
cur->beamid = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Damages the pilot.
|
|
* @param p Pilot that is taking damage.
|
|
* @param w Solid that is hitting pilot.
|
|
* @param shooter Attacker that shot the pilot.
|
|
* @param dtype type of damage.
|
|
* @param damage Amount of damage.
|
|
*/
|
|
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;
|
|
Pilot* pshooter;
|
|
|
|
/* Calculate the damage. */
|
|
outfit_calcDamage(&damage_shield, &damage_armour, &knockback, dtype, damage);
|
|
|
|
if((p->id == PLAYER_ID) &&
|
|
!pilot_isFlag(player, PILOT_HYP_BEGIN) &&
|
|
!pilot_isFlag(player, PILOT_HYPERSPACE))
|
|
player_abortAutonav("Sustaining 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 > 0.) {
|
|
p->armour -= damage_armour;
|
|
|
|
/* EMP don't kill. */
|
|
if((dtype == DAMAGE_TYPE_EMP) &&
|
|
(p->armour < PILOT_DISABLED_ARMOUR * p->armour_max))
|
|
p->armour = PILOT_DISABLED_ARMOUR * p->armour_max - 1.;
|
|
|
|
/* Officially dead. */
|
|
if(p->armour <= 0.) {
|
|
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. */
|
|
pshooter = pilot_get(shooter);
|
|
if((pshooter != NULL) && (pshooter->faction == FACTION_PLAYER)) {
|
|
mod = sqrt(p->ship->mass) / 5;
|
|
player_crating += 2*mod; /* Crating changes faster. */
|
|
faction_modPlayer(p->faction, -mod);
|
|
}
|
|
}
|
|
} else { /* Some minor effects and stuff. */
|
|
dam_mod = damage_armour / p->armour_max;
|
|
|
|
if(p->id == PLAYER_ID) /* Shake us up a bit. */
|
|
spfx_shake(SHAKE_MAX*dam_mod);
|
|
}
|
|
}
|
|
|
|
if(w != NULL)
|
|
/* 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.)));
|
|
}
|
|
|
|
/**
|
|
* @brief Pilot is dead, now will slowly explode.
|
|
* @param p Pilot that just died.
|
|
*/
|
|
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] = 0.; /* No need for AI anymore. */
|
|
p->ptimer = 1. + sqrt(10*p->armour_max * p->shield_max) / 1000.;
|
|
p->timer[1] = 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. */
|
|
pilot_runHook(p, PILOT_HOOK_DEATH);
|
|
}
|
|
|
|
/**
|
|
* @brief Tries to run a pilot hook if she has it.
|
|
* @param p Pilot to run the hook.
|
|
* @param hook_type Type of hook to run.
|
|
*/
|
|
void pilot_runHook(Pilot* p, int hook_type) {
|
|
int i;
|
|
for(i = 0; i < PILOT_HOOKS; i++) {
|
|
if(p->hook_type[i] == hook_type)
|
|
hook_runID(p->hook[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Set the pilots secondary weapon based on its name.
|
|
* @param p Pilot to set secondary weapon.
|
|
* @param secondary Name of the secondary weapon to set.
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
p->secondary = NULL;
|
|
p->ammo = NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the pilots ammo based on their secondary weapon.
|
|
* @param p Pilot to set ammo.
|
|
*/
|
|
void pilot_setAmmo(Pilot* p) {
|
|
int i;
|
|
Outfit* ammo;
|
|
|
|
/* Weapon must use ammo. */
|
|
if((p->secondary == NULL) || (outfit_ammo(p->secondary->outfit)==NULL)) {
|
|
p->ammo = NULL;
|
|
return;
|
|
}
|
|
|
|
/* Find the ammo and set it. */
|
|
ammo = outfit_ammo(p->secondary->outfit);
|
|
for(i = 0; i < p->noutfits; i++)
|
|
if(p->outfits[i].outfit == ammo) {
|
|
p->ammo = &p->outfits[i];
|
|
return;
|
|
}
|
|
|
|
/* None found, so we assume if doesn't need ammo. */
|
|
p->ammo = NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the amount of ammo pilot has for a certain outfit.
|
|
* @param p Pilot to get amount of ammo for.
|
|
* @param o Outfit to get ammo for.
|
|
* @return Amount of ammo for o on p.
|
|
*/
|
|
int pilot_getAmmo(Pilot* p, Outfit* o) {
|
|
int i;
|
|
Outfit* ammo;
|
|
|
|
/* Must be a launcher. */
|
|
if(!outfit_isLauncher(o))
|
|
return 0;
|
|
|
|
/* Try to find the ammo. */
|
|
ammo = o->u.lau.ammo;
|
|
for(i = 0; i < p->noutfits; i++)
|
|
if(p->outfits[i].outfit == ammo)
|
|
return p->outfits[i].quantity;
|
|
|
|
/* Assume none. */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the pilots afterburner if she has one.
|
|
* @param p Pilot to set 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;
|
|
}
|
|
|
|
/**
|
|
* @brief Dock the pilot on its target pilot.
|
|
* @param p Pilot that wants to dock.
|
|
* @param target Pilot to dock on.
|
|
* @return 0 on successful docking.
|
|
*/
|
|
int pilot_dock(Pilot* p, Pilot* target) {
|
|
int i;
|
|
Outfit* o;
|
|
|
|
/* Must be close. */
|
|
if(vect_dist(&p->solid->pos, &target->solid->pos) > 30.)
|
|
return -1;
|
|
|
|
/* Cannot be going much faster. */
|
|
if(vect_dist(&p->solid->vel, &target->solid->vel) > 2*MIN_VEL_ERR)
|
|
return -1;
|
|
|
|
/* Check to see if target has an available bay. */
|
|
for(i = 0; i < target->noutfits; i++) {
|
|
if(outfit_isFighterBay(target->outfits[i].outfit)) {
|
|
o = outfit_ammo(target->outfits[i].outfit);
|
|
if(outfit_isFighter(o) &&
|
|
(strcmp(p->ship->name, o->u.fig.ship)==0))
|
|
break;
|
|
}
|
|
}
|
|
if(i >= target->noutfits)
|
|
return -1;
|
|
|
|
/* Add the pilots outfit. */
|
|
if(pilot_addOutfit(target, o, 1) != 1)
|
|
return -1;
|
|
|
|
/* Destroy the pilot. */
|
|
pilot_setFlag(p, PILOT_DELETE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Make the pilot explosion.
|
|
* @param x X position of the pilot.
|
|
* @param y Y position of the pilot.
|
|
* @param radius Radius of the explosion.
|
|
* @param dtype Damage type of the explosion.
|
|
* @param damage Amount of damage by the explosion.
|
|
* @param parent ID of the pilot exploding.
|
|
*/
|
|
void pilot_explode(double x, double y, double radius,
|
|
DamageType dtype, double damage, unsigned int parent) {
|
|
|
|
int i;
|
|
double rx, ry;
|
|
double dist, rad2;
|
|
Pilot* p;
|
|
Solid s; /* Only need to manipulate mass and vel. */
|
|
|
|
rad2 = radius*radius;
|
|
|
|
for(i = 0; i < pilot_nstack; i++) {
|
|
p = pilot_stack[i];
|
|
|
|
/* Calculate a bit. */
|
|
rx = p->solid->pos.x - x;
|
|
ry = p->solid->pos.y - y;
|
|
dist = pow2(rx) + pow2(ry);
|
|
|
|
/* Pilot is hit. */
|
|
if(dist < rad2) {
|
|
/* Impact settings. */
|
|
s.mass = pow2(damage) * sqrt(rad2 - dist) / 30.;
|
|
s.vel.x = rx;
|
|
s.vel.y = ry;
|
|
|
|
/* Actual damage calculations. */
|
|
pilot_hit(p, &s, parent, dtype, damage);
|
|
|
|
/* Shock wave from the explosion. */
|
|
if(p->id == PILOT_PLAYER)
|
|
spfx_shake(pow2(damage) / pow2(100.) * SHAKE_MAX);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Render the pilot.
|
|
* @param p Pilot to render.
|
|
*/
|
|
void pilot_render(Pilot* p) {
|
|
gl_blitSprite(p->ship->gfx_space,
|
|
p->solid->pos.x, p->solid->pos.y,
|
|
p->tsx, p->tsy, NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Updates the pilot.
|
|
* @param pilot Pilot to update.
|
|
* @param dt Current delta tick.
|
|
*/
|
|
static void pilot_update(Pilot* pilot, const double dt) {
|
|
int i;
|
|
unsigned int l;
|
|
double a, px, py, vx, vy;
|
|
char buf[16];
|
|
PilotOutfit* o;
|
|
|
|
/* Update timers. */
|
|
pilot->ptimer -= dt;
|
|
pilot->tcontrol -= dt;
|
|
for(i = 0; i < MAX_AI_TIMERS; i++)
|
|
pilot->timer[i] -= dt;
|
|
for(i = 0; i < pilot->noutfits; i++) {
|
|
o = &pilot->outfits[i];
|
|
if(o->timer > 0.)
|
|
o->timer -= dt;
|
|
}
|
|
|
|
/* She's dead. */
|
|
if(pilot_isFlag(pilot, PILOT_DEAD)) {
|
|
if(pilot->ptimer < 0.) { /* 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;
|
|
}
|
|
|
|
/* Pilot death sound. */
|
|
if(!pilot_isFlag(pilot, PILOT_DEATH_SOUND) && (pilot->ptimer < 0.050)) {
|
|
/* Play random explosion sound. */
|
|
snprintf(buf, 16, "explosion%d", RNG(0,2));
|
|
sound_playPos(sound_get(buf), pilot->solid->pos.x, pilot->solid->pos.y);
|
|
|
|
pilot_setFlag(pilot, PILOT_DEATH_SOUND);
|
|
}
|
|
|
|
/* Final explosion. */
|
|
else if(!pilot_isFlag(pilot, PILOT_EXPLODED) && (pilot->ptimer < 0.200)) {
|
|
/* Damage from explosion. */
|
|
a = sqrt(pilot->solid->mass);
|
|
expl_explode(pilot->solid->pos.x, pilot->solid->pos.y,
|
|
pilot->solid->vel.x, pilot->solid->vel.y,
|
|
pilot->ship->gfx_space->sw / 2. + a,
|
|
DAMAGE_TYPE_KINETIC, 2.*a - 20.,
|
|
0, EXPL_MODE_SHIP);
|
|
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(pilot->timer[1] < 0.) {
|
|
pilot->timer[1] = 0.08 * (pilot->ptimer - pilot->timer[1]) / pilot->ptimer;
|
|
|
|
/* 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. */
|
|
pilot_runHook(pilot, PILOT_HOOK_DISABLE);
|
|
}
|
|
|
|
/* 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);
|
|
if(pilot->id == PLAYER_ID)
|
|
spfx_shake(0.75*SHAKE_DECAY*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(p->ptimer < 0.) {
|
|
if(p == player) {
|
|
player_brokeHyperspace();
|
|
} else {
|
|
pilot_runHook(p, PILOT_HOOK_JUMP);
|
|
pilot_setFlag(p, PILOT_DELETE); /* Set flag to delete pilot. */
|
|
}
|
|
return;
|
|
}
|
|
/* Keep accelerating - hyperspace uses much bigger accel. */
|
|
vect_pset(&p->solid->force, HYPERSPACE_THRUST*p->solid->mass, p->solid->dir);
|
|
}
|
|
/* Engines getting ready for the jump. */
|
|
else if(pilot_isFlag(p, PILOT_HYP_BEGIN)) {
|
|
if(p->ptimer < 0.) { /* Engines ready. */
|
|
p->ptimer = 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 = HYPERSPACE_ENGINE_DELAY;
|
|
pilot_setFlag(p, PILOT_HYP_BEGIN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Stops the pilot from hyperspaceing.
|
|
*
|
|
* Can only stop in preperation mode.
|
|
* @param p Pilot to handle stop hyperspace.
|
|
*/
|
|
void pilot_hyperspaceAbort(Pilot* p) {
|
|
if(!pilot_isFlag(p, PILOT_HYPERSPACE)) {
|
|
if(pilot_isFlag(p, PILOT_HYP_BEGIN))
|
|
pilot_rmFlag(p, PILOT_HYP_BEGIN);
|
|
if(pilot_isFlag(p, PILOT_HYP_PREP))
|
|
pilot_rmFlag(p, PILOT_HYP_PREP);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Set the mount points for an outfit.
|
|
* @param po Outfit to set mount points for.
|
|
* @param o Original number of outfits.
|
|
* @param q outfits added.
|
|
* @retrurn 0 on success.
|
|
*/
|
|
static int pilot_setOutfitMounts(Pilot* p, PilotOutfit* po, int o, int q) {
|
|
int i, n, k, min;
|
|
|
|
/* Grow the memory. */
|
|
po->mounts = realloc(po->mounts, o+q * sizeof(int));
|
|
|
|
/* Has to be done for each outfit added. */
|
|
for(n = o; n < o+q; n++) {
|
|
/* Special case no ship mounts. */
|
|
if(p->mounted == NULL) {
|
|
po->mounts[n] = 0;
|
|
continue;
|
|
}
|
|
|
|
/* Default to 0. */
|
|
k = 0;
|
|
min = INT_MAX;
|
|
/* Find mount with fewest spots. */
|
|
for(i = 1; i < p->ship->nmounts; i++) {
|
|
if(p->mounted[i] < min) {
|
|
k = i;
|
|
min = p->mounted[i];
|
|
}
|
|
}
|
|
/* Add the mount point. */
|
|
po->mounts[n] = k;
|
|
p->mounted[k]++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Add an outfit to the pilot.
|
|
* @param pilot Pilot to add the outfit to.
|
|
* @param outfit Outfit to add to the pilot.
|
|
* @param quantity Amount of the outfit to add.
|
|
* @return Amount of the outfit added.
|
|
*/
|
|
int pilot_addOutfit(Pilot* pilot, Outfit* outfit, int quantity) {
|
|
int i;
|
|
int o, q, free_space;
|
|
char* osec;
|
|
PilotOutfit* po;
|
|
|
|
free_space = pilot_freeSpace(pilot);
|
|
q = quantity;
|
|
|
|
/* Special case if it's a map. */
|
|
if(outfit_isMap(outfit)) {
|
|
if(pilot == player) /* Only player can get it. */
|
|
map_map(NULL, outfit->u.map.radius);
|
|
return 1; /* Must return 1 for paying purposes. */
|
|
}
|
|
/* Special case if it's a license. */
|
|
else if(outfit_isLicense(outfit)) {
|
|
if(pilot == player) /* Only player can get it. */
|
|
player_addLicense(outfit->name);
|
|
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) {
|
|
po = &pilot->outfits[i];
|
|
o = po->quantity;
|
|
po->quantity += q;
|
|
/* Can't be over max. */
|
|
if(po->quantity > outfit->max) {
|
|
q -= po->quantity - outfit->max;
|
|
po->quantity = outfit->max;
|
|
}
|
|
|
|
/* If it's a turret we need to find a mount spot for it. */
|
|
if(outfit_isTurret(outfit))
|
|
pilot_setOutfitMounts(pilot, po, o, q);
|
|
|
|
/* 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->noutfits++;
|
|
pilot->outfits = realloc(pilot->outfits, pilot->noutfits*sizeof(PilotOutfit));
|
|
po = &pilot->outfits[pilot->noutfits-1];
|
|
memset(po, 0, sizeof(PilotOutfit));
|
|
po->outfit = outfit;
|
|
po->quantity = q;
|
|
|
|
/* Can't be over max. */
|
|
if(po->quantity > outfit->max) {
|
|
q -= po->quantity - outfit->max;
|
|
po->quantity = outfit->max;
|
|
}
|
|
|
|
if(outfit_isTurret(outfit)) { /* Used to speed up AI. */
|
|
/* If it's a turret we need to find a mount sport for it. */
|
|
pilot_setOutfitMounts(pilot, po, 0, q);
|
|
pilot_setFlag(pilot, PILOT_HASTURRET);
|
|
}
|
|
|
|
if(outfit_isBeam(outfit))
|
|
/* Used to speed up some calculation. */
|
|
pilot_setFlag(pilot, PILOT_HASBEAMS);
|
|
|
|
/* Hack due to realloc possibility. */
|
|
pilot_setSecondary(pilot, osec);
|
|
pilot_setAfterburner(pilot);
|
|
|
|
pilot_calcStats(pilot);
|
|
return q;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove an outfit from the pilot.
|
|
* @param pilot Pilot to remove the outfit from.
|
|
* @param outfit Outfit to remove from the pilot.
|
|
* @param quantity Amount of the outfits to remove from the pilot.
|
|
* @return Number of the outfits removed from the pilot.
|
|
*/
|
|
int pilot_rmOutfit(Pilot* pilot, Outfit* outfit, int quantity) {
|
|
int i, j, q, c, o;
|
|
char* osec;
|
|
PilotOutfit* po;
|
|
|
|
c = (outfit_isMod(outfit)) ? outfit->u.mod.cargo : 0;
|
|
q = quantity;
|
|
|
|
for(i = 0; i < pilot->noutfits; i++)
|
|
if(pilot->outfits[i].outfit == outfit) {
|
|
po = &pilot->outfits[i];
|
|
|
|
/* Remove quantity. */
|
|
o = po->quantity -= quantity;
|
|
|
|
/* Remove from mount points. */
|
|
if((pilot->mounted != NULL) && (po->mounts != NULL)) {
|
|
for(j = o-1; j >= po->quantity; j--) {
|
|
if(po->mounts[j] != 0)
|
|
pilot->mounted[po->mounts[j]]--;
|
|
}
|
|
}
|
|
|
|
/* Need to remove the outfit. */
|
|
if(po->quantity <= 0) {
|
|
/* We didn't actually remove the full amount. */
|
|
q += po->quantity;
|
|
|
|
/* Hack in case it reallocs - Can happen even when shrinking. */
|
|
osec = (pilot->secondary) ? pilot->secondary->outfit->name : NULL;
|
|
|
|
/* Free some memory if needed. */
|
|
if(po->mounts != NULL)
|
|
free(po->mounts);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* @brief Get all the outfits in nice text form.
|
|
* @param pilot Pilot to get the outfits from.
|
|
*/
|
|
char* pilot_getOutfits(Pilot* pilot) {
|
|
int i;
|
|
char* buf;
|
|
int p, len;
|
|
|
|
len = 1024;
|
|
|
|
buf = malloc(sizeof(char)*len);
|
|
buf[0] = '\0';
|
|
p = 0;
|
|
/* First outfit. */
|
|
if(pilot->noutfits > 0)
|
|
p += snprintf(&buf[p], len-p, "%dx %s",
|
|
pilot->outfits[0].quantity, pilot->outfits[0].outfit->name);
|
|
else
|
|
p += snprintf(&buf[p], len-p, "None");
|
|
|
|
/* Rest of the outfits. */
|
|
for(i = 1; i < pilot->noutfits; i++)
|
|
p += snprintf(&buf[p], len-p, ", %dx %s",
|
|
pilot->outfits[i].quantity, pilot->outfits[i].outfit->name);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* Recalculate the pilot's stats based on her outfits. */
|
|
void pilot_calcStats(Pilot* pilot) {
|
|
int i;
|
|
double q;
|
|
double wrange, wspeed;
|
|
int nweaps;
|
|
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. */
|
|
nweaps = 0;
|
|
wrange = wspeed = 0.;
|
|
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;
|
|
}
|
|
else if((outfit_isWeapon(o) || outfit_isTurret(o)) && /* Primary weapon. */
|
|
!outfit_isProp(o, OUTFIT_PROP_WEAP_SECONDARY)) {
|
|
nweaps++;
|
|
wrange = MAX(wrange, outfit_range(o));
|
|
wspeed += outfit_speed(o);
|
|
}
|
|
}
|
|
|
|
/* Set weapon range and speed. */
|
|
pilot->weap_range = wrange; /* Range is max. */
|
|
pilot->weap_speed = wspeed / (double)nweaps;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the pilots free cargo space.
|
|
* @param p Pilot to get the free space of.
|
|
* @return Free cargo space on pilot.
|
|
*/
|
|
int pilot_cargoFree(Pilot* p) {
|
|
return p->cargo_free;
|
|
}
|
|
|
|
/**
|
|
* @brief Move cargo from one pilot to another.
|
|
* @param dest Destination pilot.
|
|
* @param src Source pilot.
|
|
* @return 0 on success.
|
|
*/
|
|
int pilot_moveCargo(Pilot* dest, Pilot* src) {
|
|
int i;
|
|
|
|
/* Nothing to copy, success! */
|
|
if(src->ncommodities == 0)
|
|
return 0;
|
|
|
|
/* Check if it fits. */
|
|
if(pilot_cargoUsed(src) > pilot_cargoFree(dest)) {
|
|
WARN("Unable to copy cargo over from pilot '%s' to '%s'", src->name, dest->name);
|
|
return -1;
|
|
}
|
|
|
|
/* Alloate new space. */
|
|
i = dest->ncommodities;
|
|
dest->ncommodities += src->ncommodities;
|
|
dest->commodities = realloc(dest->commodities,
|
|
sizeof(PilotCommodity)*dest->ncommodities);
|
|
|
|
/* Copy over. */
|
|
memmove(&dest->commodities[i], &src->commodities[0],
|
|
sizeof(PilotCommodity) * src->ncommodities);
|
|
|
|
/* Clean src. */
|
|
src->ncommodities = 0;
|
|
if(src->commodities != NULL)
|
|
free(src->commodities);
|
|
src->commodities = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* @brief Get how much cargo ship has on board.
|
|
* @param pilot Pilot to get used cargo space of.
|
|
* @param The used cargo space by pilot.
|
|
*/
|
|
int pilot_cargoUsed(Pilot* pilot) {
|
|
int i, q;
|
|
|
|
q = 0;
|
|
for(i = 0; i < pilot->ncommodities; i++)
|
|
q += pilot->commodities[i].quantity;
|
|
|
|
return q;
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates how much cargo ship has left and such.
|
|
* @param pilot Pilot to calculate free cargo space of.
|
|
*/
|
|
static void pilot_calcCargo(Pilot* pilot) {
|
|
int q;
|
|
|
|
q = pilot_cargoUsed(pilot);
|
|
|
|
pilot->cargo_free = pilot->ship->cap_cargo - q; /* Reduce space left. */
|
|
pilot->solid->mass = pilot->ship->mass + q; /* Cargo affects weight. */
|
|
}
|
|
|
|
/**
|
|
* @brief Add special mission cargo, can't sell it and such.
|
|
* @param pilot Pilot to add it to.
|
|
* @param cargo Comodity to add.
|
|
* @param quantity Quantity to add.
|
|
* @return The mission cargo ID of created cargo.
|
|
*/
|
|
unsigned int pilot_addMissionCargo(Pilot* pilot, Commodity* cargo, int quantity) {
|
|
int i;
|
|
unsigned int id, max_id;
|
|
int q;
|
|
q = quantity;
|
|
|
|
/* Get ID. */
|
|
id = ++mission_cargo_id;
|
|
|
|
/* Check for collisions with pilot and set ID generator to the max. */
|
|
max_id = 0;
|
|
for(i = 0; i < pilot->ncommodities; i++)
|
|
if(pilot->commodities[i].id > max_id)
|
|
max_id = pilot->commodities[i].id;
|
|
if(max_id > id)
|
|
mission_cargo_id = max_id;
|
|
id = ++mission_cargo_id;
|
|
|
|
/* Grow commodities. */
|
|
pilot->commodities = realloc(pilot->commodities,
|
|
sizeof(PilotCommodity) * (pilot->ncommodities+1));
|
|
pilot->commodities[pilot->ncommodities].commodity = cargo;
|
|
/* Add commodity. */
|
|
if(pilot_cargoFree(pilot) < quantity)
|
|
q = pilot_cargoFree(pilot);
|
|
pilot->commodities[pilot->ncommodities].id = id;
|
|
pilot->commodities[pilot->ncommodities].quantity = q;
|
|
/* Postfixing. */
|
|
pilot->cargo_free -= q;
|
|
pilot->solid->mass += q;
|
|
pilot->ncommodities++;
|
|
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove special mission cargo based on id.
|
|
* @param pilot Pilot to remove cargo from.
|
|
* @param cargo_id ID of the cargo to remove.
|
|
* @param jettison Should we jettison the cargo?
|
|
* @return 0 on success (cargo removed).
|
|
*/
|
|
int pilot_rmMissionCargo(Pilot* pilot, unsigned int cargo_id, int jettison) {
|
|
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. */
|
|
|
|
if(jettison)
|
|
commodity_Jettison(pilot->id, pilot->commodities[i].commodity,
|
|
pilot->commodities[i].quantity);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* @brief Try to get rid of quantity cargo from pilot.
|
|
* @param pilot Pilot to get rid of cargo.
|
|
* @param cargo Cargo to get rid of.
|
|
* @param quantity Amount of cargo to get rid of.
|
|
* @return Amount of cargo gotten rid of.
|
|
*/
|
|
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. */
|
|
}
|
|
|
|
/**
|
|
* @brief Add a hook to the pilot.
|
|
* @param pilot Pilot to add the hook to.
|
|
* @param type Type of the hook to add.
|
|
* @param hook ID of the hook to add.
|
|
*/
|
|
void pilot_addHook(Pilot* pilot, int type, int hook) {
|
|
int i;
|
|
|
|
for(i = 0; i < PILOT_HOOKS; i++) {
|
|
if(pilot->hook_type[i] == PILOT_HOOK_NONE) {
|
|
pilot->hook_type[i] = type;
|
|
pilot->hook[i] = hook;
|
|
return;
|
|
}
|
|
}
|
|
WARN("Pilot has maximum amount of hooks, cannot add another.");
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize pilot.
|
|
* @param pilot Pilot to initialise.
|
|
* @param ship Ship pilot will be flying.
|
|
* @param name Pilots name, if NULL ships name will be used.
|
|
* @param faction Faction of the pilot.
|
|
* @param ai Name of the AI profile to use for the pilot.
|
|
* @param dir Initial direction to face (radians).
|
|
* @param vel Initial velocity.
|
|
* @param pos Initial position.
|
|
* @param flags Used for tweaking the pilot.
|
|
*/
|
|
void pilot_init(Pilot* pilot, Ship* ship, char* name, int faction,
|
|
char* ai, const double dir, const Vec2* pos,
|
|
const Vec2* vel, const unsigned int flags) {
|
|
|
|
ShipOutfit* so;
|
|
|
|
/* Clear memory. */
|
|
memset(pilot, 0, sizeof(Pilot));
|
|
|
|
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. */
|
|
|
|
/* Basic information. */
|
|
pilot->ship = ship;
|
|
pilot->name = strdup((name == NULL) ? ship->name : name);
|
|
|
|
/* Faction. */
|
|
pilot->faction = faction;
|
|
|
|
/* Solid. */
|
|
pilot->solid = solid_create(ship->mass, dir, pos, vel);
|
|
|
|
/* Mounts. */
|
|
if(ship->nmounts > 0)
|
|
pilot->mounted = calloc(ship->nmounts, sizeof(int));
|
|
|
|
/* Outfits. */
|
|
if(!(flags & PILOT_NO_OUTFITS)) {
|
|
if(ship->outfit) {
|
|
pilot->noutfits = 0;
|
|
for(so = ship->outfit; so; so = so->next) {
|
|
pilot_addOutfit(pilot, so->data, so->quantity);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Cargo must be set before calcStats. */
|
|
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);
|
|
|
|
/* 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;
|
|
}
|
|
/* Set enter hyperspace flag if needed. */
|
|
if(flags & PILOT_HYP_END)
|
|
pilot_setFlag(pilot, PILOT_HYP_END);
|
|
|
|
/* All update the same way. */
|
|
pilot->update = pilot_update;
|
|
|
|
/* Escort stuff. */
|
|
if(flags & PILOT_ESCORT) {
|
|
pilot_setFlag(pilot, PILOT_ESCORT);
|
|
if(flags & PILOT_CARRIED)
|
|
pilot_setFlag(pilot, PILOT_CARRIED);
|
|
}
|
|
|
|
/* AI. */
|
|
pilot->target = pilot->id; /* Self = no target. */
|
|
if(ai != NULL)
|
|
ai_pinit(pilot, ai); /* Must run before ai_create. */
|
|
}
|
|
|
|
/**
|
|
* @brief Create a new pilot.
|
|
*
|
|
* See pilot_init for parameters.
|
|
* @return Pilots id.
|
|
*
|
|
* @sa pilot_init
|
|
*/
|
|
unsigned int pilot_create(Ship* ship, char* name, int faction,
|
|
char* ai, const double dir, const Vec2* pos,
|
|
const Vec2* vel, const unsigned int flags) {
|
|
|
|
Pilot* 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;
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @brief Create a pilot without adding it to the stack.
|
|
* @param ship Ship for the pilot to use.
|
|
* @param name Name of the pilot ship (NULL uses ship name).
|
|
* @param faction Faction of the ship.
|
|
* @param ai AI to use.
|
|
* @param flags Flags for tweaking, PILOT_EMPTY is added.
|
|
* @return Pointer to the new pilot (not added to stack).
|
|
*/
|
|
Pilot* pilot_createEmpty(Ship* ship, char* name,
|
|
int faction, char* ai, const unsigned int flags) {
|
|
|
|
Pilot* dyn;
|
|
dyn = MALLOC_L(Pilot);
|
|
pilot_init(dyn, ship, name, faction, ai, 0., NULL, NULL, flags | PILOT_EMPTY);
|
|
return dyn;
|
|
}
|
|
|
|
/**
|
|
* @brief Copies src pilot to dest.
|
|
* @param src Pilot to copy.
|
|
* @return Copy of src.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @brief Free and cleans up a pilot.
|
|
* @param p Pilot to free.
|
|
*/
|
|
void pilot_free(Pilot* p) {
|
|
int i;
|
|
|
|
/* Clear up pilot hooks. */
|
|
for(i = 0; i < PILOT_HOOKS; i++)
|
|
if(p->hook_type[i] != PILOT_HOOK_NONE)
|
|
hook_rm(p->hook[i]);
|
|
|
|
free(p->name);
|
|
|
|
/* Clean up data. */
|
|
if(p->ai != NULL)
|
|
ai_destroy(p); /* Must be destroyed first if applicable. */
|
|
/* Case for if pilot is the player. */
|
|
if(player == p) player = NULL;
|
|
solid_free(p->solid);
|
|
if(p->mounted != NULL) free(p->mounted);
|
|
if(p->outfits) free(p->outfits);
|
|
if(p->commodities) free(p->commodities);
|
|
if(p->escorts) free(p->escorts);
|
|
free(p);
|
|
}
|
|
|
|
/**
|
|
* @brief Destroy pilot from stack.
|
|
* @param p Pilot to destroy.
|
|
*/
|
|
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*));
|
|
}
|
|
|
|
/**
|
|
* @brief Free the pilot stack.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @brief Clean up the pilot stack - Leave 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;
|
|
}
|
|
|
|
/**
|
|
* @brief Even cleans up the player.
|
|
*/
|
|
void pilots_cleanAll(void) {
|
|
pilots_clean();
|
|
if(player != NULL) {
|
|
pilot_free(player);
|
|
player = NULL;
|
|
}
|
|
pilot_nstack = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Updates all the pilots.
|
|
* @param dt Delta tick for the update.
|
|
*/
|
|
void pilots_update(double dt) {
|
|
int i;
|
|
Pilot* p;
|
|
|
|
for(i = 0; i < pilot_nstack; i++) {
|
|
p = pilot_stack[i];
|
|
|
|
/* See if should think. */
|
|
if(p->think && !pilot_isDisabled(p)) {
|
|
/* Hyperspace gets special treatment. */
|
|
if(pilot_isFlag(p, PILOT_HYP_PREP))
|
|
pilot_hyperspace(p);
|
|
/* Entering hyperspace. */
|
|
else if(pilot_isFlag(p, PILOT_HYP_END)) {
|
|
if(VMOD(p->solid->vel) < 2*p->speed)
|
|
pilot_rmFlag(p, PILOT_HYP_END);
|
|
}
|
|
else
|
|
p->think(p);
|
|
}
|
|
if(p->update) { /* Update. */
|
|
if(pilot_isFlag(p, PILOT_DELETE))
|
|
pilot_destroy(p);
|
|
else
|
|
p->update(p, dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 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 int fleet_parse(Fleet* tmp, const xmlNodePtr parent) {
|
|
xmlNodePtr cur, node;
|
|
FleetPilot* pilot;
|
|
char* c;
|
|
int mem;
|
|
node = parent->xmlChildrenNode;
|
|
|
|
/* Sane defaults and clean up. */
|
|
memset(tmp, 0, sizeof(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(xml_isNode(node, "faction"))
|
|
tmp->faction = faction_get(xml_get(node));
|
|
else if(xml_isNode(node, "ai"))
|
|
tmp->ai = xml_getStrd(node);
|
|
else if(xml_isNode(node, "pilots")) {
|
|
cur = node->children;
|
|
mem = 0;
|
|
do {
|
|
if(xml_isNode(cur, "pilot")) {
|
|
/* See if we must grow. */
|
|
tmp->npilots++;
|
|
if(tmp->npilots > mem) {
|
|
mem += CHUNK_SIZE;
|
|
tmp->pilots = realloc(tmp->pilots, mem * sizeof(FleetPilot));
|
|
}
|
|
pilot = &tmp->pilots[tmp->npilots-1];
|
|
|
|
/* Clear memory. */
|
|
memset(pilot, 0, sizeof(FleetPilot));
|
|
|
|
/* Check for name override. */
|
|
xmlr_attr(cur, "name", c);
|
|
pilot->name = c; /* No need to free since it will have to later. */
|
|
|
|
/* Check for ai override. */
|
|
xmlr_attr(cur, "ai", pilot->ai);
|
|
|
|
/* Load pilots ship. */
|
|
pilot->ship = ship_get(xml_get(cur));
|
|
if(pilot->ship == NULL)
|
|
WARN("Pilot %s in Fleet %s has null ship", pilot->name, tmp->name);
|
|
|
|
/* Load chance. */
|
|
xmlr_attr(cur, "chance", c);
|
|
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 != NULL)
|
|
free(c); /* Free the external malloc. */
|
|
}
|
|
} while(xml_nextNode(cur));
|
|
|
|
/* Resize to minimum. */
|
|
tmp->pilots = realloc(tmp->pilots, sizeof(FleetPilot)*tmp->npilots);
|
|
}
|
|
} while(xml_nextNode(node));
|
|
#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 0;
|
|
}
|
|
|
|
/* Load the fleets. */
|
|
int fleet_load(void) {
|
|
int mem;
|
|
uint32_t bufsize;
|
|
char* buf = ldata_read(FLEET_DATA, &bufsize);
|
|
|
|
xmlNodePtr node;
|
|
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
|
|
|
|
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;
|
|
}
|
|
|
|
mem = 0;
|
|
do {
|
|
if(xml_isNode(node, XML_FLEET)) {
|
|
/* See if memory must grow. */
|
|
nfleets++;
|
|
if(nfleets > mem) {
|
|
mem += CHUNK_SIZE;
|
|
fleet_stack = realloc(fleet_stack, sizeof(Fleet)*mem);
|
|
}
|
|
|
|
/* Load the fleet. */
|
|
fleet_parse(&fleet_stack[nfleets-1], node);
|
|
}
|
|
} while(xml_nextNode(node));
|
|
|
|
/* Shrink to minimum. */
|
|
fleet_stack = realloc(fleet_stack, sizeof(Fleet)*nfleets);
|
|
|
|
xmlFreeDoc(doc);
|
|
free(buf);
|
|
|
|
DEBUG("Loaded %d fleet%s", 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);
|
|
if(fleet_stack[i].pilots[j].ai)
|
|
free(fleet_stack[i].pilots[j].ai);
|
|
}
|
|
free(fleet_stack[i].name);
|
|
free(fleet_stack[i].pilots);
|
|
free(fleet_stack[i].ai);
|
|
}
|
|
free(fleet_stack);
|
|
fleet_stack = NULL;
|
|
}
|
|
nfleets = 0;
|
|
}
|
|
|