1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 /**
6  * @file outfit.c
7  *
8  * @brief Handles all the ship outfit specifics.
9  *
10  * These outfits allow you to modify ships or make them more powerful and are
11  *  a fundamental part of the game.
12  */
13 
14 
15 #include "outfit.h"
16 
17 #include "naev.h"
18 
19 #include <math.h>
20 #include "nstring.h"
21 #include <stdlib.h>
22 
23 #include "nxml.h"
24 #include "SDL_thread.h"
25 
26 #include "log.h"
27 #include "ndata.h"
28 #include "nfile.h"
29 #include "spfx.h"
30 #include "array.h"
31 #include "ship.h"
32 #include "conf.h"
33 #include "pilot_heat.h"
34 #include "nstring.h"
35 #include "pilot.h"
36 #include "damagetype.h"
37 #include "slots.h"
38 #include "mapData.h"
39 
40 
41 #define outfit_setProp(o,p)      ((o)->properties |= p) /**< Checks outfit property. */
42 
43 #define XML_OUTFIT_TAG     "outfit"    /**< XML section identifier. */
44 
45 #define OUTFIT_SHORTDESC_MAX  256 /**< Max length of the short description of the outfit. */
46 
47 
48 /*
49  * the stack
50  */
51 static Outfit* outfit_stack = NULL; /**< Stack of outfits. */
52 
53 
54 /*
55  * Prototypes
56  */
57 /* misc */
58 static OutfitType outfit_strToOutfitType( char *buf );
59 static int outfit_setDefaultSize( Outfit *o );
60 static void outfit_launcherDesc( Outfit* o );
61 static int outfit_compareNames( const void *name1, const void *name2 );
62 /* parsing */
63 static int outfit_loadDir( char *dir );
64 static int outfit_parseDamage( Damage *dmg, xmlNodePtr node );
65 static int outfit_parse( Outfit* temp, const char* file );
66 static void outfit_parseSBolt( Outfit* temp, const xmlNodePtr parent );
67 static void outfit_parseSBeam( Outfit* temp, const xmlNodePtr parent );
68 static void outfit_parseSLauncher( Outfit* temp, const xmlNodePtr parent );
69 static void outfit_parseSAmmo( Outfit* temp, const xmlNodePtr parent );
70 static void outfit_parseSMod( Outfit* temp, const xmlNodePtr parent );
71 static void outfit_parseSAfterburner( Outfit* temp, const xmlNodePtr parent );
72 static void outfit_parseSJammer( Outfit *temp, const xmlNodePtr parent );
73 static void outfit_parseSFighterBay( Outfit *temp, const xmlNodePtr parent );
74 static void outfit_parseSFighter( Outfit *temp, const xmlNodePtr parent );
75 static void outfit_parseSMap( Outfit *temp, const xmlNodePtr parent );
76 static void outfit_parseSLocalMap( Outfit *temp, const xmlNodePtr parent );
77 static void outfit_parseSGUI( Outfit *temp, const xmlNodePtr parent );
78 static void outfit_parseSLicense( Outfit *temp, const xmlNodePtr parent );
79 
80 
81 /**
82  * @brief Gets an outfit by name.
83  *
84  *    @param name Name to match.
85  *    @return Outfit matching name or NULL if not found.
86  */
outfit_get(const char * name)87 Outfit* outfit_get( const char* name )
88 {
89    int i;
90 
91    for (i=0; i<array_size(outfit_stack); i++)
92       if (strcmp(name,outfit_stack[i].name)==0)
93          return &outfit_stack[i];
94 
95    WARN("Outfit '%s' not found in stack.", name);
96    return NULL;
97 }
98 
99 
100 /**
101  * @brief Gets an outfit by name without warning on no-find.
102  *
103  *    @param name Name to match.
104  *    @return Outfit matching name or NULL if not found.
105  */
outfit_getW(const char * name)106 Outfit* outfit_getW( const char* name )
107 {
108    int i;
109    for (i=0; i<array_size(outfit_stack); i++)
110       if (strcmp(name,outfit_stack[i].name)==0)
111          return &outfit_stack[i];
112    return NULL;
113 }
114 
115 
116 /**
117  * @brief Gets all the outfits.
118  */
outfit_getAll(int * n)119 Outfit* outfit_getAll( int *n )
120 {
121    *n = array_size(outfit_stack);
122    return outfit_stack;
123 }
124 
125 
126 /**
127  * @brief Checks to see if an outfit exists matching name (case insensitive).
128  */
outfit_existsCase(const char * name)129 const char *outfit_existsCase( const char* name )
130 {
131    int i;
132    for (i=0; i<array_size(outfit_stack); i++)
133       if (strcasecmp(name,outfit_stack[i].name)==0)
134          return outfit_stack[i].name;
135    return NULL;
136 }
137 
138 
139 /**
140  * @brief Does a fuzzy search of all the outfits.
141  */
outfit_searchFuzzyCase(const char * name,int * n)142 char **outfit_searchFuzzyCase( const char* name, int *n )
143 {
144    int i, len, nstack;
145    char **names;
146 
147    /* Overallocate to maximum. */
148    nstack = array_size(outfit_stack);
149    names = malloc( sizeof(char*) * nstack );
150 
151    /* Do fuzzy search. */
152    len = 0;
153    for (i=0; i<nstack; i++) {
154       if (nstrcasestr( outfit_stack[i].name, name ) != NULL) {
155          names[len] = outfit_stack[i].name;
156          len++;
157       }
158    }
159 
160    /* Free if empty. */
161    if (len == 0) {
162       free(names);
163       names = NULL;
164    }
165 
166    *n = len;
167    return names;
168 }
169 
170 
171 /**
172  * @brief Function meant for use with C89, C99 algorithm qsort().
173  *
174  *    @param outfit1 First argument to compare.
175  *    @param outfit2 Second argument to compare.
176  *    @return -1 if first argument is inferior, +1 if it's superior, 0 if ties.
177  */
outfit_compareTech(const void * outfit1,const void * outfit2)178 int outfit_compareTech( const void *outfit1, const void *outfit2 )
179 {
180    int ret;
181    const Outfit *o1, *o2;
182 
183    /* Get outfits. */
184    o1 = * (const Outfit**) outfit1;
185    o2 = * (const Outfit**) outfit2;
186 
187    /* Compare slot type. */
188    if (o1->slot.type < o2->slot.type)
189       return +1;
190    else if (o1->slot.type > o2->slot.type)
191       return -1;
192 
193    /* Compare intrinsic types. */
194    if (o1->type < o2->type)
195       return -1;
196    else if (o1->type > o2->type)
197       return +1;
198 
199    /* Compare named types. */
200    if ((o1->typename == NULL) && (o2->typename != NULL))
201       return -1;
202    else if ((o1->typename != NULL) && (o2->typename == NULL))
203       return +1;
204    else if ((o1->typename != NULL) && (o2->typename != NULL)) {
205       ret = strcmp( o1->typename, o2->typename );
206       if (ret != 0)
207          return ret;
208    }
209 
210    /* Compare slot sizes. */
211    if (o1->slot.size < o2->slot.size)
212       return +1;
213    else if (o1->slot.size > o2->slot.size)
214       return -1;
215 
216    /* Compare sort priority. */
217    if (o1->priority < o2->priority)
218       return +1;
219    else if (o1->priority > o2->priority)
220       return -1;
221 
222    /* Compare price. */
223    if (o1->price < o2->price)
224       return +1;
225    else if (o1->price > o2->price)
226       return -1;
227 
228    /* It turns out they're the same. */
229    return strcmp( o1->name, o2->name );
230 }
231 
232 
233 /**
234  * @brief Gets the name of the slot type of an outfit.
235  *
236  *    @param o Outfit to get slot type of.
237  *    @return The human readable name of the slot type.
238  */
outfit_slotName(const Outfit * o)239 const char *outfit_slotName( const Outfit* o )
240 {
241    switch (o->slot.type) {
242       case OUTFIT_SLOT_NULL:
243          return "NULL";
244       case OUTFIT_SLOT_NA:
245          return "NA";
246       case OUTFIT_SLOT_STRUCTURE:
247          return "Structure";
248       case OUTFIT_SLOT_UTILITY:
249          return "Utility";
250       case OUTFIT_SLOT_WEAPON:
251          return "Weapon";
252       default:
253          return "Unknown";
254    }
255 }
256 
257 
258 /**
259  * @brief Gets the name of the slot size of an outfit.
260  *
261  *    @param o Outfit to get slot size of.
262  *    @return The human readable name of the slot size.
263  */
outfit_slotSize(const Outfit * o)264 const char *outfit_slotSize( const Outfit* o )
265 {
266    switch( o->slot.size) {
267       case OUTFIT_SLOT_SIZE_NA:
268          return "NA";
269       case OUTFIT_SLOT_SIZE_LIGHT:
270          return "Small";
271       case OUTFIT_SLOT_SIZE_MEDIUM:
272          return "Medium";
273       case OUTFIT_SLOT_SIZE_HEAVY:
274          return "Large";
275       default:
276          return "Unknown";
277    }
278 }
279 
slotSize(const OutfitSlotSize o)280 const char *slotSize( const OutfitSlotSize o )
281 {
282    switch( o ) {
283       case OUTFIT_SLOT_SIZE_NA:
284          return "NA";
285       case OUTFIT_SLOT_SIZE_LIGHT:
286          return "Small";
287       case OUTFIT_SLOT_SIZE_MEDIUM:
288          return "Medium";
289       case OUTFIT_SLOT_SIZE_HEAVY:
290          return "Large";
291       default:
292          return "Unknown";
293    }
294 }
295 
296 
297 
298 /**
299  * @brief Gets the slot size colour for an outfit slot.
300  *
301  *    @param os Outfit slot to get the slot size colour of.
302  *    @return The slot size colour of the outfit slot.
303  */
outfit_slotSizeColour(const OutfitSlot * os)304 const glColour *outfit_slotSizeColour( const OutfitSlot* os )
305 {
306    if (os->size == OUTFIT_SLOT_SIZE_HEAVY)
307       return &cFontBlue;
308    else if (os->size == OUTFIT_SLOT_SIZE_MEDIUM)
309       return &cFontGreen;
310    else if (os->size == OUTFIT_SLOT_SIZE_LIGHT)
311       return &cFontYellow;
312    return NULL;
313 }
314 
315 
316 /**
317  * @brief Gets the outfit slot size from a human readable string.
318  *
319  *    @param s String representing an outfit slot size.
320  *    @return Outfit slot size matching string.
321  */
outfit_toSlotSize(const char * s)322 OutfitSlotSize outfit_toSlotSize( const char *s )
323 {
324    if (s == NULL) {
325       /*WARN( "(NULL) outfit slot size" );*/
326       return OUTFIT_SLOT_SIZE_NA;
327    }
328 
329    if (strcasecmp(s,"Large")==0)
330       return OUTFIT_SLOT_SIZE_HEAVY;
331    else if (strcasecmp(s,"Medium")==0)
332       return OUTFIT_SLOT_SIZE_MEDIUM;
333    else if (strcasecmp(s,"Small")==0)
334       return OUTFIT_SLOT_SIZE_LIGHT;
335 
336    WARN("'%s' does not match any outfit slot sizes.", s);
337    return OUTFIT_SLOT_SIZE_NA;
338 }
339 
340 
341 /**
342  * @brief Sets the outfit slot size from default outfit properties.
343  */
outfit_setDefaultSize(Outfit * o)344 static int outfit_setDefaultSize( Outfit *o )
345 {
346    if (o->mass <= 10.)
347       o->slot.size = OUTFIT_SLOT_SIZE_LIGHT;
348    else if (o->mass <= 30.)
349       o->slot.size = OUTFIT_SLOT_SIZE_MEDIUM;
350    else
351       o->slot.size = OUTFIT_SLOT_SIZE_HEAVY;
352    WARN("Outfit '%s' has implicit slot size, setting to '%s'.",o->name,outfit_slotSize(o));
353    return 0;
354 }
355 
356 /**
357  * @brief Checks if outfit is an active outfit.
358  *    @param o Outfit to check.
359  *    @return 1 if o is active.
360  */
outfit_isActive(const Outfit * o)361 int outfit_isActive( const Outfit* o )
362 {
363    if (outfit_isForward(o) || outfit_isTurret(o) || outfit_isLauncher(o) || outfit_isFighterBay(o))
364       return 1;
365    if (outfit_isMod(o) && o->u.mod.active)
366       return 1;
367    if (outfit_isJammer(o))
368       return 1;
369    if (outfit_isAfterburner(o))
370       return 1;
371    return 0;
372 }
373 
374 
375 /**
376  * @brief Checks if outfit is a fixed mounted weapon.
377  *    @param o Outfit to check.
378  *    @return 1 if o is a weapon (beam/bolt).
379  */
outfit_isForward(const Outfit * o)380 int outfit_isForward( const Outfit* o )
381 {
382    return ( (o->type==OUTFIT_TYPE_BOLT)      ||
383          (o->type==OUTFIT_TYPE_BEAM) );
384 }
385 /**
386  * @brief Checks if outfit is bolt type weapon.
387  *    @param o Outfit to check.
388  *    @return 1 if o is a bolt type weapon.
389  */
outfit_isBolt(const Outfit * o)390 int outfit_isBolt( const Outfit* o )
391 {
392    return ( (o->type==OUTFIT_TYPE_BOLT)      ||
393          (o->type==OUTFIT_TYPE_TURRET_BOLT) );
394 }
395 /**
396  * @brief Checks if outfit is a beam type weapon.
397  *    @param o Outfit to check.
398  *    @return 1 if o is a beam type weapon.
399  */
outfit_isBeam(const Outfit * o)400 int outfit_isBeam( const Outfit* o )
401 {
402    return ( (o->type==OUTFIT_TYPE_BEAM)      ||
403          (o->type==OUTFIT_TYPE_TURRET_BEAM) );
404 }
405 /**
406  * @brief Checks if outfit is a weapon launcher.
407  *    @param o Outfit to check.
408  *    @return 1 if o is a weapon launcher.
409  */
outfit_isLauncher(const Outfit * o)410 int outfit_isLauncher( const Outfit* o )
411 {
412    return ( (o->type==OUTFIT_TYPE_LAUNCHER) ||
413          (o->type==OUTFIT_TYPE_TURRET_LAUNCHER) );
414 }
415 /**
416  * @brief Checks if outfit is ammo for a launcher.
417  *    @param o Outfit to check.
418  *    @return 1 if o is ammo.
419  */
outfit_isAmmo(const Outfit * o)420 int outfit_isAmmo( const Outfit* o )
421 {
422    return (o->type==OUTFIT_TYPE_AMMO);
423 }
424 /**
425  * @brief Checks if outfit is a seeking weapon.
426  *    @param o Outfit to check.
427  *    @return 1 if o is a seeking weapon.
428  */
outfit_isSeeker(const Outfit * o)429 int outfit_isSeeker( const Outfit* o )
430 {
431    if (((o->type==OUTFIT_TYPE_AMMO) || (o->type==OUTFIT_TYPE_TURRET_LAUNCHER) ||
432             (o->type==OUTFIT_TYPE_LAUNCHER)) &&
433          (o->u.amm.ai > 0))
434       return 1;
435    return 0;
436 }
437 /**
438  * @brief Checks if outfit is a turret class weapon.
439  *    @param o Outfit to check.
440  *    @return 1 if o is a turret class weapon.
441  */
outfit_isTurret(const Outfit * o)442 int outfit_isTurret( const Outfit* o )
443 {
444    return ( (o->type==OUTFIT_TYPE_TURRET_BOLT)  ||
445          (o->type==OUTFIT_TYPE_TURRET_BEAM)     ||
446          (o->type==OUTFIT_TYPE_TURRET_LAUNCHER) );
447 }
448 /**
449  * @brief Checks if outfit is a ship modification.
450  *    @param o Outfit to check.
451  *    @return 1 if o is a ship modification.
452  */
outfit_isMod(const Outfit * o)453 int outfit_isMod( const Outfit* o )
454 {
455    return (o->type==OUTFIT_TYPE_MODIFICATION);
456 }
457 /**
458  * @brief Checks if outfit is an afterburner.
459  *    @param o Outfit to check.
460  *    @return 1 if o is an afterburner.
461  */
outfit_isAfterburner(const Outfit * o)462 int outfit_isAfterburner( const Outfit* o )
463 {
464    return (o->type==OUTFIT_TYPE_AFTERBURNER);
465 }
466 /**
467  * @brief Checks if outfit is a missile jammer.
468  *    @param o Outfit to check.
469  *    @return 1 if o is a jammer.
470  */
outfit_isJammer(const Outfit * o)471 int outfit_isJammer( const Outfit* o )
472 {
473    return (o->type==OUTFIT_TYPE_JAMMER);
474 }
475 /**
476  * @brief Checks if outfit is a fighter bay.
477  *    @param o Outfit to check.
478  *    @return 1 if o is a jammer.
479  */
outfit_isFighterBay(const Outfit * o)480 int outfit_isFighterBay( const Outfit* o )
481 {
482    return (o->type==OUTFIT_TYPE_FIGHTER_BAY);
483 }
484 /**
485  * @brief Checks if outfit is a fighter.
486  *    @param o Outfit to check.
487  *    @return 1 if o is a Fighter.
488  */
outfit_isFighter(const Outfit * o)489 int outfit_isFighter( const Outfit* o )
490 {
491    return (o->type==OUTFIT_TYPE_FIGHTER);
492 }
493 /**
494  * @brief Checks if outfit is a space map.
495  *    @param o Outfit to check.
496  *    @return 1 if o is a map.
497  */
outfit_isMap(const Outfit * o)498 int outfit_isMap( const Outfit* o )
499 {
500    return (o->type==OUTFIT_TYPE_MAP);
501 }
502 /**
503  * @brief Checks if outfit is a local space map.
504  *    @param o Outfit to check.
505  *    @return 1 if o is a map.
506  */
outfit_isLocalMap(const Outfit * o)507 int outfit_isLocalMap( const Outfit* o )
508 {
509    return (o->type==OUTFIT_TYPE_LOCALMAP);
510 }
511 /**
512  * @brief Checks if outfit is a license.
513  *    @param o Outfit to check.
514  *    @return 1 if o is a license.
515  */
outfit_isLicense(const Outfit * o)516 int outfit_isLicense( const Outfit* o )
517 {
518    return (o->type==OUTFIT_TYPE_LICENSE);
519 }
520 /**
521  * @brief Checks if outfit is a GUI.
522  *    @param o Outfit to check.
523  *    @return 1 if o is a GUI.
524  */
outfit_isGUI(const Outfit * o)525 int outfit_isGUI( const Outfit* o )
526 {
527    return (o->type==OUTFIT_TYPE_GUI);
528 }
529 
530 
531 /**
532  * @brief Checks if outfit has the secondary flag set.
533  *    @param o Outfit to check.
534  *    @return 1 if o is a secondary weapon.
535  */
outfit_isSecondary(const Outfit * o)536 int outfit_isSecondary( const Outfit* o )
537 {
538    return (o->properties & OUTFIT_PROP_WEAP_SECONDARY);
539 }
540 
541 
542 /**
543  * @brief Gets the outfit's graphic effect.
544  *    @param o Outfit to get information from.
545  */
outfit_gfx(const Outfit * o)546 glTexture* outfit_gfx( const Outfit* o )
547 {
548    if (outfit_isBolt(o)) return o->u.blt.gfx_space;
549    else if (outfit_isBeam(o)) return o->u.bem.gfx;
550    else if (outfit_isAmmo(o)) return o->u.amm.gfx_space;
551    return NULL;
552 }
553 /**
554  * @brief Gets the outfit's sound effect.
555  *    @param o Outfit to get information from.
556  */
outfit_spfxArmour(const Outfit * o)557 int outfit_spfxArmour( const Outfit* o )
558 {
559    if (outfit_isBolt(o)) return o->u.blt.spfx_armour;
560    else if (outfit_isBeam(o)) return o->u.bem.spfx_armour;
561    else if (outfit_isAmmo(o)) return o->u.amm.spfx_armour;
562    return -1;
563 }
564 /**
565  * @brief Gets the outfit's sound effect.
566  *    @param o Outfit to get information from.
567  */
outfit_spfxShield(const Outfit * o)568 int outfit_spfxShield( const Outfit* o )
569 {
570    if (outfit_isBolt(o)) return o->u.blt.spfx_shield;
571    else if (outfit_isBeam(o)) return o->u.bem.spfx_shield;
572    else if (outfit_isAmmo(o)) return o->u.amm.spfx_shield;
573    return -1;
574 }
575 /**
576  * @brief Gets the outfit's damage.
577  *    @param o Outfit to get information from.
578  */
outfit_damage(const Outfit * o)579 const Damage *outfit_damage( const Outfit* o )
580 {
581    if (outfit_isBolt(o)) return &o->u.blt.dmg;
582    else if (outfit_isBeam(o)) return &o->u.bem.dmg;
583    else if (outfit_isAmmo(o)) return &o->u.amm.dmg;
584    return NULL;
585 }
586 /**
587  * @brief Gets the outfit's delay.
588  *    @param o Outfit to get information from.
589  */
outfit_delay(const Outfit * o)590 double outfit_delay( const Outfit* o )
591 {
592    if (outfit_isBolt(o)) return o->u.blt.delay;
593    else if (outfit_isBeam(o)) return o->u.bem.delay;
594    else if (outfit_isLauncher(o)) return o->u.lau.delay;
595    else if (outfit_isFighterBay(o)) return o->u.bay.delay;
596    return -1;
597 }
598 /**
599  * @brief Gets the outfit's ammo.
600  *    @param o Outfit to get information from.
601  */
outfit_ammo(const Outfit * o)602 Outfit* outfit_ammo( const Outfit* o )
603 {
604    if (outfit_isLauncher(o)) return o->u.lau.ammo;
605    else if (outfit_isFighterBay(o)) return o->u.bay.ammo;
606    return NULL;
607 }
608 /**
609  * @brief Gets the amount an outfit can hold.
610  *    @param o Outfit to get information from.
611  */
outfit_amount(const Outfit * o)612 int outfit_amount( const Outfit* o )
613 {
614    if (outfit_isLauncher(o)) return o->u.lau.amount;
615    else if (outfit_isFighterBay(o)) return o->u.bay.amount;
616    return -1;
617 }
618 
619 /**
620  * @brief Gets the outfit's energy usage.
621  *    @param o Outfit to get information from.
622  */
outfit_energy(const Outfit * o)623 double outfit_energy( const Outfit* o )
624 {
625    if (outfit_isBolt(o)) return o->u.blt.energy;
626    else if (outfit_isBeam(o)) return o->u.bem.energy;
627    else if (outfit_isAmmo(o)) return o->u.amm.energy;
628    return -1.;
629 }
630 /**
631  * @brief Gets the outfit's heat generation.
632  *    @param o Outfit to get information from.
633  */
outfit_heat(const Outfit * o)634 double outfit_heat( const Outfit* o )
635 {
636    if (outfit_isBolt(o)) return o->u.blt.heat;
637    else if (outfit_isAfterburner(o)) return o->u.afb.heat;
638    else if (outfit_isBeam(o)) return o->u.bem.heat;
639    return -1;
640 }
641 /**
642  * @brief Gets the outfit's cpu usage.
643  *    @param o Outfit to get information from.
644  */
outfit_cpu(const Outfit * o)645 double outfit_cpu( const Outfit* o )
646 {
647    return o->cpu;
648 }
649 /**
650  * @brief Gets the outfit's range.
651  *    @param o Outfit to get information from.
652  */
outfit_range(const Outfit * o)653 double outfit_range( const Outfit* o )
654 {
655    Outfit *amm;
656    double at;
657 
658    if (outfit_isBolt(o)) return o->u.blt.falloff + (o->u.blt.range - o->u.blt.falloff)/2.;
659    else if (outfit_isBeam(o)) return o->u.bem.range;
660    else if (outfit_isAmmo(o)) {
661       if (o->u.amm.thrust) {
662          at = o->u.amm.speed / o->u.amm.thrust;
663          if (at < o->u.amm.duration)
664             return o->u.amm.speed * (o->u.amm.duration - at / 2.);
665 
666          /* Maximum speed will never be reached. */
667          return pow2(o->u.amm.duration) * o->u.amm.thrust / 2.;
668       }
669 
670       return o->u.amm.speed * o->u.amm.duration;
671    }
672    else if (outfit_isLauncher(o)) {
673       amm = outfit_ammo(o);
674       if (amm != NULL)
675          return outfit_range(amm);
676    }
677    else if (outfit_isFighterBay(o))
678       return INFINITY;
679    return -1.;
680 }
681 /**
682  * @brief Gets the outfit's speed.
683  *    @param o Outfit to get information from.
684  *    @return Outfit's speed.
685  */
outfit_speed(const Outfit * o)686 double outfit_speed( const Outfit* o )
687 {
688    Outfit *amm;
689    double t;
690    if (outfit_isBolt(o)) return o->u.blt.speed;
691    else if (outfit_isAmmo(o)) {
692       if (o->u.amm.thrust == 0)
693          return o->u.amm.speed;
694       else {     /*Gets the average speed*/
695          t = o->u.amm.speed / o->u.amm.thrust; /*time to reach max speed*/
696          if (t < o->u.amm.duration)
697             return (o->u.amm.thrust * t*t/2 +
698                   o->u.amm.speed*(o->u.amm.duration-t))/o->u.amm.duration;
699          else return o->u.amm.thrust * o->u.amm.duration/2;
700       }
701    }
702    else if (outfit_isLauncher(o)) {
703       amm = outfit_ammo(o);
704       if (amm != NULL)
705          return outfit_speed(amm);
706    }
707    return -1.;
708 }
709 /**
710  * @brief Gets the outfit's animation spin.
711  *    @param o Outfit to get information from.
712  *    @return Outfit's animation spin.
713  */
outfit_spin(const Outfit * o)714 double outfit_spin( const Outfit* o )
715 {
716    if (outfit_isBolt(o)) return o->u.blt.spin;
717    else if (outfit_isAmmo(o)) return o->u.amm.spin;
718    return -1.;
719 }
720 /**
721  * @brief Gets the outfit's sound.
722  *    @param o Outfit to get sound from.
723  *    @return Outfit's sound.
724  */
outfit_sound(const Outfit * o)725 int outfit_sound( const Outfit* o )
726 {
727    if (outfit_isBolt(o)) return o->u.blt.sound;
728    else if (outfit_isAmmo(o)) return o->u.amm.sound;
729    return -1.;
730 }
731 /**
732  * @brief Gets the outfit's hit sound.
733  *    @param o Outfit to get hit sound from.
734  *    @return Outfit's hit sound.
735  */
outfit_soundHit(const Outfit * o)736 int outfit_soundHit( const Outfit* o )
737 {
738    if (outfit_isBolt(o)) return o->u.blt.sound_hit;
739    else if (outfit_isAmmo(o)) return o->u.amm.sound_hit;
740    return -1.;
741 }
742 /**
743  * @brief Gets the outfit's duration.
744  *    @param o Outfit to get the duration of.
745  *    @return Outfit's duration.
746  */
outfit_duration(const Outfit * o)747 double outfit_duration( const Outfit* o )
748 {
749    Outfit *amm;
750    if (outfit_isMod(o)) { if (o->u.mod.active) return o->u.mod.duration; }
751    else if (outfit_isJammer(o)) return INFINITY;
752    else if (outfit_isAfterburner(o)) return INFINITY;
753    else if (outfit_isBolt(o)) return (o->u.blt.range / o->u.blt.speed);
754    else if (outfit_isBeam(o)) return o->u.bem.duration;
755    else if (outfit_isAmmo(o)) return o->u.amm.duration;
756    else if (outfit_isLauncher(o)) {
757       amm = outfit_ammo(o);
758       if (amm != NULL)
759          return outfit_duration(amm);
760    }
761    else if (outfit_isFighterBay(o)) return INFINITY;
762    return -1.;
763 }
764 /**
765  * @brief Gets the outfit's cooldown.
766  *    @param o Outfit to get the cooldown of.
767  *    @return Outfit's cooldown.
768  */
outfit_cooldown(const Outfit * o)769 double outfit_cooldown( const Outfit* o )
770 {
771    if (outfit_isMod(o)) { if (o->u.mod.active) return o->u.mod.cooldown; }
772    else if (outfit_isJammer(o)) return 0.;
773    else if (outfit_isAfterburner(o)) return 0.;
774    return -1.;
775 }
776 
777 
778 /**
779  * @brief Gets the outfit's specific type.
780  *
781  *    @param o Outfit to get specific type from.
782  *    @return The specific type in human readable form.
783  */
outfit_getType(const Outfit * o)784 const char* outfit_getType( const Outfit* o )
785 {
786    const char* outfit_typename[] = {
787          "NULL",
788          "Bolt Cannon",
789          "Beam Cannon",
790          "Bolt Turret",
791          "Beam Turret",
792          "Launcher",
793          "Ammunition",
794          "Turret Launcher",
795          "Ship Modification",
796          "Afterburner",
797          "Jammer",
798          "Fighter Bay",
799          "Fighter",
800          "Star Map",
801          "Local Map",
802          "GUI",
803          "License"
804    };
805 
806    /* Name override. */
807    if (o->typename != NULL)
808       return o->typename;
809    return outfit_typename[o->type];
810 }
811 
812 
813 /**
814  * @brief Gets the outfit's broad type.
815  *
816  *    @param o Outfit to get the type of.
817  *    @return The outfit's broad type in human readable form.
818  */
outfit_getTypeBroad(const Outfit * o)819 const char* outfit_getTypeBroad( const Outfit* o )
820 {
821    if (outfit_isBolt(o))            return "Bolt Weapon";
822    else if (outfit_isBeam(o))       return "Beam Weapon";
823    else if (outfit_isLauncher(o))   return "Launcher";
824    else if (outfit_isAmmo(o))       return "Ammo";
825    else if (outfit_isTurret(o))     return "Turret";
826    else if (outfit_isMod(o))        return "Modification";
827    else if (outfit_isAfterburner(o)) return "Afterburner";
828    else if (outfit_isJammer(o))     return "Jammer";
829    else if (outfit_isFighterBay(o)) return "Fighter Bay";
830    else if (outfit_isFighter(o))    return "Fighter";
831    else if (outfit_isMap(o))        return "Map";
832    else if (outfit_isLocalMap(o))   return "Local Map";
833    else if (outfit_isGUI(o))        return "GUI";
834    else if (outfit_isLicense(o))    return "License";
835    else                             return "Unknown";
836 }
837 
838 
839 /**
840  * @brief Gets a human-readable string describing an ammo outfit's AI.
841  *    @param o Ammo outfit.
842  *    @return Name of the outfit's AI.
843  */
outfit_getAmmoAI(const Outfit * o)844 const char* outfit_getAmmoAI( const Outfit *o )
845 {
846    const char *ai_type[] = {
847       "Dumb",
848       "Seek",
849       "Smart"
850    };
851 
852    if (!outfit_isAmmo(o)) {
853       WARN("Outfit '%s' is not an ammo outfit", o->name);
854       return NULL;
855    }
856 
857    return ai_type[o->u.amm.ai];
858 }
859 
860 
861 /**
862  * @brief Checks to see if an outfit fits a slot.
863  *
864  *    @param o Outfit to see if fits in a slot.
865  *    @param s Slot to see if outfit fits in.
866  *    @return 1 if outfit fits the slot, 0 otherwise.
867  */
outfit_fitsSlot(const Outfit * o,const OutfitSlot * s)868 int outfit_fitsSlot( const Outfit* o, const OutfitSlot* s )
869 {
870    const OutfitSlot *os;
871    os = &o->slot;
872 
873    /* Outfit must have valid slot type. */
874    if ((os->type == OUTFIT_SLOT_NULL) ||
875       (os->type == OUTFIT_SLOT_NA))
876       return 0;
877 
878    /* Outfit type must match outfit slot. */
879    if (os->type != s->type)
880       return 0;
881 
882    /* Must match slot property. */
883    if (os->spid != 0)
884       if (s->spid != os->spid)
885          return 0;
886 
887    /* Exclusive only match property. */
888    if (s->exclusive)
889       if (s->spid != os->spid)
890          return 0;
891 
892    /* Must have valid slot size. */
893    if (os->size == OUTFIT_SLOT_SIZE_NA)
894       return 0;
895 
896    /* It doesn't fit. */
897    if (os->size > s->size)
898       return 0;
899 
900    /* It meets all criteria. */
901    return 1;
902 }
903 
904 
905 /**
906  * @brief Checks to see if an outfit fits a slot type (ignoring size).
907  *
908  *    @param o Outfit to see if fits in a slot.
909  *    @param s Slot to see if outfit fits in.
910  *    @return 1 if outfit fits the slot, 0 otherwise.
911  */
outfit_fitsSlotType(const Outfit * o,const OutfitSlot * s)912 int outfit_fitsSlotType( const Outfit* o, const OutfitSlot* s )
913 {
914    const OutfitSlot *os;
915    os = &o->slot;
916 
917    /* Outfit must have valid slot type. */
918    if ((os->type == OUTFIT_SLOT_NULL) ||
919       (os->type == OUTFIT_SLOT_NA))
920       return 0;
921 
922    /* Outfit type must match outfit slot. */
923    if (os->type != s->type)
924       return 0;
925 
926    /* It meets all criteria. */
927    return 1;
928 }
929 
930 
931 /**
932  * @brief Frees an outfit slot.
933  *
934  *    @param s Slot to free.
935  */
outfit_freeSlot(OutfitSlot * s)936 void outfit_freeSlot( OutfitSlot* s )
937 {
938    (void) s;
939 }
940 
941 
942 #define O_CMP(s,t) \
943 if (strcasecmp(buf,(s))==0) return t /**< Define to help with outfit_strToOutfitType. */
944 /**
945  * @brief Gets the outfit type from a human readable string.
946  *
947  *    @param buf String to extract outfit type from.
948  *    @return Outfit type stored in buf.
949  */
outfit_strToOutfitType(char * buf)950 static OutfitType outfit_strToOutfitType( char *buf )
951 {
952    O_CMP("bolt",           OUTFIT_TYPE_BOLT);
953    O_CMP("beam",           OUTFIT_TYPE_BEAM);
954    O_CMP("turret bolt",    OUTFIT_TYPE_TURRET_BOLT);
955    O_CMP("turret beam",    OUTFIT_TYPE_TURRET_BEAM);
956    O_CMP("launcher",       OUTFIT_TYPE_LAUNCHER);
957    O_CMP("ammo",           OUTFIT_TYPE_AMMO);
958    O_CMP("turret launcher",OUTFIT_TYPE_TURRET_LAUNCHER);
959    O_CMP("modification",   OUTFIT_TYPE_MODIFICATION);
960    O_CMP("afterburner",    OUTFIT_TYPE_AFTERBURNER);
961    O_CMP("fighter bay",    OUTFIT_TYPE_FIGHTER_BAY);
962    O_CMP("fighter",        OUTFIT_TYPE_FIGHTER);
963    O_CMP("jammer",         OUTFIT_TYPE_JAMMER);
964    O_CMP("map",            OUTFIT_TYPE_MAP);
965    O_CMP("localmap",       OUTFIT_TYPE_LOCALMAP);
966    O_CMP("license",        OUTFIT_TYPE_LICENSE);
967    O_CMP("gui",            OUTFIT_TYPE_GUI);
968 
969    WARN("Invalid outfit type: '%s'",buf);
970    return  OUTFIT_TYPE_NULL;
971 }
972 #undef O_CMP
973 
974 
975 /**
976  * @brief Parses a damage node.
977  *
978  * Example damage node would be:
979  * @code
980  * <damage type="kinetic">10</damage>
981  * @endcode
982  *
983  *    @param[out] dtype Stores the damage type here.
984  *    @param[out] dmg Stores the damage here.
985  *    @param[in] node Node to parse damage from.
986  *    @return 0 on success.
987  */
outfit_parseDamage(Damage * dmg,xmlNodePtr node)988 static int outfit_parseDamage( Damage *dmg, xmlNodePtr node )
989 {
990    char *buf;
991    xmlNodePtr cur;
992 
993    /* Defaults. */
994    dmg->type         = dtype_get("normal");
995    dmg->damage       = 0.;
996    dmg->penetration  = 0.;
997    dmg->disable      = 0.;
998 
999    cur = node->xmlChildrenNode;
1000    do {
1001       xml_onlyNodes( cur );
1002 
1003       /* Core properties. */
1004       xmlr_float( cur, "penetrate", dmg->penetration );
1005       xmlr_float( cur, "physical",  dmg->damage );
1006       xmlr_float( cur, "disable",   dmg->disable );
1007 
1008       /* Get type */
1009       if (xml_isNode(cur,"type")) {
1010          buf         = xml_get( cur );
1011          dmg->type   = dtype_get(buf);
1012          if (dmg->type < 0) { /* Case damage type in outfit.xml that isn't in damagetype.xml */
1013             dmg->type = 0;
1014             WARN("Unknown damage type '%s'", buf);
1015          }
1016          continue;
1017       }
1018       WARN("Damage has unknown node '%s'", cur->name);
1019    } while (xml_nextNode(cur));
1020 
1021    /* Normalize. */
1022    dmg->penetration /= 100.;
1023 
1024    return 0;
1025 }
1026 
1027 
1028 /**
1029  * @brief Parses the specific area for a bolt weapon and loads it into Outfit.
1030  *
1031  *    @param temp Outfit to finish loading.
1032  *    @param parent Outfit's parent node.
1033  */
outfit_parseSBolt(Outfit * temp,const xmlNodePtr parent)1034 static void outfit_parseSBolt( Outfit* temp, const xmlNodePtr parent )
1035 {
1036    xmlNodePtr node;
1037    char *buf;
1038    double C, area;
1039    int l;
1040 
1041    /* Defaults */
1042    temp->u.blt.spfx_armour    = -1;
1043    temp->u.blt.spfx_shield    = -1;
1044    temp->u.blt.sound          = -1;
1045    temp->u.blt.sound_hit      = -1;
1046    temp->u.blt.falloff        = -1.;
1047    temp->u.blt.ew_lockon      = 1.;
1048 
1049    node = parent->xmlChildrenNode;
1050    do { /* load all the data */
1051       xml_onlyNodes(node);
1052       xmlr_float(node,"speed",temp->u.blt.speed);
1053       xmlr_float(node,"delay",temp->u.blt.delay);
1054       xmlr_float(node,"ew_lockon",temp->u.blt.ew_lockon);
1055       xmlr_float(node,"energy",temp->u.blt.energy);
1056       xmlr_float(node,"heatup",temp->u.blt.heatup);
1057       xmlr_float(node,"track",temp->u.blt.track);
1058       xmlr_float(node,"swivel",temp->u.blt.swivel);
1059       if (xml_isNode(node,"range")) {
1060          buf = xml_nodeProp(node,"blowup");
1061          if (buf != NULL) {
1062             if (strcmp(buf,"armour")==0)
1063                outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_SHIELD);
1064             else if (strcmp(buf,"shield")==0)
1065                outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_ARMOUR);
1066             else
1067                WARN("Outfit '%s' has invalid blowup property: '%s'",
1068                      temp->name, buf );
1069             free(buf);
1070          }
1071          temp->u.blt.range = xml_getFloat(node);
1072          continue;
1073       }
1074       xmlr_float(node,"falloff",temp->u.blt.falloff);
1075 
1076       /* Graphics. */
1077       if (xml_isNode(node,"gfx")) {
1078          temp->u.blt.gfx_space = xml_parseTexture( node,
1079                OUTFIT_GFX_PATH"space/%s.png", 6, 6,
1080                OPENGL_TEX_MAPTRANS | OPENGL_TEX_MIPMAPS );
1081          xmlr_attr(node, "spin", buf);
1082          if (buf != NULL) {
1083             outfit_setProp( temp, OUTFIT_PROP_WEAP_SPIN );
1084             temp->u.blt.spin = atof( buf );
1085             free(buf);
1086          }
1087          continue;
1088       }
1089       if (xml_isNode(node,"gfx_end")) {
1090          if (!conf.interpolate)
1091             continue;
1092          temp->u.blt.gfx_end = xml_parseTexture( node,
1093                OUTFIT_GFX_PATH"space/%s.png", 6, 6,
1094                OPENGL_TEX_MAPTRANS | OPENGL_TEX_MIPMAPS );
1095          continue;
1096       }
1097 
1098       /* Special effects. */
1099       if (xml_isNode(node,"spfx_shield")) {
1100          temp->u.blt.spfx_shield = spfx_get(xml_get(node));
1101          continue;
1102       }
1103       if (xml_isNode(node,"spfx_armour")) {
1104          temp->u.blt.spfx_armour = spfx_get(xml_get(node));
1105          continue;
1106       }
1107 
1108       /* Misc. */
1109       if (xml_isNode(node,"sound")) {
1110          temp->u.blt.sound = sound_get( xml_get(node) );
1111          continue;
1112       }
1113       if (xml_isNode(node,"sound_hit")) {
1114          temp->u.blt.sound_hit = sound_get( xml_get(node) );
1115          continue;
1116       }
1117       if (xml_isNode(node,"damage")) {
1118          outfit_parseDamage( &temp->u.blt.dmg, node );
1119          continue;
1120       }
1121       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1122    } while (xml_nextNode(node));
1123 
1124    /* If not defined assume maximum. */
1125    if (temp->u.blt.falloff < 0.)
1126       temp->u.blt.falloff = temp->u.blt.range;
1127 
1128    /* Post processing. */
1129    temp->u.blt.swivel  *= M_PI/180.;
1130    if (outfit_isTurret(temp))
1131       temp->u.blt.swivel = M_PI;
1132    /*
1133     *         dT Mthermal - Qweap
1134     * Hweap = ----------------------
1135     *                tweap
1136     */
1137    C = pilot_heatCalcOutfitC(temp);
1138    area = pilot_heatCalcOutfitArea(temp);
1139    temp->u.blt.heat     = ((800.-CONST_SPACE_STAR_TEMP)*C +
1140             STEEL_HEAT_CONDUCTIVITY * ((800-CONST_SPACE_STAR_TEMP) * area)) /
1141          temp->u.blt.heatup * temp->u.blt.delay;
1142 
1143    /* Set default outfit size if necessary. */
1144    if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
1145       outfit_setDefaultSize( temp );
1146 
1147    /* Set short description. */
1148    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1149    l = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1150          "%s [%s]\n"
1151          "%.0f CPU\n"
1152          "%.0f%% Penetration\n"
1153          "%.2f DPS [%.0f Damage]\n",
1154          outfit_getType(temp), dtype_damageTypeToStr(temp->u.blt.dmg.type),
1155          temp->cpu,
1156          temp->u.blt.dmg.penetration*100.,
1157          1./temp->u.blt.delay * temp->u.blt.dmg.damage, temp->u.blt.dmg.damage );
1158    if (temp->u.blt.dmg.disable > 0.) {
1159       l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
1160          "%.2f Disable/s [%.0f Disable]\n",
1161          1./temp->u.blt.delay * temp->u.blt.dmg.disable, temp->u.blt.dmg.disable );
1162    }
1163    l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
1164          "%.1f Shots Per Second\n"
1165          "%.1f EPS [%.0f Energy]\n"
1166          "%.0f Range\n"
1167          "%.1f second heat up",
1168          1./temp->u.blt.delay,
1169          1./temp->u.blt.delay * temp->u.blt.energy, temp->u.blt.energy,
1170          temp->u.blt.range,
1171          temp->u.blt.heatup);
1172    if (!outfit_isTurret(temp)) {
1173       l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
1174          "\n%.1f degree swivel",
1175          temp->u.blt.swivel*180./M_PI );
1176    }
1177 
1178 
1179 #define MELEMENT(o,s) \
1180 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
1181    MELEMENT(temp->u.blt.gfx_space==NULL,"gfx");
1182    MELEMENT(temp->u.blt.spfx_shield==-1,"spfx_shield");
1183    MELEMENT(temp->u.blt.spfx_armour==-1,"spfx_armour");
1184    MELEMENT((sound_disabled!=0) && (temp->u.blt.sound<0),"sound");
1185    MELEMENT(temp->mass==0.,"mass");
1186    MELEMENT(temp->u.blt.delay==0,"delay");
1187    MELEMENT(temp->u.blt.speed==0,"speed");
1188    MELEMENT(temp->u.blt.range==0,"range");
1189    MELEMENT(temp->u.blt.dmg.damage==0,"damage");
1190    MELEMENT(temp->u.blt.energy==0.,"energy");
1191    MELEMENT(temp->cpu==0.,"cpu");
1192    MELEMENT(temp->u.blt.falloff > temp->u.blt.range,"falloff");
1193    MELEMENT(temp->u.blt.heatup==0.,"heatup");
1194    MELEMENT(((temp->u.blt.swivel > 0.) || outfit_isTurret(temp)) && (temp->u.blt.track==0.),"track");
1195 #undef MELEMENT
1196 }
1197 
1198 
1199 /**
1200  * @brief Parses the beam weapon specifics of an outfit.
1201  *
1202  *    @param temp Outfit to finish loading.
1203  *    @param parent Outfit's parent node.
1204  */
outfit_parseSBeam(Outfit * temp,const xmlNodePtr parent)1205 static void outfit_parseSBeam( Outfit* temp, const xmlNodePtr parent )
1206 {
1207    int l;
1208    xmlNodePtr node;
1209    double C, area;
1210    char *prop;
1211 
1212    /* Defaults. */
1213    temp->u.bem.spfx_armour = -1;
1214    temp->u.bem.spfx_shield = -1;
1215    temp->u.bem.sound_warmup = -1;
1216    temp->u.bem.sound = -1;
1217    temp->u.bem.sound_off = -1;
1218 
1219    node = parent->xmlChildrenNode;
1220    do { /* load all the data */
1221       xml_onlyNodes(node);
1222       xmlr_float(node,"range",temp->u.bem.range);
1223       xmlr_float(node,"turn",temp->u.bem.turn);
1224       xmlr_float(node,"energy",temp->u.bem.energy);
1225       xmlr_float(node,"delay",temp->u.bem.delay);
1226       xmlr_float(node,"warmup",temp->u.bem.warmup);
1227       xmlr_float(node,"heatup",temp->u.bem.heatup);
1228 
1229       if (xml_isNode(node, "duration")) {
1230          prop = xml_nodeProp(node, "min");
1231          if (prop != NULL) {
1232             temp->u.bem.min_duration = atof(prop);
1233             free(prop);
1234          }
1235          temp->u.bem.duration = xml_getFloat(node);
1236          continue;
1237       }
1238 
1239       if (xml_isNode(node,"damage")) {
1240          outfit_parseDamage( &temp->u.bem.dmg, node );
1241          continue;
1242       }
1243 
1244       /* Graphic stuff. */
1245       if (xml_isNode(node,"gfx")) {
1246          temp->u.bem.gfx = xml_parseTexture( node,
1247                OUTFIT_GFX_PATH"space/%s.png", 1, 1, OPENGL_TEX_MIPMAPS );
1248          continue;
1249       }
1250       if (xml_isNode(node,"spfx_armour")) {
1251          temp->u.bem.spfx_armour = spfx_get(xml_get(node));
1252          continue;
1253       }
1254       if (xml_isNode(node,"spfx_shield")) {
1255          temp->u.bem.spfx_shield = spfx_get(xml_get(node));
1256          continue;
1257       }
1258 
1259       /* Sound stuff. */
1260       if (xml_isNode(node,"sound_warmup")) {
1261          temp->u.bem.sound_warmup = sound_get( xml_get(node) );
1262          continue;
1263       }
1264       if (xml_isNode(node,"sound")) {
1265          temp->u.bem.sound = sound_get( xml_get(node) );
1266          continue;
1267       }
1268       if (xml_isNode(node,"sound_off")) {
1269          temp->u.bem.sound_off = sound_get( xml_get(node) );
1270          continue;
1271       }
1272       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1273    } while (xml_nextNode(node));
1274 
1275    /* Post processing. */
1276    temp->u.bem.turn     *= M_PI/180.; /* Convert to rad/s. */
1277    C = pilot_heatCalcOutfitC(temp);
1278    area = pilot_heatCalcOutfitArea(temp);
1279    temp->u.bem.heat     = ((800.-CONST_SPACE_STAR_TEMP)*C +
1280             STEEL_HEAT_CONDUCTIVITY * ((800-CONST_SPACE_STAR_TEMP) * area)) /
1281          temp->u.bem.heatup;
1282 
1283    /* Set default outfit size if necessary. */
1284    if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
1285       outfit_setDefaultSize( temp );
1286 
1287    /* Set short description. */
1288    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1289    l = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1290          "%s\n"
1291          "%.0f CPU\n"
1292          "%.0f%% Penetration\n"
1293          "%.2f DPS [%s]\n",
1294          outfit_getType(temp),
1295          temp->cpu,
1296          temp->u.bem.dmg.penetration*100.,
1297          temp->u.bem.dmg.damage, dtype_damageTypeToStr(temp->u.bem.dmg.type) );
1298    if (temp->u.blt.dmg.disable > 0.) {
1299       l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
1300          "%.0f Disable/s\n",
1301          temp->u.bem.dmg.disable );
1302    }
1303    l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
1304          "%.1f EPS\n"
1305          "%.1f Duration %.1f Cooldown\n"
1306          "%.0f Range\n"
1307          "%.1f second heat up",
1308          temp->u.bem.energy,
1309          temp->u.bem.duration, temp->u.bem.delay,
1310          temp->u.bem.range,
1311          temp->u.bem.heatup);
1312 
1313 #define MELEMENT(o,s) \
1314 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
1315    MELEMENT(temp->u.bem.gfx==NULL,"gfx");
1316    MELEMENT(temp->u.bem.spfx_shield==-1,"spfx_shield");
1317    MELEMENT(temp->u.bem.spfx_armour==-1,"spfx_armour");
1318    MELEMENT((sound_disabled!=0) && (temp->u.bem.warmup > 0.) && (temp->u.bem.sound<0),"sound_warmup");
1319    MELEMENT((sound_disabled!=0) && (temp->u.bem.sound<0),"sound");
1320    MELEMENT((sound_disabled!=0) && (temp->u.bem.sound_off<0),"sound_off");
1321    MELEMENT(temp->u.bem.delay==0,"delay");
1322    MELEMENT(temp->u.bem.duration==0,"duration");
1323    MELEMENT(temp->u.bem.min_duration < 0,"duration");
1324    MELEMENT(temp->u.bem.range==0,"range");
1325    MELEMENT((temp->type!=OUTFIT_TYPE_BEAM) && (temp->u.bem.turn==0),"turn");
1326    MELEMENT(temp->u.bem.energy==0.,"energy");
1327    MELEMENT(temp->cpu==0.,"cpu");
1328    MELEMENT(temp->u.bem.dmg.damage==0,"damage");
1329    MELEMENT(temp->u.bem.heatup==0.,"heatup");
1330 #undef MELEMENT
1331 }
1332 
1333 
1334 /**
1335  * @brief Parses the specific area for a launcher and loads it into Outfit.
1336  *
1337  *    @param temp Outfit to finish loading.
1338  *    @param parent Outfit's parent node.
1339  */
outfit_parseSLauncher(Outfit * temp,const xmlNodePtr parent)1340 static void outfit_parseSLauncher( Outfit* temp, const xmlNodePtr parent )
1341 {
1342    xmlNodePtr node;
1343 
1344    node  = parent->xmlChildrenNode;
1345    do { /* load all the data */
1346       xml_onlyNodes(node);
1347       xmlr_float(node,"delay",temp->u.lau.delay);
1348       xmlr_strd(node,"ammo",temp->u.lau.ammo_name);
1349       xmlr_int(node,"amount",temp->u.lau.amount);
1350       xmlr_float(node,"ew_target",temp->u.lau.ew_target);
1351       xmlr_float(node,"lockon",temp->u.lau.lockon);
1352       if (!outfit_isTurret(temp))
1353          xmlr_float(node,"arc",temp->u.lau.arc);
1354       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1355    } while (xml_nextNode(node));
1356 
1357    /* Post processing. */
1358    temp->u.lau.arc *= M_PI/180.;
1359    temp->u.lau.ew_target2 = pow2( temp->u.lau.ew_target );
1360 
1361    /* Set default outfit size if necessary. */
1362    if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
1363       outfit_setDefaultSize( temp );
1364 
1365 #define MELEMENT(o,s) \
1366 if (o) WARN("Outfit '%s' missing '"s"' element", temp->name) /**< Define to help check for data errors. */
1367    MELEMENT(temp->u.lau.ammo_name==NULL,"ammo");
1368    MELEMENT(temp->u.lau.delay==0.,"delay");
1369    MELEMENT(temp->cpu==0.,"cpu");
1370    MELEMENT(temp->u.lau.amount==0.,"amount");
1371 #undef MELEMENT
1372 }
1373 
1374 
1375 /**
1376  * @brief Parses the specific area for a weapon and loads it into Outfit.
1377  *
1378  *    @param temp Outfit to finish loading.
1379  *    @param parent Outfit's parent node.
1380  */
outfit_parseSAmmo(Outfit * temp,const xmlNodePtr parent)1381 static void outfit_parseSAmmo( Outfit* temp, const xmlNodePtr parent )
1382 {
1383    xmlNodePtr node;
1384    char *buf;
1385    int l;
1386 
1387    node = parent->xmlChildrenNode;
1388 
1389    /* Defaults. */
1390    temp->slot.type         = OUTFIT_SLOT_NA;
1391    temp->slot.size         = OUTFIT_SLOT_SIZE_NA;
1392    temp->u.amm.spfx_armour = -1;
1393    temp->u.amm.spfx_shield = -1;
1394    temp->u.amm.sound       = -1;
1395    temp->u.amm.sound_hit   = -1;
1396    temp->u.amm.ai          = -1;
1397 
1398    do { /* load all the data */
1399       xml_onlyNodes(node);
1400       /* Basic */
1401       if (xml_isNode(node,"duration")) {
1402          buf = xml_nodeProp(node,"blowup");
1403          if (buf != NULL) {
1404             if (strcmp(buf,"armour")==0)
1405                outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_SHIELD);
1406             else if (strcmp(buf,"shield")==0)
1407                outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_ARMOUR);
1408             else
1409                WARN("Outfit '%s' has invalid blowup property: '%s'",
1410                      temp->name, buf );
1411             free(buf);
1412          }
1413          temp->u.amm.duration = xml_getFloat(node);
1414          continue;
1415       }
1416       xmlr_float(node,"resist",temp->u.amm.resist);
1417       /* Movement */
1418       xmlr_float(node,"thrust",temp->u.amm.thrust);
1419       xmlr_float(node,"turn",temp->u.amm.turn);
1420       xmlr_float(node,"speed",temp->u.amm.speed);
1421       xmlr_float(node,"energy",temp->u.amm.energy);
1422       if (xml_isNode(node,"gfx")) {
1423          temp->u.amm.gfx_space = xml_parseTexture( node,
1424                OUTFIT_GFX_PATH"space/%s.png", 6, 6,
1425                OPENGL_TEX_MAPTRANS | OPENGL_TEX_MIPMAPS );
1426          xmlr_attr(node, "spin", buf);
1427          if (buf != NULL) {
1428             outfit_setProp( temp, OUTFIT_PROP_WEAP_SPIN );
1429             temp->u.amm.spin = atof( buf );
1430             free(buf);
1431          }
1432          continue;
1433       }
1434       if (xml_isNode(node,"spfx_armour")) {
1435          temp->u.amm.spfx_armour = spfx_get(xml_get(node));
1436          continue;
1437       }
1438       if (xml_isNode(node,"spfx_shield")) {
1439          temp->u.amm.spfx_shield = spfx_get(xml_get(node));
1440          continue;
1441       }
1442       if (xml_isNode(node,"sound")) {
1443          temp->u.amm.sound = sound_get( xml_get(node) );
1444          continue;
1445       }
1446       if (xml_isNode(node,"sound_hit")) {
1447          temp->u.amm.sound_hit = sound_get( xml_get(node) );
1448          continue;
1449       }
1450       if (xml_isNode(node,"damage")) {
1451          outfit_parseDamage( &temp->u.amm.dmg, node );
1452          continue;
1453       }
1454       if (xml_isNode(node,"ai")) {
1455          buf = xml_get(node);
1456          if (buf != NULL) {
1457 
1458             if (strcmp(buf,"dumb")==0)
1459                temp->u.amm.ai = AMMO_AI_DUMB;
1460             else if (strcmp(buf,"seek")==0)
1461                temp->u.amm.ai = AMMO_AI_SEEK;
1462             else if (strcmp(buf,"smart")==0)
1463                temp->u.amm.ai = AMMO_AI_SMART;
1464             else
1465                WARN("Ammo '%s' has unknown ai type '%s'.", temp->name, buf);
1466          }
1467          continue;
1468       }
1469       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1470    } while (xml_nextNode(node));
1471 
1472    /* Post-processing */
1473    temp->u.amm.resist /= 100.; /* Set it in per one */
1474    temp->u.amm.turn   *= M_PI/180.; /* Convert to rad/s. */
1475 
1476    /* Set short description. */
1477    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1478    l = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1479          "%s\n"
1480          "%.0f%% Penetration\n"
1481          "%.0f Damage [%s]\n",
1482          outfit_getType(temp),
1483          temp->u.amm.dmg.penetration*100.,
1484          temp->u.amm.dmg.damage, dtype_damageTypeToStr(temp->u.amm.dmg.type) );
1485    if (temp->u.blt.dmg.disable > 0.) {
1486       l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
1487          "%.0f Disable\n",
1488          temp->u.amm.dmg.disable );
1489    }
1490    l += nsnprintf( &temp->desc_short[l], OUTFIT_SHORTDESC_MAX-l,
1491          "%.0f Energy\n"
1492          "%.0f Maximum Speed\n"
1493          "%.0f%% Jam resistance\n"
1494          "%.1f duration",
1495          temp->u.amm.energy,
1496          temp->u.amm.speed,
1497          temp->u.amm.resist,
1498          temp->u.amm.duration );
1499 
1500 #define MELEMENT(o,s) \
1501 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
1502    MELEMENT(temp->mass==0.,"mass");
1503    MELEMENT(temp->u.amm.gfx_space==NULL,"gfx");
1504    MELEMENT(temp->u.amm.spfx_shield==-1,"spfx_shield");
1505    MELEMENT(temp->u.amm.spfx_armour==-1,"spfx_armour");
1506    MELEMENT((sound_disabled!=0) && (temp->u.amm.sound<0),"sound");
1507    /* MELEMENT(temp->u.amm.thrust==0,"thrust"); */
1508    /* Dumb missiles don't need everything */
1509    if (outfit_isSeeker(temp)) {
1510       MELEMENT(temp->u.amm.turn==0,"turn");
1511    }
1512    MELEMENT(temp->u.amm.speed==0,"speed");
1513    MELEMENT(temp->u.amm.duration==0,"duration");
1514    MELEMENT(temp->u.amm.dmg.damage==0,"damage");
1515    /*MELEMENT(temp->u.amm.energy==0.,"energy");*/
1516    MELEMENT(temp->cpu!=0.,"cpu");
1517 #undef MELEMENT
1518 }
1519 
1520 
1521 /**
1522  * @brief Parses the modification tidbits of the outfit.
1523  *
1524  *    @param temp Outfit to finish loading.
1525  *    @param parent Outfit's parent node.
1526  */
outfit_parseSMod(Outfit * temp,const xmlNodePtr parent)1527 static void outfit_parseSMod( Outfit* temp, const xmlNodePtr parent )
1528 {
1529    int i;
1530    xmlNodePtr node;
1531    ShipStatList *ll;
1532    char *buf;
1533    node = parent->children;
1534 
1535    do { /* load all the data */
1536       xml_onlyNodes(node);
1537       if (xml_isNode(node,"active")) {
1538          xmlr_attr(node, "cooldown", buf);
1539          if (buf != NULL) {
1540             temp->u.mod.cooldown = atof( buf );
1541             free(buf);
1542          }
1543          temp->u.mod.duration = xml_getFloat(node);
1544          temp->u.mod.active   = 1;
1545          continue;
1546       }
1547       /* movement */
1548       xmlr_float(node,"thrust",temp->u.mod.thrust);
1549       xmlr_float(node,"turn",temp->u.mod.turn);
1550       xmlr_float(node,"speed",temp->u.mod.speed);
1551       /* health */
1552       xmlr_float(node,"armour",temp->u.mod.armour);
1553       xmlr_float(node,"shield",temp->u.mod.shield);
1554       xmlr_float(node,"energy",temp->u.mod.energy);
1555       xmlr_float(node,"fuel",temp->u.mod.fuel);
1556       xmlr_float(node,"armour_regen", temp->u.mod.armour_regen );
1557       xmlr_float(node,"shield_regen", temp->u.mod.shield_regen );
1558       xmlr_float(node,"energy_regen", temp->u.mod.energy_regen );
1559       xmlr_float(node,"energy_loss", temp->u.mod.energy_loss );
1560       xmlr_float(node,"absorb", temp->u.mod.absorb );
1561       /* misc */
1562       xmlr_float(node,"cargo",temp->u.mod.cargo);
1563       xmlr_float(node,"crew_rel", temp->u.mod.crew_rel);
1564       xmlr_float(node,"mass_rel",temp->u.mod.mass_rel);
1565       /* Stats. */
1566       ll = ss_listFromXML( node );
1567       if (ll != NULL) {
1568          ll->next          = temp->u.mod.stats;
1569          temp->u.mod.stats = ll;
1570          continue;
1571       }
1572 
1573       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1574    } while (xml_nextNode(node));
1575 
1576    /* Set default outfit size if necessary. */
1577    if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
1578       outfit_setDefaultSize( temp );
1579 
1580    /* Set short description. */
1581    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1582    i = nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1583          "%s"
1584          "%s",
1585          outfit_getType(temp),
1586          (temp->u.mod.active) ? "\n\erActivated Outfit\e0" : "" );
1587 
1588 #define DESC_ADD(x, s, n, c) \
1589 if ((x) != 0.) \
1590    i += nsnprintf( &temp->desc_short[i], OUTFIT_SHORTDESC_MAX-i, \
1591          "\n\e%c%+."n"f "s"\e0", c, x )
1592 #define DESC_ADD0(x, s)    DESC_ADD( x, s, "0", ((x)>0)?'D':'r' )
1593 #define DESC_ADD1(x, s)    DESC_ADD( x, s, "1", ((x)>0)?'D':'r' )
1594    DESC_ADD0( temp->cpu, "CPU" );
1595    DESC_ADD0( temp->u.mod.thrust, "Thrust" );
1596    DESC_ADD0( temp->u.mod.turn, "Turn Rate" );
1597    DESC_ADD0( temp->u.mod.speed, "Maximum Speed" );
1598    DESC_ADD0( temp->u.mod.armour, "Armour" );
1599    DESC_ADD0( temp->u.mod.shield, "Shield" );
1600    DESC_ADD0( temp->u.mod.energy, "Energy" );
1601    DESC_ADD0( temp->u.mod.fuel, "Fuel" );
1602    DESC_ADD1( temp->u.mod.armour_regen, "Armour Per Second" );
1603    DESC_ADD1( temp->u.mod.shield_regen, "Shield Per Second" );
1604    DESC_ADD1( temp->u.mod.energy_regen, "Energy Per Second" );
1605    DESC_ADD0( temp->u.mod.absorb, "Absorption" );
1606    DESC_ADD0( temp->u.mod.cargo, "Cargo" );
1607    DESC_ADD0( temp->u.mod.crew_rel, "%% Crew" );
1608    DESC_ADD0( temp->u.mod.mass_rel, "%% Mass" );
1609 #undef DESC_ADD1
1610 #undef DESC_ADD0
1611 #undef DESC_ADD
1612    /*i +=*/ ss_statsListDesc( temp->u.mod.stats,
1613          &temp->desc_short[i], OUTFIT_SHORTDESC_MAX-i, 1 );
1614 
1615    /* More processing. */
1616    temp->u.mod.turn       *= M_PI / 180.;
1617    temp->u.mod.absorb     /= 100.;
1618    temp->u.mod.turn_rel   /= 100.;
1619    temp->u.mod.speed_rel  /= 100.;
1620    temp->u.mod.armour_rel /= 100.;
1621    temp->u.mod.shield_rel /= 100.;
1622    temp->u.mod.energy_rel /= 100.;
1623    temp->u.mod.mass_rel   /= 100.;
1624    temp->u.mod.crew_rel   /= 100.;
1625 }
1626 
1627 
1628 /**
1629  * @brief Parses the afterburner tidbits of the outfit.
1630  *
1631  *    @param temp Outfit to finish loading.
1632  *    @param parent Outfit's parent node.
1633  */
outfit_parseSAfterburner(Outfit * temp,const xmlNodePtr parent)1634 static void outfit_parseSAfterburner( Outfit* temp, const xmlNodePtr parent )
1635 {
1636    xmlNodePtr node;
1637    node = parent->children;
1638    double C, area;
1639 
1640    /* Defaults. */
1641    temp->u.afb.sound = -1;
1642    temp->u.afb.sound_on = -1;
1643    temp->u.afb.sound_off = -1;
1644 
1645    /* must be >= 1. */
1646    temp->u.afb.thrust = 1.;
1647    temp->u.afb.speed  = 1.;
1648 
1649    do { /* parse the data */
1650       xml_onlyNodes(node);
1651       xmlr_float(node,"rumble",temp->u.afb.rumble);
1652       if (xml_isNode(node,"sound_on")) {
1653          temp->u.afb.sound_on = sound_get( xml_get(node) );
1654          continue;
1655       }
1656       if (xml_isNode(node,"sound")) {
1657          temp->u.afb.sound = sound_get( xml_get(node) );
1658          continue;
1659       }
1660       if (xml_isNode(node,"sound_off")) {
1661          temp->u.afb.sound_off = sound_get( xml_get(node) );
1662          continue;
1663       }
1664       xmlr_float(node,"thrust",temp->u.afb.thrust);
1665       xmlr_float(node,"speed",temp->u.afb.speed);
1666       xmlr_float(node,"energy",temp->u.afb.energy);
1667       xmlr_float(node,"mass_limit",temp->u.afb.mass_limit);
1668       xmlr_float(node,"heatup",temp->u.afb.heatup);
1669       xmlr_float(node,"heat_cap",temp->u.afb.heat_cap);
1670       xmlr_float(node,"heat_base",temp->u.afb.heat_base);
1671       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1672    } while (xml_nextNode(node));
1673 
1674    /* Set short description. */
1675    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1676    nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1677          "%s\n"
1678          "\erActivated Outfit\e0\n"
1679          "%.0f CPU\n"
1680          "Only one can be equipped\n"
1681          "%.0f Maximum Effective Mass\n"
1682          "%.0f%% Thrust\n"
1683          "%.0f%% Maximum Speed\n"
1684          "%.1f EPS\n"
1685          "%.1f Rumble",
1686          outfit_getType(temp),
1687          temp->cpu,
1688          temp->u.afb.mass_limit,
1689          temp->u.afb.thrust + 100.,
1690          temp->u.afb.speed + 100.,
1691          temp->u.afb.energy,
1692          temp->u.afb.rumble );
1693 
1694    /* Post processing. */
1695    temp->u.afb.thrust /= 100.;
1696    temp->u.afb.speed  /= 100.;
1697    C = pilot_heatCalcOutfitC(temp);
1698    area = pilot_heatCalcOutfitArea(temp);
1699    temp->u.afb.heat    = ((800.-CONST_SPACE_STAR_TEMP)*C +
1700             STEEL_HEAT_CONDUCTIVITY * ((800-CONST_SPACE_STAR_TEMP) * area)) /
1701          temp->u.afb.heatup;
1702 
1703    /* Set default outfit size if necessary. */
1704    if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
1705       outfit_setDefaultSize( temp );
1706 
1707 #define MELEMENT(o,s) \
1708 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
1709    MELEMENT(temp->u.afb.thrust==0.,"thrust");
1710    MELEMENT(temp->u.afb.speed==0.,"speed");
1711    MELEMENT(temp->u.afb.energy==0.,"energy");
1712    MELEMENT(temp->cpu==0.,"cpu");
1713    MELEMENT(temp->u.afb.mass_limit==0.,"mass_limit");
1714    MELEMENT(temp->u.afb.heatup==0.,"heatup");
1715 #undef MELEMENT
1716 }
1717 
1718 /**
1719  * @brief Parses the fighter bay tidbits of the outfit.
1720  *
1721  *    @param temp Outfit to finish loading.
1722  *    @param parent Outfit's parent node.
1723  */
outfit_parseSFighterBay(Outfit * temp,const xmlNodePtr parent)1724 static void outfit_parseSFighterBay( Outfit *temp, const xmlNodePtr parent )
1725 {
1726    xmlNodePtr node;
1727    node = parent->children;
1728 
1729    do {
1730       xml_onlyNodes(node);
1731       xmlr_int(node,"delay",temp->u.bay.delay);
1732       xmlr_strd(node,"ammo",temp->u.bay.ammo_name);
1733       xmlr_int(node,"amount",temp->u.bay.amount);
1734       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1735    } while (xml_nextNode(node));
1736 
1737    /* Set default outfit size if necessary. */
1738    if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
1739       outfit_setDefaultSize( temp );
1740 
1741    /* Set short description. */
1742    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1743    nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1744          "%s\n"
1745          "%.0f CPU\n"
1746          "%.1f Launches Per Second\n"
1747          "Holds %d %s",
1748          outfit_getType(temp),
1749          temp->cpu,
1750          1./temp->u.bay.delay,
1751          temp->u.bay.amount, temp->u.bay.ammo_name );
1752 
1753 #define MELEMENT(o,s) \
1754 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
1755    MELEMENT(temp->u.bay.delay==0,"delay");
1756    MELEMENT(temp->cpu==0.,"cpu");
1757    MELEMENT(temp->u.bay.ammo_name==NULL,"ammo");
1758    MELEMENT(temp->u.bay.amount==0,"amount");
1759 #undef MELEMENT
1760 }
1761 
1762 /**
1763  * @brief Parses the fighter tidbits of the outfit.
1764  *
1765  *    @param temp Outfit to finish loading.
1766  *    @param parent Outfit's parent node.
1767  */
outfit_parseSFighter(Outfit * temp,const xmlNodePtr parent)1768 static void outfit_parseSFighter( Outfit *temp, const xmlNodePtr parent )
1769 {
1770    xmlNodePtr node;
1771    node = parent->children;
1772 
1773    temp->slot.type         = OUTFIT_SLOT_NA;
1774    temp->slot.size         = OUTFIT_SLOT_SIZE_NA;
1775 
1776    do {
1777       xml_onlyNodes(node);
1778       xmlr_strd(node,"ship",temp->u.fig.ship);
1779       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1780    } while (xml_nextNode(node));
1781 
1782    /* Set short description. */
1783    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1784    nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1785          "%s",
1786          outfit_getType(temp) );
1787 
1788 #define MELEMENT(o,s) \
1789 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
1790 /**< Define to help check for data errors. */
1791    MELEMENT(temp->u.fig.ship==NULL,"ship");
1792    MELEMENT(temp->cpu!=0.,"cpu");
1793 #undef MELEMENT
1794 }
1795 
1796 /**
1797  * @brief Parses the map tidbits of the outfit.
1798  *
1799  *    @param temp Outfit to finish loading.
1800  *    @param parent Outfit's parent node.
1801  */
outfit_parseSMap(Outfit * temp,const xmlNodePtr parent)1802 static void outfit_parseSMap( Outfit *temp, const xmlNodePtr parent )
1803 {
1804    int i, j;
1805    xmlNodePtr node, cur;
1806    void *buf;
1807    StarSystem *sys, *system_stack;
1808    Planet *asset;
1809    JumpPoint *jump;
1810    int nsys;
1811 
1812    node = parent->children;
1813 
1814    temp->slot.type         = OUTFIT_SLOT_NA;
1815    temp->slot.size         = OUTFIT_SLOT_SIZE_NA;
1816 
1817    temp->u.map->systems = array_create(StarSystem*);
1818    temp->u.map->assets  = array_create(Planet*);
1819    temp->u.map->jumps   = array_create(JumpPoint*);
1820 
1821    do {
1822       xml_onlyNodes(node);
1823 
1824       if (xml_isNode(node,"sys")) {
1825          buf = xml_nodeProp(node,"name");
1826          if ((buf != NULL) && ((sys = system_get(buf)) != NULL)) {
1827             free(buf);
1828             array_grow( &temp->u.map->systems ) = sys;
1829 
1830             cur = node->children;
1831 
1832             do {
1833                xml_onlyNodes(cur);
1834 
1835                if (xml_isNode(cur,"asset")) {
1836                   buf = xml_get(cur);
1837                   if ((buf != NULL) && ((asset = planet_get(buf)) != NULL))
1838                      array_grow( &temp->u.map->assets ) = asset;
1839                   else
1840                      WARN("Map '%s' has invalid asset '%s'", temp->name, buf);
1841                }
1842                else if (xml_isNode(cur,"jump")) {
1843                   buf = xml_get(cur);
1844                   if ((buf != NULL) && ((jump = jump_get(xml_get(cur),
1845                         temp->u.map->systems[array_size(temp->u.map->systems)-1] )) != NULL))
1846                      array_grow( &temp->u.map->jumps ) = jump;
1847                   else
1848                      WARN("Map '%s' has invalid jump point '%s'", temp->name, buf);
1849                }
1850                else
1851                   WARN("Outfit '%s' has unknown node '%s'",temp->name, cur->name);
1852             } while (xml_nextNode(cur));
1853          }
1854          else
1855             WARN("Map '%s' has invalid system '%s'", temp->name, buf);
1856       }
1857       else if (xml_isNode(node,"short_desc")) {
1858          temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1859          nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX, "%s", xml_get(node) );
1860       }
1861       else if (xml_isNode(node,"all")) { /* Add everything to the map */
1862          system_stack = system_getAll(&nsys);
1863          for (i=0;i<nsys;i++) {
1864             array_grow( &temp->u.map->systems ) = &system_stack[i];
1865             for (j=0;j<system_stack[i].nplanets;j++)
1866                array_grow( &temp->u.map->assets ) = system_stack[i].planets[j];
1867             for (j=0;j<system_stack[i].njumps;j++)
1868                array_grow( &temp->u.map->jumps ) = &system_stack[i].jumps[j];
1869          }
1870       }
1871       else
1872          WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1873    } while (xml_nextNode(node));
1874 
1875    array_shrink( &temp->u.map->systems );
1876    array_shrink( &temp->u.map->assets  );
1877    array_shrink( &temp->u.map->jumps   );
1878 
1879    if (temp->desc_short == NULL) {
1880       /* Set short description based on type. */
1881       temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1882       nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1883             "%s", outfit_getType(temp) );
1884    }
1885 
1886 
1887 #define MELEMENT(o,s) \
1888 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
1889 /**< Define to help check for data errors. */
1890    MELEMENT(temp->mass!=0.,"cpu");
1891    MELEMENT(temp->cpu!=0.,"cpu");
1892 #undef MELEMENT
1893 }
1894 
1895 
1896 /**
1897  * @brief Parses the map tidbits of the outfit.
1898  *
1899  *    @param temp Outfit to finish loading.
1900  *    @param parent Outfit's parent node.
1901  */
outfit_parseSLocalMap(Outfit * temp,const xmlNodePtr parent)1902 static void outfit_parseSLocalMap( Outfit *temp, const xmlNodePtr parent )
1903 {
1904    xmlNodePtr node;
1905    node = parent->children;
1906 
1907    temp->slot.type         = OUTFIT_SLOT_NA;
1908    temp->slot.size         = OUTFIT_SLOT_SIZE_NA;
1909 
1910    do {
1911       xml_onlyNodes(node);
1912       xmlr_float(node,"asset_detect",temp->u.lmap.asset_detect);
1913       xmlr_float(node,"jump_detect",temp->u.lmap.jump_detect);
1914       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1915    } while (xml_nextNode(node));
1916 
1917    temp->u.lmap.asset_detect = pow2( temp->u.lmap.asset_detect );
1918    temp->u.lmap.jump_detect  = pow2( temp->u.lmap.jump_detect );
1919 
1920    /* Set short description. */
1921    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1922    nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1923          "%s",
1924          outfit_getType(temp) );
1925 
1926 #define MELEMENT(o,s) \
1927 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
1928 /**< Define to help check for data errors. */
1929    MELEMENT(temp->mass!=0.,"cpu");
1930    MELEMENT(temp->cpu!=0.,"cpu");
1931 #undef MELEMENT
1932 }
1933 
1934 
1935 /**
1936  * @brief Parses the GUI tidbits of the outfit.
1937  *
1938  *    @param temp Outfit to finish loading.
1939  *    @param parent Outfit's parent node.
1940  */
outfit_parseSGUI(Outfit * temp,const xmlNodePtr parent)1941 static void outfit_parseSGUI( Outfit *temp, const xmlNodePtr parent )
1942 {
1943    xmlNodePtr node;
1944 
1945    temp->slot.type         = OUTFIT_SLOT_NA;
1946    temp->slot.size         = OUTFIT_SLOT_SIZE_NA;
1947 
1948    node = parent->children;
1949 
1950    do {
1951       xml_onlyNodes(node);
1952       xmlr_strd(node,"gui",temp->u.gui.gui);
1953       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1954    } while (xml_nextNode(node));
1955 
1956    /* Set short description. */
1957    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1958    nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1959          "GUI (Graphical User Interface)" );
1960 
1961 #define MELEMENT(o,s) \
1962 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
1963 /**< Define to help check for data errors. */
1964    MELEMENT(temp->u.gui.gui==NULL,"gui");
1965    MELEMENT(temp->mass!=0.,"cpu");
1966    MELEMENT(temp->cpu!=0.,"cpu");
1967 #undef MELEMENT
1968 }
1969 
1970 
1971 /**
1972  * @brief Parses the license tidbits of the outfit.
1973  *
1974  *    @param temp Outfit to finish loading.
1975  *    @param parent Outfit's parent node.
1976  */
outfit_parseSLicense(Outfit * temp,const xmlNodePtr parent)1977 static void outfit_parseSLicense( Outfit *temp, const xmlNodePtr parent )
1978 {
1979    temp->slot.type         = OUTFIT_SLOT_NA;
1980    temp->slot.size         = OUTFIT_SLOT_SIZE_NA;
1981 
1982    xmlNodePtr node;
1983    node = parent->children;
1984 
1985    do {
1986       xml_onlyNodes(node);
1987       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
1988    } while (xml_nextNode(node));
1989 
1990    /* Set short description. */
1991    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
1992    nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
1993          "%s",
1994          outfit_getType(temp) );
1995 
1996 #define MELEMENT(o,s) \
1997 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name)
1998 /**< Define to help check for data errors. */
1999    MELEMENT(temp->mass!=0.,"cpu");
2000    MELEMENT(temp->cpu!=0.,"cpu");
2001 #undef MELEMENT
2002 }
2003 
2004 
2005 /**
2006  * @brief Parses the jammer tidbits of the outfit.
2007  *
2008  *    @param temp Outfit to finish loading.
2009  *    @param parent Outfit's parent node.
2010  */
outfit_parseSJammer(Outfit * temp,const xmlNodePtr parent)2011 static void outfit_parseSJammer( Outfit *temp, const xmlNodePtr parent )
2012 {
2013    xmlNodePtr node;
2014    node = parent->children;
2015 
2016    do {
2017       xml_onlyNodes(node);
2018       xmlr_float(node,"energy",temp->u.jam.energy);
2019       xmlr_float(node,"range",temp->u.jam.range);
2020       xmlr_float(node,"power",temp->u.jam.power);
2021       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
2022    } while (xml_nextNode(node));
2023 
2024    /* Set default outfit size if necessary. */
2025    if (temp->slot.size == OUTFIT_SLOT_SIZE_NA)
2026       outfit_setDefaultSize( temp );
2027    temp->u.jam.energy = -temp->u.jam.energy;
2028 
2029    /* Set short description. */
2030    temp->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
2031    nsnprintf( temp->desc_short, OUTFIT_SHORTDESC_MAX,
2032          "%s\n"
2033          "\erActivated Outfit\e0\n"
2034          "%.0f CPU\n"
2035          "Only one can be equipped\n"
2036          "%.0f Range\n"
2037          "%.0f%% Power\n"
2038          "%.1f EPS",
2039          outfit_getType(temp),
2040          temp->cpu,
2041          temp->u.jam.range,
2042          temp->u.jam.power,
2043          temp->u.jam.energy );
2044 
2045    temp->u.jam.power  /= 100.; /* Put in per one, instead of percent */
2046    temp->u.jam.range2  = pow2( temp->u.jam.range ); /**< We square it already. */
2047 
2048 #define MELEMENT(o,s) \
2049 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
2050    MELEMENT(temp->u.jam.range==0.,"range");
2051    MELEMENT(temp->u.jam.power==0.,"power");
2052    MELEMENT(temp->cpu==0.,"cpu");
2053 #undef MELEMENT
2054 }
2055 
2056 
2057 /**
2058  * @brief Parses and returns Outfit from parent node.
2059 
2060  *    @param temp Outfit to load into.
2061  *    @param parent Parent node to parse outfit from.
2062  *    @return 0 on success.
2063  */
outfit_parse(Outfit * temp,const char * file)2064 static int outfit_parse( Outfit* temp, const char* file )
2065 {
2066    xmlNodePtr cur, node, parent;
2067    char *prop;
2068    const char *cprop;
2069    int group;
2070    uint32_t bufsize;
2071    char *buf = ndata_read( file, &bufsize );
2072 
2073    xmlDocPtr doc = xmlParseMemory( buf, bufsize );
2074 
2075    parent = doc->xmlChildrenNode; /* first system node */
2076    if (parent == NULL) {
2077       ERR("Malformed '"OUTFIT_DATA_PATH"' file: does not contain elements");
2078       return -1;
2079    }
2080 
2081    /* Clear data. */
2082    memset( temp, 0, sizeof(Outfit) );
2083 
2084    temp->name = xml_nodeProp(parent,"name"); /* already mallocs */
2085    if (temp->name == NULL)
2086       WARN("Outfit in "OUTFIT_DATA_PATH" has invalid or no name");
2087 
2088    node = parent->xmlChildrenNode;
2089 
2090    do { /* load all the data */
2091 
2092       /* Only handle nodes. */
2093       xml_onlyNodes(node);
2094 
2095       if (xml_isNode(node,"general")) {
2096          cur = node->children;
2097          do {
2098             xml_onlyNodes(cur);
2099             xmlr_strd(cur,"license",temp->license);
2100             xmlr_float(cur,"mass",temp->mass);
2101             xmlr_float(cur,"cpu",temp->cpu);
2102             xmlr_long(cur,"price",temp->price);
2103             xmlr_strd(cur,"limit",temp->limit);
2104             xmlr_strd(cur,"description",temp->description);
2105             xmlr_strd(cur,"typename",temp->typename);
2106             xmlr_int(cur,"priority",temp->priority);
2107             if (xml_isNode(cur,"gfx_store")) {
2108                temp->gfx_store = xml_parseTexture( cur,
2109                      OUTFIT_GFX_PATH"store/%s.png", 1, 1, OPENGL_TEX_MIPMAPS );
2110                continue;
2111             }
2112             else if (xml_isNode(cur,"slot")) {
2113                cprop = xml_get(cur);
2114                if (cprop == NULL)
2115                   WARN("Outfit '%s' has an slot type invalid.", temp->name);
2116                else if (strcmp(cprop,"structure") == 0)
2117                   temp->slot.type = OUTFIT_SLOT_STRUCTURE;
2118                else if (strcmp(cprop,"utility") == 0)
2119                   temp->slot.type = OUTFIT_SLOT_UTILITY;
2120                else if (strcmp(cprop,"weapon") == 0)
2121                   temp->slot.type = OUTFIT_SLOT_WEAPON;
2122                else
2123                   WARN("Outfit '%s' has unknown slot type '%s'.", temp->name, cprop);
2124 
2125                /* Property. */
2126                xmlr_attr( cur, "prop", prop );
2127                if (prop != NULL)
2128                   temp->slot.spid = sp_get( prop );
2129                free( prop );
2130                continue;
2131             }
2132             else if (xml_isNode(cur,"size")) {
2133                temp->slot.size = outfit_toSlotSize( xml_get(cur) );
2134                continue;
2135             }
2136             WARN("Outfit '%s' has unknown general node '%s'",temp->name, cur->name);
2137          } while (xml_nextNode(cur));
2138          continue;
2139       }
2140 
2141       if (xml_isNode(node,"specific")) { /* has to be processed separately */
2142 
2143          /* get the type */
2144          prop = xml_nodeProp(node,"type");
2145          if (prop == NULL)
2146             ERR("Outfit '%s' element 'specific' missing property 'type'",temp->name);
2147          temp->type = outfit_strToOutfitType(prop);
2148          free(prop);
2149 
2150          /* is secondary weapon? */
2151          prop = xml_nodeProp(node,"secondary");
2152          if (prop != NULL) {
2153             if ((int)atoi(prop))
2154                outfit_setProp(temp, OUTFIT_PROP_WEAP_SECONDARY);
2155             free(prop);
2156          }
2157 
2158          /* Check for manually-defined group. */
2159          prop = xml_nodeProp(node, "group");
2160          if (prop != NULL) {
2161             group = atoi(prop);
2162             if (group > PILOT_WEAPON_SETS || group < 1) {
2163                WARN("Outfit '%s' has group '%d', should be in the 1-%d range",
2164                      temp->name, group, PILOT_WEAPON_SETS);
2165             }
2166 
2167             temp->group = CLAMP(0, 9, group - 1);
2168             free(prop);
2169          }
2170 
2171          if (temp->type==OUTFIT_TYPE_NULL)
2172             WARN("Outfit '%s' is of type NONE", temp->name);
2173          else if (outfit_isBolt(temp))
2174             outfit_parseSBolt( temp, node );
2175          else if (outfit_isBeam(temp))
2176             outfit_parseSBeam( temp, node );
2177          else if (outfit_isLauncher(temp))
2178             outfit_parseSLauncher( temp, node );
2179          else if (outfit_isAmmo(temp))
2180             outfit_parseSAmmo( temp, node );
2181          else if (outfit_isMod(temp))
2182             outfit_parseSMod( temp, node );
2183          else if (outfit_isAfterburner(temp))
2184             outfit_parseSAfterburner( temp, node );
2185          else if (outfit_isJammer(temp))
2186             outfit_parseSJammer( temp, node );
2187          else if (outfit_isFighterBay(temp))
2188             outfit_parseSFighterBay( temp, node );
2189          else if (outfit_isFighter(temp))
2190             outfit_parseSFighter( temp, node );
2191          else if (outfit_isMap(temp)) {
2192             temp->u.map = malloc( sizeof(OutfitMapData_t) ); /**< deal with maps after the universe is loaded */
2193             temp->slot.type         = OUTFIT_SLOT_NA;
2194             temp->slot.size         = OUTFIT_SLOT_SIZE_NA;
2195          }
2196          else if (outfit_isLocalMap(temp))
2197             outfit_parseSLocalMap( temp, node );
2198          else if (outfit_isGUI(temp))
2199             outfit_parseSGUI( temp, node );
2200          else if (outfit_isLicense(temp))
2201             outfit_parseSLicense( temp, node );
2202 
2203          continue;
2204       }
2205       WARN("Outfit '%s' has unknown node '%s'",temp->name, node->name);
2206    } while (xml_nextNode(node));
2207 
2208 #define MELEMENT(o,s) \
2209 if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
2210    MELEMENT(temp->name==NULL,"name");
2211    MELEMENT(temp->slot.type==OUTFIT_SLOT_NULL,"slot");
2212    MELEMENT((temp->slot.type!=OUTFIT_SLOT_NA) && (temp->slot.size==OUTFIT_SLOT_SIZE_NA),"size");
2213    MELEMENT(temp->gfx_store==NULL,"gfx_store");
2214    /*MELEMENT(temp->mass==0,"mass"); Not really needed */
2215    MELEMENT(temp->type==0,"type");
2216    /*MELEMENT(temp->price==0,"price");*/
2217    MELEMENT(temp->description==NULL,"description");
2218 #undef MELEMENT
2219 
2220    xmlFreeDoc(doc);
2221    free(buf);
2222 
2223    return 0;
2224 }
2225 
2226 
2227 /**
2228  * @brief Loads all the files in a directory.
2229  *
2230  *    @param dir Directory to load files from.
2231  *    @return 0 on success.
2232  */
outfit_loadDir(char * dir)2233 static int outfit_loadDir( char *dir )
2234 {
2235    uint32_t nfiles;
2236    char **outfit_files;
2237    int i;
2238 
2239    outfit_files = ndata_listRecursive( dir, &nfiles );
2240    for (i=0; i<(int)nfiles; i++) {
2241       outfit_parse( &array_grow(&outfit_stack), outfit_files[i] );
2242       free( outfit_files[i] );
2243    }
2244    free( outfit_files );
2245 
2246    /* Reduce size. */
2247    array_shrink( &outfit_stack );
2248 
2249    return 0;
2250 }
2251 
2252 /**
2253  * @brief Loads all the outfits.
2254  *
2255  *    @return 0 on success.
2256  */
outfit_load(void)2257 int outfit_load (void)
2258 {
2259    int i, noutfits;
2260    Outfit *o;
2261 
2262    /* First pass, loads up ammunition. */
2263    outfit_stack = array_create(Outfit);
2264    outfit_loadDir( OUTFIT_DATA_PATH );
2265    array_shrink(&outfit_stack);
2266    noutfits = array_size(outfit_stack);
2267 
2268    /* Second pass, sets up ammunition relationships. */
2269    for (i=0; i<noutfits; i++) {
2270       o = &outfit_stack[i];
2271       if (outfit_isLauncher(&outfit_stack[i])) {
2272          o->u.lau.ammo = outfit_get( o->u.lau.ammo_name );
2273          if (outfit_isSeeker(o) && /* Smart seekers. */
2274                (o->u.lau.ammo->u.amm.ai)) {
2275             if (o->u.lau.ew_target == 0.)
2276                WARN("Outfit '%s' missing/invalid 'ew_target' element", o->name);
2277             if (o->u.lau.lockon == 0.)
2278                WARN("Outfit '%s' missing/invalid 'lockon' element", o->name);
2279             if (!outfit_isTurret(o) && (o->u.lau.arc == 0.))
2280                WARN("Outfit '%s' missing/invalid 'arc' element", o->name);
2281          }
2282 
2283          outfit_launcherDesc(o);
2284       }
2285       else if (outfit_isFighterBay(&outfit_stack[i]))
2286          o->u.bay.ammo = outfit_get( o->u.bay.ammo_name );
2287    }
2288 
2289 #ifdef DEBUGGING
2290    char **outfit_names = malloc( noutfits * sizeof(char*) );
2291    int start;
2292 
2293    for (i=0; i<noutfits; i++)
2294       outfit_names[i] = outfit_stack[i].name;
2295 
2296    qsort( outfit_names, noutfits, sizeof(char*) , outfit_compareNames );
2297    for (i=0; i<(noutfits - 1); i++) {
2298       start = i;
2299       while (strcmp(outfit_names[i], outfit_names[i+1]) == 0)
2300          i++;
2301 
2302       if (i == start)
2303          continue;
2304 
2305       WARN("Name collision! %d outfits are named '%s'", i+1 - start,
2306             outfit_names[start]);
2307    }
2308    free(outfit_names);
2309 #endif
2310 
2311    DEBUG("Loaded %d Outfit%s", noutfits, (noutfits == 1) ? "" : "s" );
2312 
2313    return 0;
2314 }
2315 
2316 
2317 /**
2318  * @brief qsort compare function for names.
2319  */
outfit_compareNames(const void * name1,const void * name2)2320 static int outfit_compareNames( const void *name1, const void *name2 )
2321 {
2322    const char *n1, *n2;
2323 
2324    n1 = *(const char**) name1;
2325    n2 = *(const char**) name2;
2326 
2327    return strcmp(n1, n2);
2328 }
2329 
2330 
2331 /**
2332  * @brief Parses all the maps.
2333  *
2334  */
outfit_mapParse(void)2335 int outfit_mapParse (void)
2336 {
2337    int i, len;
2338    Outfit *o;
2339    uint32_t bufsize, nfiles;
2340    char *buf;
2341    xmlNodePtr node, cur;
2342    xmlDocPtr doc;
2343    char **map_files;
2344    char *file, *n;
2345 
2346    map_files = ndata_list( MAP_DATA_PATH, &nfiles );
2347    for (i=0; i<(int)nfiles; i++) {
2348 
2349       len  = strlen(MAP_DATA_PATH)+strlen(map_files[i])+2;
2350       file = malloc( len );
2351       nsnprintf( file, len, "%s%s", MAP_DATA_PATH, map_files[i] );
2352 
2353       buf = ndata_read( file, &bufsize );
2354       doc = xmlParseMemory( buf, bufsize );
2355 
2356       node = doc->xmlChildrenNode; /* first system node */
2357       if (node == NULL) {
2358          WARN("Malformed '"OUTFIT_DATA_PATH"' file: does not contain elements");
2359          free(file);
2360          xmlFreeDoc(doc);
2361          free(buf);
2362          return -1;
2363       }
2364 
2365       n = xml_nodeProp( node,"name" );
2366       o = outfit_get( n );
2367       free(n);
2368       if (!outfit_isMap(o)) { /* If its not a map, we don't care. */
2369          free(file);
2370          xmlFreeDoc(doc);
2371          free(buf);
2372          continue;
2373       }
2374 
2375       cur = node->xmlChildrenNode;
2376       do { /* load all the data */
2377          /* Only handle nodes. */
2378          xml_onlyNodes(cur);
2379 
2380          if (xml_isNode(cur,"specific"))
2381             outfit_parseSMap(o, cur);
2382 
2383       } while (xml_nextNode(cur));
2384 
2385       /* Clean up. */
2386       free(file);
2387       xmlFreeDoc(doc);
2388       free(buf);
2389    }
2390 
2391    /* Clean up. */
2392    for (i=0; i<(int)nfiles; i++)
2393       free( map_files[i] );
2394    free( map_files );
2395 
2396    return 0;
2397 }
2398 
2399 
2400 /**
2401  * @brief Generates short descs for launchers, including ammo info.
2402  *
2403  *    @param o Launcher.
2404  */
outfit_launcherDesc(Outfit * o)2405 static void outfit_launcherDesc( Outfit* o )
2406 {
2407    int l;
2408    Outfit *a; /* Launcher's ammo. */
2409 
2410    if (o->desc_short != NULL) {
2411       WARN("Outfit '%s' already has a short description", o->name);
2412       return;
2413    }
2414 
2415    a = o->u.lau.ammo;
2416 
2417    o->desc_short = malloc( OUTFIT_SHORTDESC_MAX );
2418    l = nsnprintf( o->desc_short, OUTFIT_SHORTDESC_MAX,
2419          "%s [%s]\n"
2420          "%.0f CPU\n"
2421          "%.0f%% Penetration\n"
2422          "%.2f DPS [%.0f Damage]\n",
2423          outfit_getType(o), dtype_damageTypeToStr(a->u.amm.dmg.type),
2424          o->cpu,
2425          a->u.amm.dmg.penetration * 100.,
2426          1. / o->u.lau.delay * a->u.amm.dmg.damage, a->u.amm.dmg.damage );
2427 
2428    if (a->u.amm.dmg.disable > 0.)
2429       l += nsnprintf( &o->desc_short[l], OUTFIT_SHORTDESC_MAX - l,
2430             "%.2f Disable/s [%.0f Disable]\n",
2431             1. / o->u.lau.delay * a->u.amm.dmg.disable, a->u.amm.dmg.disable );
2432 
2433    l += nsnprintf( &o->desc_short[l], OUTFIT_SHORTDESC_MAX - l,
2434          "%.1f Shots Per Second\n"
2435          "Holds %d %s",
2436          1. / o->u.lau.delay,
2437          o->u.lau.amount, o->u.lau.ammo_name );
2438 }
2439 
2440 
2441 /**
2442  * @brief Frees the outfit stack.
2443  */
outfit_free(void)2444 void outfit_free (void)
2445 {
2446    int i;
2447    Outfit *o;
2448    for (i=0; i < array_size(outfit_stack); i++) {
2449       o = &outfit_stack[i];
2450 
2451       /* free graphics */
2452       if (outfit_gfx(&outfit_stack[i]))
2453          gl_freeTexture(outfit_gfx(&outfit_stack[i]));
2454 
2455       /* Free slot. */
2456       outfit_freeSlot( &outfit_stack[i].slot );
2457 
2458       /* Type specific. */
2459       if (outfit_isBolt(o) && o->u.blt.gfx_end)
2460          gl_freeTexture(o->u.blt.gfx_end);
2461       if (outfit_isLauncher(o) && o->u.lau.ammo_name)
2462          free(o->u.lau.ammo_name);
2463       if (outfit_isFighterBay(o) && o->u.bay.ammo_name)
2464          free(o->u.bay.ammo_name);
2465       if (outfit_isFighter(o) && o->u.fig.ship)
2466          free(o->u.fig.ship);
2467       if (outfit_isGUI(o) && o->u.gui.gui)
2468          free(o->u.gui.gui);
2469       if (o->type == OUTFIT_TYPE_MODIFICATION)
2470          ss_free( o->u.mod.stats );
2471       if (outfit_isMap(o)) {
2472          array_free( o->u.map->systems );
2473          array_free( o->u.map->assets );
2474          array_free( o->u.map->jumps );
2475          free( o->u.map );
2476       }
2477 
2478       /* strings */
2479       free(o->typename);
2480       free(o->description);
2481       free(o->limit);
2482       free(o->desc_short);
2483       free(o->license);
2484       free(o->name);
2485       if (o->gfx_store)
2486          gl_freeTexture(o->gfx_store);
2487    }
2488 
2489    array_free(outfit_stack);
2490 }
2491 
2492