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