Lephisto/src/pilot.c

524 lines
14 KiB
C

#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h> // We don't need this?
#include "main.h"
#include "log.h"
#include "weapon.h"
#include "pack.h"
#include "xml.h"
#include "pilot.h"
#define XML_ID "Fleets" // XML section identifier.
#define XML_FLEET "fleet"
#define FLEET_DATA "../dat/fleet.xml"
// Stack of pilot id's to assure uniqueness.
static unsigned int pilot_id = PLAYER_ID;
// 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;
// Stack of fleets.
static Fleet* fleet_stack = NULL;
static int nfleets = 0;
// External.
extern void ai_destroy(Pilot* p); // Ai.
extern void ai_think(Pilot* pilot); // Ai.c
extern void player_think(Pilot* pilot); // Player.c
extern int gui_load(const char* name); // Player.c
// Internal.
static void pilot_shootWeapon(Pilot* p, PilotOutfit* w, const unsigned int t);
static void pilot_update(Pilot* pilot, const double dt);
void pilot_render(Pilot* pilot);
static void pilot_free(Pilot* p);
static Fleet* fleet_parse(const xmlNodePtr parent);
// 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((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;
}
// 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) {
// WElll... Trying to shoot when you have no ammo?? FUUU
int quantity = (outfit_isAmmo(w->outfit) && p->secondary) ?
p->secondary->quantity : w->quantity;
// Check to see if weapon is ready.
if((SDL_GetTicks() - w->timer) < (w->outfit->delay / quantity)) return;
// Regular weapons.
if(outfit_isWeapon(w->outfit)) {
// Different weapons.
switch(w->outfit->type) {
case OUTFIT_TYPE_BOLT:
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, Shootee can't be the target.
else if(outfit_isLauncher(w->outfit) && (w == p->secondary) && (p->id != t)) {
if(p->ammo && (p->ammo->quantity > 0)) {
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 double damage_shield, const double damage_armour) {
if(p->shield - damage_shield > 0.)
p->shield -= damage_shield;
else if(p->shield > 0.) {
// Shields can take part of the blow.
p->armour -= p->shield/damage_shield*damage_armour;
p->shield = 0.;
}
else if(p->armour-damage_armour > 0.)
p->armour -= damage_armour;
else
p->armour = 0.;
}
// 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->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) {
int sx, sy;
// Get the sprite corresponding to the direction facing.
gl_getSpriteFromDir(&sx, &sy, p->ship->gfx_space, p->solid->dir);
gl_blitSprite(p->ship->gfx_space, p->solid->pos.x, p->solid->pos.y, sx, sy, NULL);
}
// Update the pilot.
static void pilot_update(Pilot* pilot, const double dt) {
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);
return;
}
// We are still alive.
else if(pilot->armour < pilot->armour_max)
pilot->armour += pilot->ship->armour_regen*dt;
else
pilot->shield += pilot->ship->shield_regen*dt;
if(pilot->armour > pilot->armour_max) pilot->armour = pilot->armour_max;
if(pilot->shield > pilot->shield_max) pilot->shield = pilot->shield_max;
// Update the solid.
(*pilot->solid->update)(pilot->solid, dt);
if(VMOD(pilot->solid->vel) > pilot->ship->speed) {
// Should not go faster.
vect_pset(&pilot->solid->vel, pilot->ship->speed, VANGLE(pilot->solid->vel));
}
}
// ==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, Faction* faction, AI_Profile* ai,
const double dir, const Vec2* pos, const Vec2* vel, const int flags) {
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);
// Max shields armour.
pilot->armour_max = ship->armour;
pilot->shield_max = ship->shield;
pilot->energy_max = ship->energy;
pilot->armour = pilot->armour_max;
pilot->shield = pilot->shield_max;
pilot->energy = pilot->energy_max;
// Initially idle.
pilot->task = NULL;
// Outfits.
pilot->outfits = NULL;
pilot->secondary = NULL;
pilot->ammo = NULL;
ShipOutfit* so;
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(flags & PILOT_PLAYER) {
pilot->think = player_think; // Players don't need to thing! :P
pilot->render = NULL;
pilot_setFlag(pilot, PILOT_PLAYER); // It's a player!
player = pilot;
gui_load(pilot->ship->gui); // Load the GUI.
} else {
pilot->think = ai_think;
pilot->render = pilot_render;
}
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, Faction* faction, AI_Profile* ai, const double dir,
const Vec2* pos, const Vec2* vel, const int flags) {
Pilot* 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.
pilot_stack = realloc(pilot_stack, ++mpilots*sizeof(Pilot*));
pilot_stack[pilots-1] = dyn;
}
return dyn->id;
}
// Frees and cleans up a pilot.
static void pilot_free(Pilot* p) {
solid_free(p->solid);
free(p->outfits);
free(p->name);
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;
while(i < pilots) {
pilot_stack[i] = pilot_stack[i+1];
i++;
}
pilot_free(p);
}
// Free the prisoned pilot!
void pilots_free(void) {
int i;
for(i = 0; 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]))
pilot_stack[i]->think(pilot_stack[i]);
if(pilot_stack[i]->update)
pilot_stack[i]->update(pilot_stack[i], dt);
if(pilot_stack[i]->render)
pilot_stack[i]->render(pilot_stack[i]);
}
}
// 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]);
}
// Return the fleet based on 'name'
Fleet* fleet_get(const char* name) {
int i;
for(i = 0; i < nfleets; i++)
if(strcmp(name, fleet_stack[i].name)==0)
return fleet_stack+i;
return NULL;
}
// 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->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) == NULL) WARN("Fleet '%s' missing '"s"' element", tmp->name)
MELEMENT(tmp->ai, "ai");
MELEMENT(tmp->faction, "faction");
MELEMENT(tmp->pilots, "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(node->type == XML_NODE_START && strcmp((char*)node->name, XML_FLEET)==0) {
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;
}