Lephisto/src/ship.c

464 lines
13 KiB
C

#include <string.h>
#include <limits.h>
#include "lephisto.h"
#include "log.h"
#include "pack.h"
#include "xml.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 VIEW_WIDTH 300
#define VIEW_HEIGHT 300
#define BUTTON_WIDTH 80
#define BUTTON_HEIGHT 30
static Ship* ship_stack = NULL;
static int ship_nstack = 0;
static Ship* ship_parse(xmlNodePtr parent);
static void ship_view_close(char* btn);
/* 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_CIV_LIGHT:
return "Civilian Light";
case SHIP_CLASS_CIV_MEDIUM:
return "Civilian Medium";
case SHIP_CLASS_CIV_HEAVY:
return "Civilian Heavy";
/* Military. */
case SHIP_CLASS_MIL_LIGHT:
return "Military Light";
case SHIP_CLASS_MIL_MEDIUM:
return "Military Medium";
case SHIP_CLASS_MIL_HEAVY:
return "Military Heavy";
/* Robotic. */
case SHIP_CLASS_ROB_LIGHT:
return "Robotic Light";
case SHIP_CLASS_ROB_MEDIUM:
return "Robotic Medium";
case SHIP_CLASS_ROB_HEAVY:
return "Robotic Heavy";
/* Hybrid. */
case SHIP_CLASS_HYB_LIGHT:
return "Hybrid Light";
case SHIP_CLASS_HYB_MEDIUM:
return "Hybrid Medium";
case SHIP_CLASS_HYB_HEAVY:
return "Hybrid Heavy";
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, "civ light")==0)
return SHIP_CLASS_CIV_LIGHT;
if(strcmp(str, "civ medium")==0)
return SHIP_CLASS_CIV_MEDIUM;
if(strcmp(str, "civ heavy")==0)
return SHIP_CLASS_CIV_HEAVY;
/* Military. */
if(strcmp(str, "mil light")==0)
return SHIP_CLASS_MIL_LIGHT;
if(strcmp(str, "mil medium")==0)
return SHIP_CLASS_MIL_MEDIUM;
if(strcmp(str, "mil heavy")==0)
return SHIP_CLASS_MIL_HEAVY;
/* Robotic. */
if(strcmp(str, "rob light")==0)
return SHIP_CLASS_ROB_LIGHT;
if(strcmp(str, "rob medium")==0)
return SHIP_CLASS_ROB_MEDIUM;
if(strcmp(str, "rob heavy")==0)
return SHIP_CLASS_ROB_HEAVY;
/* Hybrid. */
if(strcmp(str, "hyb light")==0)
return SHIP_CLASS_HYB_LIGHT;
if(strcmp(str, "hyb medium")==0)
return SHIP_CLASS_HYB_MEDIUM;
if(strcmp(str, "hyb heavy")==0)
return SHIP_CLASS_HYB_HEAVY;
/* 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;
return price;
}
static Ship* ship_parse(xmlNodePtr parent) {
xmlNodePtr cur, node;
Ship* tmp = CALLOC_L(Ship);
ShipOutfit* otmp, *ocur;
char str[PATH_MAX] = "\0";
char* stmp;
xmlr_attr(parent, "name", tmp->name);
if(tmp->name == NULL) WARN("Ship in "SHIP_DATA" has invalid or no name");
node = parent->xmlChildrenNode;
do {
/* Load all the data. */
if(xml_isNode(node,"GFX")) {
/* Load the base graphic. */
snprintf(str, strlen(xml_get(node)) +
sizeof(SHIP_GFX) + sizeof(SHIP_EXT),
SHIP_GFX"%s"SHIP_EXT, xml_get(node));
tmp->gfx_space = gl_newSprite(str, 6, 6);
xmlr_attr(node, "target", stmp);
if(stmp != NULL) {
snprintf(str, strlen(stmp) +
sizeof(SHIP_GFX)+sizeof(SHIP_TARGET)+sizeof(SHIP_EXT),
SHIP_GFX"%s"SHIP_TARGET SHIP_EXT, stmp);
tmp->gfx_target = gl_newImage(str);
free(stmp);
} else { /* Load standard target graphic. */
snprintf(str, strlen(xml_get(node)) +
sizeof(SHIP_GFX)+sizeof(SHIP_TARGET)+sizeof(SHIP_EXT),
SHIP_GFX"%s"SHIP_TARGET SHIP_EXT, xml_get(node));
tmp->gfx_target = gl_newImage(str);
}
}
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, "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 tmp;
}
int ships_load(void) {
uint32_t bufsize;
char* buf = pack_readfile(DATA, SHIP_DATA, &bufsize);
xmlNodePtr node;
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
Ship* tmp = NULL;
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;
}
do {
if(node->type == XML_NODE_START && strcmp((char*)node->name, XML_SHIP)==0) {
tmp = ship_parse(node);
ship_stack = realloc(ship_stack, sizeof(Ship)*(++ship_nstack));
memcpy(ship_stack+ship_nstack-1, tmp, sizeof(Ship));
free(tmp);
}
} while((node = node->next));
xmlFreeDoc(doc);
free(buf);
xmlCleanupParser();
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);
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_target);
}
free(ship_stack);
ship_stack = NULL;
}
/* Used to visualize the ships status. */
void ship_view(char* shipname) {
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", ship_view_close);
}
static void ship_view_close(char* btn) {
window_destroy(window_get(btn+5)); /* "closefoo -> Foo" */
}