#include #include #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); }