#include #include #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" */ }