Lephisto/src/outfit.c
Allanis f1cc928709 [Add] Ammunition can now have accuracy (missiles etc..)
[Fix] Fixed a typo in weapon system.
[Add] New outfit.
2014-05-19 19:03:39 +01:00

1139 lines
34 KiB
C

#include <math.h>
#include <string.h>
#include "SDL_thread.h"
#include "lephisto.h"
#include "log.h"
#include "ldata.h"
#include "lxml.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/"
#define CHUNK_SIZE 64 /** Size to reallocate by. */
/* The Stack. */
static Outfit* outfit_stack = NULL;
static int outfit_nstack = 0;
/* Misc. */
static DamageType outfit_strToDamageType(char* buf);
static OutfitType outfit_strToOutfitType(char* buf);
/* Parsing. */
static int outfit_parseDamage(DamageType* dtype, double* dmg, xmlNodePtr node);
static int outfit_parse(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSBolt(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSBeam(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSLauncher(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSAmmo(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSMod(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSAfterburner(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSJammer(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSFighterBay(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSFighter(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSMap(Outfit* tmp, const xmlNodePtr parent);
static void outfit_parseSLicense(Outfit* tmp, const xmlNodePtr parent);
/* Return an outfit. */
Outfit* outfit_get(const char* name) {
int i;
for(i = 0; i < outfit_nstack; i++)
if(strcmp(name, outfit_stack[i].name)==0)
return &outfit_stack[i];
WARN("Outfit '%s' not found in stack.", name);
return NULL;
}
/* Return all the outfits. */
Outfit** outfit_getTech(int* n, const int* tech, const int techmax) {
int i, j, k, num, price;
Outfit** outfits;
Outfit** result;
OutfitType type;
outfits = malloc(sizeof(Outfit*)*outfit_nstack);
/* Get the available techs. */
num = 0;
for(i = 0; i < outfit_nstack; i++)
if(outfit_stack[i].tech <= tech[0]) { /* Check vs base tech. */
outfits[num] = &outfit_stack[i];
num++;
} else {
for(j = 0; j < techmax; j++) /* Check vs special techs. */
if(tech[j] == outfit_stack[i].tech) {
outfits[num] = &outfit_stack[i];
num++;
}
}
/* Now sort by type and price. */
*n = 0;
price = -1;
type = OUTFIT_TYPE_NULL+1; /* First type. */
result = malloc(sizeof(Outfit*)*num);
/* Sort by type */
while(type < OUTFIT_TYPE_SENTINEL) {
/* Check for cheapest. */
for(j = 0; j < num; j++) {
/* Must be of the current type. */
if(outfits[j]->type != type) continue;
/* Is this the cheapest? */
if((price == -1) || (outfits[price]->price > outfits[j]->price)) {
/* Check if already in stack. */
for(k = 0; k < (*n); k++)
if(strcmp(result[k]->name, outfits[j]->name)==0)
break;
/* Not in stack and therefore is cheapest. */
if(k == (*n))
price = j;
}
}
if(price == -1)
type++;
else {
/* Add current cheapest to stack. */
result[*n] = outfits[price];
(*n)++;
price = -1;
}
}
/* Cleanup. */
free(outfits);
return result;
}
/* Give the real shield damage, armour damage and knockback modifier. */
void outfit_calcDamage(double* dshield, double* darmour, double* knockback,
DamageType dtype, double dmg) {
double ds, da, kn;
switch(dtype) {
case DAMAGE_TYPE_ENERGY:
ds = dmg*1.1;
da = dmg*0.7;
kn = 0.1;
break;
case DAMAGE_TYPE_KINETIC:
ds = dmg*0.8;
da = dmg*1.2;
kn = 1.;
break;
case DAMAGE_TYPE_ION:
ds = dmg;
da = dmg;
kn = 0.4;
break;
case DAMAGE_TYPE_RADIATION:
ds = dmg*0.15; /* Still take damage, just very little. */
da = dmg;
kn = 0.8;
case DAMAGE_TYPE_EMP:
ds = dmg*0.6;
da = dmg*1.3;
kn = 0.;
break;
default:
WARN("Unknown damage type: %d!", dtype);
ds = da = kn = 0.;
break;
}
if(dshield) *dshield = ds;
if(darmour) *darmour = da;
if(knockback) *knockback = kn;
}
/**
* @fn int outfit_isWeapon(const Outfit* o)
*
* @brief Check if outfit is a fixed mounted weapon.
* @param o Outfit to check.
* @return 1 if o is weapon (beam/bolt).
*/
int outfit_isWeapon(const Outfit* o) {
return ((o->type == OUTFIT_TYPE_BOLT) || (o->type == OUTFIT_TYPE_BEAM));
}
/**
* @fn int outfit_isBolt(const outfit* o)
*
* @brief Check if outfit is bolt type weapon.
* @param o Outfit to check.
* @return 1 if o is a bolt type weapon.
*/
int outfit_isBolt(const Outfit* o) {
return((o->type == OUTFIT_TYPE_BOLT) || (o->type == OUTFIT_TYPE_TURRET_BOLT));
}
/**
* @fn int outfit_isBeam(const Outfit* o)
*
* @brief Check if outfit is a weapon launcher.
* @param o Outfit to check.
* @return 1 if o is a weapon launcher.
*/
int outfit_isBeam(const Outfit* o) {
return((o->type == OUTFIT_TYPE_BEAM) || (o->type == OUTFIT_TYPE_TURRET_BEAM));
}
int outfit_isLauncher(const Outfit* o) {
return((o->type == OUTFIT_TYPE_MISSILE_DUMB) ||
(o->type == OUTFIT_TYPE_TURRET_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));
}
/**
* @fn int outfit_isAmmo(const Outfit* o)
*
* @brief Check if outfit is ammo for a launcher.
* @param o outfit to check.
* @return 1 if o is ammo.
*/
int outfit_isAmmo(const Outfit* o) {
return((o->type == OUTFIT_TYPE_MISSILE_DUMB_AMMO) ||
(o->type == OUTFIT_TYPE_TURRET_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));
}
/**
* @fn int outfit_isSeeker(const Outfit* o)
*
* @brief Check if outfit is a seeking weapon.
* @param o Outfit to check.
* @return 1 if o is a seerker weapon.
*/
int outfit_isSeeker(const Outfit* o) {
if((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))
return 1;
return 0;
}
/**
* @fn int outfit_isTurret(const Outfit* o)
*
* @brief Check if outfit is a turret class weapon.
* @param o Outfit to check.
* @return 1 if o is a turret class weapon.
*/
int outfit_isTurret(const Outfit* o) {
return ((o->type==OUTFIT_TYPE_TURRET_BOLT) ||
(o->type==OUTFIT_TYPE_TURRET_BEAM) ||
(o->type==OUTFIT_TYPE_TURRET_DUMB));
}
/**
* @fn int outfit_isMod(const Outfit* o)
*
* @breif Check if outfit is a ship modification.
* @param o Outfit to check.
* @return 1 if o is a ship modification.
*/
int outfit_isMod(const Outfit* o) {
return (o->type == OUTFIT_TYPE_MODIFICATION);
}
/**
* @fn int outfit_isAfterburner(const Outfit* o)
* @brief Check if outfit is an afterburner.
* @param o Outfit to check.
* @return 1 if o is an afterburner.
*/
int outfit_isAfterburner(const Outfit* o) {
return (o->type == OUTFIT_TYPE_AFTERBURNER);
}
/**
* @fn int outfit_isJammer(const Outfit* o)
*
* @brief Check if outfit is a missile jammer.
* @param o Outfit to check.
* @return 1 if o is a jammer.
*/
int outfit_isJammer(const Outfit* o) {
return(o->type == OUTFIT_TYPE_JAMMER);
}
/**
* @fn int outfit_isFighterBay(const Outfit* o)
*
* @brief Check if outfit is a fighter bay.
* @param o Outfit to check.
* @param 1 if o is a jammer.
*/
int outfit_isFighterBay(const Outfit* o) {
return (o->type == OUTFIT_TYPE_FIGHTER_BAY);
}
/**
* @fn int outfit_isFighter(const Outfit* o)
*
* @brief Check if outfit is a fighter.
* @param o Outfit to check.
* @param 1 if o is a jammer.
*/
int outfit_isFighter(const Outfit* o) {
return (o->type == OUTFIT_TYPE_FIGHTER);
}
/**
* @fn int outfit_isMap(const Outfit* o)
*
* @brief Check if outfit is a space map.
* @param o Outfit to check.
* @return 1 if o is a map.
*/
int outfit_isMap(const Outfit* o) {
return(o->type == OUTFIT_TYPE_MAP);
}
/**
* @brief Check if outfit is a licence.
* @param o Outfit to check.
* @return 1 if o is a map.
*/
int outfit_isLicense(const Outfit* o) {
return (o->type == OUTFIT_TYPE_LICENSE);
}
/**
* @fn glTexture* outfit_gfx(const Outfit* o)
*
* @brief Get the outfits graphic effect.
* @param o Outfit to get information from.
*/
glTexture* outfit_gfx(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.gfx_space;
else if(outfit_isBeam(o)) return o->u.bem.gfx;
else if(outfit_isAmmo(o)) return o->u.amm.gfx_space;
return NULL;
}
/**
* @fn glTexture* outfit_spfx(const Outfit* o)
*
* @brief Get the outfits sound effect.
* @param o Outfit to get information from.
*/
int outfit_spfxArmour(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.spfx_armour;
else if(outfit_isBeam(o)) return o->u.bem.spfx_armour;
else if(outfit_isAmmo(o)) return o->u.amm.spfx_armour;
return -1;
}
/**
* @brief Get the outfits sound effect.
* @param o Outfit to get information from.
*/
int outfit_spfxShield(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.spfx_shield;
else if(outfit_isBeam(o)) return o->u.bem.spfx_shield;
else if(outfit_isAmmo(o)) return o->u.amm.spfx_shield;
return -1;
}
/**
* @fn double outfit_damage(const Outfit* o)
*
* @brief Get the outfits damage.
* @param o Outfit to get information.
*/
double outfit_damage(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.damage;
else if(outfit_isBeam(o)) return o->u.bem.damage;
else if(outfit_isAmmo(o)) return o->u.amm.damage;
return -1;
}
/**
* @fn DamageType outfit_damageType(const Outfit* o)
*
* @brief Get the outfits damage type.
* @param o Outfit to get information from.
*/
DamageType outfit_damageType(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.dtype;
else if(outfit_isBeam(o)) return o->u.bem.dtype;
else if(outfit_isAmmo(o)) return o->u.amm.dtype;
return DAMAGE_TYPE_NULL;
}
/**
* @fn int outfit_delay(const Outfit* o)
*
* @brief Get the ourfits delay.
* @param o Outfit to get information from.
*/
int outfit_delay(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.delay;
else if(outfit_isBeam(o)) return o->u.bem.delay;
else if(outfit_isLauncher(o)) return o->u.lau.delay;
else if(outfit_isFighterBay(o)) return o->u.bay.delay;
return -1;
}
/**
* @brief Get the outfits ammo.
* @param o Outfit to get information from.
*/
Outfit* outfit_ammo(const Outfit* o) {
if(outfit_isLauncher(o)) return o->u.lau.ammo;
else if(outfit_isFighterBay(o)) return o->u.bay.ammo;
return NULL;
}
/**
* @fn double outfit_energy(const Outfit* o)
*
* @brief Get the outfits energy usage.
* @param o Outfit to get information from.
*/
double outfit_energy(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.energy;
else if(outfit_isBeam(o)) return o->u.bem.energy;
else if(outfit_isAmmo(o)) return o->u.amm.energy;
return -1.;
}
/**
* @fn double outfit_range(const Outfit* o)
*
* @brief Get the outfits range.
* @param o Outfit to get information from.
*/
double outfit_range(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.range;
else if(outfit_isBeam(o)) return o->u.bem.range;
else if(outfit_isAmmo(o)) return 0.8*o->u.amm.speed*o->u.amm.duration;
return -1.;
}
/**
* @fn double outfit_speed(const Outfit* o)
*
* @brief Get the outfits speed.
* @param o Outfit to get information from.
*/
double outfit_speed(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.speed;
else if(outfit_isAmmo(o)) return o->u.amm.speed;
return -1.;
}
/**
* @brief Get the outfit animation spin.
* @param o Outfit to get information from.
*/
double outfit_spin(const Outfit* o) {
if(outfit_isBolt(o)) return o->u.blt.spin;
else if(outfit_isAmmo(o)) return o->u.amm.spin;
return -1;
}
/**
* @fn const char* outfit_getType(const Outfit* o)
*
* @brief Get the outfits specific type.
* @param o Outfit to get specific type from.
* @return The specific type in human readable form.
*/
const char* outfit_getType(const Outfit* o) {
const char* outfit_typename[] = {
"NULL",
"Bolt Cannon",
"Beam Cannon",
"Bolt Turret",
"Beam Turret",
"Dumb Missile",
"Dumb Missile Ammunition",
"Projectile Turret",
"Projectile Turret 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",
"Ship Modification",
"Afterburner",
"Jammer",
"Fighter Bay",
"Fighter",
"Map",
"License"
};
return outfit_typename[o->type];
}
/**
* @fn const char* outfit_getTypeBroad(const Outfit* o)
*
* @brief Get the outfits broad type.
* @param o Outfit to get the type of.
* @return the outfits broad type in human readable form.
*/
const char* outfit_getTypeBroad(const Outfit* o) {
if(outfit_isBolt(o)) return "Bolt Weapon";
else if(outfit_isBeam(o)) return "Beam Weapon";
else if(outfit_isLauncher(o)) return "Launcher";
else if(outfit_isAmmo(o)) return "Ammo";
else if(outfit_isTurret(o)) return "Turret";
else if(outfit_isMod(o)) return "Modification";
else if(outfit_isAfterburner(o)) return "Afterburner";
else if(outfit_isJammer(o)) return "Jammer";
else if(outfit_isFighterBay(o)) return "Fighter Bay";
else if(outfit_isFighter(o)) return "Fighter";
else if(outfit_isMap(o)) return "Map";
else if(outfit_isLicense(o)) return "License";
else return "Unknown";
}
/* Return the damage type from a str. */
static DamageType outfit_strToDamageType(char* buf) {
if(strcmp(buf, "energy")==0) return DAMAGE_TYPE_ENERGY;
else if(strcmp(buf, "kinetic")==0) return DAMAGE_TYPE_KINETIC;
else if(strcmp(buf, "ion")==0) return DAMAGE_TYPE_ION;
else if(strcmp(buf, "radiation")==0) return DAMAGE_TYPE_RADIATION;
else if(strcmp(buf, "emp")==0) return DAMAGE_TYPE_EMP;
WARN("Invalid damage type: '%s'", buf);
return DAMAGE_TYPE_NULL;
}
#define O_CMP(s, t) \
if(strcmp(buf, (s))==0) return t; /**< Define to help with outfit_strToOutfitType. */
/**
* @brief Get the outfit type from human readable string.
* @param buf String to extract outfit type from.
* @return Outfit type stored in buf.
*/
static OutfitType outfit_strToOutfitType(char* buf) {
O_CMP("bolt", OUTFIT_TYPE_BOLT);
O_CMP("beam", OUTFIT_TYPE_BEAM);
O_CMP("turret bolt", OUTFIT_TYPE_TURRET_BOLT);
O_CMP("turret beam", OUTFIT_TYPE_TURRET_BEAM);
O_CMP("missile dumb", OUTFIT_TYPE_MISSILE_DUMB);
O_CMP("missile dumb ammo", OUTFIT_TYPE_MISSILE_DUMB_AMMO);
O_CMP("turret dumb", OUTFIT_TYPE_TURRET_DUMB);
O_CMP("turret dumb ammo", OUTFIT_TYPE_TURRET_DUMB_AMMO);
O_CMP("missile seek", OUTFIT_TYPE_MISSILE_SEEK);
O_CMP("missile seek ammo", OUTFIT_TYPE_MISSILE_SEEK_AMMO);
O_CMP("missile smart", OUTFIT_TYPE_MISSILE_SEEK_SMART);
O_CMP("missile smart ammo", OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO);
O_CMP("missile swarm", OUTFIT_TYPE_MISSILE_SWARM);
O_CMP("missile swarm ammo", OUTFIT_TYPE_MISSILE_SWARM_AMMO);
O_CMP("missile swarm smart", OUTFIT_TYPE_MISSILE_SWARM_SMART);
O_CMP("missile swarm smart ammo", OUTFIT_TYPE_MISSILE_SWARM_SMART_AMMO);
O_CMP("modification", OUTFIT_TYPE_MODIFICATION);
O_CMP("afterburner", OUTFIT_TYPE_AFTERBURNER);
O_CMP("fighter bay", OUTFIT_TYPE_FIGHTER_BAY);
O_CMP("fighter", OUTFIT_TYPE_FIGHTER);
O_CMP("jammer", OUTFIT_TYPE_JAMMER);
O_CMP("map", OUTFIT_TYPE_MAP);
O_CMP("license", OUTFIT_TYPE_LICENSE);
WARN("Invalid outfit type '%s'", buf);
return OUTFIT_TYPE_NULL;
}
#undef O_CMP
/**
* @brief Parses a damage node.
*
* Example damage node would be:
* @code
* <damage type="kinetic">10</damage>
* @endcode
*
* @param[out] dtype Store the damage type here.
* @param[out] dmg Store the damage here.
* @param[in] node Node to parse damage from.
* @return 0 on success.
*/
static int outfit_parseDamage(DamageType* dtype, double* dmg, xmlNodePtr node) {
char* buf;
if(xml_isNode(node, "damage")) {
/* Get type. */
xmlr_attr(node, "type", buf);
(*dtype) = outfit_strToDamageType(buf);
if(buf) free(buf);
/* Get damage. */
(*dmg) = xml_getFloat(node);
return 0;
}
/* Unknown type. */
(*dtype) = DAMAGE_TYPE_NULL;
(*dmg) = 0;
WARN("Trying to parse non-damage node as damage node!");
return 1;
}
/* Parses the specific area for a weapon and loads it into outfit. */
static void outfit_parseSBolt(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
char* buf;
/* Defaults. */
tmp->u.blt.spfx_armour = -1;
tmp->u.blt.spfx_shield = -1;
tmp->u.blt.sound = -1;
node = parent->xmlChildrenNode;
do {
/* Load all the things. */
xmlr_float(node, "speed", tmp->u.blt.speed);
xmlr_float(node, "delay", tmp->u.blt.delay);
xmlr_float(node, "range", tmp->u.blt.range);
xmlr_float(node, "accuracy", tmp->u.blt.accuracy);
xmlr_float(node, "energy", tmp->u.blt.energy);
if(xml_isNode(node, "gfx")) {
tmp->u.blt.gfx_space = xml_parseTexture(node,
OUTFIT_GFX"space/%s.png", 6, 6,
OPENGL_TEX_MAPTRANS);
xmlr_attr(node, "spin", buf);
if(buf != NULL) {
outfit_setProp(tmp, OUTFIT_PROP_WEAP_SPIN);
tmp->u.blt.spin = atof(buf);
free(buf);
}
continue;
}
if(xml_isNode(node, "spfx_shield")) {
tmp->u.blt.spfx_shield = spfx_get(xml_get(node));
continue;
}
if(xml_isNode(node, "spfx_armour")) {
tmp->u.blt.spfx_armour = spfx_get(xml_get(node));
continue;
}
if(xml_isNode(node, "sound")) {
tmp->u.blt.sound = sound_get(xml_get(node));
continue;
}
if(xml_isNode(node, "damage")) {
outfit_parseDamage(&tmp->u.blt.dtype, &tmp->u.blt.damage, node);
continue;
}
} while(xml_nextNode(node));
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", tmp->name)
/**< Define to help check for data errors. */
MELEMENT(tmp->u.blt.gfx_space==NULL, "gfx");
MELEMENT(tmp->u.blt.spfx_shield==-1, "spfx_shield");
MELEMENT(tmp->u.blt.spfx_armour==-1, "spfx_armour");
MELEMENT((sound_disabled!=0) && (tmp->u.blt.sound<0), "sound");
MELEMENT(tmp->u.blt.delay==0, "delay");
MELEMENT(tmp->u.blt.speed==0, "speed");
MELEMENT(tmp->u.blt.range==0, "range");
MELEMENT(tmp->u.blt.accuracy==0, "accuracy");
MELEMENT(tmp->u.blt.damage==0, "damage");
#undef MELEMENT
}
/**
* @brief Parse the beam weapon specifics of an outfit.
* @param tmp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSBeam(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
/* Defaults. */
tmp->u.bem.spfx_armour = -1;
tmp->u.bem.spfx_shield = -1;
tmp->u.bem.sound_warmup = -1;
tmp->u.bem.sound = -1;
tmp->u.bem.sound_off = -1;
node = parent->xmlChildrenNode;
do { /* Load all the data. */
xmlr_float(node, "range", tmp->u.bem.range);
xmlr_float(node, "turn", tmp->u.bem.turn);
xmlr_float(node, "energy", tmp->u.bem.energy);
xmlr_long(node, "delay", tmp->u.bem.delay);
xmlr_float(node, "warmup", tmp->u.bem.warmup);
xmlr_float(node, "duration", tmp->u.bem.duration);
if(xml_isNode(node, "damage")) {
outfit_parseDamage(&tmp->u.bem.dtype, &tmp->u.bem.damage, node);
continue;
}
/* Graphics stuff. */
if(xml_isNode(node, "gfx")) {
tmp->u.bem.gfx = xml_parseTexture(node,
OUTFIT_GFX"space/%s.png", 1, 1, 0);
continue;
}
if(xml_isNode(node, "spfx_armour")) {
tmp->u.bem.spfx_armour = spfx_get(xml_get(node));
continue;
}
if(xml_isNode(node, "spfx_shield")) {
tmp->u.bem.spfx_shield = spfx_get(xml_get(node));
continue;
}
/* Sound stuff. */
if(xml_isNode(node, "sound_warmup")) {
tmp->u.bem.sound_warmup = sound_get(xml_get(node));
continue;
}
if(xml_isNode(node, "sound")) {
tmp->u.bem.sound = sound_get(xml_get(node));
continue;
}
if(xml_isNode(node, "sound_off")) {
tmp->u.bem.sound_off = sound_get(xml_get(node));
continue;
}
} while(xml_nextNode(node));
#define MELEMENT(o,s) if(0) WARN("Outfit '%s' missing/invalid '"s"' element", tmp->name)
MELEMENT(tmp->u.bem.gfx==NULL, "gfx");
MELEMENT(tmp->u.bem.spfx_shield==-1, "spfx_shield");
MELEMENT(tmp->u.bem.spfx_armour==-1, "spfx_armour");
MELEMENT((sound_disabled!=0) && (tmp->u.bem.warmup > 0.) && (tmp->u.bem.sound<0),"sound_warmup");
MELEMENT((sound_disabled!=0) && (tmp->u.bem.sound < 0), "sound");
MELEMENT((sound_disabled!=0) && (tmp->u.bem.sound_off < 0), "sound_off");
MELEMENT(tmp->u.bem.delay==0, "range");
MELEMENT(tmp->u.bem.duration==0, "duration");
MELEMENT(tmp->u.bem.range==0, "range");
MELEMENT((tmp->type!=OUTFIT_TYPE_BEAM) && (tmp->u.bem.turn==0), "turn");
MELEMENT(tmp->u.bem.energy==0, "energy");
MELEMENT(tmp->u.bem.damage==0, "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. */
xmlr_int(node, "delay", tmp->u.lau.delay);
xmlr_strd(node, "ammo", tmp->u.lau.ammo_name);
} while((node = node->next));
#define MELEMENT(o,s) if(o) WARN("Outfit '%s' missing '"s"' element", tmp->name)
/**< Define to help check for data errors. */
MELEMENT(tmp->u.lau.ammo_name == 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 node;
char* buf;
node = parent->xmlChildrenNode;
/* Defaults. */
tmp->u.amm.spfx_armour = -1;
tmp->u.amm.spfx_shield = -1;
tmp->u.amm.sound = -1;
do { /* Load all the data. */
/* Basic. */
xmlr_float(node, "duration", tmp->u.amm.duration);
xmlr_float(node, "lockon", tmp->u.amm.lockon);
xmlr_float(node, "resist", tmp->u.amm.resist);
/* Movement. */
xmlr_float(node, "thrust", tmp->u.amm.thrust);
xmlr_float(node, "turn", tmp->u.amm.turn);
xmlr_float(node, "speed", tmp->u.amm.speed);
xmlr_float(node, "accuracy", tmp->u.amm.accuracy);
xmlr_float(node, "energy", tmp->u.amm.energy);
if(xml_isNode(node, "gfx")) {
tmp->u.amm.gfx_space = xml_parseTexture(node,
OUTFIT_GFX"space/%s.png", 6, 6,
OPENGL_TEX_MAPTRANS);
xmlr_attr(node, "spin", buf);
if(buf != NULL) {
outfit_setProp(tmp, OUTFIT_PROP_WEAP_SPIN);
tmp->u.blt.spin = atof(buf);
free(buf);
}
continue;
}
else if(xml_isNode(node, "spfx_armour"))
tmp->u.amm.spfx_armour = spfx_get(xml_get(node));
else if(xml_isNode(node, "spfx_shield"))
tmp->u.amm.spfx_shield = 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"))
outfit_parseDamage(&tmp->u.amm.dtype, &tmp->u.amm.damage, node);
} while(xml_nextNode(node));
/* Post-processing. */
tmp->u.amm.resist /= 100.; /* Set it in per one. */
#define MELEMENT(o,s) if(o) WARN("Outfit '%s' missing '"s"' element", tmp->name)
MELEMENT(tmp->u.amm.gfx_space == NULL, "gfx");
MELEMENT(tmp->u.amm.spfx_shield==-1, "spfx_shield");
MELEMENT(tmp->u.amm.spfx_armour==-1, "spfx_armour");
MELEMENT((sound_disabled != 0) && (tmp->u.amm.sound < 0), "sound");
/*MELEMENT(tmp->u.amm.thrust==0, "thrust");*/
/* Dumb missiles don't need everything. */
if(outfit_isSeeker(tmp)) {
MELEMENT(tmp->u.amm.turn==0, "turn");
MELEMENT(tmp->u.amm.lockon==0, "lockon");
}
MELEMENT(tmp->u.amm.speed==0, "speed");
MELEMENT(tmp->u.amm.duration==0, "duration");
MELEMENT(tmp->u.amm.damage==0, "damage");
#undef MELEMENT
}
static void outfit_parseSMod(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
node = parent->children;
/* Load all the data. */
do {
/* Movement. */
xmlr_float(node, "thrust", tmp->u.mod.thrust);
xmlr_float(node, "turn", tmp->u.mod.turn);
xmlr_float(node, "speed", tmp->u.mod.speed);
/* Health. */
xmlr_float(node, "armour", tmp->u.mod.armour);
xmlr_float(node, "shield", tmp->u.mod.shield);
xmlr_float(node, "energy", tmp->u.mod.energy);
xmlr_float(node, "fuel", tmp->u.mod.fuel);
if(xml_isNode(node, "armour_regen"))
tmp->u.mod.armour_regen = xml_getFloat(node)/60.0;
else if(xml_isNode(node, "shield_regen"))
tmp->u.mod.shield_regen = xml_getFloat(node)/60.0;
else if(xml_isNode(node, "energy_regen"))
tmp->u.mod.energy_regen = xml_getFloat(node)/60.0;
/* Misc. */
xmlr_int(node, "cargo", tmp->u.mod.cargo);
} while((node = node->next));
}
/* Parses the afterburner tidbits of the outfit. */
static void outfit_parseSAfterburner(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
node = parent->children;
/* Must be >= 1. */
tmp->u.afb.thrust_perc = 1.;
tmp->u.afb.speed_perc = 1.;
do {
xmlr_float(node, "rumble", tmp->u.afb.rumble);
if(xml_isNode(node, "sound"))
tmp->u.afb.sound = sound_get(xml_get(node));
if(xml_isNode(node, "thrust_perc"))
tmp->u.afb.thrust_perc = 1. + xml_getFloat(node)/100.;
xmlr_float(node, "thrust_abs", tmp->u.afb.thrust_abs);
if(xml_isNode(node, "speed_perc"))
tmp->u.afb.speed_perc = 1. + xml_getFloat(node)/100.;
xmlr_float(node, "speed_abs", tmp->u.afb.speed_abs);
xmlr_float(node, "energy", tmp->u.afb.energy);
} while((node = node->next));
}
/**
* @fn static void outfit_parseSFighterBay(Outfit* tmp, const xmlNodePtr parent)
*
* @brief Parse the fighter bay tidbits of the outfit.
* @param tmp Outfit to finish laiding.
* @param parent Outfits parent node.
*/
static void outfit_parseSFighterBay(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
node = parent->children;
do {
xmlr_int(node, "delay", tmp->u.bay.delay);
xmlr_strd(node, "ammo", tmp->u.bay.ammo_name);
} while(xml_nextNode(node));
#define MELEMENT(o,s) \
if(o) WARN("Outfit '%s' missing/invalid '"s"' element", tmp->name)
MELEMENT(tmp->u.bay.delay==0, "delay");
MELEMENT(tmp->u.bay.ammo_name==NULL, "ammo");
#undef MELEMENT
}
/**
* @fn static void outfit_parseSFighter(Outfit* tmp, const xmlNodePtr parent)
*
* @brief Parse the fighter tidbits of the outfit.
* @param tmp Outfit to finish loading.
* @param parent Outfits parent node.
*/
static void outfit_parseSFighter(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
node = parent->children;
do {
xmlr_strd(node, "ship", tmp->u.fig.ship);
} while(xml_nextNode(node));
#define MELEMENT(o,s) \
if(o) WARN("Outfit '%s' missing/invalid '"s"' element", tmp->name)
MELEMENT(tmp->u.fig.ship==NULL, "ship");
#undef MELEMENT
}
static void outfit_parseSMap(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
node = parent->children;
do {
xmlr_int(node, "radius", tmp->u.map.radius);
} while(xml_nextNode(node));
if(tmp->u.map.radius == 0)
WARN("Outfit '%s' missing/invalid 'radius' element", tmp->name);
}
/**
* @brief Parses the license tidbits of the outfit.
* @param tmp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSLicense(Outfit* tmp, const xmlNodePtr parent) {
/* Licenses have no specific tidbits. */
(void)tmp;
(void)parent;
}
/* Parses the jammer tidbits of the outfit. */
static void outfit_parseSJammer(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr node;
node = parent->children;
do {
xmlr_float(node, "range", tmp->u.jam.range);
xmlr_float(node, "chance", tmp->u.jam.chance);
xmlr_float(node, "energy", tmp->u.jam.energy);
} while(xml_nextNode(node));
tmp->u.jam.chance /= 100.; /* Put in per one, instead of percent. */
tmp->u.jam.energy /= 60.; /* It's per minute. */
#define MELEMENT(o, s) \
if(o) WARN("Outfit '%s' missing/invalid '"s"' element", tmp->name)
MELEMENT(tmp->u.jam.range==0., "range");
MELEMENT(tmp->u.jam.chance==0., "chance");
#undef MELEMENT
}
/* Parse and return Outfits from parent node. */
static int outfit_parse(Outfit* tmp, const xmlNodePtr parent) {
xmlNodePtr cur, node;
char* prop;
/* Clear data. */
memset(tmp, 0, sizeof(Outfit));
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 {
xmlr_int(cur, "max", tmp->max);
xmlr_int(cur, "tech", tmp->tech);
xmlr_strd(cur, "license", tmp->license);
xmlr_int(cur, "mass", tmp->mass);
xmlr_int(cur, "price", tmp->price);
xmlr_strd(cur, "description", tmp->description);
if(xml_isNode(cur, "gfx_store")) {
tmp->gfx_store = xml_parseTexture(cur,
OUTFIT_GFX"store/%s.png", 1, 1, 0);
}
} while(xml_nextNode(cur));
}
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 = outfit_strToOutfitType(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_isBolt(tmp))
outfit_parseSBolt(tmp, node);
else if(outfit_isBeam(tmp))
outfit_parseSBeam(tmp, node);
else if(outfit_isLauncher(tmp))
outfit_parseSLauncher(tmp, node);
else if(outfit_isAmmo(tmp))
outfit_parseSAmmo(tmp, node);
else if(outfit_isMod(tmp))
outfit_parseSMod(tmp, node);
else if(outfit_isAfterburner(tmp))
outfit_parseSAfterburner(tmp, node);
else if(outfit_isJammer(tmp))
outfit_parseSJammer(tmp, node);
else if(outfit_isFighterBay(tmp))
outfit_parseSFighterBay(tmp, node);
else if(outfit_isFighter(tmp))
outfit_parseSFighter(tmp, node);
else if(outfit_isMap(tmp))
outfit_parseSMap(tmp, node);
else if (outfit_isLicense(tmp))
outfit_parseSLicense(tmp, node);
}
} while(xml_nextNode(node));
#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->gfx_store==NULL, "gfx_store");
/*MELEMENT(tmp->mass==0, "mass"); */
MELEMENT(tmp->type==0, "type");
MELEMENT(tmp->price==0, "price");
MELEMENT(tmp->description==NULL, "description");
#undef MELEMENT
return 0;
}
/* Load all the outfits into the outfit stack. */
int outfit_load(void) {
int i, mem;
uint32_t bufsize;
char* buf = ldata_read(OUTFIT_DATA, &bufsize);
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;
}
/* First pass, load up ammunition. */
mem = 0;
do {
if(xml_isNode(node, XML_OUTFIT_TAG)) {
outfit_nstack++;
if(outfit_nstack > mem) {
mem += CHUNK_SIZE;
outfit_stack = realloc(outfit_stack, sizeof(Outfit)*mem);
}
outfit_parse(&outfit_stack[outfit_nstack-1], node);
}
} while((node = node->next));
/* Shrink back to minimum - Shouldn't change ever. */
outfit_stack = realloc(outfit_stack, sizeof(Outfit) * outfit_nstack);
/* Second pass, set up ammunition relationships. */
for(i = 0; i < outfit_nstack; i++) {
if(outfit_isLauncher(&outfit_stack[i]))
outfit_stack[i].u.lau.ammo = outfit_get(outfit_stack[i].u.lau.ammo_name);
else if(outfit_isFighterBay(&outfit_stack[i]))
outfit_stack[i].u.bay.ammo = outfit_get(outfit_stack[i].u.bay.ammo_name);
}
xmlFreeDoc(doc);
free(buf);
DEBUG("Loaded %d outfit%s", outfit_nstack, (outfit_nstack==1) ? "" : "s");
return 0;
}
/* Frees the outfit stack. */
void outfit_free(void) {
int i;
Outfit* o;
for(i = 0; i < outfit_nstack; i++) {
o = &outfit_stack[i];
/* Free graphics. */
if(outfit_gfx(&outfit_stack[i]))
gl_freeTexture(outfit_gfx(&outfit_stack[i]));
/* Type specification. */
if(outfit_isLauncher(o) && o->u.lau.ammo_name)
free(o->u.lau.ammo_name);
if(outfit_isFighterBay(o) && o->u.bay.ammo_name)
free(o->u.bay.ammo_name);
if(outfit_isFighter(o) && o->u.fig.ship)
free(o->u.fig.ship);
/* Strings. */
if(o->description)
free(o->description);
if(o->gfx_store)
gl_freeTexture(o->gfx_store);
if(o->license)
free(o->license);
free(o->name);
}
free(outfit_stack);
}