1119 lines
32 KiB
C
1119 lines
32 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 "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 - yes, they come in stacks now.
|
|
Pilot** pilot_stack = NULL; // Not static, it is used in player.c and weapon.c and ai.c
|
|
int pilots = 0;
|
|
static int mpilots = 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_calcStats(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_getNext(const unsigned int id) {
|
|
// Binary search.
|
|
int l, m, h;
|
|
l = 0;
|
|
h = pilots-1;
|
|
while(l <= h) {
|
|
m = (l+h)/2;
|
|
if(pilot_stack[m]->id > id) h = m-1;
|
|
else if(pilot_stack[m]->id < id) l = m+1;
|
|
else break;
|
|
}
|
|
|
|
if(m == (pilots-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_getNearest(const Pilot* p) {
|
|
unsigned int tp;
|
|
int i;
|
|
double d, td;
|
|
|
|
for(tp = 0, d = 0., i = 0; i < pilots; 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_getHostile(void) {
|
|
unsigned int tp;
|
|
int i;
|
|
double d, td;
|
|
for(tp = PLAYER_ID, d = 0., i = 0; i < pilots; i++)
|
|
if(pilot_isFlag(pilot_stack[i], PILOT_HOSTILE)) {
|
|
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 = pilots-1;
|
|
while(l <= h) {
|
|
m = (l+h)/2;
|
|
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;
|
|
}
|
|
|
|
// 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 double damage_shield, const double damage_armour) {
|
|
|
|
double dam_mod;
|
|
|
|
if(p->shield - damage_shield > 0.) {
|
|
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) {
|
|
player_crating += MAX(1, p->ship->mass/50);
|
|
faction_modPlayer(p->faction, -(p->ship->mass/10));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
w->vel.x * (dam_mod/6. + w->mass/p->solid->mass/6.),
|
|
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;
|
|
|
|
if(secondary == NULL) {
|
|
p->secondary = NULL;
|
|
p->ammo = NULL;
|
|
return;
|
|
}
|
|
|
|
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;
|
|
|
|
if((p->secondary == NULL) || !outfit_isLauncher(p->secondary->outfit)) {
|
|
p->ammo = NULL;
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
p->ammo = 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) {
|
|
unsigned int t, l;
|
|
double a, px, py, vx, vy;
|
|
|
|
if(pilot_isFlag(pilot, PILOT_DEAD)) {
|
|
t = SDL_GetTicks();
|
|
|
|
if(t > pilot->ptimer) {
|
|
if(pilot->id == PLAYER_ID)
|
|
player_destroyed();
|
|
pilot_setFlag(pilot, PILOT_DELETE); // It'll get deleted next frame.
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
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);
|
|
|
|
// Pupose fallthrough to get the movement similar to disabled.
|
|
if(pilot != player && pilot->armour < PILOT_DISABLED_ARMOUR * pilot->armour_max) {
|
|
// We are disabled.
|
|
pilot_setFlag(pilot, PILOT_DISABLED);
|
|
// 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);
|
|
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) {
|
|
pilot->armour += pilot->armour_regen*dt;
|
|
pilot->energy += pilot->energy_regen*dt;
|
|
} else {
|
|
pilot->shield += pilot->shield_regen*dt;
|
|
pilot->energy += pilot->energy_regen*dt;
|
|
}
|
|
|
|
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.
|
|
if(pilot_isFlag(pilot, PILOT_AFTERBURNER) && // Must have enough energy.
|
|
(player->energy > pilot->afterburner->outfit->u.afb.energy * dt)) {
|
|
limit_speed(&pilot->solid->vel,
|
|
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
|
|
limit_speed(&pilot->solid->vel, pilot->speed, dt);
|
|
}
|
|
}
|
|
|
|
// Pilot is getting ready or is in, hyperspace.
|
|
static void pilot_hyperspace(Pilot* p) {
|
|
double diff;
|
|
|
|
if(pilot_isFlag(p, PILOT_HYPERSPACE)) {
|
|
// Pilot is actually in hyperspace.
|
|
if(SDL_GetTicks() > p->ptimer) {
|
|
if(p == player) {
|
|
player_brokeHyperspace();
|
|
} else
|
|
pilot_setFlag(p, PILOT_DELETE); // Set flag to delete pilot.
|
|
return;
|
|
}
|
|
vect_pset(&p->solid->force, p->thrust * 3., p->solid->dir);
|
|
}
|
|
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.
|
|
|
|
if(VMOD(p->solid->vel) > MIN_VEL_ERR) {
|
|
diff = pilot_face(p, VANGLE(p->solid->vel) + M_PI);
|
|
|
|
if(ABS(diff) < MAX_DIR_ERR) // Brake.
|
|
vect_pset(&p->solid->force, p->thrust, p->solid->dir);
|
|
} else {
|
|
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;
|
|
char* s;
|
|
|
|
q = quantity;
|
|
|
|
for(i = 0; i < pilot->noutfits; i++)
|
|
if(strcmp(outfit->name, pilot->outfits[i].outfit->name)==0) {
|
|
pilot->outfits[i].quantity += quantity;
|
|
// 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;
|
|
}
|
|
pilot_calcStats(pilot);
|
|
return q;
|
|
}
|
|
|
|
s = (pilot->secondary) ? pilot->secondary->outfit->name : NULL;
|
|
pilot->outfits = realloc(pilot->outfits, (pilot->noutfits+1)*sizeof(PilotOutfit));
|
|
pilot->outfits[pilot->noutfits].outfit = outfit;
|
|
pilot->outfits[pilot->noutfits].quantity = quantity;
|
|
// 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;
|
|
(pilot->noutfits)++;
|
|
|
|
if(outfit_isTurret(outfit))
|
|
// Used to speed up AI.
|
|
pilot_setFlag(pilot, PILOT_HASTURRET);
|
|
|
|
// Hack due to realloc possibility.
|
|
pilot_setSecondary(pilot, s);
|
|
|
|
pilot_calcStats(pilot);
|
|
return q;
|
|
}
|
|
|
|
// Remove an outfit from the pilot.
|
|
int pilot_rmOutfit(Pilot* pilot, Outfit* outfit, int quantity) {
|
|
int i, q;
|
|
char* s;
|
|
|
|
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.
|
|
s = (pilot->secondary) ? pilot->secondary->outfit->name : NULL;
|
|
// Clear it if it's the afterburner.
|
|
if(&pilot->outfits[i] == pilot->afterburner)
|
|
pilot->afterburner = 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));
|
|
|
|
pilot_setSecondary(pilot, s);
|
|
}
|
|
pilot_calcStats(pilot); // Recalculate stats.
|
|
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.
|
|
static 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;
|
|
|
|
// Now add outfit changes.
|
|
for(i = 0; i < pilot->noutfits; i++) {
|
|
if(outfit_isMod(pilot->outfits[i].outfit)) {
|
|
q = (double) pilot->outfits[i].quantity;
|
|
o = pilot->outfits[i].outfit;
|
|
|
|
// 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(pilot->outfits[i].outfit)) {
|
|
// Set the afterburner.
|
|
pilot->afterburner = &pilot->outfits[i];
|
|
}
|
|
}
|
|
|
|
// 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_freeCargo(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;
|
|
|
|
q = quantity;
|
|
for(i = 0; i < pilot->ncommodities; i++)
|
|
if(!pilot->commodities[i].id && (pilot->commodities[i].commodity == cargo)) {
|
|
if(pilot->cargo_free < quantity)
|
|
q = pilot->cargo_free;
|
|
pilot->commodities[i].quantity += q;
|
|
pilot->cargo_free -= 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->cargo_free < quantity)
|
|
q = pilot->cargo_free;
|
|
pilot->commodities[pilot->ncommodities].id = 0;
|
|
pilot->commodities[pilot->ncommodities].quantity = q;
|
|
pilot->cargo_free -= q;
|
|
pilot->ncommodities++;
|
|
|
|
return q;
|
|
}
|
|
|
|
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->cargo_free < quantity)
|
|
q = pilot->cargo_free;
|
|
pilot->commodities[pilot->ncommodities].id = ++mission_cargo_id;
|
|
pilot->commodities[pilot->ncommodities].quantity = q;
|
|
pilot->cargo_free -= q;
|
|
pilot->ncommodities++;
|
|
|
|
return pilot->commodities[pilot->ncommodities-1].id;
|
|
}
|
|
|
|
int pilot_rmMissionCargo(Pilot* pilot, unsigned int cargo_id) {
|
|
int i;
|
|
|
|
for(i = 0; i < pilot->ncommodities; i++)
|
|
if(pilot->commodities[i].id == cargo_id)
|
|
break;
|
|
|
|
if(i >= pilot->ncommodities)
|
|
return 1;
|
|
|
|
// Remove cargo.
|
|
pilot->cargo_free += 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;
|
|
|
|
q = quantity;
|
|
for(i = 0; i < pilot->ncommodities; i++)
|
|
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;
|
|
return q;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Cargo.
|
|
pilot->credits = 0;
|
|
pilot->commodities = NULL;
|
|
pilot->ncommodities = 0;
|
|
pilot->cargo_free = pilot->ship->cap_cargo;
|
|
|
|
// 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);
|
|
|
|
if(flags & PILOT_PLAYER) {
|
|
// Player.
|
|
if(!pilot_stack) {
|
|
pilot_stack = MALLOC_L(Pilot*);
|
|
pilots = 1;
|
|
mpilots = 1;
|
|
}
|
|
pilot_stack[0] = dyn;
|
|
} else {
|
|
// Add to the stack.
|
|
pilots++; // There is a new pilot.
|
|
|
|
if(pilots >= mpilots) {
|
|
// Need to grow. About 20 at a time.
|
|
mpilots += PILOT_CHUNK;
|
|
tp = pilot_stack;
|
|
pilot_stack = realloc(pilot_stack, mpilots*sizeof(Pilot*));
|
|
if((pilot_stack != tp) && player)
|
|
// Take into account possible mem move.
|
|
player = pilot_stack[0];
|
|
}
|
|
|
|
pilot_stack[pilots-1] = dyn;
|
|
}
|
|
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;
|
|
for(i = 0; i < pilots; i++)
|
|
if(pilot_stack[i] == p)
|
|
break;
|
|
|
|
pilots--;
|
|
|
|
while(i < pilots) {
|
|
pilot_stack[i] = pilot_stack[i+1];
|
|
i++;
|
|
}
|
|
pilot_free(p);
|
|
}
|
|
|
|
// Free the prisoned pilot!
|
|
void pilots_free(void) {
|
|
int i;
|
|
if(player) pilot_free(player);
|
|
for(i = 1; i < pilots; i++)
|
|
pilot_free(pilot_stack[i]);
|
|
free(pilot_stack);
|
|
pilot_stack = NULL;
|
|
pilots = 0;
|
|
}
|
|
|
|
// Clean up the pilots - Leaves the player.
|
|
void pilots_clean(void) {
|
|
int i;
|
|
for(i = 1; i < pilots; i++)
|
|
pilot_free(pilot_stack[i]);
|
|
pilots = 1;
|
|
}
|
|
|
|
// Update all pilots.
|
|
void pilots_update(double dt) {
|
|
int i;
|
|
for(i = 0; i < pilots; 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 = 1; i < pilots; i++)
|
|
// Skip the player.
|
|
if(pilot_stack[i]->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;
|
|
}
|
|
|