Lephisto/src/outfit.c

389 lines
12 KiB
C

#include <math.h>
#include <string.h>
#include "SDL_thread.h"
#include "lephisto.h"
#include "log.h"
#include "pack.h"
#include "xml.h"
#include "spfx.h"
#include "outfit.h"
#define outfit_setProp(o,p) ((o)->properties |= p)
#define XML_OUTFIT_ID "Outfits"
#define XML_OUTFIT_TAG "outfit"
#define OUTFIT_DATA "../dat/outfit.xml"
#define OUTFIT_GFX "../gfx/outfit/"
extern SDL_mutex* sound_lock; // Sound.c
// The Stack.
static Outfit* outfit_stack = NULL;
static int outfits = 0;
static Outfit* outfit_parse(const xmlNodePtr parent);
static void outfit_parseSWeapon(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSLauncher(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSAmmo(Outfit* tmp, const xmlNodePtr parent);
// Return an outfit.
Outfit* outfit_get(const char* name) {
int i;
for(i = 0; i < outfits; i++)
if(strcmp(name, outfit_stack[i].name)==0)
return &outfit_stack[i];
return NULL;
}
// Return all the outfits.
char** outfit_getAll(int* n) {
char** outfitnames = malloc(sizeof(Outfit*) * outfits);
for((*n) = 0; (*n) < outfits; (*n)++)
outfitnames[*n] = strdup(outfit_stack[*n].name);
return outfitnames;
}
// Return 1 if outfit is a weapon (beam/bolt).
int outfit_isWeapon(const Outfit* o) {
return ((o->type == OUTFIT_TYPE_BOLT) || (o->type == OUTFIT_TYPE_BEAM));
}
// Return 1 if outfit is a launcher.
int outfit_isLauncher(const Outfit* o) {
return((o->type == OUTFIT_TYPE_MISSILE_DUMB) ||
(o->type == OUTFIT_TYPE_MISSILE_SEEK) ||
(o->type == OUTFIT_TYPE_MISSILE_SEEK_SMART) ||
(o->type == OUTFIT_TYPE_MISSILE_SWARM) ||
(o->type == OUTFIT_TYPE_MISSILE_SWARM_SMART));
}
// Return 1 if outfit is weapon ammunition.
int outfit_isAmmo(const Outfit* o) {
return((o->type == OUTFIT_TYPE_MISSILE_DUMB_AMMO) ||
(o->type == OUTFIT_TYPE_MISSILE_SEEK_AMMO) ||
(o->type == OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO) ||
(o->type == OUTFIT_TYPE_MISSILE_SWARM_AMMO) ||
(o->type == OUTFIT_TYPE_MISSILE_SWARM_SMART_AMMO));
}
int outfit_isTurret(const Outfit* o) {
return ((o->type == OUTFIT_TYPE_TURRET_BOLT) ||
(o->type == OUTFIT_TYPE_TURRET_BEAM));
}
// Get the outfit graphics.
glTexture* outfit_gfx(const Outfit* o) {
if(outfit_isWeapon(o)) return o->u.blt.gfx_space;
else if(outfit_isAmmo(o)) return o->u.amm.gfx_space;
else if(outfit_isTurret(o)) return o->u.blt.gfx_space;
return NULL;
}
// Get the outfit spfx if applicable.
int outfit_spfx(const Outfit* o) {
if(outfit_isWeapon(o)) return o->u.blt.spfx;
else if(outfit_isAmmo(o)) return o->u.amm.spfx;
else if(outfit_isTurret(o)) return o->u.blt.spfx;
return -1;
}
double outfit_dmgShield(const Outfit* o) {
if(outfit_isWeapon(o)) return o->u.blt.damage_armour;
else if(outfit_isAmmo(o)) return o->u.amm.damage_armour;
else if(outfit_isTurret(o)) return o->u.blt.damage_armour;
return -1;
}
double outfit_dmgArmour(const Outfit* o) {
if(outfit_isWeapon(o)) return o->u.blt.damage_shield;
else if(outfit_isAmmo(o)) return o->u.amm.damage_shield;
else if(outfit_isTurret(o)) return o->u.blt.damage_shield;
return -1;
}
int outfit_delay(const Outfit* o) {
if(outfit_isWeapon(o)) return o->u.blt.delay;
else if(outfit_isLauncher(o)) return o->u.lau.delay;
else if(outfit_isTurret(o)) return o->u.blt.delay;
return -1;
}
const char* outfit_typename[] = {
"NULL",
"Bolt Cannon",
"Beam Cannon",
"Dumb Missile",
"Dumb Missile Ammunition",
"Seeker Missile",
"Seeker Missile Ammunition",
"Smart Seeker Missile",
"Smart Seeker Missile Ammunition",
"Swam Missile",
"Swarm Missile Ammunition Pack",
"Smart Swarm Missile",
"Smart Swarm Missile Ammunition Pack",
"Bolt Turret",
"Beam Turret"
};
// Return the broad outfit type.
const char* outfit_typenamebroad[] = {
"NULL",
"Weapon",
"Launcher",
"Ammo",
"Turret"
};
const char* outfit_getTypeBroad(const Outfit* o) {
int i = 0;
if(outfit_isWeapon(o)) i = 1;
else if(outfit_isLauncher(o)) i = 2;
else if(outfit_isAmmo(o)) i = 3;
else if(outfit_isTurret(o)) i = 4;
return outfit_typenamebroad[i];
}
const char* outfit_getType(const Outfit* o) {
return outfit_typename[o->type];
}
// Parses the specific area for a weapon and loads it into outfit.
static void outfit_parseSWeapon(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr cur, node;
node = parent->xmlChildrenNode;
char str[PATH_MAX] = "\0";
do {
// Load all the things.
if(xml_isNode(node, "speed")) tmp->u.blt.speed = xml_getFloat(node);
else if(xml_isNode(node, "delay")) tmp->u.blt.delay = xml_getInt(node);
else if(xml_isNode(node, "range")) tmp->u.blt.range = xml_getFloat(node);
else if(xml_isNode(node, "accuracy")) tmp->u.blt.accuracy = xml_getFloat(node);
else if(xml_isNode(node, "gfx")) {
snprintf(str, strlen(xml_get(node))+sizeof(OUTFIT_GFX)+4,
OUTFIT_GFX"%s.png", xml_get(node));
tmp->u.blt.gfx_space = gl_newSprite(str, 6, 6);
}
else if(xml_isNode(node, "spfx"))
tmp->u.blt.spfx = spfx_get(xml_get(node));
else if(xml_isNode(node, "sound"))
tmp->u.blt.sound = sound_get(xml_get(node));
else if(xml_isNode(node, "damage")) {
cur = node->children;
do {
if(xml_isNode(cur, "armour"))
tmp->u.blt.damage_armour = xml_getFloat(cur);
else if(xml_isNode(cur, "shield"))
tmp->u.blt.damage_shield = xml_getFloat(cur);
} while((cur = cur->next));
}
} while((node = node->next));
#define MELEMENT(o,s) if((o) == 0) \
WARN("Outfit '%s' missing '"s"' element", tmp->name)
if(tmp->u.blt.gfx_space == NULL)
WARN("Outfit '%s' missing 'gfx' element", tmp->name);
MELEMENT(tmp->u.blt.sound, "sound");
MELEMENT(tmp->u.blt.delay, "delay");
MELEMENT(tmp->u.blt.speed, "speed");
MELEMENT(tmp->u.blt.range, "range");
MELEMENT(tmp->u.blt.accuracy, "accuracy");
MELEMENT(tmp->u.blt.damage_armour, "armour' from element 'damage");
MELEMENT(tmp->u.blt.damage_shield, "shield' from element 'damage");
#undef MELEMENT
}
// Parse the specific area for a launcher and loads it into Outfit.
static void outfit_parseSLauncher(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
node = parent->xmlChildrenNode;
do {
// Load the dataz.
if(xml_isNode(node, "delay")) tmp->u.lau.delay = xml_getInt(node);
else if(xml_isNode(node, "ammo")) tmp->u.lau.ammo = strdup(xml_get(node));
} while((node = node->next));
#define MELEMENT(o,s) if(o) WARN("Outfit '%s' missing '"s"' element", tmp->name)
MELEMENT(tmp->u.lau.ammo == NULL, "ammo");
MELEMENT(tmp->u.lau.delay==0, "delay");
#undef MELEMENT
}
// Parse the specific area for a weapon and load it into Outfit.
static void outfit_parseSAmmo(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr cur, node;
node = parent->xmlChildrenNode;
char str[PATH_MAX] = "\0";
do {
if(xml_isNode(node, "thrust")) tmp->u.amm.thrust = xml_getFloat(node);
else if(xml_isNode(node, "turn")) tmp->u.amm.turn = xml_getFloat(node);
else if(xml_isNode(node, "speed")) tmp->u.amm.speed = xml_getFloat(node);
else if(xml_isNode(node, "duration"))
tmp->u.amm.duration = (unsigned int)1000.*xml_getFloat(node);
else if(xml_isNode(node, "lockon"))
tmp->u.amm.lockon = (unsigned int)1000.*xml_getFloat(node);
else if(xml_isNode(node, "gfx")) {
snprintf(str, strlen(xml_get(node))+sizeof(OUTFIT_GFX)+4,
OUTFIT_GFX"%s.png", xml_get(node));
tmp->u.amm.gfx_space = gl_newSprite(str, 6, 6);
}
else if(xml_isNode(node, "spfx"))
tmp->u.amm.spfx = spfx_get(xml_get(node));
else if(xml_isNode(node, "sound"))
tmp->u.amm.sound = sound_get(xml_get(node));
else if(xml_isNode(node, "damage")) {
cur = node->children;
do {
if(xml_isNode(cur, "armour")) tmp->u.amm.damage_armour = xml_getFloat(cur);
else if(xml_isNode(cur, "shield")) tmp->u.amm.damage_shield = xml_getFloat(cur);
} while((cur = cur->next));
}
} while((node = node->next));
#define MELEMENT(o,s) if(o) WARN("Outfit '%s' missing '"s"' element", tmp->name)
MELEMENT(tmp->u.amm.gfx_space == NULL, "gfx");
MELEMENT((sound_lock != NULL) && (tmp->u.amm.sound == 0), "sound");
MELEMENT(tmp->u.amm.thrust==0, "thrust");
MELEMENT(tmp->u.amm.turn==0, "turn");
MELEMENT(tmp->u.amm.speed==0, "speed");
MELEMENT(tmp->u.amm.duration==0, "duration");
MELEMENT(tmp->u.amm.lockon==0, "lockon");
MELEMENT(tmp->u.amm.damage_armour==0, "armour' from element 'damage");
MELEMENT(tmp->u.amm.damage_shield==0, "shield' from element 'damage");
#undef MELEMENT
}
// Parse and return Outfits from parent node.
static Outfit* outfit_parse(const xmlNodePtr parent) {
Outfit* tmp = CALLOC_L(Outfit);
xmlNodePtr cur, node;
char* prop;
tmp->name = xml_nodeProp(parent, "name"); // Already malloced.
if(tmp->name == NULL) WARN("Outfit in "OUTFIT_DATA" has invalid or no name");
node = parent->xmlChildrenNode;
do {
// Load all the things.
if(xml_isNode(node, "general")) {
cur = node->children;
do {
if(xml_isNode(cur, "max")) tmp->max = xml_getInt(cur);
else if(xml_isNode(cur, "tech")) tmp->tech = xml_getInt(cur);
else if(xml_isNode(cur, "mass")) tmp->mass = xml_getInt(cur);
else if(xml_isNode(cur, "price")) tmp->price = xml_getInt(cur);
else if(xml_isNode(cur, "description")) tmp->description = strdup(xml_get(cur));
} while((cur = cur->next));
}
else if(xml_isNode(node, "specific")) {
// Has to be processed seperately.
// Get the type.
prop = xml_nodeProp(node, "type");
if(prop == NULL)
ERR("Outfit '%s' element 'specific' missing property 'type'", tmp->name);
tmp->type = atoi(prop);
free(prop);
// Is this the secondary weapon?
prop = xml_nodeProp(node, "secondary");
if(prop != NULL) {
if((int)atoi(prop)) outfit_setProp(tmp, OUTFIT_PROP_WEAP_SECONDARY);
free(prop);
}
if(tmp->type == OUTFIT_TYPE_NULL)
WARN("Outfit '%s' is of type NONE", tmp->name);
else if(outfit_isWeapon(tmp))
outfit_parseSWeapon(tmp, node);
else if(outfit_isLauncher(tmp))
outfit_parseSLauncher(tmp, node);
else if(outfit_isAmmo(tmp))
outfit_parseSAmmo(tmp, node);
else if(outfit_isTurret(tmp))
outfit_parseSWeapon(tmp, node);
}
} while((node = node->next));
#define MELEMENT(o,s) if(o) WARN("Outfit '%s' missing '"s"' element", tmp->name)
MELEMENT(tmp->name==NULL, "name");
MELEMENT(tmp->max==0, "max");
MELEMENT(tmp->tech==0, "tech");
//MELEMENT(tmp->mass==0, "mass");
MELEMENT(tmp->type==0, "type");
MELEMENT(tmp->price==0, "price");
MELEMENT(tmp->description==NULL, "description");
#undef MELEMENT
return tmp;
}
// Load all the outfits into the outfit stack.
int outfit_load(void) {
uint32_t bufsize;
char* buf = pack_readfile(DATA, OUTFIT_DATA, &bufsize);
Outfit* tmp;
xmlNodePtr node;
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
node = doc->xmlChildrenNode;
if(!xml_isNode(node, XML_OUTFIT_ID)) {
ERR("Malformed "OUTFIT_DATA" file: missing root element '"XML_OUTFIT_ID"'");
return -1;
}
node = node->xmlChildrenNode; // First system node.
if(node == NULL) {
ERR("Malformed "OUTFIT_DATA" file: does not contain elements");
return -1;
}
do {
if(xml_isNode(node, XML_OUTFIT_TAG)) {
tmp = outfit_parse(node);
outfit_stack = realloc(outfit_stack, sizeof(Outfit)*(++outfits));
memcpy(outfit_stack+outfits-1, tmp, sizeof(Outfit));
free(tmp);
}
} while((node = node->next));
xmlFreeDoc(doc);
free(buf);
xmlCleanupParser();
DEBUG("Loaded %d outfit%s", outfits, (outfits==1) ? "" : "s");
return 0;
}
// Frees the outfit stack.
void outfit_free(void) {
int i;
for(i = 0; i < outfits; i++) {
// Free graphics.
if(outfit_gfx(&outfit_stack[i]))
gl_freeTexture(outfit_gfx(&outfit_stack[i]));
// Strings.
if(outfit_isLauncher(&outfit_stack[i]) && outfit_stack[i].u.lau.ammo)
free(outfit_stack[i].u.lau.ammo);
if(outfit_stack[i].description)
free(outfit_stack[i].description);
free(outfit_stack[i].name);
}
free(outfit_stack);
}