513 lines
14 KiB
C
513 lines
14 KiB
C
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
#include "lephisto.h"
|
|
#include "log.h"
|
|
#include "ldata.h"
|
|
#include "lxml.h"
|
|
#include "toolkit.h"
|
|
#include "ship.h"
|
|
|
|
#define XML_ID "Ships" /* XML section identifier. */
|
|
#define XML_SHIP "ship"
|
|
|
|
#define SHIP_DATA "../dat/ship.xml"
|
|
#define SHIP_GFX "../gfx/ship/"
|
|
#define SHIP_EXT ".png"
|
|
#define SHIP_TARGET "_target"
|
|
#define SHIP_COMM "_comm"
|
|
|
|
#define VIEW_WIDTH 300
|
|
#define VIEW_HEIGHT 300
|
|
|
|
#define BUTTON_WIDTH 80
|
|
#define BUTTON_HEIGHT 30
|
|
|
|
#define CHUNK_SIZE 32
|
|
|
|
static Ship* ship_stack = NULL;
|
|
static int ship_nstack = 0;
|
|
|
|
static int ship_parse(Ship* tmp, xmlNodePtr parent);
|
|
|
|
/* Get a ship based on it's name. */
|
|
Ship* ship_get(const char* name) {
|
|
Ship* tmp = ship_stack;
|
|
int i;
|
|
|
|
for(i = 0; i < ship_nstack; i++)
|
|
if(strcmp((tmp+i)->name, name)==0) break;
|
|
|
|
if(i == ship_nstack) /* Ship doesn't exist, game will probably crash now. */
|
|
WARN("Ship %s does not exist", name);
|
|
|
|
return tmp+i;
|
|
}
|
|
|
|
/* Return all the ships in text form. */
|
|
Ship** ship_getTech(int* n, const int* tech, const int techmax) {
|
|
int i, j, k, num, price;
|
|
Ship** result;
|
|
Ship** ships;
|
|
|
|
/* Get availabble ships for tech. */
|
|
ships = malloc(sizeof(Ship*) * ship_nstack);
|
|
num = 0;
|
|
for(i = 0; i < ship_nstack; i++) {
|
|
if(ship_stack[i].tech <= tech[0]) { /* Check vs base tech. */
|
|
ships[num] = &ship_stack[i];
|
|
num++;
|
|
} else {
|
|
for(j = 0; j < techmax; j++)
|
|
if(tech[j] == ship_stack[i].tech) { /* Check vs special tech. */
|
|
ships[num] = &ship_stack[i];
|
|
num++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now sort by price. */
|
|
*n = 0;
|
|
price = -1;
|
|
result = malloc(sizeof(Ship*) * num);
|
|
/* Until we fill the new stack. */
|
|
for(i = 0; i < num; i++) {
|
|
/* Check for cheapest. */
|
|
for(j = 0; j < num; j++) {
|
|
/* Is cheapest? */
|
|
if((price == -1) || (ships[price]->price > ships[j]->price)) {
|
|
/* Check if already in stack. */
|
|
for(k = 0; k < (*n); k++)
|
|
if(strcmp(result[k]->name, ships[j]->name)==0)
|
|
break;
|
|
|
|
/* Not in stack and therefore is cheapest. */
|
|
if(k == (*n))
|
|
price = j;
|
|
}
|
|
}
|
|
/* Add the current cheapest to stack. */
|
|
result[i] = ships[price];
|
|
(*n)++;
|
|
price = -1;
|
|
}
|
|
|
|
/* Cleanup */
|
|
free(ships);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Get the ship's classname. */
|
|
|
|
char* ship_class(Ship* s) {
|
|
switch(s->class) {
|
|
case SHIP_CLASS_NULL:
|
|
return "NULL";
|
|
/* Civilian. */
|
|
case SHIP_CLASS_YACHT:
|
|
return "Yacht";
|
|
case SHIP_CLASS_LUXURY_YACHT:
|
|
return "Luxury Yacht";
|
|
case SHIP_CLASS_CRUISE_SHIP:
|
|
return "Cruise Ship";
|
|
|
|
/* Merchant. */
|
|
case SHIP_CLASS_COURIER:
|
|
return "Courier";
|
|
case SHIP_CLASS_FREIGHTER:
|
|
return "Freighter";
|
|
case SHIP_CLASS_BULK_CARRIER:
|
|
return "Bulk Carrier";
|
|
|
|
/* Military. */
|
|
case SHIP_CLASS_SCOUT:
|
|
return "Scout";
|
|
case SHIP_CLASS_FIGHTER:
|
|
return "Fighter";
|
|
case SHIP_CLASS_BOMBER:
|
|
return "Bomber";
|
|
case SHIP_CLASS_CORVETTE:
|
|
return "Corvette";
|
|
case SHIP_CLASS_DESTROYER:
|
|
return "Destroyer";
|
|
case SHIP_CLASS_CRUISER:
|
|
return "Cruiser";
|
|
case SHIP_CLASS_CARRIER:
|
|
return "Carrier";
|
|
|
|
/* Robotic. */
|
|
case SHIP_CLASS_DRONE:
|
|
return "Drone";
|
|
case SHIP_CLASS_HEAVY_DRONE:
|
|
return "Heavy Drone";
|
|
case SHIP_CLASS_MOTHERSHIP:
|
|
return "Mothership";
|
|
|
|
/* Unknown. */
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @fn ShipClass ship_classFromString(char* str)
|
|
*
|
|
* @brief Get the machine ship class identifier from a human readable string.
|
|
* @param str String to extract ship class identifier from.
|
|
*/
|
|
ShipClass ship_classFromString(char* str) {
|
|
/* Civilian. */
|
|
if(strcmp(str, "Yacht")==0)
|
|
return SHIP_CLASS_YACHT;
|
|
else if(strcmp(str, "Luxury Yacht")==0)
|
|
return SHIP_CLASS_LUXURY_YACHT;
|
|
else if(strcmp(str, "Cruise Ship")==0)
|
|
return SHIP_CLASS_CRUISE_SHIP;
|
|
|
|
/* Merchant. */
|
|
else if(strcmp(str, "Courier")==0)
|
|
return SHIP_CLASS_COURIER;
|
|
else if(strcmp(str, "Freighter")==0)
|
|
return SHIP_CLASS_FREIGHTER;
|
|
else if(strcmp(str, "Bulk Carrier")==0)
|
|
return SHIP_CLASS_BULK_CARRIER;
|
|
|
|
/* Military. */
|
|
else if(strcmp(str, "Scout")==0)
|
|
return SHIP_CLASS_SCOUT;
|
|
else if(strcmp(str, "Fighter")==0)
|
|
return SHIP_CLASS_FIGHTER;
|
|
else if(strcmp(str, "Bomber")==0)
|
|
return SHIP_CLASS_BOMBER;
|
|
else if(strcmp(str, "Corvette")==0)
|
|
return SHIP_CLASS_CORVETTE;
|
|
else if(strcmp(str, "Destroyer")==0)
|
|
return SHIP_CLASS_DESTROYER;
|
|
else if(strcmp(str, "Cruiser")==0)
|
|
return SHIP_CLASS_CRUISER;
|
|
else if(strcmp(str, "Carrier")==0)
|
|
return SHIP_CLASS_CARRIER;
|
|
|
|
/* Robotic. */
|
|
else if(strcmp(str, "Drone")==0)
|
|
return SHIP_CLASS_DRONE;
|
|
else if(strcmp(str, "Heavy Drone")==0)
|
|
return SHIP_CLASS_HEAVY_DRONE;
|
|
else if(strcmp(str, "Mothership")==0)
|
|
return SHIP_CLASS_MOTHERSHIP;
|
|
|
|
/* Unknown. */
|
|
return SHIP_CLASS_NULL;
|
|
}
|
|
|
|
/**
|
|
* @fn int ship_basePrice(Ship* s)
|
|
*
|
|
* @brief Get the ships base price (no outfits).
|
|
*/
|
|
int ship_basePrice(Ship* s) {
|
|
int price;
|
|
ShipOutfit* o;
|
|
|
|
/* Base price is ships price minus it's outfits. */
|
|
price = s->price;
|
|
for(o = s->outfit; o != NULL; o = o->next)
|
|
price -= o->quantity * o->data->price;
|
|
|
|
if(price < 0) {
|
|
WARN("Negative ship base price!");
|
|
price = 0;
|
|
}
|
|
|
|
return price;
|
|
}
|
|
|
|
static int ship_parse(Ship* tmp, xmlNodePtr parent) {
|
|
xmlNodePtr cur, node;
|
|
ShipOutfit* otmp, *ocur;
|
|
|
|
char str[PATH_MAX];
|
|
char* stmp;
|
|
|
|
/* Clear memory. */
|
|
memset(tmp, 0, sizeof(Ship));
|
|
|
|
/* Defaults. */
|
|
str[0] = '\0';
|
|
|
|
/* Get name. */
|
|
xmlr_attr(parent, "name", tmp->name);
|
|
if(tmp->name == NULL)
|
|
WARN("Ship in "SHIP_DATA" has invalid or no name.");
|
|
|
|
/* Load data. */
|
|
node = parent->xmlChildrenNode;
|
|
|
|
do {
|
|
/* Load all the data. */
|
|
if(xml_isNode(node,"GFX")) {
|
|
|
|
/* Load the base graphic. */
|
|
tmp->gfx_space = xml_parseTexture(node,
|
|
SHIP_GFX"%s"SHIP_EXT, 6, 6,
|
|
OPENGL_TEX_MAPTRANS);
|
|
|
|
/* Load the comm graphic. */
|
|
tmp->gfx_comm = xml_parseTexture(node,
|
|
SHIP_GFX"%s"SHIP_COMM SHIP_EXT, 1, 1, 0);
|
|
|
|
/* Load the target graphic. */
|
|
xmlr_attr(node, "target", stmp);
|
|
if(stmp != NULL) {
|
|
snprintf(str, PATH_MAX,
|
|
SHIP_GFX"%s"SHIP_TARGET SHIP_EXT, stmp);
|
|
tmp->gfx_target = gl_newImage(str, 0);
|
|
free(stmp);
|
|
} else { /* Load standard target graphic. */
|
|
snprintf(str, PATH_MAX,
|
|
SHIP_GFX"%s"SHIP_TARGET SHIP_EXT, xml_get(node));
|
|
tmp->gfx_target = gl_newImage(str, 0);
|
|
}
|
|
}
|
|
xmlr_strd(node, "GUI", tmp->gui);
|
|
if(xml_isNode(node, "sound")) {
|
|
tmp->sound = sound_get(xml_get(node));
|
|
continue;
|
|
}
|
|
if(xml_isNode(node, "class")) {
|
|
tmp->class = ship_classFromString(xml_get(node));
|
|
}
|
|
xmlr_int( node, "price", tmp->price);
|
|
xmlr_int( node, "tech", tmp->tech);
|
|
xmlr_strd(node, "license", tmp->license);
|
|
xmlr_strd(node, "fabricator", tmp->fabricator);
|
|
xmlr_strd(node, "description", tmp->description);
|
|
if(xml_isNode(node, "movement")) {
|
|
cur = node->children;
|
|
do {
|
|
xmlr_int(cur, "thrust", tmp->thrust);
|
|
xmlr_int(cur, "turn", tmp->turn);
|
|
xmlr_int(cur, "speed", tmp->speed);
|
|
} while(xml_nextNode(cur));
|
|
continue;
|
|
}
|
|
if(xml_isNode(node, "health")) {
|
|
cur = node->children;
|
|
do {
|
|
xmlr_float(cur, "armour", tmp->armour);
|
|
xmlr_float(cur, "shield", tmp->shield);
|
|
xmlr_float(cur, "energy", tmp->energy);
|
|
if(xml_isNode(cur, "armour_regen"))
|
|
tmp->armour_regen = (double)(xml_getInt(cur))/60.0;
|
|
else if(xml_isNode(cur, "shield_regen"))
|
|
tmp->shield_regen = (double)(xml_getInt(cur))/60.0;
|
|
else if(xml_isNode(cur, "energy_regen"))
|
|
tmp->energy_regen = (double)(xml_getInt(cur))/60.0;
|
|
} while(xml_nextNode(cur));
|
|
continue;
|
|
}
|
|
if(xml_isNode(node, "characteristics")) {
|
|
cur = node->children;
|
|
do {
|
|
xmlr_int(cur, "crew", tmp->crew);
|
|
xmlr_float(cur, "mass", tmp->mass);
|
|
xmlr_int(cur, "fuel", tmp->fuel);
|
|
xmlr_int(cur, "cap_weapon", tmp->cap_weapon);
|
|
xmlr_int(cur, "cap_cargo", tmp->cap_cargo);
|
|
} while(xml_nextNode(cur));
|
|
continue;
|
|
}
|
|
if(xml_isNode(node, "outfits")) {
|
|
cur = node->children;
|
|
do {
|
|
if(xml_isNode(cur, "outfit")) {
|
|
otmp = MALLOC_L(ShipOutfit);
|
|
otmp->data = outfit_get(xml_get(cur));
|
|
stmp = xml_nodeProp(cur, "quantity");
|
|
if(!stmp)
|
|
WARN("Ship '%s' is missing tag 'quantity for outfit '%s'",
|
|
tmp->name, otmp->data->name);
|
|
otmp->quantity = atoi(stmp);
|
|
free(stmp);
|
|
otmp->next = NULL;
|
|
|
|
if((ocur = tmp->outfit) == NULL) tmp->outfit = otmp;
|
|
else {
|
|
while(ocur->next) ocur = ocur->next;
|
|
ocur->next = otmp;
|
|
}
|
|
}
|
|
} while(xml_nextNode(cur));
|
|
continue;
|
|
}
|
|
} while(xml_nextNode(node));
|
|
|
|
tmp->thrust *= tmp->mass; /* Helps keep number sane. */
|
|
|
|
#define MELEMENT(o,s) if(o) WARN("Ship '%s' missing '"s"' element", tmp->name)
|
|
MELEMENT(tmp->name == NULL, "name");
|
|
MELEMENT(tmp->gfx_space == NULL, "GFX");
|
|
MELEMENT(tmp->gui == NULL, "GUI");
|
|
MELEMENT(tmp->class==SHIP_CLASS_NULL, "class");
|
|
MELEMENT(tmp->price==0, "price");
|
|
MELEMENT(tmp->tech==0, "tech");
|
|
MELEMENT(tmp->fabricator==0, "fabricator");
|
|
MELEMENT(tmp->description==0, "description");
|
|
MELEMENT(tmp->thrust==0, "thrust");
|
|
MELEMENT(tmp->turn==0, "turn");
|
|
MELEMENT(tmp->speed==0, "speed");
|
|
MELEMENT(tmp->armour==0, "armour");
|
|
MELEMENT(tmp->armour_regen==0, "armour_regen");
|
|
MELEMENT(tmp->shield==0, "shield");
|
|
MELEMENT(tmp->shield_regen==0, "shield_regen");
|
|
MELEMENT(tmp->energy==0, "energy");
|
|
MELEMENT(tmp->energy_regen==0, "energy_regen");
|
|
MELEMENT(tmp->fuel==0, "fuel");
|
|
MELEMENT(tmp->crew==0, "crew");
|
|
MELEMENT(tmp->mass==0, "mass");
|
|
MELEMENT(tmp->cap_cargo==0, "cap_cargo");
|
|
MELEMENT(tmp->cap_weapon==0, "cap_weapon");
|
|
#undef MELEMENT
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ships_load(void) {
|
|
int mem;
|
|
uint32_t bufsize;
|
|
char* buf = ldata_read(SHIP_DATA, &bufsize);
|
|
|
|
xmlNodePtr node;
|
|
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
|
|
|
|
node = doc->xmlChildrenNode; /* Ships node. */
|
|
if(strcmp((char*)node->name, XML_ID)) {
|
|
ERR("Malformed "SHIP_DATA" file: missing root element '"XML_ID"'");
|
|
return -1;
|
|
}
|
|
|
|
node = node->xmlChildrenNode; /* First ship node. */
|
|
if(node == NULL) {
|
|
ERR("Malformed "SHIP_DATA" file: Does not contain elements");
|
|
return -1;
|
|
}
|
|
|
|
mem = 0;
|
|
do {
|
|
if(xml_isNode(node, XML_SHIP)) {
|
|
ship_nstack++;
|
|
|
|
/* Check to see if we need to grow the stack. */
|
|
if(ship_nstack > mem) {
|
|
mem += CHUNK_SIZE;
|
|
ship_stack = realloc(ship_stack, sizeof(Ship)*mem);
|
|
}
|
|
|
|
/* Load the ship. */
|
|
ship_parse(&ship_stack[ship_nstack-1], node);
|
|
}
|
|
} while(xml_nextNode(node));
|
|
|
|
/* Shrink to minimum size - Won't change later. */
|
|
ship_stack = realloc(ship_stack, sizeof(Ship) * ship_nstack);
|
|
|
|
xmlFreeDoc(doc);
|
|
free(buf);
|
|
|
|
DEBUG("Loaded %d ship%s", ship_nstack, (ship_nstack==1) ? "" : "s");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ships_free(void) {
|
|
ShipOutfit* so, *sot;
|
|
int i;
|
|
for(i = 0; i < ship_nstack; i++) {
|
|
/* Free stored strings. */
|
|
if(ship_stack[i].name) free(ship_stack[i].name);
|
|
if(ship_stack[i].description) free(ship_stack[i].description);
|
|
if(ship_stack[i].gui) free(ship_stack[i].gui);
|
|
if(ship_stack[i].fabricator) free(ship_stack[i].fabricator);
|
|
if(ship_stack[i].license != NULL) free(ship_stack[i].license);
|
|
|
|
so = ship_stack[i].outfit;
|
|
while(so) { /* free the ship outfit. */
|
|
sot = so;
|
|
so = so->next;
|
|
free(sot);
|
|
}
|
|
gl_freeTexture(ship_stack[i].gfx_space);
|
|
gl_freeTexture(ship_stack[i].gfx_comm);
|
|
gl_freeTexture(ship_stack[i].gfx_target);
|
|
}
|
|
free(ship_stack);
|
|
ship_stack = NULL;
|
|
}
|
|
|
|
/* Used to visualize the ships status. */
|
|
void ship_view(unsigned int unused, char* shipname) {
|
|
(void) unused;
|
|
Ship* s;
|
|
char buf[1024];
|
|
unsigned int wid;
|
|
int h;
|
|
s = ship_get(shipname);
|
|
|
|
snprintf(buf, 1024,
|
|
"Name:\n"
|
|
"Class:\n"
|
|
"Crew:\n"
|
|
"Mass:\n"
|
|
"\n"
|
|
"Thrust:\n"
|
|
"Max Speed:\n"
|
|
"Turn:\n"
|
|
"\n"
|
|
"Shield:\n"
|
|
"Armour:\n"
|
|
"Energy:\n"
|
|
"\n"
|
|
"Weapon Space:\n"
|
|
"Cargo Space:\n"
|
|
"Fuel:\n");
|
|
h = gl_printHeight(&gl_smallFont, VIEW_WIDTH, buf);
|
|
|
|
wid = window_create(shipname, -1, -1, VIEW_WIDTH, h+60+BUTTON_HEIGHT);
|
|
window_addText(wid, 20, -40, VIEW_WIDTH, h,
|
|
0, "txtLabel", &gl_smallFont, &cDConsole, buf);
|
|
|
|
snprintf(buf, 1024,
|
|
"%s\n" /* Name. */
|
|
"%s\n" /* Class. */
|
|
"%d\n" /* Crew. */
|
|
"%d Tons\n" /* Mass. */
|
|
"\n"
|
|
"%.2f MN/ton\n" /* Thrust. */
|
|
"%.2f M/s\n" /* Speed. */
|
|
"%.2f Grad/s\n" /* Turn. */
|
|
"\n"
|
|
"%.2f MJ (%.2f MJ/s)\n)" /* Shield. */
|
|
"%.2f MJ (%.2f MJ/s)\n)" /* Armour. */
|
|
"%.2f MJ (%.2f MJ/s)\n)" /* Energy. */
|
|
"\n"
|
|
"%d Tons\n" /* Weapon. */
|
|
"%d Tons\n" /* Cargo. */
|
|
"%d Units\n", /* Fuel. */
|
|
s->name, ship_class(s), s->crew, s->mass,
|
|
s->thrust/s->mass, s->speed, s->turn,
|
|
s->shield, s->shield_regen, s->armour, s->armour_regen,
|
|
s->energy, s->energy_regen,
|
|
s->cap_weapon, s->cap_cargo, s->fuel);
|
|
|
|
window_addText(wid, 120, -40, VIEW_WIDTH-140, h,
|
|
0, "txtProperties", &gl_smallFont, &cBlack, buf);
|
|
|
|
/* Close the button. */
|
|
snprintf(buf, 37, "close%s", shipname);
|
|
window_addButton(wid, -20, 20,
|
|
BUTTON_WIDTH, BUTTON_HEIGHT,
|
|
buf, "Close", window_close);
|
|
}
|
|
|