Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2465 lines (2185 sloc) 72.783 kB
/*
* See Licensing and Copyright notice in naev.h
*/
/**
* @file outfit.c
*
* @brief Handles all the ship outfit specifics.
*
* These outfits allow you to modify ships or make them more powerful and are
* a fundamental part of the game.
*/
#include "outfit.h"
#include "naev.h"
#include <math.h>
#include "nstring.h"
#include <stdlib.h>
#include "nxml.h"
#include "SDL_thread.h"
#include "log.h"
#include "ndata.h"
#include "nfile.h"
#include "spfx.h"
#include "array.h"
#include "ship.h"
#include "conf.h"
#include "pilot_heat.h"
#include "nstring.h"
#include "pilot.h"
#include "damagetype.h"
#include "slots.h"
#include "mapData.h"
#define outfit_setProp(o,p) ((o)->properties |= p) /**< Checks outfit property. */
#define XML_OUTFIT_TAG "outfit" /**< XML section identifier. */
#define OUTFIT_SHORTDESC_MAX 256 /**< Max length of the short description of the outfit. */
/*
* the stack
*/
static Outfit* outfit_stack = NULL; /**< Stack of outfits. */
/*
* Prototypes
*/
/* misc */
static OutfitType outfit_strToOutfitType( char *buf );
static int outfit_setDefaultSize( Outfit *o );
static void outfit_launcherDesc( Outfit* o );
static int outfit_compareNames( const void *name1, const void *name2 );
/* parsing */
static int outfit_loadDir( char *dir );
static int outfit_parseDamage( Damage *dmg, xmlNodePtr node );
static int outfit_parse( Outfit* temp, const char* file );
static void outfit_parseSBolt( Outfit* temp, const xmlNodePtr parent );
static void outfit_parseSBeam( Outfit* temp, const xmlNodePtr parent );
static void outfit_parseSLauncher( Outfit* temp, const xmlNodePtr parent );
static void outfit_parseSAmmo( Outfit* temp, const xmlNodePtr parent );
static void outfit_parseSMod( Outfit* temp, const xmlNodePtr parent );
static void outfit_parseSAfterburner( Outfit* temp, const xmlNodePtr parent );
static void outfit_parseSJammer( Outfit *temp, const xmlNodePtr parent );
static void outfit_parseSFighterBay( Outfit *temp, const xmlNodePtr parent );
static void outfit_parseSFighter( Outfit *temp, const xmlNodePtr parent );
static void outfit_parseSMap( Outfit *temp, const xmlNodePtr parent );
static void outfit_parseSLocalMap( Outfit *temp, const xmlNodePtr parent );
static void outfit_parseSGUI( Outfit *temp, const xmlNodePtr parent );
static void outfit_parseSLicense( Outfit *temp, const xmlNodePtr parent );
/**
* @brief Gets an outfit by name.
*
* @param name Name to match.
* @return Outfit matching name or NULL if not found.
*/
Outfit* outfit_get( const char* name )
{
int i;
for (i=0; i<array_size(outfit_stack); i++)
if (strcmp(name,outfit_stack[i].name)==0)
return &outfit_stack[i];
WARN("Outfit '%s' not found in stack.", name);
return NULL;
}
/**
* @brief Gets an outfit by name without warning on no-find.
*
* @param name Name to match.
* @return Outfit matching name or NULL if not found.
*/
Outfit* outfit_getW( const char* name )
{
int i;
for (i=0; i<array_size(outfit_stack); i++)
if (strcmp(name,outfit_stack[i].name)==0)
return &outfit_stack[i];
return NULL;
}
/**
* @brief Gets all the outfits.
*/
Outfit* outfit_getAll( int *n )
{
*n = array_size(outfit_stack);
return outfit_stack;
}
/**
* @brief Checks to see if an outfit exists matching name (case insensitive).
*/
const char *outfit_existsCase( const char* name )
{
int i;
for (i=0; i<array_size(outfit_stack); i++)
if (strcasecmp(name,outfit_stack[i].name)==0)
return outfit_stack[i].name;
return NULL;
}
/**
* @brief Does a fuzzy search of all the outfits.
*/
char **outfit_searchFuzzyCase( const char* name, int *n )
{
int i, len, nstack;
char **names;
/* Overallocate to maximum. */
nstack = array_size(outfit_stack);
names = malloc( sizeof(char*) * nstack );
/* Do fuzzy search. */
len = 0;
for (i=0; i<nstack; i++) {
if (nstrcasestr( outfit_stack[i].name, name ) != NULL) {
names[len] = outfit_stack[i].name;
len++;
}
}
/* Free if empty. */
if (len == 0) {
free(names);
names = NULL;
}
*n = len;
return names;
}
/**
* @brief Function meant for use with C89, C99 algorithm qsort().
*
* @param outfit1 First argument to compare.
* @param outfit2 Second argument to compare.
* @return -1 if first argument is inferior, +1 if it's superior, 0 if ties.
*/
int outfit_compareTech( const void *outfit1, const void *outfit2 )
{
int ret;
const Outfit *o1, *o2;
/* Get outfits. */
o1 = * (const Outfit**) outfit1;
o2 = * (const Outfit**) outfit2;
/* Compare slot type. */
if (o1->slot.type < o2->slot.type)
return +1;
else if (o1->slot.type > o2->slot.type)
return -1;
/* Compare intrinsic types. */
if (o1->type < o2->type)
return -1;
else if (o1->type > o2->type)
return +1;
/* Compare named types. */
if ((o1->typename == NULL) && (o2->typename != NULL))
return -1;
else if ((o1->typename != NULL) && (o2->typename == NULL))
return +1;
else if ((o1->typename != NULL) && (o2->typename != NULL)) {
ret = strcmp( o1->typename, o2->typename );
if (ret != 0)
return ret;
}
/* Compare slot sizes. */
if (o1->slot.size < o2->slot.size)
return +1;
else if (o1->slot.size > o2->slot.size)
return -1;
/* Compare sort priority. */
if (o1->priority < o2->priority)
return +1;
else if (o1->priority > o2->priority)
return -1;
/* Compare price. */
if (o1->price < o2->price)
return +1;
else if (o1->price > o2->price)
return -1;
/* It turns out they're the same. */
return strcmp( o1->name, o2->name );
}
/**
* @brief Gets the name of the slot type of an outfit.
*
* @param o Outfit to get slot type of.
* @return The human readable name of the slot type.
*/
const char *outfit_slotName( const Outfit* o )
{
switch (o->slot.type) {
case OUTFIT_SLOT_NULL:
return "NULL";
case OUTFIT_SLOT_NA:
return "NA";
case OUTFIT_SLOT_STRUCTURE:
return "Structure";
case OUTFIT_SLOT_UTILITY:
return "Utility";
case OUTFIT_SLOT_WEAPON:
return "Weapon";
default:
return "Unknown";
}
}
/**
* @brief Gets the name of the slot size of an outfit.
*
* @param o Outfit to get slot size of.
* @return The human readable name of the slot size.
*/
const char *outfit_slotSize( const Outfit* o )
{
switch( o->slot.size) {
case OUTFIT_SLOT_SIZE_NA:
return "NA";
case OUTFIT_SLOT_SIZE_LIGHT:
return "Small";
case OUTFIT_SLOT_SIZE_MEDIUM:
return "Medium";
case OUTFIT_SLOT_SIZE_HEAVY:
return "Large";
default:
return "Unknown";
}
}
const char *slotSize( const OutfitSlotSize o )
{
switch( o ) {
case OUTFIT_SLOT_SIZE_NA:
return "NA";
case OUTFIT_SLOT_SIZE_LIGHT:
return "Small";
case OUTFIT_SLOT_SIZE_MEDIUM:
return "Medium";
case OUTFIT_SLOT_SIZE_HEAVY:
return "Large";
default:
return "Unknown";
}
}
/**
* @brief Gets the slot size colour for an outfit slot.
*
* @param os Outfit slot to get the slot size colour of.
* @return The slot size colour of the outfit slot.
*/
const glColour *outfit_slotSizeColour( const OutfitSlot* os )
{
if (os->size == OUTFIT_SLOT_SIZE_HEAVY)
return &cFontBlue;
else if (os->size == OUTFIT_SLOT_SIZE_MEDIUM)
return &cFontGreen;
else if (os->size == OUTFIT_SLOT_SIZE_LIGHT)
return &cFontYellow;
return NULL;
}
/**
* @brief Gets the outfit slot size from a human readable string.
*
* @param s String representing an outfit slot size.
* @return Outfit slot size matching string.
*/
OutfitSlotSize outfit_toSlotSize( const char *s )
{
if (s == NULL) {
/*WARN( "(NULL) outfit slot size" );*/
return OUTFIT_SLOT_SIZE_NA;
}
if (strcasecmp(s,"Large")==0)
return OUTFIT_SLOT_SIZE_HEAVY;
else if (strcasecmp(s,"Medium")==0)
return OUTFIT_SLOT_SIZE_MEDIUM;
else if (strcasecmp(s,"Small")==0)
return OUTFIT_SLOT_SIZE_LIGHT;
WARN("'%s' does not match any outfit slot sizes.", s);
return OUTFIT_SLOT_SIZE_NA;
}
/**
* @brief Sets the outfit slot size from default outfit properties.
*/
static int outfit_setDefaultSize( Outfit *o )
{
if (o->mass <= 10.)
o->slot.size = OUTFIT_SLOT_SIZE_LIGHT;
else if (o->mass <= 30.)
o->slot.size = OUTFIT_SLOT_SIZE_MEDIUM;
else
o->slot.size = OUTFIT_SLOT_SIZE_HEAVY;
WARN("Outfit '%s' has implicit slot size, setting to '%s'.",o->name,outfit_slotSize(o));
return 0;
}
/**
* @brief Checks if outfit is an active outfit.
* @param o Outfit to check.
* @return 1 if o is active.
*/
int outfit_isActive( const Outfit* o )
{
if (outfit_isForward(o) || outfit_isTurret(o) || outfit_isLauncher(o) || outfit_isFighterBay(o))
return 1;
if (outfit_isMod(o) && o->u.mod.active)
return 1;
if (outfit_isJammer(o))
return 1;
if (outfit_isAfterburner(o))
return 1;
return 0;
}
/**
* @brief Checks if outfit is a fixed mounted weapon.
* @param o Outfit to check.
* @return 1 if o is a weapon (beam/bolt).
*/
int outfit_isForward( const Outfit* o )
{
return ( (o->type==OUTFIT_TYPE_BOLT) ||
(o->type==OUTFIT_TYPE_BEAM) );
}
/**
* @brief Checks 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) );
}
/**
* @brief Checks if outfit is a beam type weapon.
* @param o Outfit to check.
* @return 1 if o is a beam type weapon.
*/
int outfit_isBeam( const Outfit* o )
{
return ( (o->type==OUTFIT_TYPE_BEAM) ||
(o->type==OUTFIT_TYPE_TURRET_BEAM) );
}
/**
* @brief Checks if outfit is a weapon launcher.
* @param o Outfit to check.
* @return 1 if o is a weapon launcher.
*/
int outfit_isLauncher( const Outfit* o )
{
return ( (o->type==OUTFIT_TYPE_LAUNCHER) ||
(o->type==OUTFIT_TYPE_TURRET_LAUNCHER) );
}
/**
* @brief Checks 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_AMMO);
}
/**
* @brief Checks if outfit is a seeking weapon.
* @param o Outfit to check.
* @return 1 if o is a seeking weapon.
*/
int outfit_isSeeker( const Outfit* o )
{
if (((o->type==OUTFIT_TYPE_AMMO) || (o->type==OUTFIT_TYPE_TURRET_LAUNCHER) ||
(o->type==OUTFIT_TYPE_LAUNCHER)) &&
(o->u.amm.ai > 0))
return 1;
return 0;
}
/**
* @brief Checks 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_LAUNCHER) );
}
/**
* @brief Checks 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);
}
/**
* @brief Checks 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);
}
/**
* @brief Checks 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);
}
/**
* @brief Checks if outfit is a fighter bay.
* @param o Outfit to check.
* @return 1 if o is a jammer.
*/
int outfit_isFighterBay( const Outfit* o )
{
return (o->type==OUTFIT_TYPE_FIGHTER_BAY);
}
/**
* @brief Checks if outfit is a fighter.
* @param o Outfit to check.
* @return 1 if o is a Fighter.
*/
int outfit_isFighter( const Outfit* o )
{
return (o->type==OUTFIT_TYPE_FIGHTER);
}
/**
* @brief Checks 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 Checks if outfit is a local space map.
* @param o Outfit to check.
* @return 1 if o is a map.
*/
int outfit_isLocalMap( const Outfit* o )
{
return (o->type==OUTFIT_TYPE_LOCALMAP);
}
/**
* @brief Checks if outfit is a license.
* @param o Outfit to check.
* @return 1 if o is a license.
*/
int outfit_isLicense( const Outfit* o )
{
return (o->type==OUTFIT_TYPE_LICENSE);
}
/**
* @brief Checks if outfit is a GUI.
* @param o Outfit to check.
* @return 1 if o is a GUI.
*/
int outfit_isGUI( const Outfit* o )
{
return (o->type==OUTFIT_TYPE_GUI);
}
/**
* @brief Checks if outfit has the secondary flag set.
* @param o Outfit to check.
* @return 1 if o is a secondary weapon.
*/
int outfit_isSecondary( const Outfit* o )
{
return (o->properties & OUTFIT_PROP_WEAP_SECONDARY);
}
/**
* @brief Gets the outfit's 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;
}
/**
* @brief Gets the outfit's 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 Gets the outfit's 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;
}
/**
* @brief Gets the outfit's damage.
* @param o Outfit to get information from.
*/
const Damage *outfit_damage( const Outfit* o )
{
if (outfit_isBolt(o)) return &o->u.blt.dmg;
else if (outfit_isBeam(o)) return &o->u.bem.dmg;
else if (outfit_isAmmo(o)) return &o->u.amm.dmg;
return NULL;
}
/**
* @brief Gets the outfit's delay.
* @param o Outfit to get information from.
*/
double 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 Gets the outfit's 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;
}
/**
* @brief Gets the amount an outfit can hold.
* @param o Outfit to get information from.
*/
int outfit_amount( const Outfit* o )
{
if (outfit_isLauncher(o)) return o->u.lau.amount;
else if (outfit_isFighterBay(o)) return o->u.bay.amount;
return -1;
}
/**
* @brief Gets the outfit's 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.;
}
/**
* @brief Gets the outfit's heat generation.
* @param o Outfit to get information from.
*/
double outfit_heat( const Outfit* o )
{
if (outfit_isBolt(o)) return o->u.blt.heat;
else if (outfit_isAfterburner(o)) return o->u.afb.heat;
else if (outfit_isBeam(o)) return o->u.bem.heat;
return -1;
}
/**
* @brief Gets the outfit's cpu usage.
* @param o Outfit to get information from.
*/
double outfit_cpu( const Outfit* o )
{
return o->cpu;
}
/**
* @brief Gets the outfit's range.
* @param o Outfit to get information from.
*/
double outfit_range( const Outfit* o )
{
Outfit *amm;
double at;
if (outfit_isBolt(o)) return o->u.blt.falloff + (o->u.blt.range - o->u.blt.falloff)/2.;
else if (outfit_isBeam(o)) return o->u.bem.range;
else if (outfit_isAmmo(o)) {
if (o->u.amm.thrust) {
at = o->u.amm.speed / o->u.amm.thrust;
if (at < o->u.amm.duration)
return o->u.amm.speed * (o->u.amm.duration - at / 2.);
/* Maximum speed will never be reached. */
return pow2(o->u.amm.duration) * o->u.amm.thrust / 2.;
}
return o->u.amm.speed * o->u.amm.duration;
}
else if (outfit_isLauncher(o)) {
amm = outfit_ammo(o);
if (amm != NULL)
return outfit_range(amm);
}
else if (outfit_isFighterBay(o))
return INFINITY;
return -1.;
}
/**
* @brief Gets the outfit's speed.
* @param o Outfit to get information from.
* @return Outfit's speed.
*/
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 Gets the outfit's animation spin.
* @param o Outfit to get information from.
* @return Outfit's animation spin.
*/
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.;
}
/**
* @brief Gets the outfit's sound.
* @param o Outfit to get sound from.
* @return Outfit's sound.
*/
int outfit_sound( const Outfit* o )
{
if (outfit_isBolt(o)) return o->u.blt.sound;
else if (outfit_isAmmo(o)) return o->u.amm.sound;
return -1.;
}
/**
* @brief Gets the outfit's hit sound.
* @param o Outfit to get hit sound from.
* @return Outfit's hit sound.
*/
int outfit_soundHit( const Outfit* o )
{
if (outfit_isBolt(o)) return o->u.blt.sound_hit;
else if (outfit_isAmmo(o)) return o->u.amm.sound_hit;
return -1.;
}
/**
* @brief Gets the outfit's duration.
* @param o Outfit to get the duration of.
* @return Outfit's duration.
*/
double outfit_duration( const Outfit* o )
{
if (outfit_isMod(o)) { if (o->u.mod.active) return o->u.mod.duration; }
else if (outfit_isJammer(o)) return INFINITY;
else if (outfit_isAfterburner(o)) return INFINITY;
return -1.;
}
/**
* @brief Gets the outfit's cooldown.
* @param o Outfit to get the cooldown of.
* @return Outfit's cooldown.
*/
double outfit_cooldown( const Outfit* o )
{
if (outfit_isMod(o)) { if (o->u.mod.active) return o->u.mod.cooldown; }
else if (outfit_isJammer(o)) return 0.;
else if (outfit_isAfterburner(o)) return 0.;
return -1.;
}
/**
* @brief Gets the outfit's 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",
"Launcher",
"Ammunition",
"Turret Launcher",
"Ship Modification",
"Afterburner",
"Jammer",
"Fighter Bay",
"Fighter",
"Star Map",
"Local Map",
"GUI",
"License"
};
/* Name override. */
if (o->typename != NULL)
return o->typename;
return outfit_typename[o->type];
}
/**
* @brief Gets the outfit's broad type.
*
* @param o Outfit to get the type of.
* @return The outfit's 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_isLocalMap(o)) return "Local Map";
else if (outfit_isGUI(o)) return "GUI";
else if (outfit_isLicense(o)) return "License";
else return "Unknown";
}
/**
* @brief Gets a human-readable string describing an ammo outfit's AI.
* @param o Ammo outfit.
* @return Name of the outfit's AI.
*/
const char* outfit_getAmmoAI( const Outfit *o )
{
const char *ai_type[] = {
"Dumb",
"Seek",
"Smart"
};
if (!outfit_isAmmo(o)) {
WARN("Outfit '%s' is not an ammo outfit", o->name);
return NULL;
}
return ai_type[o->u.amm.ai];
}
/**
* @brief Checks to see if an outfit fits a slot.
*
* @param o Outfit to see if fits in a slot.
* @param s Slot to see if outfit fits in.
* @return 1 if outfit fits the slot, 0 otherwise.
*/
int outfit_fitsSlot( const Outfit* o, const OutfitSlot* s )
{
const OutfitSlot *os;
os = &o->slot;
/* Outfit must have valid slot type. */
if ((os->type == OUTFIT_SLOT_NULL) ||
(os->type == OUTFIT_SLOT_NA))
return 0;
/* Outfit type must match outfit slot. */
if (os->type != s->type)
return 0;
/* Must match slot property. */
if (os->spid != 0)
if (s->spid != os->spid)
return 0;
/* Exclusive only match property. */
if (s->exclusive)
if (s->spid != os->spid)
return 0;
/* Must have valid slot size. */
if (os->size == OUTFIT_SLOT_SIZE_NA)
return 0;
/* It doesn't fit. */
if (os->size > s->size)
return 0;
/* It meets all criteria. */
return 1;
}
/**
* @brief Checks to see if an outfit fits a slot type (ignoring size).
*
* @param o Outfit to see if fits in a slot.
* @param s Slot to see if outfit fits in.
* @return 1 if outfit fits the slot, 0 otherwise.
*/
int outfit_fitsSlotType( const Outfit* o, const OutfitSlot* s )
{
const OutfitSlot *os;
os = &o->slot;
/* Outfit must have valid slot type. */
if ((os->type == OUTFIT_SLOT_NULL) ||
(os->type == OUTFIT_SLOT_NA))
return 0;
/* Outfit type must match outfit slot. */
if (os->type != s->type)
return 0;
/* It meets all criteria. */
return 1;
}
/**
* @brief Frees an outfit slot.
*
* @param s Slot to free.
*/
void outfit_freeSlot( OutfitSlot* s )
{
(void) s;
}
#define O_CMP(s,t) \
if (strcasecmp(buf,(s))==0) return t /**< Define to help with outfit_strToOutfitType. */
/**
* @brief Gets the outfit type from a 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("launcher", OUTFIT_TYPE_LAUNCHER);
O_CMP("ammo", OUTFIT_TYPE_AMMO);
O_CMP("turret launcher",OUTFIT_TYPE_TURRET_LAUNCHER);
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("localmap", OUTFIT_TYPE_LOCALMAP);
O_CMP("license", OUTFIT_TYPE_LICENSE);
O_CMP("gui", OUTFIT_TYPE_GUI);
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 Stores the damage type here.
* @param[out] dmg Stores the damage here.
* @param[in] node Node to parse damage from.
* @return 0 on success.
*/
static int outfit_parseDamage( Damage *dmg, xmlNodePtr node )
{
char *buf;
xmlNodePtr cur;
/* Defaults. */
dmg->type = dtype_get("normal");
dmg->damage = 0.;
dmg->penetration = 0.;
dmg->disable = 0.;
cur = node->xmlChildrenNode;
do {
xml_onlyNodes( cur );
/* Core properties. */
xmlr_float( cur, "penetrate", dmg->penetration );
xmlr_float( cur, "physical", dmg->damage );
xmlr_float( cur, "disable", dmg->disable );
/* Get type */
if (xml_isNode(cur,"type")) {
buf = xml_get( cur );
dmg->type = dtype_get(buf);
if (dmg->type < 0) { /* Case damage type in outfit.xml that isn't in damagetype.xml */
dmg->type = 0;
WARN("Unknown damage type '%s'", buf);
}
continue;
}
WARN("Damage has unknown node '%s'", cur->name);
} while (xml_nextNode(cur));
/* Normalize. */
dmg->penetration /= 100.;
return 0;
}
/**
* @brief Parses the specific area for a bolt weapon and loads it into Outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSBolt( Outfit* temp, const xmlNodePtr parent )
{
xmlNodePtr node;
char *buf;
double C, area;
int l;
/* Defaults */
temp->u.blt.spfx_armour = -1;
temp->u.blt.spfx_shield = -1;
temp->u.blt.sound = -1;
temp->u.blt.sound_hit = -1;
temp->u.blt.falloff = -1.;
temp->u.blt.ew_lockon = 1.;
node = parent->xmlChildrenNode;
do { /* load all the data */
xml_onlyNodes(node);
xmlr_float(node,"speed",temp->u.blt.speed);
xmlr_float(node,"delay",temp->u.blt.delay);
xmlr_float(node,"ew_lockon",temp->u.blt.ew_lockon);
xmlr_float(node,"energy",temp->u.blt.energy);
xmlr_float(node,"heatup",temp->u.blt.heatup);
xmlr_float(node,"track",temp->u.blt.track);
xmlr_float(node,"swivel",temp->u.blt.swivel);
if (xml_isNode(node,"range")) {
buf = xml_nodeProp(node,"blowup");
if (buf != NULL) {
if (strcmp(buf,"armour")==0)
outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_SHIELD);
else if (strcmp(buf,"shield")==0)
outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_ARMOUR);
else
WARN("Outfit '%s' has invalid blowup property: '%s'",
temp->name, buf );
free(buf);
}
temp->u.blt.range = xml_getFloat(node);
continue;
}
xmlr_float(node,"falloff",temp->u.blt.falloff);
/* Graphics. */
if (xml_isNode(node,"gfx")) {
temp->u.blt.gfx_space = xml_parseTexture( node,
OUTFIT_GFX_PATH"space/%s.png", 6, 6,
OPENGL_TEX_MAPTRANS | OPENGL_TEX_MIPMAPS );
xmlr_attr(node, "spin", buf);
if (buf != NULL) {
outfit_setProp( temp, OUTFIT_PROP_WEAP_SPIN );
temp->u.blt.spin = atof( buf );
free(buf);
}
continue;
}
if (xml_isNode(node,"gfx_end")) {
if (!conf.interpolate)
continue;
temp->u.blt.gfx_end = xml_parseTexture( node,
OUTFIT_GFX_PATH"space/%s.png", 6, 6,
OPENGL_TEX_MAPTRANS | OPENGL_TEX_MIPMAPS );
continue;
}
/* Special effects. */
if (xml_isNode(node,"spfx_shield")) {
temp->u.blt.spfx_shield = spfx_get(xml_get(node));
continue;
}
if (xml_isNode(node,"spfx_armour")) {
temp->u.blt.spfx_armour = spfx_get(xml_get(node));
continue;
}
/* Misc. */
if (xml_isNode(node,"sound")) {
temp->u.blt.sound = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"sound_hit")) {
temp->u.blt.sound_hit = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"damage")) {
outfit_parseDamage( &temp->u.blt.dmg, node );
continue;
}
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* If not defined assume maximum. */
if (temp->u.blt.falloff < 0.)
temp->u.blt.falloff = temp->u.blt.range;
/* Post processing. */
temp->u.blt.swivel *= M_PI/180.;
if (outfit_isTurret(temp))
temp->u.blt.swivel = M_PI;
/*
* dT Mthermal - Qweap
* Hweap = ----------------------
* tweap
*/
C = pilot_heatCalcOutfitC(temp);
area = pilot_heatCalcOutfitArea(temp);
temp->u.blt.heat = ((800.-CONST_SPACE_STAR_TEMP)*C +
STEEL_HEAT_CONDUCTIVITY * ((800-CONST_SPACE_STAR_TEMP) * area)) /
temp->u.blt.heatup * temp->u.blt.delay;
/* Set default outfit size if necessary. */
if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
outfit_setDefaultSize( temp );
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
l = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s [%s]\n"
"%.0f CPU\n"
"%.0f%% Penetration\n"
"%.2f DPS [%.0f Damage]\n",
outfit_getType(temp), dtype_damageTypeToStr(temp->u.blt.dmg.type),
temp->cpu,
temp->u.blt.dmg.penetration*100.,
1./temp->u.blt.delay * temp->u.blt.dmg.damage, temp->u.blt.dmg.damage );
if (temp->u.blt.dmg.disable > 0.) {
l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
"%.2f Disable/s [%.0f Disable]\n",
1./temp->u.blt.delay * temp->u.blt.dmg.disable, temp->u.blt.dmg.disable );
}
l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
"%.1f Shots Per Second\n"
"%.1f EPS [%.0f Energy]\n"
"%.0f Range\n"
"%.1f second heat up",
1./temp->u.blt.delay,
1./temp->u.blt.delay * temp->u.blt.energy, temp->u.blt.energy,
temp->u.blt.range,
temp->u.blt.heatup);
if (!outfit_isTurret(temp)) {
l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
"\n%.1f degree swivel",
temp->u.blt.swivel*180./M_PI );
}
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->u.blt.gfx_space==NULL,"gfx");
MELEMENT(temp->u.blt.spfx_shield==-1,"spfx_shield");
MELEMENT(temp->u.blt.spfx_armour==-1,"spfx_armour");
MELEMENT((sound_disabled!=0) && (temp->u.blt.sound<0),"sound");
MELEMENT(temp->mass==0.,"mass");
MELEMENT(temp->u.blt.delay==0,"delay");
MELEMENT(temp->u.blt.speed==0,"speed");
MELEMENT(temp->u.blt.range==0,"range");
MELEMENT(temp->u.blt.dmg.damage==0,"damage");
MELEMENT(temp->u.blt.energy==0.,"energy");
MELEMENT(temp->cpu==0.,"cpu");
MELEMENT(temp->u.blt.falloff > temp->u.blt.range,"falloff");
MELEMENT(temp->u.blt.heatup==0.,"heatup");
MELEMENT(((temp->u.blt.swivel > 0.) || outfit_isTurret(temp)) && (temp->u.blt.track==0.),"track");
#undef MELEMENT
}
/**
* @brief Parses the beam weapon specifics of an outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSBeam( Outfit* temp, const xmlNodePtr parent )
{
int l;
xmlNodePtr node;
double C, area;
char *prop;
/* Defaults. */
temp->u.bem.spfx_armour = -1;
temp->u.bem.spfx_shield = -1;
temp->u.bem.sound_warmup = -1;
temp->u.bem.sound = -1;
temp->u.bem.sound_off = -1;
node = parent->xmlChildrenNode;
do { /* load all the data */
xml_onlyNodes(node);
xmlr_float(node,"range",temp->u.bem.range);
xmlr_float(node,"turn",temp->u.bem.turn);
xmlr_float(node,"energy",temp->u.bem.energy);
xmlr_float(node,"delay",temp->u.bem.delay);
xmlr_float(node,"warmup",temp->u.bem.warmup);
xmlr_float(node,"heatup",temp->u.bem.heatup);
if (xml_isNode(node, "duration")) {
prop = xml_nodeProp(node, "min");
if (prop != NULL) {
temp->u.bem.min_duration = atof(prop);
free(prop);
}
temp->u.bem.duration = xml_getFloat(node);
continue;
}
if (xml_isNode(node,"damage")) {
outfit_parseDamage( &temp->u.bem.dmg, node );
continue;
}
/* Graphic stuff. */
if (xml_isNode(node,"gfx")) {
temp->u.bem.gfx = xml_parseTexture( node,
OUTFIT_GFX_PATH"space/%s.png", 1, 1, OPENGL_TEX_MIPMAPS );
continue;
}
if (xml_isNode(node,"spfx_armour")) {
temp->u.bem.spfx_armour = spfx_get(xml_get(node));
continue;
}
if (xml_isNode(node,"spfx_shield")) {
temp->u.bem.spfx_shield = spfx_get(xml_get(node));
continue;
}
/* Sound stuff. */
if (xml_isNode(node,"sound_warmup")) {
temp->u.bem.sound_warmup = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"sound")) {
temp->u.bem.sound = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"sound_off")) {
temp->u.bem.sound_off = sound_get( xml_get(node) );
continue;
}
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Post processing. */
temp->u.bem.turn *= M_PI/180.; /* Convert to rad/s. */
C = pilot_heatCalcOutfitC(temp);
area = pilot_heatCalcOutfitArea(temp);
temp->u.bem.heat = ((800.-CONST_SPACE_STAR_TEMP)*C +
STEEL_HEAT_CONDUCTIVITY * ((800-CONST_SPACE_STAR_TEMP) * area)) /
temp->u.bem.heatup;
/* Set default outfit size if necessary. */
if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
outfit_setDefaultSize( temp );
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
l = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s\n"
"%.0f CPU\n"
"%.0f%% Penetration\n"
"%.2f DPS [%s]\n",
outfit_getType(temp),
temp->cpu,
temp->u.bem.dmg.penetration*100.,
temp->u.bem.dmg.damage, dtype_damageTypeToStr(temp->u.bem.dmg.type) );
if (temp->u.blt.dmg.disable > 0.) {
l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
"%.0f Disable/s\n",
temp->u.bem.dmg.disable );
}
l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
"%.1f EPS\n"
"%.1f Duration %.1f Cooldown\n"
"%.0f Range\n"
"%.1f second heat up",
temp->u.bem.energy,
temp->u.bem.duration, temp->u.bem.delay,
temp->u.bem.range,
temp->u.bem.heatup);
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->u.bem.gfx==NULL,"gfx");
MELEMENT(temp->u.bem.spfx_shield==-1,"spfx_shield");
MELEMENT(temp->u.bem.spfx_armour==-1,"spfx_armour");
MELEMENT((sound_disabled!=0) && (temp->u.bem.warmup > 0.) && (temp->u.bem.sound<0),"sound_warmup");
MELEMENT((sound_disabled!=0) && (temp->u.bem.sound<0),"sound");
MELEMENT((sound_disabled!=0) && (temp->u.bem.sound_off<0),"sound_off");
MELEMENT(temp->u.bem.delay==0,"delay");
MELEMENT(temp->u.bem.duration==0,"duration");
MELEMENT(temp->u.bem.min_duration < 0,"duration");
MELEMENT(temp->u.bem.range==0,"range");
MELEMENT((temp->type!=OUTFIT_TYPE_BEAM) && (temp->u.bem.turn==0),"turn");
MELEMENT(temp->u.bem.energy==0.,"energy");
MELEMENT(temp->cpu==0.,"cpu");
MELEMENT(temp->u.bem.dmg.damage==0,"damage");
MELEMENT(temp->u.bem.heatup==0.,"heatup");
#undef MELEMENT
}
/**
* @brief Parses the specific area for a launcher and loads it into Outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSLauncher( Outfit* temp, const xmlNodePtr parent )
{
xmlNodePtr node;
node = parent->xmlChildrenNode;
do { /* load all the data */
xml_onlyNodes(node);
xmlr_float(node,"delay",temp->u.lau.delay);
xmlr_strd(node,"ammo",temp->u.lau.ammo_name);
xmlr_int(node,"amount",temp->u.lau.amount);
xmlr_float(node,"ew_target",temp->u.lau.ew_target);
xmlr_float(node,"lockon",temp->u.lau.lockon);
if (!outfit_isTurret(temp))
xmlr_float(node,"arc",temp->u.lau.arc);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Post processing. */
temp->u.lau.arc *= M_PI/180.;
temp->u.lau.ew_target2 = pow2( temp->u.lau.ew_target );
/* Set default outfit size if necessary. */
if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
outfit_setDefaultSize( temp );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->u.lau.ammo_name==NULL,"ammo");
MELEMENT(temp->u.lau.delay==0.,"delay");
MELEMENT(temp->cpu==0.,"cpu");
MELEMENT(temp->u.lau.amount==0.,"amount");
#undef MELEMENT
}
/**
* @brief Parses the specific area for a weapon and loads it into Outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSAmmo( Outfit* temp, const xmlNodePtr parent )
{
xmlNodePtr node;
char *buf;
int l;
node = parent->xmlChildrenNode;
/* Defaults. */
temp->slot.type = OUTFIT_SLOT_NA;
temp->slot.size = OUTFIT_SLOT_SIZE_NA;
temp->u.amm.spfx_armour = -1;
temp->u.amm.spfx_shield = -1;
temp->u.amm.sound = -1;
temp->u.amm.sound_hit = -1;
temp->u.amm.ai = -1;
do { /* load all the data */
xml_onlyNodes(node);
/* Basic */
if (xml_isNode(node,"duration")) {
buf = xml_nodeProp(node,"blowup");
if (buf != NULL) {
if (strcmp(buf,"armour")==0)
outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_SHIELD);
else if (strcmp(buf,"shield")==0)
outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_ARMOUR);
else
WARN("Outfit '%s' has invalid blowup property: '%s'",
temp->name, buf );
free(buf);
}
temp->u.amm.duration = xml_getFloat(node);
continue;
}
xmlr_float(node,"resist",temp->u.amm.resist);
/* Movement */
xmlr_float(node,"thrust",temp->u.amm.thrust);
xmlr_float(node,"turn",temp->u.amm.turn);
xmlr_float(node,"speed",temp->u.amm.speed);
xmlr_float(node,"energy",temp->u.amm.energy);
if (xml_isNode(node,"gfx")) {
temp->u.amm.gfx_space = xml_parseTexture( node,
OUTFIT_GFX_PATH"space/%s.png", 6, 6,
OPENGL_TEX_MAPTRANS | OPENGL_TEX_MIPMAPS );
xmlr_attr(node, "spin", buf);
if (buf != NULL) {
outfit_setProp( temp, OUTFIT_PROP_WEAP_SPIN );
temp->u.amm.spin = atof( buf );
free(buf);
}
continue;
}
if (xml_isNode(node,"spfx_armour")) {
temp->u.amm.spfx_armour = spfx_get(xml_get(node));
continue;
}
if (xml_isNode(node,"spfx_shield")) {
temp->u.amm.spfx_shield = spfx_get(xml_get(node));
continue;
}
if (xml_isNode(node,"sound")) {
temp->u.amm.sound = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"sound_hit")) {
temp->u.amm.sound_hit = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"damage")) {
outfit_parseDamage( &temp->u.amm.dmg, node );
continue;
}
if (xml_isNode(node,"ai")) {
buf = xml_get(node);
if (buf != NULL) {
if (strcmp(buf,"dumb")==0)
temp->u.amm.ai = AMMO_AI_DUMB;
else if (strcmp(buf,"seek")==0)
temp->u.amm.ai = AMMO_AI_SEEK;
else if (strcmp(buf,"smart")==0)
temp->u.amm.ai = AMMO_AI_SMART;
else
WARN("Ammo '%s' has unknown ai type '%s'.", temp->name, buf);
}
continue;
}
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Post-processing */
temp->u.amm.resist /= 100.; /* Set it in per one */
temp->u.amm.turn *= M_PI/180.; /* Convert to rad/s. */
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
l = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s\n"
"%.0f%% Penetration\n"
"%.0f Damage [%s]\n",
outfit_getType(temp),
temp->u.amm.dmg.penetration*100.,
temp->u.amm.dmg.damage, dtype_damageTypeToStr(temp->u.amm.dmg.type) );
if (temp->u.blt.dmg.disable > 0.) {
l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
"%.0f Disable\n",
temp->u.amm.dmg.disable );
}
l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
"%.0f Energy\n"
"%.0f Maximum Speed\n"
"%.0f%% Jam resistance\n"
"%.1f duration",
temp->u.amm.energy,
temp->u.amm.speed,
temp->u.amm.resist,
temp->u.amm.duration );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->mass==0.,"mass");
MELEMENT(temp->u.amm.gfx_space==NULL,"gfx");
MELEMENT(temp->u.amm.spfx_shield==-1,"spfx_shield");
MELEMENT(temp->u.amm.spfx_armour==-1,"spfx_armour");
MELEMENT((sound_disabled!=0) && (temp->u.amm.sound<0),"sound");
/* MELEMENT(temp->u.amm.thrust==0,"thrust"); */
/* Dumb missiles don't need everything */
if (outfit_isSeeker(temp)) {
MELEMENT(temp->u.amm.turn==0,"turn");
}
MELEMENT(temp->u.amm.speed==0,"speed");
MELEMENT(temp->u.amm.duration==0,"duration");
MELEMENT(temp->u.amm.dmg.damage==0,"damage");
/*MELEMENT(temp->u.amm.energy==0.,"energy");*/
MELEMENT(temp->cpu!=0.,"cpu");
#undef MELEMENT
}
/**
* @brief Parses the modification tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSMod( Outfit* temp, const xmlNodePtr parent )
{
int i;
xmlNodePtr node;
ShipStatList *ll;
char *buf;
node = parent->children;
do { /* load all the data */
xml_onlyNodes(node);
if (xml_isNode(node,"active")) {
xmlr_attr(node, "cooldown", buf);
if (buf != NULL) {
temp->u.mod.cooldown = atof( buf );
free(buf);
}
temp->u.mod.duration = xml_getFloat(node);
temp->u.mod.active = 1;
continue;
}
/* movement */
xmlr_float(node,"thrust",temp->u.mod.thrust);
xmlr_float(node,"turn",temp->u.mod.turn);
xmlr_float(node,"speed",temp->u.mod.speed);
/* health */
xmlr_float(node,"armour",temp->u.mod.armour);
xmlr_float(node,"shield",temp->u.mod.shield);
xmlr_float(node,"energy",temp->u.mod.energy);
xmlr_float(node,"fuel",temp->u.mod.fuel);
xmlr_float(node,"armour_regen", temp->u.mod.armour_regen );
xmlr_float(node,"shield_regen", temp->u.mod.shield_regen );
xmlr_float(node,"energy_regen", temp->u.mod.energy_regen );
xmlr_float(node,"energy_loss", temp->u.mod.energy_loss );
xmlr_float(node,"absorb", temp->u.mod.absorb );
/* misc */
xmlr_float(node,"cargo",temp->u.mod.cargo);
xmlr_float(node,"crew_rel", temp->u.mod.crew_rel);
xmlr_float(node,"mass_rel",temp->u.mod.mass_rel);
/* Stats. */
ll = ss_listFromXML( node );
if (ll != NULL) {
ll->next = temp->u.mod.stats;
temp->u.mod.stats = ll;
continue;
}
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Set default outfit size if necessary. */
if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
outfit_setDefaultSize( temp );
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
i = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s"
"%s",
outfit_getType(temp),
(temp->u.mod.active) ? "\n\erActivated Outfit\e0" : "" );
#define DESC_ADD(x, s, n, c) \
if ((x) != 0.) \
i += nsnprintf( &temp->desc_short[i], OUTFIT_SHORTDESC_MAX-i, \
"\n\e%c%+."n"f "s"\e0", c, x )
#define DESC_ADD0(x, s) DESC_ADD( x, s, "0", ((x)>0)?'D':'r' )
#define DESC_ADD1(x, s) DESC_ADD( x, s, "1", ((x)>0)?'D':'r' )
DESC_ADD0( temp->cpu, "CPU" );
DESC_ADD0( temp->u.mod.thrust, "Thrust" );
DESC_ADD0( temp->u.mod.turn, "Turn Rate" );
DESC_ADD0( temp->u.mod.speed, "Maximum Speed" );
DESC_ADD0( temp->u.mod.armour, "Armour" );
DESC_ADD0( temp->u.mod.shield, "Shield" );
DESC_ADD0( temp->u.mod.energy, "Energy" );
DESC_ADD0( temp->u.mod.fuel, "Fuel" );
DESC_ADD1( temp->u.mod.armour_regen, "Armour Per Second" );
DESC_ADD1( temp->u.mod.shield_regen, "Shield Per Second" );
DESC_ADD1( temp->u.mod.energy_regen, "Energy Per Second" );
DESC_ADD0( temp->u.mod.absorb, "Absorption" );
DESC_ADD0( temp->u.mod.cargo, "Cargo" );
DESC_ADD0( temp->u.mod.crew_rel, "%% Crew" );
DESC_ADD0( temp->u.mod.mass_rel, "%% Mass" );
#undef DESC_ADD1
#undef DESC_ADD0
#undef DESC_ADD
/*i +=*/ ss_statsListDesc( temp->u.mod.stats,
&temp->desc_short[i], OUTFIT_SHORTDESC_MAX-i, 1 );
/* More processing. */
temp->u.mod.turn *= M_PI / 180.;
temp->u.mod.absorb /= 100.;
temp->u.mod.turn_rel /= 100.;
temp->u.mod.speed_rel /= 100.;
temp->u.mod.armour_rel /= 100.;
temp->u.mod.shield_rel /= 100.;
temp->u.mod.energy_rel /= 100.;
temp->u.mod.mass_rel /= 100.;
temp->u.mod.crew_rel /= 100.;
}
/**
* @brief Parses the afterburner tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSAfterburner( Outfit* temp, const xmlNodePtr parent )
{
xmlNodePtr node;
node = parent->children;
double C, area;
/* Defaults. */
temp->u.afb.sound = -1;
temp->u.afb.sound_on = -1;
temp->u.afb.sound_off = -1;
/* must be >= 1. */
temp->u.afb.thrust = 1.;
temp->u.afb.speed = 1.;
do { /* parse the data */
xml_onlyNodes(node);
xmlr_float(node,"rumble",temp->u.afb.rumble);
if (xml_isNode(node,"sound_on")) {
temp->u.afb.sound_on = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"sound")) {
temp->u.afb.sound = sound_get( xml_get(node) );
continue;
}
if (xml_isNode(node,"sound_off")) {
temp->u.afb.sound_off = sound_get( xml_get(node) );
continue;
}
xmlr_float(node,"thrust",temp->u.afb.thrust);
xmlr_float(node,"speed",temp->u.afb.speed);
xmlr_float(node,"energy",temp->u.afb.energy);
xmlr_float(node,"mass_limit",temp->u.afb.mass_limit);
xmlr_float(node,"heatup",temp->u.afb.heatup);
xmlr_float(node,"heat_cap",temp->u.afb.heat_cap);
xmlr_float(node,"heat_base",temp->u.afb.heat_base);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s\n"
"\erActivated Outfit\e0\n"
"%.0f CPU\n"
"Only one can be equipped\n"
"%.0f Maximum Effective Mass\n"
"%.0f%% Thrust\n"
"%.0f%% Maximum Speed\n"
"%.1f EPS\n"
"%.1f Rumble",
outfit_getType(temp),
temp->cpu,
temp->u.afb.mass_limit,
temp->u.afb.thrust + 100.,
temp->u.afb.speed + 100.,
temp->u.afb.energy,
temp->u.afb.rumble );
/* Post processing. */
temp->u.afb.thrust /= 100.;
temp->u.afb.speed /= 100.;
C = pilot_heatCalcOutfitC(temp);
area = pilot_heatCalcOutfitArea(temp);
temp->u.afb.heat = ((800.-CONST_SPACE_STAR_TEMP)*C +
STEEL_HEAT_CONDUCTIVITY * ((800-CONST_SPACE_STAR_TEMP) * area)) /
temp->u.afb.heatup;
/* Set default outfit size if necessary. */
if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
outfit_setDefaultSize( temp );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->u.afb.thrust==0.,"thrust");
MELEMENT(temp->u.afb.speed==0.,"speed");
MELEMENT(temp->u.afb.energy==0.,"energy");
MELEMENT(temp->cpu==0.,"cpu");
MELEMENT(temp->u.afb.mass_limit==0.,"mass_limit");
MELEMENT(temp->u.afb.heatup==0.,"heatup");
#undef MELEMENT
}
/**
* @brief Parses the fighter bay tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSFighterBay( Outfit *temp, const xmlNodePtr parent )
{
xmlNodePtr node;
node = parent->children;
do {
xml_onlyNodes(node);
xmlr_int(node,"delay",temp->u.bay.delay);
xmlr_strd(node,"ammo",temp->u.bay.ammo_name);
xmlr_int(node,"amount",temp->u.bay.amount);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Set default outfit size if necessary. */
if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
outfit_setDefaultSize( temp );
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s\n"
"%.0f CPU\n"
"%.1f Launches Per Second\n"
"Holds %d %s",
outfit_getType(temp),
temp->cpu,
1./temp->u.bay.delay,
temp->u.bay.amount, temp->u.bay.ammo_name );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->u.bay.delay==0,"delay");
MELEMENT(temp->cpu==0.,"cpu");
MELEMENT(temp->u.bay.ammo_name==NULL,"ammo");
MELEMENT(temp->u.bay.amount==0,"amount");
#undef MELEMENT
}
/**
* @brief Parses the fighter tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSFighter( Outfit *temp, const xmlNodePtr parent )
{
xmlNodePtr node;
node = parent->children;
temp->slot.type = OUTFIT_SLOT_NA;
temp->slot.size = OUTFIT_SLOT_SIZE_NA;
do {
xml_onlyNodes(node);
xmlr_strd(node,"ship",temp->u.fig.ship);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s",
outfit_getType(temp) );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
/**< Define to help check for data errors. */
MELEMENT(temp->u.fig.ship==NULL,"ship");
MELEMENT(temp->cpu!=0.,"cpu");
#undef MELEMENT
}
/**
* @brief Parses the map tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSMap( Outfit *temp, const xmlNodePtr parent )
{
int i, j;
xmlNodePtr node, cur;
void *buf;
StarSystem *sys, *system_stack;
Planet *asset;
JumpPoint *jump;
int nsys;
node = parent->children;
temp->slot.type = OUTFIT_SLOT_NA;
temp->slot.size = OUTFIT_SLOT_SIZE_NA;
temp->u.map->systems = array_create(StarSystem*);
temp->u.map->assets = array_create(Planet*);
temp->u.map->jumps = array_create(JumpPoint*);
do {
xml_onlyNodes(node);
if (xml_isNode(node,"sys")) {
buf = xml_nodeProp(node,"name");
if ((buf != NULL) && ((sys = system_get(buf)) != NULL)) {
free(buf);
array_grow( &temp->u.map->systems ) = sys;
cur = node->children;
do {
xml_onlyNodes(cur);
if (xml_isNode(cur,"asset")) {
buf = xml_get(cur);
if ((buf != NULL) && ((asset = planet_get(buf)) != NULL))
array_grow( &temp->u.map->assets ) = asset;
else
WARN("Map '%s' has invalid asset '%s'", temp->name, buf);
}
else if (xml_isNode(cur,"jump")) {
buf = xml_get(cur);
if ((buf != NULL) && ((jump = jump_get(xml_get(cur),
temp->u.map->systems[array_size(temp->u.map->systems)-1] )) != NULL))
array_grow( &temp->u.map->jumps ) = jump;
else
WARN("Map '%s' has invalid jump point '%s'", temp->name, buf);
}
else
WARN("Outfit '%s' has unknown node '%s'",temp->name, cur->name);
} while (xml_nextNode(cur));
}
else
WARN("Map '%s' has invalid system '%s'", temp->name, buf);
}
else if (xml_isNode(node,"short_desc")) {
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX, "%s", xml_get(node) );
}
else if (xml_isNode(node,"all")) { /* Add everything to the map */
system_stack = system_getAll(&nsys);
for (i=0;i<nsys;i++) {
array_grow( &temp->u.map->systems ) = &system_stack[i];
for (j=0;j<system_stack[i].nplanets;j++)
array_grow( &temp->u.map->assets ) = system_stack[i].planets[j];
for (j=0;j<system_stack[i].njumps;j++)
array_grow( &temp->u.map->jumps ) = &system_stack[i].jumps[j];
}
}
else
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
array_shrink( &temp->u.map->systems );
array_shrink( &temp->u.map->assets );
array_shrink( &temp->u.map->jumps );
if (temp->desc_short == NULL) {
/* Set short description based on type. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s", outfit_getType(temp) );
}
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
/**< Define to help check for data errors. */
MELEMENT(temp->mass!=0.,"cpu");
MELEMENT(temp->cpu!=0.,"cpu");
#undef MELEMENT
}
/**
* @brief Parses the map tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSLocalMap( Outfit *temp, const xmlNodePtr parent )
{
xmlNodePtr node;
node = parent->children;
temp->slot.type = OUTFIT_SLOT_NA;
temp->slot.size = OUTFIT_SLOT_SIZE_NA;
do {
xml_onlyNodes(node);
xmlr_float(node,"asset_detect",temp->u.lmap.asset_detect);
xmlr_float(node,"jump_detect",temp->u.lmap.jump_detect);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
temp->u.lmap.asset_detect = pow2( temp->u.lmap.asset_detect );
temp->u.lmap.jump_detect = pow2( temp->u.lmap.jump_detect );
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s",
outfit_getType(temp) );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
/**< Define to help check for data errors. */
MELEMENT(temp->mass!=0.,"cpu");
MELEMENT(temp->cpu!=0.,"cpu");
#undef MELEMENT
}
/**
* @brief Parses the GUI tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSGUI( Outfit *temp, const xmlNodePtr parent )
{
xmlNodePtr node;
temp->slot.type = OUTFIT_SLOT_NA;
temp->slot.size = OUTFIT_SLOT_SIZE_NA;
node = parent->children;
do {
xml_onlyNodes(node);
xmlr_strd(node,"gui",temp->u.gui.gui);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"GUI (Graphical User Interface)" );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
/**< Define to help check for data errors. */
MELEMENT(temp->u.gui.gui==NULL,"gui");
MELEMENT(temp->mass!=0.,"cpu");
MELEMENT(temp->cpu!=0.,"cpu");
#undef MELEMENT
}
/**
* @brief Parses the license tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSLicense( Outfit *temp, const xmlNodePtr parent )
{
temp->slot.type = OUTFIT_SLOT_NA;
temp->slot.size = OUTFIT_SLOT_SIZE_NA;
xmlNodePtr node;
node = parent->children;
do {
xml_onlyNodes(node);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s",
outfit_getType(temp) );
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
/**< Define to help check for data errors. */
MELEMENT(temp->mass!=0.,"cpu");
MELEMENT(temp->cpu!=0.,"cpu");
#undef MELEMENT
}
/**
* @brief Parses the jammer tidbits of the outfit.
*
* @param temp Outfit to finish loading.
* @param parent Outfit's parent node.
*/
static void outfit_parseSJammer( Outfit *temp, const xmlNodePtr parent )
{
xmlNodePtr node;
node = parent->children;
do {
xml_onlyNodes(node);
xmlr_float(node,"energy",temp->u.jam.energy);
xmlr_float(node,"range",temp->u.jam.range);
xmlr_float(node,"power",temp->u.jam.power);
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
/* Set default outfit size if necessary. */
if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
outfit_setDefaultSize( temp );
temp->u.jam.energy = -temp->u.jam.energy;
/* Set short description. */
temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
"%s\n"
"\erActivated Outfit\e0\n"
"%.0f CPU\n"
"Only one can be equipped\n"
"%.0f Range\n"
"%.0f%% Power\n"
"%.1f EPS",
outfit_getType(temp),
temp->cpu,
temp->u.jam.range,
temp->u.jam.power,
temp->u.jam.energy );
temp->u.jam.power /= 100.; /* Put in per one, instead of percent */
temp->u.jam.range2 = pow2( temp->u.jam.range ); /**< We square it already. */
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->u.jam.range==0.,"range");
MELEMENT(temp->u.jam.power==0.,"power");
MELEMENT(temp->cpu==0.,"cpu");
#undef MELEMENT
}
/**
* @brief Parses and returns Outfit from parent node.
* @param temp Outfit to load into.
* @param parent Parent node to parse outfit from.
* @return 0 on success.
*/
static int outfit_parse( Outfit* temp, const char* file )
{
xmlNodePtr cur, node, parent;
char *prop;
const char *cprop;
int group;
uint32_t bufsize;
char *buf = ndata_read( file, &bufsize );
xmlDocPtr doc = xmlParseMemory( buf, bufsize );
parent = doc->xmlChildrenNode; /* first system node */
if (parent == NULL) {
ERR("Malformed '"OUTFIT_DATA_PATH"' file: does not contain elements");
return -1;
}
/* Clear data. */
memset( temp, 0, sizeof(Outfit) );
temp->name = xml_nodeProp(parent,"name"); /* already mallocs */
if (temp->name == NULL)
WARN("Outfit in "OUTFIT_DATA_PATH" has invalid or no name");
node = parent->xmlChildrenNode;
do { /* load all the data */
/* Only handle nodes. */
xml_onlyNodes(node);
if (xml_isNode(node,"general")) {
cur = node->children;
do {
xml_onlyNodes(cur);
xmlr_strd(cur,"license",temp->license);
xmlr_float(cur,"mass",temp->mass);
xmlr_float(cur,"cpu",temp->cpu);
xmlr_long(cur,"price",temp->price);
xmlr_strd(cur,"limit",temp->limit);
xmlr_strd(cur,"description",temp->description);
xmlr_strd(cur,"typename",temp->typename);
xmlr_int(cur,"priority",temp->priority);
if (xml_isNode(cur,"gfx_store")) {
temp->gfx_store = xml_parseTexture( cur,
OUTFIT_GFX_PATH"store/%s.png", 1, 1, OPENGL_TEX_MIPMAPS );
continue;
}
else if (xml_isNode(cur,"slot")) {
cprop = xml_get(cur);
if (cprop == NULL)
WARN("Outfit '%s' has an slot type invalid.", temp->name);
else if (strcmp(cprop,"structure") == 0)
temp->slot.type = OUTFIT_SLOT_STRUCTURE;
else if (strcmp(cprop,"utility") == 0)
temp->slot.type = OUTFIT_SLOT_UTILITY;
else if (strcmp(cprop,"weapon") == 0)
temp->slot.type = OUTFIT_SLOT_WEAPON;
else
WARN("Outfit '%s' has unknown slot type '%s'.", temp->name, cprop);
/* Property. */
xmlr_attr( cur, "prop", prop );
if (prop != NULL)
temp->slot.spid = sp_get( prop );
free( prop );
continue;
}
else if (xml_isNode(cur,"size")) {
temp->slot.size = outfit_toSlotSize( xml_get(cur) );
continue;
}
WARN("Outfit '%s' has unknown general node '%s'",temp->name, cur->name);
} while (xml_nextNode(cur));
continue;
}
if (xml_isNode(node,"specific")) { /* has to be processed separately */
/* get the type */
prop = xml_nodeProp(node,"type");
if (prop == NULL)
ERR("Outfit '%s' element 'specific' missing property 'type'",temp->name);
temp->type = outfit_strToOutfitType(prop);
free(prop);
/* is secondary weapon? */
prop = xml_nodeProp(node,"secondary");
if (prop != NULL) {
if ((int)atoi(prop))
outfit_setProp(temp, OUTFIT_PROP_WEAP_SECONDARY);
free(prop);
}
/* Check for manually-defined group. */
prop = xml_nodeProp(node, "group");
if (prop != NULL) {
group = atoi(prop);
if (group > PILOT_WEAPON_SETS || group < 1) {
WARN("Outfit '%s' has group '%d', should be in the 1-%d range",
temp->name, group, PILOT_WEAPON_SETS);
}
temp->group = CLAMP(0, 9, group - 1);
free(prop);
}
if (temp->type==OUTFIT_TYPE_NULL)
WARN("Outfit '%s' is of type NONE", temp->name);
else if (outfit_isBolt(temp))
outfit_parseSBolt( temp, node );
else if (outfit_isBeam(temp))
outfit_parseSBeam( temp, node );
else if (outfit_isLauncher(temp))
outfit_parseSLauncher( temp, node );
else if (outfit_isAmmo(temp))
outfit_parseSAmmo( temp, node );
else if (outfit_isMod(temp))
outfit_parseSMod( temp, node );
else if (outfit_isAfterburner(temp))
outfit_parseSAfterburner( temp, node );
else if (outfit_isJammer(temp))
outfit_parseSJammer( temp, node );
else if (outfit_isFighterBay(temp))
outfit_parseSFighterBay( temp, node );
else if (outfit_isFighter(temp))
outfit_parseSFighter( temp, node );
else if (outfit_isMap(temp)) {
temp->u.map = malloc( sizeof(OutfitMapData_t) ); /**< deal with maps after the universe is loaded */
temp->slot.type = OUTFIT_SLOT_NA;
temp->slot.size = OUTFIT_SLOT_SIZE_NA;
}
else if (outfit_isLocalMap(temp))
outfit_parseSLocalMap( temp, node );
else if (outfit_isGUI(temp))
outfit_parseSGUI( temp, node );
else if (outfit_isLicense(temp))
outfit_parseSLicense( temp, node );
continue;
}
WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
} while (xml_nextNode(node));
#define MELEMENT(o,s) \
if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
MELEMENT(temp->name==NULL,"name");
MELEMENT(temp->slot.type==OUTFIT_SLOT_NULL,"slot");
MELEMENT((temp->slot.type!=OUTFIT_SLOT_NA) && (temp->slot.size==OUTFIT_SLOT_SIZE_NA),"size");
MELEMENT(temp->gfx_store==NULL,"gfx_store");
/*MELEMENT(temp->mass==0,"mass"); Not really needed */
MELEMENT(temp->type==0,"type");
/*MELEMENT(temp->price==0,"price");*/
MELEMENT(temp->description==NULL,"description");
#undef MELEMENT
xmlFreeDoc(doc);
free(buf);
return 0;
}
/**
* @brief Loads all the files in a directory.
*
* @param dir Directory to load files from.
* @return 0 on success.
*/
static int outfit_loadDir( char *dir )
{
uint32_t nfiles;
char **outfit_files;
int i;
outfit_files = ndata_listRecursive( dir, &nfiles );
for (i=0; i<(int)nfiles; i++) {
outfit_parse( &array_grow(&outfit_stack), outfit_files[i] );
free( outfit_files[i] );
}
free( outfit_files );
/* Reduce size. */
array_shrink( &outfit_stack );
return 0;
}
/**
* @brief Loads all the outfits.
*
* @return 0 on success.
*/
int outfit_load (void)
{
int i, noutfits;
Outfit *o;
/* First pass, loads up ammunition. */
outfit_stack = array_create(Outfit);
outfit_loadDir( OUTFIT_DATA_PATH );
array_shrink(&outfit_stack);
noutfits = array_size(outfit_stack);
/* Second pass, sets up ammunition relationships. */
for (i=0; i<noutfits; i++) {
o = &outfit_stack[i];
if (outfit_isLauncher(&outfit_stack[i])) {
o->u.lau.ammo = outfit_get( o->u.lau.ammo_name );
if (outfit_isSeeker(o) && /* Smart seekers. */
(o->u.lau.ammo->u.amm.ai)) {
if (o->u.lau.ew_target == 0.)
WARN("Outfit '%s' missing/invalid 'ew_target' element", o->name);
if (o->u.lau.lockon == 0.)
WARN("Outfit '%s' missing/invalid 'lockon' element", o->name);
if (!outfit_isTurret(o) && (o->u.lau.arc == 0.))
WARN("Outfit '%s' missing/invalid 'arc' element", o->name);
}
outfit_launcherDesc(o);
}
else if (outfit_isFighterBay(&outfit_stack[i]))
o->u.bay.ammo = outfit_get( o->u.bay.ammo_name );
}
#ifdef DEBUGGING
char **outfit_names = malloc( noutfits * sizeof(char*) );
int start;
for (i=0; i<noutfits; i++)
outfit_names[i] = outfit_stack[i].name;
qsort( outfit_names, noutfits, sizeof(char*) , outfit_compareNames );
for (i=0; i<(noutfits - 1); i++) {
start = i;
while (strcmp(outfit_names[i], outfit_names[i+1]) == 0)
i++;
if (i == start)
continue;
WARN("Name collision! %d outfits are named '%s'", i+1 - start,
outfit_names[start]);
}
free(outfit_names);
#endif
DEBUG("Loaded %d Outfit%s", noutfits, (noutfits == 1) ? "" : "s" );
return 0;
}
/**
* @brief qsort compare function for names.
*/
static int outfit_compareNames( const void *name1, const void *name2 )
{
const char *n1, *n2;
n1 = *(const char**) name1;
n2 = *(const char**) name2;
return strcmp(n1, n2);
}
/**
* @brief Parses all the maps.
*
*/
int outfit_mapParse (void)
{
int i, len;
Outfit *o;
uint32_t bufsize, nfiles;
char *buf;
xmlNodePtr node, cur;
xmlDocPtr doc;
char **map_files;
char *file, *n;
map_files = ndata_list( MAP_DATA_PATH, &nfiles );
for (i=0; i<(int)nfiles; i++) {
len = strlen(MAP_DATA_PATH)+strlen(map_files[i])+2;
file = malloc( len );
nsnprintf( file, len, "%s%s", MAP_DATA_PATH, map_files[i] );
buf = ndata_read( file, &bufsize );
doc = xmlParseMemory( buf, bufsize );
node = doc->xmlChildrenNode; /* first system node */
if (node == NULL) {
WARN("Malformed '"OUTFIT_DATA_PATH"' file: does not contain elements");
free(file);
xmlFreeDoc(doc);
free(buf);
return -1;
}
n = xml_nodeProp( node,"name" );
o = outfit_get( n );
free(n);
if (!outfit_isMap(o)) { /* If its not a map, we don't care. */
free(file);
xmlFreeDoc(doc);
free(buf);
continue;
}
cur = node->xmlChildrenNode;
do { /* load all the data */
/* Only handle nodes. */
xml_onlyNodes(cur);
if (xml_isNode(cur,"specific"))
outfit_parseSMap(o, cur);
} while (xml_nextNode(cur));
/* Clean up. */
free(file);
xmlFreeDoc(doc);
free(buf);
}
/* Clean up. */
for (i=0; i<(int)nfiles; i++)
free( map_files[i] );
free( map_files );
return 0;
}
/**
* @brief Generates short descs for launchers, including ammo info.
*
* @param o Launcher.
*/
static void outfit_launcherDesc( Outfit* o )
{
int l;
Outfit *a; /* Launcher's ammo. */
if (o->desc_short != NULL) {
WARN("Outfit '%s' already has a short description", o->name);
return;
}
a = o->u.lau.ammo;
o->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
l = nsnprintf( o->desc_short, OUTFIT_SHORTDESC_MAX,
"%s [%s]\n"
"%.0f CPU\n"
"%.0f%% Penetration\n"
"%.2f DPS [%.0f Damage]\n",
outfit_getType(o), dtype_damageTypeToStr(a->u.amm.dmg.type),
o->cpu,
a->u.amm.dmg.penetration * 100.,
1. / o->u.lau.delay * a->u.amm.dmg.damage, a->u.amm.dmg.damage );
if (a->u.amm.dmg.disable > 0.)
l += nsnprintf( &o->desc_short[l], OUTFIT_SHORTDESC_MAX - l,
"%.2f Disable/s [%.0f Disable]\n",
1. / o->u.lau.delay * a->u.amm.dmg.disable, a->u.amm.dmg.disable );
l += nsnprintf( &o->desc_short[l], OUTFIT_SHORTDESC_MAX - l,
"%.1f Shots Per Second\n"
"Holds %d %s",
1. / o->u.lau.delay,
o->u.lau.amount, o->u.lau.ammo_name );
}
/**
* @brief Frees the outfit stack.
*/
void outfit_free (void)
{
int i;
Outfit *o;
for (i=0; i < array_size(outfit_stack); i++) {
o = &outfit_stack[i];
/* free graphics */
if (outfit_gfx(&outfit_stack[i]))
gl_freeTexture(outfit_gfx(&outfit_stack[i]));
/* Free slot. */
outfit_freeSlot( &outfit_stack[i].slot );
/* Type specific. */
if (outfit_isBolt(o) && o->u.blt.gfx_end)
gl_freeTexture(o->u.blt.gfx_end);
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);
if (outfit_isGUI(o) && o->u.gui.gui)
free(o->u.gui.gui);
if (o->type == OUTFIT_TYPE_MODIFICATION)
ss_free( o->u.mod.stats );
if (outfit_isMap(o)) {
array_free( o->u.map->systems );
array_free( o->u.map->assets );
array_free( o->u.map->jumps );
free( o->u.map );
}
/* strings */
free(o->typename);
free(o->description);
free(o->limit);
free(o->desc_short);
free(o->license);
free(o->name);
if (o->gfx_store)
gl_freeTexture(o->gfx_store);
}
array_free(outfit_stack);
}
Jump to Line
Something went wrong with that request. Please try again.