naev 0.11.5
outfit.c
Go to the documentation of this file.
1/*
2 * See Licensing and Copyright notice in naev.h
3 */
13#include <math.h>
14#include <stdlib.h>
15#include "SDL_thread.h"
16#include "physfs.h"
17
18#include "naev.h"
21#include "outfit.h"
22
23#include "array.h"
24#include "conf.h"
25#include "damagetype.h"
26#include "log.h"
27#include "mapData.h"
28#include "ndata.h"
29#include "nfile.h"
30#include "nlua.h"
31#include "nlua_gfx.h"
32#include "nlua_pilotoutfit.h"
33#include "nlua_outfit.h"
34#include "nlua_camera.h"
35#include "nlua_hook.h"
36#include "nlua_munition.h"
37#include "nstring.h"
38#include "nstring.h"
39#include "nxml.h"
40#include "pilot.h"
41#include "pilot_heat.h"
42#include "pilot_outfit.h"
43#include "ship.h"
44#include "slots.h"
45#include "spfx.h"
46#include "start.h"
47#include "threadpool.h"
48#include "unistd.h"
49
50#define outfit_setProp(o,p) ((o)->properties |= p)
52#define XML_OUTFIT_TAG "outfit"
54#define OUTFIT_SHORTDESC_MAX STRMAX_SHORT
59typedef struct OutfitThreadData_ {
60 char *filename;
61 Outfit outfit;
62 int ret;
64
65/*
66 * the stack
67 */
68static Outfit* outfit_stack = NULL;
69static char **license_stack = NULL;
71/*
72 * Helper stuff for setting up short descriptions for outfits.
73 */
74#define SDESC_ADD( l, temp, txt, ... ) \
75(l) += scnprintf( &(temp)->summary_raw[l], OUTFIT_SHORTDESC_MAX - (l), (txt), ## __VA_ARGS__ )
76
77/*
78 * Prototypes
79 */
80/* misc */
81static OutfitType outfit_strToOutfitType( char *buf );
82/* parsing */
83static int outfit_loadDir( const char *dir );
84static int outfit_parseDamage( Damage *dmg, xmlNodePtr node );
85static int outfit_parseThread( void *ptr );
86static int outfit_parse( Outfit* temp, const char* file );
87static void outfit_parseSBolt( Outfit* temp, const xmlNodePtr parent );
88static void outfit_parseSBeam( Outfit* temp, const xmlNodePtr parent );
89static void outfit_parseSLauncher( Outfit* temp, const xmlNodePtr parent );
90static void outfit_parseSMod( Outfit* temp, const xmlNodePtr parent );
91static void outfit_parseSAfterburner( Outfit* temp, const xmlNodePtr parent );
92static void outfit_parseSFighterBay( Outfit *temp, const xmlNodePtr parent );
93static void outfit_parseSMap( Outfit *temp, const xmlNodePtr parent );
94static void outfit_parseSLocalMap( Outfit *temp, const xmlNodePtr parent );
95static void outfit_parseSGUI( Outfit *temp, const xmlNodePtr parent );
96static void outfit_parseSLicense( Outfit *temp, const xmlNodePtr parent );
97static int outfit_loadPLG( Outfit *temp, const char *buf );
98static int outfit_loadGFX( Outfit *temp, const xmlNodePtr node );
99static void sdesc_miningRarity( int *l, Outfit *temp, int rarity );
100/* Display */
101
102typedef struct s_Outfitstat {
103 const char *name;
104 const char *unit;
105 int colour;
106 int colour_threshold;
107 int hide_zero;
108 int precision;
109} t_os_stat;
110typedef const t_os_stat os_opts;
111
112/* Printing functions. */
113static int os_printD( char *buf, int len, double value, const os_opts *opts );
114static int os_printD_range( char *buffer, int i, double minValue, double maxValue, const t_os_stat *opts );
115static int os_printD_rate( char *buffer, int i, double val, const t_os_stat *val_opts, int multiplier, double rate, const t_os_stat *rate_opts );
116
117/* Helpers for different attributes. */
118static os_opts darmour_opts = { N_("Armour Damage"), _UNIT_PERCENT, 1, 100, 0, 0 };
119static os_opts dshield_opts = { N_("Shield Damage"), _UNIT_PERCENT, 1, 100, 0, 0 };
120static os_opts dknockback_opts = { N_("Knockback"), _UNIT_PERCENT, 0, 0, 1, 0 };
121static os_opts cpu_opts = { N_("CPU"), _UNIT_CPU, 1, 0, 1, 0 };
122static os_opts mass_opts = { N_("Mass"), _UNIT_MASS, 0, 0, 1, 0 };
123static os_opts penetration_opts = { N_("Penetration"), _UNIT_PERCENT, 0, 0, 1, 0 };
124static os_opts damage_opts = {N_("Damage"), _UNIT_ENERGY, 0, 1, 1, 1 };
125static os_opts dps_opts = {N_("Damage Rate"), _UNIT_POWER, 0, 0, 1, 1 };
126static os_opts disable_opts = {N_("Disable"), _UNIT_ENERGY, 0, 1, 1, 1 };
127static os_opts disable_rate_opts = {N_("Disable Rate"), _UNIT_POWER, 0, 0, 1, 1 };
128static os_opts fire_rate_opts = {N_("Fire Rate"), _UNIT_PER_TIME, 0, 0, 0, 1 };
129static os_opts energy_opts = { N_("Energy"), _UNIT_ENERGY, 0, 1, 1, 1 };
130static os_opts power_opts = { N_("Power"), _UNIT_POWER, 0, 0, 1, 1 };
131static os_opts range_opts = { N_("Range"), _UNIT_DISTANCE, 0, 0, 1, 0 };
132static os_opts speed_opts = { N_("Speed"), _UNIT_SPEED, 0, 0, 1, 0 };
133static os_opts heatup_opts = { N_("Overheat"), _UNIT_TIME, 0, 0, 1, 1 };
134static os_opts dispersion_opts = { N_("Dispersion"), _UNIT_ANGLE, 0, 0, 1, 0 };
135static os_opts swivel_opts = { N_("Swivel"), _UNIT_ANGLE, 0, 0, 1, 0 };
136static os_opts tracking_opts = { N_("Tracking"), _UNIT_DISTANCE, 0, 0, 1, 0 };
137static os_opts duration_opts = { N_("Duration"), _UNIT_TIME, 0, 0, 1, 1 };
138static os_opts cooldown_opts = { N_("Cooldown"), _UNIT_TIME, 0, 0, 1, 1 };
139static os_opts lockon_opts = { N_("Lock On"), _UNIT_TIME, 0, 0, 1, 0 };
140static os_opts inflight_calib_opts = { N_("Inflight Calibration"), _UNIT_TIME, 0, 0, 1, 1 };
141static os_opts initial_speed_opts = { N_("Launch Speed"), _UNIT_SPEED, 0, 0, 1, 0 };
142static os_opts accel_opts = { N_("Accel"), _UNIT_ACCEL, 0, 0, 1, 0 };
143static os_opts max_speed_opts = { N_("Max Speed"), _UNIT_SPEED, 0, 0, 1, 0 };
144static os_opts reload_opts = { N_("Reload Time"), _UNIT_TIME, 0, 0, 1, 1 };
145static os_opts armour_opts = { N_("Armour"), _UNIT_ENERGY, 0, 0, 1, 1 };
146static os_opts absorp_opts = { N_("Absorption"), _UNIT_PERCENT, 0, 0, 1, 1 };
147static os_opts jam_res_opts = { N_("Jam Resistance"), _UNIT_PERCENT, 0, 0, 1, 0 };
148static os_opts max_mass_opts = { N_("Max Effective Mass"), _UNIT_MASS, 0, 0, 1, 0 };
149static os_opts rumble_opts = { N_("Rumble"), NULL, 0, 0, 1, 1 };
150static os_opts shots_delay_opts = { N_("Shots Delay"), _UNIT_TIME, 0, 0, 1, 1 };
151
152static int outfit_cmp( const void *p1, const void *p2 )
153{
154 const Outfit *o1, *o2;
155 o1 = (const Outfit*) p1;
156 o2 = (const Outfit*) p2;
157 return strcmp( o1->name, o2->name );
158}
159
166const Outfit* outfit_get( const char* name )
167{
168 const Outfit *o = outfit_getW( name );
169 if (o==NULL)
170 WARN(_("Outfit '%s' not found in stack."), name);
171 return o;
172}
173
180const Outfit* outfit_getW( const char* name )
181{
182 const Outfit s = {.name = (char*)name };
183 return bsearch( &s, outfit_stack, array_size(outfit_stack), sizeof(Outfit), outfit_cmp );
184}
185
190{
191 return outfit_stack;
192}
193
197const char *outfit_existsCase( const char* name )
198{
199 for (int i=0; i<array_size(outfit_stack); i++)
200 if (strcasecmp(name,outfit_stack[i].name)==0)
201 return outfit_stack[i].name;
202 return NULL;
203}
204
208char **outfit_searchFuzzyCase( const char* name, int *n )
209{
210 int len, nstack;
211 char **names;
212
213 /* Overallocate to maximum. */
214 nstack = array_size(outfit_stack);
215 names = malloc( sizeof(char*) * nstack );
216
217 /* Do fuzzy search. */
218 len = 0;
219 for (int i=0; i<nstack; i++) {
220 if (SDL_strcasestr( _(outfit_stack[i].name), name ) != NULL) {
221 names[len] = outfit_stack[i].name;
222 len++;
223 }
224 }
225
226 /* Free if empty. */
227 if (len == 0) {
228 free(names);
229 names = NULL;
230 }
231
232 *n = len;
233 return names;
234}
235
243int outfit_compareTech( const void *outfit1, const void *outfit2 )
244{
245 int ret;
246 const Outfit *o1, *o2;
247
248 /* Get outfits. */
249 o1 = * (const Outfit**) outfit1;
250 o2 = * (const Outfit**) outfit2;
251
252 /* Compare slot type. */
253 if (o1->slot.type < o2->slot.type)
254 return +1;
255 else if (o1->slot.type > o2->slot.type)
256 return -1;
257
258 /* Compare intrinsic types. */
259 if (o1->type < o2->type)
260 return -1;
261 else if (o1->type > o2->type)
262 return +1;
263
264 /* Compare named types. */
265 if ((o1->typename == NULL) && (o2->typename != NULL))
266 return -1;
267 else if ((o1->typename != NULL) && (o2->typename == NULL))
268 return +1;
269 else if ((o1->typename != NULL) && (o2->typename != NULL)) {
270 ret = strcmp( o1->typename, o2->typename );
271 if (ret != 0)
272 return ret;
273 }
274
275 /* Compare slot sizes. */
276 if (o1->slot.size < o2->slot.size)
277 return +1;
278 else if (o1->slot.size > o2->slot.size)
279 return -1;
280
281 /* Compare sort priority. */
282 if (o1->priority < o2->priority)
283 return +1;
284 else if (o1->priority > o2->priority)
285 return -1;
286
287 /* Special prices are listed first. */
288 if ((o1->lua_price != LUA_NOREF) && (o2->lua_price == LUA_NOREF))
289 return -1;
290 else if ((o1->lua_price == LUA_NOREF) && (o2->lua_price != LUA_NOREF))
291 return +1;
292
293 /* Compare price. */
294 if (o1->price < o2->price)
295 return +1;
296 else if (o1->price > o2->price)
297 return -1;
298
299 /* It turns out they're the same. */
300 return strcmp( o1->name, o2->name );
301}
302
303int outfit_filterWeapon( const Outfit *o )
304{ return ((o->slot.type == OUTFIT_SLOT_WEAPON) && !sp_required( o->slot.spid )); }
305
306int outfit_filterUtility( const Outfit *o )
307{ return ((o->slot.type == OUTFIT_SLOT_UTILITY) && !sp_required( o->slot.spid )); }
308
309int outfit_filterStructure( const Outfit *o )
310{ return ((o->slot.type == OUTFIT_SLOT_STRUCTURE) && !sp_required( o->slot.spid )); }
311
312int outfit_filterCore( const Outfit *o )
313{ return sp_required( o->slot.spid ); }
314
315int outfit_filterOther( const Outfit *o )
316{
317 return (!sp_required( o->slot.spid ) && ((o->slot.type == OUTFIT_SLOT_NULL)
318 || (o->slot.type == OUTFIT_SLOT_NA)));
319}
320
327const char *outfit_slotName( const Outfit* o )
328{
329 return slotName( o->slot.type );
330}
331
335const char *slotName( const OutfitSlotType type )
336{
337 switch (type) {
338 case OUTFIT_SLOT_NULL:
339 return "NULL";
340 case OUTFIT_SLOT_NA:
341 return gettext_noop("N/A");
342 case OUTFIT_SLOT_INTRINSIC:
343 return gettext_noop("Intrinsic");
344 case OUTFIT_SLOT_STRUCTURE:
345 return gettext_noop("Structure");
346 case OUTFIT_SLOT_UTILITY:
347 return gettext_noop("Utility");
348 case OUTFIT_SLOT_WEAPON:
349 return gettext_noop("Weapon");
350 default:
351 return gettext_noop("Unknown");
352 }
353}
354
358const char *slotSize( const OutfitSlotSize o )
359{
360 switch (o) {
361 case OUTFIT_SLOT_SIZE_NA:
362 return gettext_noop("N/A");
363 case OUTFIT_SLOT_SIZE_LIGHT:
364 return gettext_noop("Small");
365 case OUTFIT_SLOT_SIZE_MEDIUM:
366 return gettext_noop("Medium");
367 case OUTFIT_SLOT_SIZE_HEAVY:
368 return gettext_noop("Large");
369 default:
370 return gettext_noop("Unknown");
371 }
372}
373
380const char *outfit_slotSize( const Outfit* o )
381{
382 return slotSize( o->slot.size );
383}
384
391const glColour *outfit_slotSizeColour( const OutfitSlot* os )
392{
393 if (os->size == OUTFIT_SLOT_SIZE_HEAVY)
394 return &cOutfitHeavy;
395 else if (os->size == OUTFIT_SLOT_SIZE_MEDIUM)
396 return &cOutfitMedium;
397 else if (os->size == OUTFIT_SLOT_SIZE_LIGHT)
398 return &cOutfitLight;
399 return NULL;
400}
401
409{
410 if (os->size == OUTFIT_SLOT_SIZE_HEAVY)
411 return 'r';
412 else if (os->size == OUTFIT_SLOT_SIZE_MEDIUM)
413 return 'b';
414 else if (os->size == OUTFIT_SLOT_SIZE_LIGHT)
415 return 'y';
416 return '0';
417}
418
426{
427 if (os->type == OUTFIT_SLOT_WEAPON)
428 return 'p';
429 else if (os->type == OUTFIT_SLOT_UTILITY)
430 return 'g';
431 else if (os->type == OUTFIT_SLOT_STRUCTURE)
432 return 'n';
433 return '0';
434}
435
444size_t outfit_getNameWithClass( const Outfit* outfit, char* buf, size_t size )
445{
446 size_t p = scnprintf( &buf[0], size, "%s", _(outfit->name) );
447 if (outfit->slot.type != OUTFIT_SLOT_NA) {
448 p += scnprintf( &buf[p], size-p, _("\n#%c%s #%c%s #0slot"),
449 outfit_slotSizeColourFont( &outfit->slot ), _(outfit_slotSize( outfit )),
450 outfit_slotTypeColourFont( &outfit->slot ), _(outfit_slotName( outfit )) );
451 }
452 return p;
453}
454
461OutfitSlotSize outfit_toSlotSize( const char *s )
462{
463 if (s == NULL) {
464 /*WARN( "(NULL) outfit slot size" );*/
465 return OUTFIT_SLOT_SIZE_NA;
466 }
467
468 if (strcasecmp(s,"Large")==0)
469 return OUTFIT_SLOT_SIZE_HEAVY;
470 else if (strcasecmp(s,"Medium")==0)
471 return OUTFIT_SLOT_SIZE_MEDIUM;
472 else if (strcasecmp(s,"Small")==0)
473 return OUTFIT_SLOT_SIZE_LIGHT;
474
475 WARN(_("'%s' does not match any outfit slot sizes."), s);
476 return OUTFIT_SLOT_SIZE_NA;
477}
478
484int outfit_isActive( const Outfit* o )
485{
487 return 1;
488 if (outfit_isMod(o) && o->u.mod.active)
489 return 1;
491 return 1;
492 return 0;
493}
494
501{
502 /* Must be active. */
503 if (!outfit_isActive(o))
504 return 0;
505
506 /* Special case it is lua-based and not toggleable. */
507 if (outfit_isMod(o) && (o->lua_env != LUA_NOREF) && (o->lua_ontoggle == LUA_NOREF))
508 return 0;
509
510 return 1;
511}
512
518int outfit_isWeapon( const Outfit *o )
519{
520 return ( (o->type==OUTFIT_TYPE_BOLT) ||
521 (o->type==OUTFIT_TYPE_BEAM) ||
522 (o->type==OUTFIT_TYPE_TURRET_BOLT) ||
523 (o->type==OUTFIT_TYPE_TURRET_BEAM) ||
524 (o->type==OUTFIT_TYPE_LAUNCHER) ||
525 (o->type==OUTFIT_TYPE_TURRET_LAUNCHER) ||
526 (o->type==OUTFIT_TYPE_FIGHTER_BAY));
527}
528
535{
536 return ( (o->type==OUTFIT_TYPE_BOLT) ||
537 (o->type==OUTFIT_TYPE_BEAM) );
538}
544int outfit_isBolt( const Outfit* o )
545{
546 return ( (o->type==OUTFIT_TYPE_BOLT) ||
547 (o->type==OUTFIT_TYPE_TURRET_BOLT) );
548}
554int outfit_isBeam( const Outfit* o )
555{
556 return ( (o->type==OUTFIT_TYPE_BEAM) ||
557 (o->type==OUTFIT_TYPE_TURRET_BEAM) );
558}
565{
566 return ( (o->type==OUTFIT_TYPE_LAUNCHER) ||
567 (o->type==OUTFIT_TYPE_TURRET_LAUNCHER) );
568}
574int outfit_isSeeker( const Outfit* o )
575{
576 if (((o->type==OUTFIT_TYPE_TURRET_LAUNCHER) ||
577 (o->type==OUTFIT_TYPE_LAUNCHER)) &&
578 (o->u.lau.ai > 0))
579 return 1;
580 return 0;
581}
587int outfit_isTurret( const Outfit* o )
588{
589 return ((o->type==OUTFIT_TYPE_TURRET_BOLT) ||
590 (o->type==OUTFIT_TYPE_TURRET_BEAM) ||
591 (o->type==OUTFIT_TYPE_TURRET_LAUNCHER));
592}
598int outfit_isMod( const Outfit* o )
599{
600 return (o->type==OUTFIT_TYPE_MODIFICATION);
601}
608{
609 return (o->type==OUTFIT_TYPE_AFTERBURNER);
610}
617{
618 return (o->type==OUTFIT_TYPE_FIGHTER_BAY);
619}
625int outfit_isMap( const Outfit* o )
626{
627 return (o->type==OUTFIT_TYPE_MAP);
628}
635{
636 return (o->type==OUTFIT_TYPE_LOCALMAP);
637}
644{
645 return (o->type==OUTFIT_TYPE_LICENSE);
646}
652int outfit_isGUI( const Outfit* o )
653{
654 return (o->type==OUTFIT_TYPE_GUI);
655}
656
663{
664 return (o->properties & OUTFIT_PROP_WEAP_SECONDARY) != 0;
665}
666
671const OutfitGFX* outfit_gfx( const Outfit* o )
672{
673 if (outfit_isBolt(o)) return &o->u.blt.gfx;
674 else if (outfit_isLauncher(o)) return &o->u.lau.gfx;
675 return NULL;
676}
681const CollPoly* outfit_plg( const Outfit* o )
682{
683 if (outfit_isBolt(o)) return o->u.blt.gfx.polygon;
684 else if (outfit_isLauncher(o)) return o->u.lau.gfx.polygon;
685 return NULL;
686}
692{
693 if (outfit_isBolt(o)) return o->u.blt.spfx_armour;
694 else if (outfit_isBeam(o)) return o->u.bem.spfx_armour;
695 else if (outfit_isLauncher(o)) return o->u.lau.spfx_armour;
696 return -1;
697}
703{
704 if (outfit_isBolt(o)) return o->u.blt.spfx_shield;
705 else if (outfit_isBeam(o)) return o->u.bem.spfx_shield;
706 else if (outfit_isLauncher(o)) return o->u.lau.spfx_shield;
707 return -1;
708}
713const Damage *outfit_damage( const Outfit* o )
714{
715 if (outfit_isBolt(o)) return &o->u.blt.dmg;
716 else if (outfit_isBeam(o)) return &o->u.bem.dmg;
717 else if (outfit_isLauncher(o)) return &o->u.lau.dmg;
718 return NULL;
719}
724double outfit_radius( const Outfit* o )
725{
726 if (outfit_isBolt(o)) return o->u.blt.radius;
727 else if (outfit_isLauncher(o)) return o->u.lau.radius;
728 return 0.;
729}
734double outfit_delay( const Outfit* o )
735{
736 if (outfit_isBolt(o)) return o->u.blt.delay;
737 else if (outfit_isBeam(o)) return o->u.bem.delay;
738 else if (outfit_isLauncher(o)) return o->u.lau.delay;
739 else if (outfit_isFighterBay(o)) return o->u.bay.delay;
740 return -1;
741}
746int outfit_amount( const Outfit* o )
747{
748 if (outfit_isLauncher(o)) return o->u.lau.amount;
749 else if (outfit_isFighterBay(o)) return o->u.bay.amount;
750 return -1;
751}
752
757double outfit_energy( const Outfit* o )
758{
759 if (outfit_isBolt(o)) return o->u.blt.energy;
760 else if (outfit_isBeam(o)) return o->u.bem.energy;
761 else if (outfit_isLauncher(o)) return o->u.lau.energy;
762 return -1.;
763}
768double outfit_heat( const Outfit* o )
769{
770 if (outfit_isBolt(o)) return o->u.blt.heat;
771 else if (outfit_isAfterburner(o)) return o->u.afb.heat;
772 else if (outfit_isBeam(o)) return o->u.bem.heat;
773 return -1;
774}
779double outfit_cpu( const Outfit* o )
780{
781 return o->cpu;
782}
787double outfit_range( const Outfit* o )
788{
789 return pilot_outfitRange( NULL, o );
790}
796double outfit_speed( const Outfit* o )
797{
798 if (outfit_isBolt(o))
799 return o->u.blt.speed;
800 else if (outfit_isLauncher(o)) {
801 if (o->u.lau.accel == 0.)
802 return o->u.lau.speed;
803 else { /* Gets the average speed. */
804 double t;
805 if (o->u.lau.speed > 0.) /* Ammo that don't start stopped don't have max speed. */
806 t = INFINITY;
807 else
808 t = (o->u.lau.speed_max - o->u.lau.speed) / o->u.lau.accel; /* Time to reach max speed */
809
810 /* Reaches max speed. */
811 if (t < o->u.lau.duration)
812 return (o->u.lau.accel * t * t / 2. + (o->u.lau.speed_max - o->u.lau.speed) * (o->u.lau.duration - t)) / o->u.lau.duration + o->u.lau.speed;
813 /* Doesn't reach max speed. */
814 else
815 return o->u.lau.accel * o->u.lau.duration / 2. + o->u.lau.speed;
816 }
817 }
818 return -1.;
819}
825double outfit_swivel( const Outfit* o )
826{
827 if (outfit_isBolt(o)) return o->u.blt.swivel;
828 else if (outfit_isLauncher(o)) return o->u.lau.swivel;
829 else if (outfit_isBeam(o)) return o->u.bem.swivel;
830 return -1.;
831}
837double outfit_spin( const Outfit* o )
838{
839 if (outfit_isBolt(o)) return o->u.blt.gfx.spin;
840 else if (outfit_isLauncher(o)) return o->u.lau.gfx.spin;
841 return -1.;
842}
848double outfit_trackmin( const Outfit* o )
849{
850 if (outfit_isBolt(o)) return o->u.blt.trackmin;
851 else if (outfit_isLauncher(o)) return o->u.lau.trackmin;
852 else if (outfit_isBeam(o)) return 0.;
853 return -1.;
854}
860double outfit_trackmax( const Outfit* o )
861{
862 if (outfit_isBolt(o)) return o->u.blt.trackmax;
863 else if (outfit_isLauncher(o)) return o->u.lau.trackmax;
864 else if (outfit_isBeam(o)) return 1.;
865 return -1.;
866}
873{
874 if (outfit_isBolt(o)) return o->u.blt.mining_rarity;
875 else if (outfit_isLauncher(o)) return o->u.lau.mining_rarity;
876 else if (outfit_isBeam(o)) return o->u.bem.mining_rarity;
877 return -1;
878}
884int outfit_sound( const Outfit* o )
885{
886 if (outfit_isBolt(o)) return o->u.blt.sound;
887 else if (outfit_isLauncher(o)) return o->u.lau.sound;
888 return -1.;
889}
895int outfit_soundHit( const Outfit* o )
896{
897 if (outfit_isBolt(o)) return o->u.blt.sound_hit;
898 else if (outfit_isLauncher(o)) return o->u.lau.sound_hit;
899 return -1.;
900}
906double outfit_ammoMass( const Outfit *o )
907{
908 if (outfit_isLauncher(o)) return o->u.lau.ammo_mass;
909 else if (outfit_isFighterBay(o)) return o->u.bay.ship_mass;
910 return -1.;
911}
917double outfit_duration( const Outfit* o )
918{
919 if (outfit_isMod(o)) { if (o->u.mod.active) return o->u.mod.duration; }
920 else if (outfit_isAfterburner(o)) return INFINITY;
921 else if (outfit_isBolt(o)) return (o->u.blt.range / o->u.blt.speed);
922 else if (outfit_isBeam(o)) return o->u.bem.duration;
923 else if (outfit_isLauncher(o)) return o->u.lau.duration;
924 else if (outfit_isFighterBay(o)) return INFINITY;
925 return -1.;
926}
932double outfit_cooldown( const Outfit* o )
933{
934 if (outfit_isMod(o)) { if (o->u.mod.active) return o->u.mod.cooldown; }
935 else if (outfit_isAfterburner(o)) return 0.;
936 return -1.;
937}
938
945const char* outfit_getType( const Outfit* o )
946{
947 const char* outfit_typename[] = {
948 N_("NULL"),
949 N_("Bolt Cannon"),
950 N_("Beam Cannon"),
951 N_("Bolt Turret"),
952 N_("Beam Turret"),
953 N_("Launcher"),
954 N_("Turret Launcher"),
955 N_("Ship Modification"),
956 N_("Afterburner"),
957 N_("Fighter Bay"),
958 N_("Star Map"),
959 N_("Local Map"),
960 N_("GUI"),
961 N_("License"),
962 };
963
964 /* Name override. */
965 if (o->typename != NULL)
966 return o->typename;
967 return outfit_typename[o->type];
968}
969
976const char* outfit_getTypeBroad( const Outfit* o )
977{
978 if (outfit_isBolt(o)) return N_("Bolt Weapon");
979 else if (outfit_isBeam(o)) return N_("Beam Weapon");
980 else if (outfit_isLauncher(o)) return N_("Launcher");
981 //else if (outfit_isTurret(o)) return N_("Turret");
982 else if (outfit_isMod(o)) return N_("Modification");
983 else if (outfit_isAfterburner(o)) return N_("Afterburner");
984 else if (outfit_isFighterBay(o)) return N_("Fighter Bay");
985 else if (outfit_isMap(o)) return N_("Map");
986 else if (outfit_isLocalMap(o)) return N_("Local Map");
987 else if (outfit_isGUI(o)) return N_("GUI");
988 else if (outfit_isLicense(o)) return N_("License");
989 else return N_("Unknown");
990}
991
997const char* outfit_getAmmoAI( const Outfit *o )
998{
999 const char *ai_type[] = {
1000 N_("Unguided"),
1001 N_("Seek"),
1002 N_("Smart")
1003 };
1004
1005 if (!outfit_isLauncher(o)) {
1006 WARN(_("Outfit '%s' is not a launcher outfit"), o->name);
1007 return NULL;
1008 }
1009
1010 return ai_type[o->u.lau.ai];
1011}
1012
1021const char* outfit_description( const Outfit *o )
1022{
1023 return pilot_outfitDescription( NULL, o );
1024}
1025
1035const char* outfit_summary( const Outfit *o, int withname )
1036{
1037 return pilot_outfitSummary( NULL, o, withname );
1038}
1039
1047int outfit_fitsSlot( const Outfit* o, const OutfitSlot* s )
1048{
1049 const OutfitSlot *os = &o->slot;
1050
1051 /* Outfit must have valid slot type. */
1052 if ((os->type == OUTFIT_SLOT_NULL) ||
1053 (os->type == OUTFIT_SLOT_NA) ||
1054 (os->type == OUTFIT_SLOT_INTRINSIC))
1055 return 0;
1056
1057 /* Outfit type must match outfit slot. */
1058 if (os->type != s->type)
1059 return 0;
1060
1061 /* It doesn't fit. */
1062 if (os->size > s->size)
1063 return 0;
1064
1065 /* Must match slot property. */
1066 if (os->spid != 0)
1067 if (s->spid != os->spid)
1068 return 0;
1069
1070 /* Exclusive only match property. */
1071 if (s->exclusive)
1072 if (s->spid != os->spid)
1073 return 0;
1074
1075 /* Must have valid slot size. */
1076 /*
1077 if (os->size == OUTFIT_SLOT_SIZE_NA)
1078 return 0;
1079 */
1080
1081 /* It meets all criteria. */
1082 return 1;
1083}
1084
1092int outfit_fitsSlotType( const Outfit* o, const OutfitSlot* s )
1093{
1094 const OutfitSlot *os = &o->slot;
1095
1096 /* Outfit must have valid slot type. */
1097 if ((os->type == OUTFIT_SLOT_NULL) ||
1098 (os->type == OUTFIT_SLOT_NA) ||
1099 (os->type == OUTFIT_SLOT_INTRINSIC))
1100 return 0;
1101
1102 /* Outfit type must match outfit slot. */
1103 if (os->type != s->type)
1104 return 0;
1105
1106 /* It meets all criteria. */
1107 return 1;
1108}
1109
1116{
1117 (void) s;
1118}
1119
1120#define O_CMP(s,t) \
1121if (strcasecmp(buf,(s))==0) return t
1128static OutfitType outfit_strToOutfitType( char *buf )
1129{
1130 O_CMP("bolt", OUTFIT_TYPE_BOLT);
1131 O_CMP("beam", OUTFIT_TYPE_BEAM);
1132 O_CMP("turret bolt", OUTFIT_TYPE_TURRET_BOLT);
1133 O_CMP("turret beam", OUTFIT_TYPE_TURRET_BEAM);
1134 O_CMP("launcher", OUTFIT_TYPE_LAUNCHER);
1135 O_CMP("turret launcher",OUTFIT_TYPE_TURRET_LAUNCHER);
1136 O_CMP("modification", OUTFIT_TYPE_MODIFICATION);
1137 O_CMP("afterburner", OUTFIT_TYPE_AFTERBURNER);
1138 O_CMP("fighter bay", OUTFIT_TYPE_FIGHTER_BAY);
1139 O_CMP("map", OUTFIT_TYPE_MAP);
1140 O_CMP("localmap", OUTFIT_TYPE_LOCALMAP);
1141 O_CMP("license", OUTFIT_TYPE_LICENSE);
1142 O_CMP("gui", OUTFIT_TYPE_GUI);
1143
1144 WARN(_("Invalid outfit type: '%s'"),buf);
1145 return OUTFIT_TYPE_NULL;
1146}
1147#undef O_CMP
1148
1152static void sdesc_miningRarity( int *l, Outfit *temp, int rarity )
1153{
1154 if (rarity == 0)
1155 return;
1156 if (rarity == 2)
1157 SDESC_ADD( *l, temp, "\n#g%s#0", _("Can mine uncommon and rare minerals") );
1158 else
1159 SDESC_ADD( *l, temp, "\n#g%s#0", _("Can mine uncommon minerals") );
1160}
1161
1174static int outfit_parseDamage( Damage *dmg, xmlNodePtr node )
1175{
1176 xmlNodePtr cur;
1177
1178 /* Defaults. */
1180 dmg->damage = 0.;
1181 dmg->penetration = 0.;
1182 dmg->disable = 0.;
1183
1184 cur = node->xmlChildrenNode;
1185 do {
1186 xml_onlyNodes( cur );
1187
1188 /* Core properties. */
1189 xmlr_float( cur, "penetrate", dmg->penetration );
1190 xmlr_float( cur, "physical", dmg->damage );
1191 xmlr_float( cur, "disable", dmg->disable );
1192
1193 /* Get type */
1194 if (xml_isNode(cur,"type")) {
1195 char *buf = xml_get( cur );
1196 dmg->type = dtype_get(buf);
1197 if (dmg->type < 0) { /* Case damage type in outfit.xml that isn't in damagetype.xml */
1198 dmg->type = 0;
1199 WARN(_("Unknown damage type '%s'"), buf);
1200 }
1201 }
1202 else WARN(_("Damage has unknown node '%s'"), cur->name);
1203
1204 } while (xml_nextNode(cur));
1205
1206 /* Normalize. */
1207 dmg->penetration /= 100.;
1208
1209 return 0;
1210}
1211
1218static int outfit_loadPLG( Outfit *temp, const char *buf )
1219{
1220 char *file;
1221 OutfitGFX *gfx;
1222 xmlDocPtr doc;
1223 xmlNodePtr node;
1224
1225 if (outfit_isLauncher(temp))
1226 gfx = &temp->u.lau.gfx;
1227 else if (outfit_isBolt(temp))
1228 gfx = &temp->u.blt.gfx;
1229 else {
1230 WARN(_("Trying to load polygon for non-compatible outfit '%s'!"),temp->name);
1231 return -1;
1232 }
1233
1234 SDL_asprintf( &file, "%s%s.xml", OUTFIT_POLYGON_PATH, buf );
1235
1236 /* See if the file does exist. */
1237 if (!PHYSFS_exists(file)) {
1238 WARN(_("%s xml collision polygon does not exist!\n \
1239 Please use the script 'polygon_from_sprite.py' \
1240that can be found in Naev's artwork repo."), file);
1241 free(file);
1242 return 0;
1243 }
1244
1245 /* Load the XML. */
1246 doc = xml_parsePhysFS( file );
1247
1248 if (doc == NULL) {
1249 free(file);
1250 return 0;
1251 }
1252
1253 node = doc->xmlChildrenNode; /* First polygon node */
1254 if (node == NULL) {
1255 xmlFreeDoc(doc);
1256 WARN(_("Malformed %s file: does not contain elements"), file);
1257 free(file);
1258 return 0;
1259 }
1260
1261 free(file);
1262
1263 do { /* load the polygon data */
1264 if (xml_isNode(node,"polygons")) {
1265 xmlNodePtr cur = node->children;
1266 gfx->polygon = array_create_size( CollPoly, 36 );
1267 do {
1268 if (xml_isNode(cur,"polygon")) {
1269 CollPoly *polygon = &array_grow( &gfx->polygon );
1270 LoadPolygon( polygon, cur );
1271 }
1272 } while (xml_nextNode(cur));
1273 }
1274 } while (xml_nextNode(node));
1275
1276 xmlFreeDoc(doc);
1277 return 0;
1278}
1279
1283static int outfit_loadGFX( Outfit *temp, const xmlNodePtr node )
1284{
1285 char *type;
1286 OutfitGFX *gfx;
1287 int flags;
1288
1289 if (outfit_isLauncher(temp))
1290 gfx = &temp->u.lau.gfx;
1291 else if (outfit_isBolt(temp))
1292 gfx = &temp->u.blt.gfx;
1293 else {
1294 WARN(_("Trying to load graphics for non-compatible outfit '%s'!"),temp->name);
1295 return -1;
1296 }
1297
1298 /* Comomn properties. */
1299 xmlr_attr_float(node, "spin", gfx->spin);
1300 if (gfx->spin != 0.)
1301 outfit_setProp( temp, OUTFIT_PROP_WEAP_SPIN );
1302
1303 /* Split by type. */
1304 xmlr_attr_strd(node,"type",type);
1305 if ((type != NULL) && (strcmp(type,"shader")==0)) {
1306 char *vertex;
1307
1308 xmlr_attr_strd(node,"vertex",vertex);
1309 if (vertex == NULL)
1310 vertex = strdup("project_pos.vert");
1311 gl_contextSet();
1312 gfx->program = gl_program_vert_frag( vertex, xml_get(node), NULL );
1313 free( vertex );
1314 gfx->vertex = glGetAttribLocation( gfx->program, "vertex" );
1315 gfx->projection= glGetUniformLocation( gfx->program, "projection" );
1316 gfx->dimensions= glGetUniformLocation( gfx->program, "dimensions" );
1317 gfx->u_r = glGetUniformLocation( gfx->program, "u_r" );
1318 gfx->u_time = glGetUniformLocation( gfx->program, "u_time" );
1319 gfx->u_fade = glGetUniformLocation( gfx->program, "u_fade" );
1320 gl_contextUnset();
1321
1322 xmlr_attr_float_def(node,"size",gfx->size,-1.);
1323 if (gfx->size < 0.)
1324 WARN(_("Outfit '%s' has GFX shader but no 'size' set!"),temp->name);
1325
1326 xmlr_attr_float_def(node,"col_size",gfx->col_size,gfx->size*0.8);
1327
1328 free(type);
1329 return 0;
1330 }
1331 else if (type != NULL) {
1332 WARN(_("Outfit '%s' has unknown gfx type '%s'!"), temp->name, type);
1333 free(type);
1334 return -1;
1335 }
1336
1337 /* Load the collision polygon. */
1338 char *buf = xml_get(node);
1339 outfit_loadPLG( temp, buf );
1340
1341 /* Load normal graphics. */
1342 flags = OPENGL_TEX_MIPMAPS;
1343 if (gfx->polygon==NULL)
1344 flags |= OPENGL_TEX_MAPTRANS;
1345 gfx->tex = xml_parseTexture( node, OUTFIT_GFX_PATH"space/%s", 6, 6, flags );
1346 gfx->size = (gfx->tex->sw + gfx->tex->sh)*0.5;
1347
1348 /* Validity check: there must be 1 polygon per sprite. */
1349 if (array_size(gfx->polygon) != 36) {
1350 WARN(_("Outfit '%s': the number of collision polygons is wrong.\n \
1351 npolygon = %i and sx*sy = %i"),
1352 temp->name, array_size(gfx->polygon), 36);
1353 }
1354
1355 return 0;
1356}
1357
1364static void outfit_parseSBolt( Outfit* temp, const xmlNodePtr parent )
1365{
1366 ShipStatList *ll;
1367 xmlNodePtr node;
1368 double C, area;
1369 double dshield, darmour, dknockback;
1370 int l;
1371
1372 /* Defaults */
1373 temp->u.blt.spfx_armour = -1;
1374 temp->u.blt.spfx_shield = -1;
1375 temp->u.blt.sound = -1;
1376 temp->u.blt.sound_hit = -1;
1377 temp->u.blt.falloff = -1.;
1378 temp->u.blt.trackmin = -1.;
1379 temp->u.blt.trackmax = -1.;
1380 temp->u.blt.shots = 1;
1381
1382 node = parent->xmlChildrenNode;
1383 do { /* load all the data */
1384 xml_onlyNodes(node);
1385 xmlr_float(node,"speed",temp->u.blt.speed);
1386 xmlr_float(node,"delay",temp->u.blt.delay);
1387 xmlr_float(node,"energy",temp->u.blt.energy);
1388 xmlr_float(node,"heatup",temp->u.blt.heatup);
1389 xmlr_float(node,"trackmin",temp->u.blt.trackmin);
1390 xmlr_float(node,"trackmax",temp->u.blt.trackmax);
1391 xmlr_float(node,"swivel",temp->u.blt.swivel);
1392 xmlr_float(node,"dispersion",temp->u.blt.dispersion);
1393 xmlr_float(node,"speed_dispersion",temp->u.blt.speed_dispersion);
1394 xmlr_int(node,"shots",temp->u.blt.shots);
1395 xmlr_int(node,"mining_rarity",temp->u.blt.mining_rarity);
1396 xmlr_strd(node,"lua",temp->lua_file);
1397 if (xml_isNode(node,"radius")) {
1398 char *buf;
1399 temp->u.blt.radius = xml_getFloat(node);
1400 xmlr_attr_strd(node,"friendlyfire",buf);
1401 if (buf != NULL) {
1402 outfit_setProp(temp, OUTFIT_PROP_WEAP_FRIENDLYFIRE);
1403 free(buf);
1404 }
1405 continue;
1406 }
1407 if (xml_isNode(node,"pointdefense")) {
1408 outfit_setProp(temp, OUTFIT_PROP_WEAP_POINTDEFENSE);
1409 continue;
1410 }
1411 if (xml_isNode(node,"miss_ships")) {
1412 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_SHIPS);
1413 continue;
1414 }
1415 if (xml_isNode(node,"miss_asteroids")) {
1416 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_ASTEROIDS);
1417 continue;
1418 }
1419 if (xml_isNode(node,"miss_explode")) {
1420 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_EXPLODE);
1421 continue;
1422 }
1423 if (xml_isNode(node,"onlyhittarget")) {
1424 outfit_setProp(temp, OUTFIT_PROP_WEAP_ONLYHITTARGET);
1425 continue;
1426 }
1427 if (xml_isNode(node,"range")) {
1428 char *buf;
1429 xmlr_attr_strd(node,"blowup",buf);
1430 if (buf != NULL) {
1431 if (strcmp(buf,"armour")==0)
1432 outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_SHIELD);
1433 else if (strcmp(buf,"shield")==0)
1434 outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_ARMOUR);
1435 else
1436 WARN(_("Outfit '%s' has invalid blowup property: '%s'"),
1437 temp->name, buf );
1438 free(buf);
1439 }
1440 temp->u.blt.range = xml_getFloat(node);
1441 continue;
1442 }
1443 xmlr_float(node,"falloff",temp->u.blt.falloff);
1444
1445 /* Graphics. */
1446 if (xml_isNode(node,"gfx")) {
1447 outfit_loadGFX( temp, node );
1448 continue;
1449 }
1450 if (xml_isNode(node,"gfx_end")) {
1451 temp->u.blt.gfx.tex_end = xml_parseTexture( node,
1452 OUTFIT_GFX_PATH"space/%s", 6, 6, OPENGL_TEX_MIPMAPS );
1453 continue;
1454 }
1455
1456 /* Special effects. */
1457 if (xml_isNode(node,"spfx_shield")) {
1458 temp->u.blt.spfx_shield = spfx_get(xml_get(node));
1459 if (temp->u.blt.spfx_shield < 0)
1460 WARN(_("Outfit '%s' has unknown spfx_shield '%s'!"),temp->name,xml_get(node));
1461 continue;
1462 }
1463 if (xml_isNode(node,"spfx_armour")) {
1464 temp->u.blt.spfx_armour = spfx_get(xml_get(node));
1465 if (temp->u.blt.spfx_armour < 0)
1466 WARN(_("Outfit '%s' has unknown spfx_armour '%s'!"),temp->name,xml_get(node));
1467 continue;
1468 }
1469
1470 /* Misc. */
1471 if (xml_isNode(node,"sound")) {
1472 temp->u.blt.sound = sound_get( xml_get(node) );
1473 continue;
1474 }
1475 if (xml_isNode(node,"sound_hit")) {
1476 temp->u.blt.sound_hit = sound_get( xml_get(node) );
1477 continue;
1478 }
1479 if (xml_isNode(node,"damage")) {
1480 outfit_parseDamage( &temp->u.blt.dmg, node );
1481 continue;
1482 }
1483
1484 /* Stats. */
1485 ll = ss_listFromXML( node );
1486 if (ll != NULL) {
1487 ll->next = temp->stats;
1488 temp->stats = ll;
1489 continue;
1490 }
1491 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
1492 } while (xml_nextNode(node));
1493
1494 /* If not defined assume maximum. */
1495 if (temp->u.blt.falloff < 0.)
1496 temp->u.blt.falloff = temp->u.blt.range;
1497
1498 /* Post processing. */
1499 temp->u.blt.swivel *= M_PI/180.;
1500 temp->u.blt.dispersion *= M_PI/180.;
1501 if (outfit_isTurret(temp))
1502 temp->u.blt.swivel = M_PI;
1503 /*
1504 * dT Mthermal - Qweap
1505 * Hweap = ----------------------
1506 * tweap
1507 */
1508 C = pilot_heatCalcOutfitC(temp);
1509 area = pilot_heatCalcOutfitArea(temp);
1510 temp->u.blt.heat = ((800.-CONST_SPACE_STAR_TEMP)*C +
1511 STEEL_HEAT_CONDUCTIVITY * ((800.-CONST_SPACE_STAR_TEMP) * area)) /
1512 temp->u.blt.heatup * temp->u.blt.delay;
1513
1514 /* Set short description. */
1515 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
1516 l = 0;
1517 SDESC_ADD( l, temp, p_("outfitstats","%s [%s]"), _(outfit_getType(temp)),
1518 _(dtype_damageTypeToStr(temp->u.blt.dmg.type)) );
1519 dtype_raw( temp->u.blt.dmg.type, &dshield, &darmour, &dknockback );
1520 // new_opts(name, unit, colour, threshold, hidezero, precision)
1521 l = os_printD( temp->summary_raw, l, darmour*100., &darmour_opts );
1522 l = os_printD( temp->summary_raw, l, dshield*100., &dshield_opts );
1523 l = os_printD( temp->summary_raw, l, dknockback*100., &dknockback_opts );
1524 l = os_printD( temp->summary_raw, l, temp->cpu, &cpu_opts );
1525 l = os_printD( temp->summary_raw, l, temp->mass, &mass_opts );
1526 /* Higher level stats. */
1527 l = os_printD_rate( temp->summary_raw, l,
1528 temp->u.blt.dmg.damage, &damage_opts, temp->u.blt.shots,
1529 (double)temp->u.blt.shots * temp->u.blt.dmg.damage / temp->u.blt.delay, &dps_opts );
1530 l = os_printD_rate( temp->summary_raw, l,
1531 temp->u.blt.dmg.disable, &disable_opts, temp->u.blt.shots,
1532 (double) temp->u.blt.shots * temp->u.blt.dmg.disable / temp->u.blt.delay, &disable_rate_opts );
1533 l = os_printD_rate( temp->summary_raw, l,
1534 temp->u.blt.energy, &energy_opts, 1,
1535 (double)temp->u.blt.energy / temp->u.blt.delay, &power_opts );
1536 /* Standard stats. */
1537 l = os_printD( temp->summary_raw, l, temp->u.blt.dmg.penetration * 100., &penetration_opts );
1538 l = os_printD( temp->summary_raw, l, 1./temp->u.blt.delay, &fire_rate_opts );
1539 if (temp->u.blt.radius > 0.) {
1540 char radius[STRMAX_SHORT];
1541 snprintf(radius, sizeof(radius), outfit_isProp(temp, OUTFIT_PROP_WEAP_FRIENDLYFIRE) ? p_("friendlyfire","#r!! %s !!#0") : "%s", _("Hit radius"));
1542 const t_os_stat radius_opts = {
1543 .name = radius,
1544 .unit = _UNIT_DISTANCE,
1545 .colour = 0,
1546 .colour_threshold = 0,
1547 .hide_zero = 1,
1548 .precision = 0,
1549 };
1550 l = os_printD( temp->summary_raw, l, temp->u.blt.radius, &radius_opts );
1551 }
1552 l = os_printD( temp->summary_raw, l, temp->u.blt.range, &range_opts );
1553 l = os_printD( temp->summary_raw, l, temp->u.blt.speed, &speed_opts );
1554 l = os_printD( temp->summary_raw, l, temp->u.blt.heatup, &heatup_opts );
1555 l = os_printD( temp->summary_raw, l, temp->u.blt.dispersion*180./M_PI, &dispersion_opts );
1556 if (!outfit_isTurret(temp))
1557 l = os_printD( temp->summary_raw, l, temp->u.blt.swivel*180./M_PI, &swivel_opts );
1558 l = os_printD_range( temp->summary_raw, l, temp->u.blt.trackmin, temp->u.blt.trackmax, &tracking_opts );
1559 sdesc_miningRarity( &l, temp, temp->u.blt.mining_rarity );
1560
1561#define MELEMENT(o,s) \
1562if (o) WARN(_("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
1563 //MELEMENT(temp->u.blt.gfx_space==NULL,"gfx");
1564 MELEMENT(temp->u.blt.spfx_shield==-1,"spfx_shield");
1565 MELEMENT(temp->u.blt.spfx_armour==-1,"spfx_armour");
1566 MELEMENT((sound_disabled!=0) && (temp->u.blt.sound<0),"sound");
1567 MELEMENT(temp->mass==0.,"mass");
1568 MELEMENT(temp->u.blt.delay==0,"delay");
1569 MELEMENT(temp->u.blt.speed==0,"speed");
1570 MELEMENT(temp->u.blt.range==0,"range");
1571 MELEMENT(temp->u.blt.dmg.damage==0,"damage");
1572 MELEMENT(temp->u.blt.energy==0.,"energy");
1573 //MELEMENT(temp->cpu==0.,"cpu");
1574 MELEMENT(temp->u.blt.falloff > temp->u.blt.range,"falloff");
1575 MELEMENT(temp->u.blt.heatup==0.,"heatup");
1576 MELEMENT(((temp->u.blt.swivel > 0.) || outfit_isTurret(temp)) && (temp->u.blt.trackmin<0.),"trackmin");
1577 MELEMENT(((temp->u.blt.swivel > 0.) || outfit_isTurret(temp)) && (temp->u.blt.trackmax<0.),"trackmax");
1578#undef MELEMENT
1579}
1580
1587static void outfit_parseSBeam( Outfit* temp, const xmlNodePtr parent )
1588{
1589 ShipStatList *ll;
1590 int l;
1591 xmlNodePtr node;
1592 double C, area;
1593 double dshield, darmour, dknockback;
1594 char *shader;
1595
1596 /* Defaults. */
1597 temp->u.bem.spfx_armour = -1;
1598 temp->u.bem.spfx_shield = -1;
1599 temp->u.bem.sound_warmup = -1;
1600 temp->u.bem.sound = -1;
1601 temp->u.bem.sound_off = -1;
1602
1603 node = parent->xmlChildrenNode;
1604 do { /* load all the data */
1605 xml_onlyNodes(node);
1606 xmlr_float(node,"range",temp->u.bem.range);
1607 xmlr_float(node,"turn",temp->u.bem.turn);
1608 xmlr_float(node,"energy",temp->u.bem.energy);
1609 xmlr_float(node,"delay",temp->u.bem.delay);
1610 xmlr_float(node,"warmup",temp->u.bem.warmup);
1611 xmlr_float(node,"heatup",temp->u.bem.heatup);
1612 xmlr_float(node,"swivel",temp->u.bem.swivel);
1613 xmlr_int(node,"mining_rarity",temp->u.bem.mining_rarity);
1614 xmlr_strd(node,"lua",temp->lua_file);
1615 if (xml_isNode(node,"pointdefense")) {
1616 outfit_setProp(temp, OUTFIT_PROP_WEAP_POINTDEFENSE);
1617 continue;
1618 }
1619 if (xml_isNode(node,"miss_ships")) {
1620 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_SHIPS);
1621 continue;
1622 }
1623 if (xml_isNode(node,"miss_asteroids")) {
1624 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_ASTEROIDS);
1625 continue;
1626 }
1627
1628 if (xml_isNode(node, "duration")) {
1629 xmlr_attr_float(node, "min", temp->u.bem.min_duration);
1630 temp->u.bem.duration = xml_getFloat(node);
1631 continue;
1632 }
1633
1634 if (xml_isNode(node,"damage")) {
1635 outfit_parseDamage( &temp->u.bem.dmg, node );
1636 continue;
1637 }
1638
1639 /* Graphic stuff. */
1640 if (xml_isNode(node,"shader")) {
1641 xmlr_attr_float(node, "r", temp->u.bem.colour.r);
1642 xmlr_attr_float(node, "g", temp->u.bem.colour.g);
1643 xmlr_attr_float(node, "b", temp->u.bem.colour.b);
1644 xmlr_attr_float(node, "a", temp->u.bem.colour.a);
1645 xmlr_attr_float(node, "width", temp->u.bem.width);
1646 col_gammaToLinear( &temp->u.bem.colour );
1647 shader = xml_get(node);
1648 if (gl_has( OPENGL_SUBROUTINES )) {
1649 gl_contextSet();
1650 temp->u.bem.shader = glGetSubroutineIndex( shaders.beam.program, GL_FRAGMENT_SHADER, shader );
1651 if (temp->u.bem.shader == GL_INVALID_INDEX)
1652 WARN("Beam outfit '%s' has unknown shader function '%s'", temp->name, shader);
1653 gl_contextUnset();
1654 }
1655 continue;
1656 }
1657 if (xml_isNode(node,"spfx_armour")) {
1658 temp->u.bem.spfx_armour = spfx_get(xml_get(node));
1659 continue;
1660 }
1661 if (xml_isNode(node,"spfx_shield")) {
1662 temp->u.bem.spfx_shield = spfx_get(xml_get(node));
1663 continue;
1664 }
1665
1666 /* Sound stuff. */
1667 if (xml_isNode(node,"sound_warmup")) {
1668 temp->u.bem.sound_warmup = sound_get( xml_get(node) );
1669 continue;
1670 }
1671 if (xml_isNode(node,"sound")) {
1672 temp->u.bem.sound = sound_get( xml_get(node) );
1673 continue;
1674 }
1675 if (xml_isNode(node,"sound_off")) {
1676 temp->u.bem.sound_off = sound_get( xml_get(node) );
1677 continue;
1678 }
1679
1680 /* Stats. */
1681 ll = ss_listFromXML( node );
1682 if (ll != NULL) {
1683 ll->next = temp->stats;
1684 temp->stats = ll;
1685 continue;
1686 }
1687 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
1688 } while (xml_nextNode(node));
1689
1690 /* Post processing. */
1691 temp->u.bem.swivel *= M_PI/180.;
1692 temp->u.bem.turn *= M_PI/180.; /* Convert to rad/s. */
1693 C = pilot_heatCalcOutfitC(temp);
1694 area = pilot_heatCalcOutfitArea(temp);
1695 temp->u.bem.heat = ((800.-CONST_SPACE_STAR_TEMP)*C +
1696 STEEL_HEAT_CONDUCTIVITY * ((800.-CONST_SPACE_STAR_TEMP) * area)) /
1697 temp->u.bem.heatup * (temp->u.bem.delay+temp->u.bem.warmup+temp->u.bem.duration) / temp->u.bem.delay;
1698
1699 /* Set short description. */
1700 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
1701 l = 0;
1702 SDESC_ADD( l, temp, "%s [%s]", _(outfit_getType(temp)),_(dtype_damageTypeToStr(temp->u.bem.dmg.type)) );
1703 dtype_raw( temp->u.bem.dmg.type, &dshield, &darmour, &dknockback );
1704 l = os_printD( temp->summary_raw, l, darmour*100., &darmour_opts );
1705 l = os_printD( temp->summary_raw, l, dshield*100., &dshield_opts );
1706 l = os_printD( temp->summary_raw, l, dknockback*100., &dknockback_opts );
1707 l = os_printD( temp->summary_raw, l, temp->cpu, &cpu_opts );
1708 l = os_printD( temp->summary_raw, l, temp->mass, &mass_opts );
1709 /* Higher level stats. */
1710 l = os_printD( temp->summary_raw, l, temp->u.bem.dmg.damage, &dps_opts ); /* TODO display DPS also? */
1711 l = os_printD( temp->summary_raw, l, temp->u.bem.dmg.disable, &disable_rate_opts );
1712 l = os_printD( temp->summary_raw, l, temp->u.bem.energy, &power_opts );
1713 /* Standard stats. */
1714 l = os_printD( temp->summary_raw, l, temp->u.bem.dmg.penetration * 100, &penetration_opts );
1715 l = os_printD_range( temp->summary_raw, l,
1716 temp->u.bem.min_duration,
1717 temp->u.bem.duration,
1718 &duration_opts );
1719 l = os_printD( temp->summary_raw, l, temp->u.bem.delay, &cooldown_opts );
1720 l = os_printD( temp->summary_raw, l, temp->u.bem.range, &range_opts );
1721 l = os_printD( temp->summary_raw, l, temp->u.bem.heatup, &heatup_opts );
1722 if (!outfit_isTurret(temp))
1723 l = os_printD( temp->summary_raw, l, temp->u.bem.swivel*180./M_PI, &swivel_opts );
1724 sdesc_miningRarity( &l, temp, temp->u.bem.mining_rarity );
1725
1726#define MELEMENT(o,s) \
1727if (o) WARN( _("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
1728 MELEMENT(temp->u.bem.width==0.,"shader width");
1729 MELEMENT(temp->u.bem.spfx_shield==-1,"spfx_shield");
1730 MELEMENT(temp->u.bem.spfx_armour==-1,"spfx_armour");
1731 MELEMENT((sound_disabled!=0) && (temp->u.bem.warmup > 0.) && (temp->u.bem.sound<0),"sound_warmup");
1732 MELEMENT((sound_disabled!=0) && (temp->u.bem.sound<0),"sound");
1733 MELEMENT((sound_disabled!=0) && (temp->u.bem.sound_off<0),"sound_off");
1734 MELEMENT(temp->u.bem.delay==0,"delay");
1735 MELEMENT(temp->u.bem.duration==0,"duration");
1736 MELEMENT(temp->u.bem.min_duration < 0,"duration");
1737 MELEMENT(temp->u.bem.range==0,"range");
1738 MELEMENT((temp->type!=OUTFIT_TYPE_BEAM) && (temp->u.bem.turn==0),"turn");
1739 MELEMENT(temp->u.bem.energy==0.,"energy");
1740 MELEMENT(temp->cpu==0.,"cpu");
1741 MELEMENT(temp->u.bem.dmg.damage==0,"damage");
1742 MELEMENT(temp->u.bem.heatup==0.,"heatup");
1743#undef MELEMENT
1744}
1745
1752static void outfit_parseSLauncher( Outfit* temp, const xmlNodePtr parent )
1753{
1754 ShipStatList *ll;
1755 xmlNodePtr node;
1756 double dshield, darmour, dknockback;
1757 int l;
1758
1759 temp->u.lau.trackmin = -1.;
1760 temp->u.lau.trackmax = -1.;
1761 temp->u.lau.spfx_armour = -1;
1762 temp->u.lau.spfx_shield = -1;
1763 temp->u.lau.sound = -1;
1764 temp->u.lau.sound_hit = -1;
1765 temp->u.lau.trail_spec = NULL;
1766 temp->u.lau.ai = -1;
1767 temp->u.lau.speed_max = -1.;
1768 temp->u.lau.shots = 1;
1769
1770 node = parent->xmlChildrenNode;
1771 do { /* load all the data */
1772 xml_onlyNodes(node);
1773 xmlr_float(node,"delay",temp->u.lau.delay);
1774 xmlr_int(node,"amount",temp->u.lau.amount);
1775 xmlr_float(node,"reload_time",temp->u.lau.reload_time);
1776 xmlr_float(node,"trackmin",temp->u.lau.trackmin);
1777 xmlr_float(node,"trackmax",temp->u.lau.trackmax);
1778 xmlr_float(node,"lockon",temp->u.lau.lockon);
1779 xmlr_float(node,"iflockon",temp->u.lau.iflockon);
1780 xmlr_float(node,"swivel",temp->u.lau.swivel);
1781 xmlr_float(node,"dispersion",temp->u.lau.dispersion);
1782 xmlr_float(node,"speed_dispersion",temp->u.lau.speed_dispersion);
1783 xmlr_float(node,"armour",temp->u.lau.armour);
1784 xmlr_float(node,"absorb",temp->u.lau.dmg_absorb);
1785 xmlr_int(node,"shots",temp->u.lau.shots);
1786 xmlr_int(node,"mining_rarity",temp->u.lau.mining_rarity);
1787 xmlr_strd(node,"lua",temp->lua_file);
1788 if (xml_isNode(node,"radius")) {
1789 char *buf;
1790 temp->u.lau.radius = xml_getFloat(node);
1791 xmlr_attr_strd(node,"friendlyfire",buf);
1792 if (buf != NULL) {
1793 outfit_setProp(temp, OUTFIT_PROP_WEAP_FRIENDLYFIRE);
1794 free(buf);
1795 }
1796 continue;
1797 }
1798 if (xml_isNode(node,"pointdefense")) {
1799 outfit_setProp(temp, OUTFIT_PROP_WEAP_POINTDEFENSE);
1800 continue;
1801 }
1802 if (xml_isNode(node,"miss_ships")) {
1803 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_SHIPS);
1804 continue;
1805 }
1806 if (xml_isNode(node,"miss_asteroids")) {
1807 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_ASTEROIDS);
1808 continue;
1809 }
1810 if (xml_isNode(node,"miss_explode")) {
1811 outfit_setProp(temp, OUTFIT_PROP_WEAP_MISS_EXPLODE);
1812 continue;
1813 }
1814 if (xml_isNode(node,"onlyhittarget")) {
1815 outfit_setProp(temp, OUTFIT_PROP_WEAP_ONLYHITTARGET);
1816 continue;
1817 }
1818
1819 if (!outfit_isTurret(temp))
1820 xmlr_float(node,"arc",temp->u.lau.arc); /* This is in semi-arc like swivel. */
1821
1822 /* Ammo stuff. */
1823 /* Basic */
1824 if (xml_isNode(node,"duration")) {
1825 char *buf;
1826 xmlr_attr_strd(node,"blowup",buf);
1827 if (buf != NULL) {
1828 if (strcmp(buf,"armour")==0)
1829 outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_SHIELD);
1830 else if (strcmp(buf,"shield")==0)
1831 outfit_setProp(temp, OUTFIT_PROP_WEAP_BLOWUP_ARMOUR);
1832 else
1833 WARN(_("Outfit '%s' has invalid blowup property: '%s'"),
1834 temp->name, buf );
1835 free(buf);
1836 }
1837 temp->u.lau.duration = xml_getFloat(node);
1838 continue;
1839 }
1840 xmlr_float(node,"resist",temp->u.lau.resist);
1841 /* Movement */
1842 xmlr_float(node,"accel",temp->u.lau.accel);
1843 xmlr_float(node,"turn",temp->u.lau.turn);
1844 xmlr_float(node,"speed",temp->u.lau.speed);
1845 xmlr_float(node,"speed_max",temp->u.lau.speed_max);
1846 xmlr_float(node,"energy",temp->u.lau.energy);
1847 xmlr_float(node,"ammo_mass",temp->u.lau.ammo_mass);
1848 if (xml_isNode(node,"gfx")) {
1849 outfit_loadGFX( temp, node );
1850 continue;
1851 }
1852 if (xml_isNode(node,"spfx_armour")) {
1853 temp->u.lau.spfx_armour = spfx_get(xml_get(node));
1854 continue;
1855 }
1856 if (xml_isNode(node,"spfx_shield")) {
1857 temp->u.lau.spfx_shield = spfx_get(xml_get(node));
1858 continue;
1859 }
1860 if (xml_isNode(node,"sound")) {
1861 temp->u.lau.sound = sound_get( xml_get(node) );
1862 continue;
1863 }
1864 if (xml_isNode(node,"sound_hit")) {
1865 temp->u.lau.sound_hit = sound_get( xml_get(node) );
1866 continue;
1867 }
1868 if (xml_isNode(node,"damage")) {
1869 outfit_parseDamage( &temp->u.lau.dmg, node );
1870 continue;
1871 }
1872 if (xml_isNode(node,"trail_generator")) {
1873 xmlr_attr_float( node, "x", temp->u.lau.trail_x_offset );
1874 char *buf = xml_get(node);
1875 if (buf == NULL)
1876 buf = "default";
1877 temp->u.lau.trail_spec = trailSpec_get( buf );
1878 continue;
1879 }
1880 if (xml_isNode(node,"ai")) {
1881 char *buf = xml_get(node);
1882 if (buf != NULL) {
1883 if (strcmp(buf,"unguided")==0)
1884 temp->u.lau.ai = AMMO_AI_UNGUIDED;
1885 else if (strcmp(buf,"seek")==0)
1886 temp->u.lau.ai = AMMO_AI_SEEK;
1887 else if (strcmp(buf,"smart")==0)
1888 temp->u.lau.ai = AMMO_AI_SMART;
1889 else
1890 WARN(_("Ammo '%s' has unknown ai type '%s'."), temp->name, buf);
1891 }
1892 continue;
1893 }
1894
1895 /* Stats. */
1896 ll = ss_listFromXML( node );
1897 if (ll != NULL) {
1898 ll->next = temp->stats;
1899 temp->stats = ll;
1900 continue;
1901 }
1902 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
1903 } while (xml_nextNode(node));
1904
1905 /* Post processing. */
1906 if (!outfit_isProp(temp,OUTFIT_PROP_TEMPLATE)) {
1907 temp->mass -= temp->u.lau.ammo_mass*temp->u.lau.amount;
1908 if (temp->mass < 0.)
1909 WARN(_("Launcher outfit '%s' has negative mass when subtracting ammo mass!"), temp->name);
1910 }
1911 temp->u.lau.dmg_absorb /= 100.;
1912 temp->u.lau.swivel *= M_PI/180.;
1913 temp->u.lau.arc *= M_PI/180.;
1914 /* Note that arc will be 0. for turrets. */
1915 if (outfit_isTurret(temp))
1916 temp->u.lau.swivel = M_PI;
1917 temp->u.lau.dispersion *= M_PI/180.;
1918 temp->u.lau.turn *= M_PI/180.; /* Convert to rad/s. */
1919 if (temp->u.lau.speed_max < 0.)
1920 temp->u.lau.speed_max = temp->u.lau.speed;
1921 else if (temp->u.lau.speed > 0. && temp->u.lau.accel > 0.) /* Condition for not taking max_speed into account. */
1922 WARN(_("Max speed of ammo '%s' will be ignored."), temp->name);
1923 temp->u.lau.resist /= 100.;
1924
1925 /* Short description. */
1926 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
1927 l = 0;
1928 SDESC_ADD( l, temp, "%s [%s]", _(outfit_getType(temp)),
1929 _(dtype_damageTypeToStr(temp->u.lau.dmg.type)) );
1930 dtype_raw( temp->u.lau.dmg.type, &dshield, &darmour, &dknockback );
1931 l = os_printD( temp->summary_raw, l, darmour*100., &darmour_opts );
1932 l = os_printD( temp->summary_raw, l, dshield*100., &dshield_opts );
1933 l = os_printD( temp->summary_raw, l, dknockback*100., &dknockback_opts );
1934 l = os_printD( temp->summary_raw, l, temp->cpu, &cpu_opts );
1935 l = os_printD( temp->summary_raw, l, temp->mass+temp->u.lau.ammo_mass*temp->u.lau.amount, &mass_opts ); /* Include ammo. */
1936 /* Higher level stats. */
1937 l = os_printD_rate( temp->summary_raw, l,
1938 temp->u.lau.dmg.damage, &damage_opts, temp->u.lau.shots,
1939 temp->u.lau.dmg.damage * (double)temp->u.lau.shots / temp->u.lau.delay, &dps_opts );
1940 l = os_printD_rate( temp->summary_raw, l,
1941 temp->u.lau.dmg.disable, &disable_opts, temp->u.lau.shots,
1942 temp->u.lau.dmg.disable * (double)temp->u.lau.shots / temp->u.lau.delay, &disable_rate_opts );
1943 l = os_printD_rate( temp->summary_raw, l,
1944 temp->u.lau.energy, &energy_opts, 1,
1945 temp->u.lau.delay * temp->u.lau.energy, &power_opts );
1946 /* Standard stats. */
1947 l = os_printD( temp->summary_raw, l, temp->u.lau.dmg.penetration*100., &penetration_opts );
1948 if (outfit_isSeeker(temp)) {
1949 l = os_printD( temp->summary_raw, l, temp->u.lau.lockon, &lockon_opts );
1950 l = os_printD( temp->summary_raw, l, temp->u.lau.iflockon, &inflight_calib_opts );
1951 l = os_printD_range( temp->summary_raw, l, temp->u.lau.trackmin, temp->u.lau.trackmax, &tracking_opts );
1952 }
1953 else {
1954 SDESC_ADD( l, temp, "\n%s", _("No Seeking") );
1955 if (outfit_isTurret(temp) || temp->u.lau.swivel > 0.) {
1956 l = os_printD_range( temp->summary_raw, l, temp->u.lau.trackmin, temp->u.lau.trackmax, &tracking_opts );
1957 l = os_printD( temp->summary_raw, l, temp->u.lau.swivel*180./M_PI, &swivel_opts );
1958 }
1959 }
1960
1961 SDESC_ADD( l, temp, _("\n Holds %d ammo"), temp->u.lau.amount );
1962 if (temp->u.lau.radius > 0.) {
1963 char radius[STRMAX_SHORT];
1964 snprintf(radius, sizeof(radius), outfit_isProp(temp, OUTFIT_PROP_WEAP_FRIENDLYFIRE) ? p_("friendlyfire","#r!! %s !!#0") : "%s", _("Hit radius"));
1965 t_os_stat radius_opts = {
1966 .name = radius,
1967 .unit = _UNIT_DISTANCE,
1968 .colour = 0,
1969 .colour_threshold = 0,
1970 .hide_zero = 1,
1971 .precision = 0,
1972 };
1973 l = os_printD( temp->summary_raw, l, temp->u.lau.radius, &radius_opts );
1974 }
1975 l = os_printD( temp->summary_raw, l, 1. / temp->u.lau.delay, &fire_rate_opts);
1976 l = os_printD( temp->summary_raw, l, outfit_range(temp), &range_opts );
1977 //l = os_printD( temp->summary_raw, l, temp->u.lau.duration, &duration_opts );
1978
1979 if (temp->u.lau.accel > 0.) {
1980 if (temp->u.lau.speed > 0.)
1981 l = os_printD( temp->summary_raw, l, temp->u.lau.speed, &initial_speed_opts );
1982 l = os_printD( temp->summary_raw, l, temp->u.lau.accel, &accel_opts );
1983 }
1984 else
1985 l = os_printD( temp->summary_raw, l, temp->u.lau.speed, &initial_speed_opts );
1986 if (!(temp->u.lau.accel > 0. && temp->u.lau.speed > 0.))
1987 l = os_printD( temp->summary_raw, l, temp->u.lau.speed_max, &max_speed_opts );
1988 l = os_printD( temp->summary_raw, l, temp->u.lau.reload_time, &reload_opts );
1989 l = os_printD( temp->summary_raw, l, temp->u.lau.armour, &armour_opts );
1990 l = os_printD( temp->summary_raw, l, temp->u.lau.dmg_absorb*100., &absorp_opts );
1991 l = os_printD( temp->summary_raw, l, temp->u.lau.resist*100., &jam_res_opts );
1992 sdesc_miningRarity( &l, temp, temp->u.lau.mining_rarity );
1993
1994#define MELEMENT(o,s) \
1995if (o) WARN(_("Outfit '%s' missing '%s' element"), temp->name, s)
1996 MELEMENT(temp->u.lau.delay==0.,"delay");
1997 //MELEMENT(temp->cpu==0.,"cpu");
1998 MELEMENT(temp->u.lau.amount==0.,"amount");
1999 MELEMENT(temp->u.lau.reload_time==0.,"reload_time");
2000 MELEMENT(temp->u.lau.ammo_mass==0.,"mass");
2001 //MELEMENT(!outfit_isProp(temp,OUTFIT_PROP_SHOOT_DRY)&&temp->u.lau.gfx_space==NULL,"gfx");
2002 MELEMENT(!outfit_isProp(temp,OUTFIT_PROP_SHOOT_DRY)&&temp->u.lau.spfx_shield==-1,"spfx_shield");
2003 MELEMENT(!outfit_isProp(temp,OUTFIT_PROP_SHOOT_DRY)&&temp->u.lau.spfx_armour==-1,"spfx_armour");
2004 MELEMENT((sound_disabled!=0) && (temp->u.lau.sound<0),"sound");
2005 /* MELEMENT(temp->u.lau.accel==0,"accel"); */
2006 /* Unguided missiles don't need everything */
2007 if (outfit_isSeeker(temp)) {
2008 MELEMENT(temp->u.lau.turn==0,"turn");
2009 MELEMENT(temp->u.lau.trackmin<0,"trackmin");
2010 MELEMENT(temp->u.lau.trackmax<0,"trackmax");
2011 MELEMENT(temp->u.lau.lockon<0,"lockon");
2012 MELEMENT(!outfit_isTurret(temp) && (temp->u.lau.arc==0.),"arc");
2013 }
2014 MELEMENT(!outfit_isProp(temp,OUTFIT_PROP_SHOOT_DRY)&&temp->u.lau.speed_max==0,"speed_max");
2015 MELEMENT(!outfit_isProp(temp,OUTFIT_PROP_SHOOT_DRY)&&temp->u.lau.duration==0,"duration");
2016 MELEMENT(temp->u.lau.dmg.damage==0,"damage");
2017 /*MELEMENT(temp->u.lau.energy==0.,"energy");*/
2018#undef MELEMENT
2019 if (!outfit_isProp(temp,OUTFIT_PROP_SHOOT_DRY)&&temp->u.lau.speed==0. && temp->u.lau.accel==0.)
2020 WARN(_("Outfit '%s' has no speed nor accel set!"), temp->name);
2021 if (!outfit_isProp(temp,OUTFIT_PROP_SHOOT_DRY)&&temp->u.lau.iflockon >= temp->u.lau.duration)
2022 WARN(_("Outfit '%s' has longer 'iflockon' than ammo 'duration'"), temp->name);
2023}
2024
2031static void outfit_parseSMod( Outfit* temp, const xmlNodePtr parent )
2032{
2033 xmlNodePtr node = parent->children;
2034
2035 do { /* load all the data */
2036 ShipStatList *ll;
2037
2038 xml_onlyNodes(node);
2039 xmlr_strd(node,"lua",temp->lua_file);
2040
2041 if (xml_isNode(node,"active")) {
2042 xmlr_attr_float(node, "cooldown", temp->u.mod.cooldown);
2043 temp->u.mod.active = 1;
2044 temp->u.mod.duration = xml_getFloat(node);
2045
2046 /* Infinity if no duration specified. */
2047 if (temp->u.mod.duration == 0)
2048 temp->u.mod.duration = INFINITY;
2049
2050 continue;
2051 }
2052
2053 /* Stats. */
2054 ll = ss_listFromXML( node );
2055 if (ll != NULL) {
2056 ll->next = temp->stats;
2057 temp->stats = ll;
2058 continue;
2059 }
2060
2061 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2062 } while (xml_nextNode(node));
2063
2064 /* Set short description. */
2065 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2066 /* Modifier outfits are actually done in the third pass... */
2067}
2068
2075static void outfit_parseSAfterburner( Outfit* temp, const xmlNodePtr parent )
2076{
2077 double C, area;
2078 size_t l;
2079 xmlNodePtr node = parent->children;
2080
2081 /* Defaults. */
2082 temp->u.afb.sound = -1;
2083 temp->u.afb.sound_on = -1;
2084 temp->u.afb.sound_off = -1;
2085
2086 /* must be >= 1. */
2087 temp->u.afb.accel = 1.;
2088 temp->u.afb.speed = 1.;
2089
2090 do { /* parse the data */
2091 ShipStatList *ll;
2092
2093 xml_onlyNodes(node);
2094 xmlr_float(node,"rumble",temp->u.afb.rumble);
2095 xmlr_strd(node,"lua",temp->lua_file);
2096 if (xml_isNode(node,"sound_on")) {
2097 temp->u.afb.sound_on = sound_get( xml_get(node) );
2098 continue;
2099 }
2100 if (xml_isNode(node,"sound")) {
2101 temp->u.afb.sound = sound_get( xml_get(node) );
2102 continue;
2103 }
2104 if (xml_isNode(node,"sound_off")) {
2105 temp->u.afb.sound_off = sound_get( xml_get(node) );
2106 continue;
2107 }
2108 xmlr_float(node,"accel",temp->u.afb.accel);
2109 xmlr_float(node,"speed",temp->u.afb.speed);
2110 xmlr_float(node,"energy",temp->u.afb.energy);
2111 xmlr_float(node,"mass_limit",temp->u.afb.mass_limit);
2112 xmlr_float(node,"heatup",temp->u.afb.heatup);
2113 xmlr_float(node,"heat_cap",temp->overheat_max);
2114 xmlr_float(node,"heat_base",temp->overheat_min);
2115
2116 /* Stats. */
2117 ll = ss_listFromXML( node );
2118 if (ll != NULL) {
2119 ll->next = temp->stats;
2120 temp->stats = ll;
2121 continue;
2122 }
2123 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2124 } while (xml_nextNode(node));
2125
2126 /* Set short description. */
2127 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2128 l = 0;
2129 SDESC_ADD( l, temp, "%s", _(outfit_getType(temp)) );
2130 SDESC_ADD( l, temp, "\n#o%s#0", _("Activated Outfit") );
2131
2132 l = os_printD( temp->summary_raw, l, temp->cpu, &cpu_opts );
2133 l = os_printD( temp->summary_raw, l, temp->mass, &mass_opts );
2134 l = os_printD( temp->summary_raw, l, temp->u.afb.mass_limit, &max_mass_opts );
2135 SDESC_ADD( l, temp, "\n%s", _("Only one can be equipped") );
2136 l = os_printD( temp->summary_raw, l, temp->u.afb.accel+100., &accel_opts );
2137 l = os_printD( temp->summary_raw, l, temp->u.afb.speed+100., &max_speed_opts );
2138 l = os_printD( temp->summary_raw, l, temp->u.afb.energy, &power_opts );
2139 l = os_printD( temp->summary_raw, l, temp->u.afb.rumble, &rumble_opts );
2140
2141 /* Post processing. */
2142 temp->u.afb.accel /= 100.;
2143 temp->u.afb.speed /= 100.;
2144 C = pilot_heatCalcOutfitC(temp);
2145 area = pilot_heatCalcOutfitArea(temp);
2146 temp->u.afb.heat = ((800.-CONST_SPACE_STAR_TEMP)*C +
2147 STEEL_HEAT_CONDUCTIVITY * ((800.-CONST_SPACE_STAR_TEMP) * area)) /
2148 temp->u.afb.heatup;
2149
2150#define MELEMENT(o,s) \
2151if (o) WARN(_("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
2152 MELEMENT(temp->u.afb.accel==0.,"accel");
2153 MELEMENT(temp->u.afb.speed==0.,"speed");
2154 MELEMENT(temp->u.afb.energy==0.,"energy");
2155 //MELEMENT(temp->cpu==0.,"cpu");
2156 MELEMENT(temp->u.afb.mass_limit==0.,"mass_limit");
2157 MELEMENT(temp->u.afb.heatup==0.,"heatup");
2158#undef MELEMENT
2159}
2160
2167static void outfit_parseSFighterBay( Outfit *temp, const xmlNodePtr parent )
2168{
2169 size_t l;
2170 xmlNodePtr node = parent->children;
2171
2172 do {
2173 ShipStatList *ll;
2174
2175 xml_onlyNodes(node);
2176 xmlr_float(node,"delay",temp->u.bay.delay);
2177 xmlr_float(node,"reload_time",temp->u.bay.reload_time);
2178 xmlr_strd(node,"ship",temp->u.bay.shipname);
2179 xmlr_float(node,"ship_mass",temp->u.bay.ship_mass);
2180 xmlr_int(node,"amount",temp->u.bay.amount);
2181 xmlr_strd(node,"lua",temp->lua_file);
2182
2183 /* Stats. */
2184 ll = ss_listFromXML( node );
2185 if (ll != NULL) {
2186 ll->next = temp->stats;
2187 temp->stats = ll;
2188 continue;
2189 }
2190 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2191 } while (xml_nextNode(node));
2192
2193 /* Post-processing. */
2194 temp->mass -= temp->u.bay.ship_mass*temp->u.bay.amount;
2195 if (temp->mass < 0.)
2196 WARN(_("Fighter bay outfit '%s' has negative mass when subtracting ship mass!"), temp->name);
2197
2198 /* Set short description. */
2199 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2200 l = 0;
2201 SDESC_ADD( l, temp, "%s", _(outfit_getType(temp)) );
2202 l = os_printD( temp->summary_raw, l, temp->cpu, &cpu_opts );
2203 l = os_printD( temp->summary_raw, l, temp->mass+temp->u.bay.ship_mass*temp->u.bay.amount, &mass_opts );
2204 SDESC_ADD( l, temp, _("\n Holds %d ships"), temp->u.bay.amount );
2205 l = os_printD( temp->summary_raw, l, temp->u.bay.delay, &shots_delay_opts );
2206 l = os_printD( temp->summary_raw, l, temp->u.bay.reload_time, &reload_opts );
2207
2208#define MELEMENT(o,s) \
2209if (o) WARN(_("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
2210 MELEMENT(temp->u.bay.shipname==NULL,"ship");
2211 MELEMENT(temp->u.bay.ship_mass<=0.,"ship_mass");
2212 MELEMENT(temp->u.bay.delay==0,"delay");
2213 MELEMENT(temp->u.bay.reload_time==0.,"reload_time");
2214 MELEMENT(temp->cpu==0.,"cpu");
2215 MELEMENT(temp->u.bay.amount==0,"amount");
2216#undef MELEMENT
2217}
2218
2225static void outfit_parseSMap( Outfit *temp, const xmlNodePtr parent )
2226{
2227 xmlNodePtr node;
2228 char *buf;
2229
2230 node = parent->children;
2231
2232 temp->slot.type = OUTFIT_SLOT_NA;
2233 temp->slot.size = OUTFIT_SLOT_SIZE_NA;
2234
2235 temp->u.map->systems = array_create(StarSystem*);
2236 temp->u.map->spobs = array_create(Spob*);
2237 temp->u.map->jumps = array_create(JumpPoint*);
2238
2239 do {
2240 xml_onlyNodes(node);
2241
2242 if (xml_isNode(node,"sys")) {
2243 xmlr_attr_strd(node,"name",buf);
2244 StarSystem *sys = system_get(buf);
2245 if (sys == NULL) {
2246 WARN(_("Map '%s' has invalid system '%s'"), temp->name, buf);
2247 free(buf);
2248 continue;
2249 }
2250
2251 free(buf);
2252 array_push_back( &temp->u.map->systems, sys );
2253
2254 xmlNodePtr cur = node->children;
2255 do {
2256 xml_onlyNodes(cur);
2257
2258 if (xml_isNode(cur,"spob")) {
2259 buf = xml_get(cur);
2260 Spob *spob = (buf != NULL) ? spob_get(buf) : NULL;
2261 if ((buf != NULL) && (spob != NULL))
2262 array_push_back( &temp->u.map->spobs, spob );
2263 else
2264 WARN(_("Map '%s' has invalid spob '%s'"), temp->name, buf);
2265 }
2266 else if (xml_isNode(cur,"jump")) {
2267 JumpPoint *jump;
2268 buf = xml_get(cur);
2269 if ((buf != NULL) && ((jump = jump_get(xml_get(cur),
2270 temp->u.map->systems[array_size(temp->u.map->systems)-1] )) != NULL))
2271 array_push_back( &temp->u.map->jumps, jump );
2272 else
2273 WARN(_("Map '%s' has invalid jump point '%s'"), temp->name, buf);
2274 }
2275 else
2276 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, cur->name);
2277 } while (xml_nextNode(cur));
2278 }
2279 else if (xml_isNode(node,"short_desc")) {
2280 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2281 snprintf( temp->summary_raw, OUTFIT_SHORTDESC_MAX, "%s", xml_get(node) );
2282 }
2283 else if (xml_isNode(node,"all")) { /* Add everything to the map */
2284 StarSystem *system_stack = system_getAll();
2285 for (int i=0;i<array_size(system_stack);i++) {
2286 StarSystem *ss = &system_stack[i];
2287 array_push_back( &temp->u.map->systems, ss );
2288 for (int j=0;j<array_size(system_stack[i].spobs);j++)
2289 array_push_back( &temp->u.map->spobs, ss->spobs[j] );
2290 for (int j=0;j<array_size(system_stack[i].jumps);j++)
2291 array_push_back( &temp->u.map->jumps, &ss->jumps[j] );
2292 }
2293 }
2294 else
2295 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2296 } while (xml_nextNode(node));
2297
2298 array_shrink( &temp->u.map->systems );
2299 array_shrink( &temp->u.map->spobs );
2300 array_shrink( &temp->u.map->jumps );
2301
2302 if (temp->summary_raw == NULL) {
2303 /* Set short description based on type. */
2304 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2305 snprintf( temp->summary_raw, OUTFIT_SHORTDESC_MAX,
2306 "%s", _(outfit_getType(temp)) );
2307 }
2308
2309#define MELEMENT(o,s) \
2310if (o) WARN(_("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
2312 MELEMENT(temp->mass!=0.,"cpu");
2313 MELEMENT(temp->cpu!=0.,"cpu");
2314#undef MELEMENT
2315}
2316
2323static void outfit_parseSLocalMap( Outfit *temp, const xmlNodePtr parent )
2324{
2325 xmlNodePtr node = parent->children;
2326
2327 temp->slot.type = OUTFIT_SLOT_NA;
2328 temp->slot.size = OUTFIT_SLOT_SIZE_NA;
2329
2330 do {
2331 xml_onlyNodes(node);
2332 xmlr_float(node,"spob_detect",temp->u.lmap.spob_detect);
2333 xmlr_float(node,"jump_detect",temp->u.lmap.jump_detect);
2334 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2335 } while (xml_nextNode(node));
2336
2337 temp->u.lmap.spob_detect = pow2( temp->u.lmap.spob_detect );
2338 temp->u.lmap.jump_detect = pow2( temp->u.lmap.jump_detect );
2339
2340 /* Set short description. */
2341 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2342 snprintf( temp->summary_raw, OUTFIT_SHORTDESC_MAX,
2343 "%s",
2344 _(outfit_getType(temp)) );
2345
2346#define MELEMENT(o,s) \
2347if (o) WARN(_("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
2349 MELEMENT(temp->mass!=0.,"cpu");
2350 MELEMENT(temp->cpu!=0.,"cpu");
2351#undef MELEMENT
2352}
2353
2360static void outfit_parseSGUI( Outfit *temp, const xmlNodePtr parent )
2361{
2362 xmlNodePtr node = parent->children;
2363
2364 temp->slot.type = OUTFIT_SLOT_NA;
2365 temp->slot.size = OUTFIT_SLOT_SIZE_NA;
2366
2367 do {
2368 xml_onlyNodes(node);
2369 xmlr_strd(node,"gui",temp->u.gui.gui);
2370 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2371 } while (xml_nextNode(node));
2372
2373 /* Set short description. */
2374 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2375 snprintf( temp->summary_raw, OUTFIT_SHORTDESC_MAX,
2376 _("GUI (Graphical User Interface)") );
2377
2378#define MELEMENT(o,s) \
2379if (o) WARN(_("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
2381 MELEMENT(temp->u.gui.gui==NULL,"gui");
2382 MELEMENT(temp->mass!=0.,"cpu");
2383 MELEMENT(temp->cpu!=0.,"cpu");
2384#undef MELEMENT
2385}
2386
2393static void outfit_parseSLicense( Outfit *temp, const xmlNodePtr parent )
2394{
2395 xmlNodePtr node = parent->children;
2396
2397 temp->slot.type = OUTFIT_SLOT_NA;
2398 temp->slot.size = OUTFIT_SLOT_SIZE_NA;
2399
2400 do {
2401 xml_onlyNodes(node);
2402 xmlr_strd(node,"provides",temp->u.lic.provides);
2403 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2404 } while (xml_nextNode(node));
2405
2406 if (temp->u.lic.provides==NULL)
2407 temp->u.lic.provides = strdup( temp->name );
2408
2409 /* Set short description. */
2410 temp->summary_raw = malloc( OUTFIT_SHORTDESC_MAX );
2411 snprintf( temp->summary_raw, OUTFIT_SHORTDESC_MAX,
2412 "%s",
2413 _(outfit_getType(temp)) );
2414
2415#define MELEMENT(o,s) \
2416if (o) WARN(_("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
2418 MELEMENT(temp->mass!=0.,"cpu");
2419 MELEMENT(temp->cpu!=0.,"cpu");
2420#undef MELEMENT
2421}
2422
2430static int outfit_parse( Outfit* temp, const char* file )
2431{
2432 xmlNodePtr node, parent;
2433 char *prop;
2434 const char *cprop;
2435 int group;
2436
2437 xmlDocPtr doc = xml_parsePhysFS( file );
2438 if (doc == NULL)
2439 return -1;
2440
2441 parent = doc->xmlChildrenNode; /* first outfit node */
2442 if (parent == NULL) {
2443 ERR( _("Malformed '%s' file: does not contain elements"), file);
2444 return -1;
2445 }
2446
2447 /* Clear data. */
2448 memset( temp, 0, sizeof(Outfit) );
2449 temp->filename = strdup( file );
2450
2451 /* Defaults. */
2452 temp->overheat_min = 350.;
2453 temp->overheat_max = 500.;
2454 temp->lua_env = LUA_NOREF;
2455 temp->lua_descextra = LUA_NOREF;
2456 temp->lua_onadd = LUA_NOREF;
2457 temp->lua_onremove = LUA_NOREF;
2458 temp->lua_init = LUA_NOREF;
2459 temp->lua_cleanup = LUA_NOREF;
2460 temp->lua_update = LUA_NOREF;
2461 temp->lua_ontoggle = LUA_NOREF;
2462 temp->lua_onshoot = LUA_NOREF;
2463 temp->lua_onhit = LUA_NOREF;
2464 temp->lua_outofenergy= LUA_NOREF;
2465 temp->lua_onshootany = LUA_NOREF;
2466 temp->lua_onstealth = LUA_NOREF;
2467 temp->lua_onscanned = LUA_NOREF;
2468 temp->lua_onscan = LUA_NOREF;
2469 temp->lua_cooldown = LUA_NOREF;
2470 temp->lua_land = LUA_NOREF;
2471 temp->lua_takeoff = LUA_NOREF;
2472 temp->lua_jumpin = LUA_NOREF;
2473 temp->lua_board = LUA_NOREF;
2474 temp->lua_keydoubletap=LUA_NOREF;
2475 temp->lua_keyrelease = LUA_NOREF;
2476 temp->lua_onimpact = LUA_NOREF;
2477 temp->lua_onmiss = LUA_NOREF;
2478 temp->lua_price = LUA_NOREF;
2479 temp->lua_buy = LUA_NOREF;
2480 temp->lua_sell = LUA_NOREF;
2481
2482 xmlr_attr_strd(parent,"name",temp->name);
2483 if (temp->name == NULL)
2484 WARN(_("Outfit '%s' has invalid or no name"), file);
2485
2486 node = parent->xmlChildrenNode;
2487
2488 do { /* load all the data */
2489
2490 /* Only handle nodes. */
2491 xml_onlyNodes(node);
2492
2493 if (xml_isNode(node,"general")) {
2494 xmlNodePtr cur = node->children;
2495 do {
2496 xml_onlyNodes(cur);
2497 xmlr_int(cur,"rarity",temp->rarity);
2498 xmlr_strd(cur,"shortname",temp->shortname);
2499 xmlr_strd(cur,"license",temp->license);
2500 xmlr_strd(cur,"cond",temp->cond);
2501 xmlr_strd(cur,"condstr",temp->condstr);
2502 xmlr_float(cur,"mass",temp->mass);
2503 xmlr_float(cur,"cpu",temp->cpu);
2504 xmlr_long(cur,"price",temp->price);
2505 xmlr_strd(cur,"limit",temp->limit);
2506 xmlr_strd(cur,"description",temp->desc_raw);
2507 xmlr_strd(cur,"desc_extra",temp->desc_extra);
2508 xmlr_strd(cur,"typename",temp->typename);
2509 xmlr_int(cur,"priority",temp->priority);
2510 xmlr_float(cur,"overheat_min",temp->overheat_min);
2511 xmlr_float(cur,"overheat_max",temp->overheat_max);
2512 if (xml_isNode(cur,"unique")) {
2513 outfit_setProp(temp, OUTFIT_PROP_UNIQUE);
2514 continue;
2515 }
2516 if (xml_isNode(cur,"shoot_dry")) {
2517 outfit_setProp(temp, OUTFIT_PROP_SHOOT_DRY);
2518 continue;
2519 }
2520 if (xml_isNode(cur,"template")) {
2521 outfit_setProp(temp, OUTFIT_PROP_TEMPLATE);
2522 continue;
2523 }
2524 else if (xml_isNode(cur,"gfx_store")) {
2525 temp->gfx_store = xml_parseTexture( cur,
2526 OUTFIT_GFX_PATH"store/%s", 1, 1, OPENGL_TEX_MIPMAPS );
2527 continue;
2528 }
2529 else if (xml_isNode(cur,"gfx_overlays")) {
2530 xmlNodePtr ccur = cur->children;
2532 do {
2533 xml_onlyNodes(ccur);
2534 if (xml_isNode(ccur,"gfx_overlay"))
2536 xml_parseTexture( ccur, OVERLAY_GFX_PATH"%s", 1, 1, OPENGL_TEX_MIPMAPS ) );
2537 } while (xml_nextNode(ccur));
2538 continue;
2539 }
2540 else if (xml_isNode(cur,"slot")) {
2541 cprop = xml_get(cur);
2542 if (cprop == NULL)
2543 WARN(_("Outfit '%s' has an slot type invalid."), temp->name);
2544 else if (strcmp(cprop,"structure") == 0)
2545 temp->slot.type = OUTFIT_SLOT_STRUCTURE;
2546 else if (strcmp(cprop,"utility") == 0)
2547 temp->slot.type = OUTFIT_SLOT_UTILITY;
2548 else if (strcmp(cprop,"weapon") == 0)
2549 temp->slot.type = OUTFIT_SLOT_WEAPON;
2550 else if (strcmp(cprop,"intrinsic") == 0)
2551 temp->slot.type = OUTFIT_SLOT_INTRINSIC;
2552 else if (strcmp(cprop,"none") == 0)
2553 temp->slot.type = OUTFIT_SLOT_NA;
2554 else
2555 WARN(_("Outfit '%s' has unknown slot type '%s'."), temp->name, cprop);
2556
2557 /* Property. */
2558 xmlr_attr_strd( cur, "prop", prop );
2559 if (prop != NULL)
2560 temp->slot.spid = sp_get( prop );
2561 free( prop );
2562 continue;
2563 }
2564 else if (xml_isNode(cur,"size")) {
2565 temp->slot.size = outfit_toSlotSize( xml_get(cur) );
2566 continue;
2567 }
2568 else if (xml_isNode(cur, "illegalto")) {
2569 xmlNodePtr ccur = cur->xmlChildrenNode;
2570 temp->illegaltoS = array_create( char* );
2571 do {
2572 xml_onlyNodes(ccur);
2573 if (xml_isNode(ccur, "faction")) {
2574 const char *s = xml_get(ccur);
2575 if (s==NULL)
2576 WARN(_("Empty faction string for outfit '%s' legality!"), temp->name);
2577 else
2578 array_push_back( &temp->illegaltoS, strdup(s) );
2579 }
2580 } while (xml_nextNode(ccur));
2581 if (array_size(temp->illegaltoS) <= 0 )
2582 WARN(_("Outfit '%s' has no factions defined in <illegalto> block!"), temp->name);
2583 continue;
2584 }
2585 WARN(_("Outfit '%s' has unknown general node '%s'"),temp->name, cur->name);
2586 } while (xml_nextNode(cur));
2587 continue;
2588 }
2589
2590 if (xml_isNode(node,"stats")) {
2591 xmlNodePtr cur = node->children;
2592 do {
2593 ShipStatList *ll;
2594 xml_onlyNodes(cur);
2595 /* Stats. */
2596 ll = ss_listFromXML( cur );
2597 if (ll != NULL) {
2598 ll->next = temp->stats;
2599 temp->stats = ll;
2600 continue;
2601 }
2602 WARN(_("Outfit '%s' has unknown node '%s'"), temp->name, cur->name);
2603 } while (xml_nextNode(cur));
2604 continue;
2605 }
2606
2607 /* Parse tags. */
2608 if (xml_isNode(node, "tags")) {
2609 xmlNodePtr cur = node->children;
2610 temp->tags = array_create( char* );
2611 do {
2612 xml_onlyNodes(cur);
2613 if (xml_isNode(cur, "tag")) {
2614 const char *tmp = xml_get(cur);
2615 if (tmp != NULL)
2616 array_push_back( &temp->tags, strdup(tmp) );
2617 continue;
2618 }
2619 WARN(_("Outfit '%s' has unknown node in tags '%s'."), temp->name, cur->name );
2620 } while (xml_nextNode(cur));
2621 continue;
2622 }
2623
2624 if (xml_isNode(node,"specific")) { /* has to be processed separately */
2625 /* get the type */
2626 xmlr_attr_strd(node, "type", prop);
2627 if (prop == NULL)
2628 ERR(_("Outfit '%s' element 'specific' missing property 'type'"),temp->name);
2629 temp->type = outfit_strToOutfitType(prop);
2630 free(prop);
2631
2632 /* is secondary weapon? */
2633 xmlr_attr_strd(node, "secondary", prop);
2634 if (prop != NULL) {
2635 if ((int)atoi(prop))
2636 outfit_setProp(temp, OUTFIT_PROP_WEAP_SECONDARY);
2637 free(prop);
2638 }
2639
2640 /* Check for manually-defined group. */
2641 xmlr_attr_int_def(node, "group", group, -1);
2642 if (group != -1) {
2643 if (group > PILOT_WEAPON_SETS || group < 1) {
2644 WARN(_("Outfit '%s' has group '%d', should be in the 1-%d range"),
2645 temp->name, group, PILOT_WEAPON_SETS);
2646 }
2647
2648 temp->group = CLAMP(0, 9, group);
2649 }
2650
2651 /*
2652 * Parse type.
2653 */
2654 if (temp->type==OUTFIT_TYPE_NULL)
2655 WARN(_("Outfit '%s' is of type NONE"), temp->name);
2656 else if (outfit_isBolt(temp))
2657 outfit_parseSBolt( temp, node );
2658 else if (outfit_isBeam(temp))
2659 outfit_parseSBeam( temp, node );
2660 else if (outfit_isLauncher(temp))
2661 outfit_parseSLauncher( temp, node );
2662 else if (outfit_isMod(temp))
2663 outfit_parseSMod( temp, node );
2664 else if (outfit_isAfterburner(temp))
2665 outfit_parseSAfterburner( temp, node );
2666 else if (outfit_isFighterBay(temp))
2667 outfit_parseSFighterBay( temp, node );
2668 else if (outfit_isMap(temp)) {
2669 temp->u.map = malloc( sizeof(OutfitMapData_t) );
2670 temp->slot.type = OUTFIT_SLOT_NA;
2671 temp->slot.size = OUTFIT_SLOT_SIZE_NA;
2672 }
2673 else if (outfit_isLocalMap(temp))
2674 outfit_parseSLocalMap( temp, node );
2675 else if (outfit_isGUI(temp))
2676 outfit_parseSGUI( temp, node );
2677 else if (outfit_isLicense(temp))
2678 outfit_parseSLicense( temp, node );
2679
2680 /* Sort stats. */
2681 ss_sort( &temp->stats );
2682
2683 continue;
2684 }
2685 WARN(_("Outfit '%s' has unknown node '%s'"),temp->name, node->name);
2686 } while (xml_nextNode(node));
2687
2688#define MELEMENT(o,s) \
2689if (o) WARN( _("Outfit '%s' missing/invalid '%s' element"), temp->name, s)
2690 MELEMENT(temp->name==NULL,"name");
2691 if (!outfit_isProp(temp,OUTFIT_PROP_TEMPLATE)) {
2692 MELEMENT(temp->slot.type==OUTFIT_SLOT_NULL,"slot");
2693 MELEMENT((temp->slot.type!=OUTFIT_SLOT_NA) && (temp->slot.type!=OUTFIT_SLOT_INTRINSIC) && (temp->slot.size==OUTFIT_SLOT_SIZE_NA),"size");
2694 MELEMENT(temp->gfx_store==NULL,"gfx_store");
2695 MELEMENT(temp->desc_raw==NULL,"description");
2696 }
2697 /*MELEMENT(temp->mass==0,"mass"); Not really needed */
2698 MELEMENT(temp->type==0,"type");
2699 /*MELEMENT(temp->price==0,"price");*/
2700 MELEMENT((temp->cond!=NULL) && (temp->condstr==NULL), "condstr");
2701 MELEMENT((temp->cond==NULL) && (temp->condstr!=NULL), "cond");
2702#undef MELEMENT
2703
2704 xmlFreeDoc(doc);
2705
2706 return 0;
2707}
2708
2709static int outfit_parseThread( void *ptr )
2710{
2711 OutfitThreadData *data = ptr;
2712 data->ret = outfit_parse( &data->outfit, data->filename );
2713 /* Render if necessary. */
2715 gl_contextSet();
2717 gl_contextUnset();
2718 }
2719 return data->ret;
2720}
2721
2728static int outfit_loadDir( const char *dir )
2729{
2730 char **outfit_files = ndata_listRecursive( dir );
2731 ThreadQueue *tq = vpool_create();
2733
2734 for (int i=0; i < array_size( outfit_files ); i++) {
2735 if (ndata_matchExt( outfit_files[i], "xml" )) {
2736 OutfitThreadData *od = &array_grow( &odata );
2737 od->filename = outfit_files[i];
2738 }
2739 else
2740 free( outfit_files[i] );
2741 }
2742 array_free( outfit_files );
2743
2744 /* Enqueue the jobs after the data array is done. */
2745 for (int i=0; i<array_size(odata); i++)
2746 vpool_enqueue( tq, outfit_parseThread, &odata[i] );
2747
2748 /* Wait until done processing. */
2749 SDL_GL_MakeCurrent( gl_screen.window, NULL );
2750 vpool_wait( tq );
2751 SDL_GL_MakeCurrent( gl_screen.window, gl_screen.context );
2752
2753 /* Properly load the data. */
2754 license_stack = array_create( char* );
2755 for (int i=0; i<array_size(odata); i++) {
2756 OutfitThreadData *od = &odata[i];
2757 if (!od->ret)
2758 array_push_back( &outfit_stack, od->outfit );
2759 if (outfit_isLicense( &od->outfit ))
2760 array_push_back( &license_stack, od->outfit.u.lic.provides );
2761 free( od->filename );
2762 }
2763 array_free(odata);
2764
2765 return 0;
2766}
2767
2773int outfit_load (void)
2774{
2775#if DEBUGGING
2776 Uint32 time = SDL_GetTicks();
2777#endif /* DEBUGGING */
2778 int noutfits;
2779
2780 /* First pass, Loads up all outfits, without filling ammunition and the likes. */
2782 outfit_loadDir( OUTFIT_DATA_PATH );
2784 noutfits = array_size(outfit_stack);
2785 /* Sort up licenses. */
2786 qsort( outfit_stack, noutfits, sizeof(Outfit), outfit_cmp );
2787 if (license_stack != NULL)
2788 qsort( license_stack, array_size(license_stack), sizeof(char*), strsort );
2789
2790#if DEBUGGING
2791 for (int i=1; i<noutfits; i++)
2792 if (strcmp( outfit_stack[i-1].name, outfit_stack[i].name )==0)
2793 WARN(_("Duplicated outfit name '%s' detected!"), outfit_stack[i].name);
2794#endif /* DEBUGGING */
2795
2796 /* Second pass. */
2797 for (int i=0; i<noutfits; i++) {
2798 Outfit *o = &outfit_stack[i];
2799 if (o->lua_file==NULL)
2800 continue;
2801
2802 nlua_env env;
2803 size_t sz;
2804 char *dat = ndata_read( o->lua_file, &sz );
2805 if (dat==NULL) {
2806 WARN(_("Outfit '%s' failed to read Lua '%s'!"), o->name, o->lua_file );
2807 continue;
2808 }
2809
2810 env = nlua_newEnv();
2811 o->lua_env = env;
2812 /* TODO limit libraries here. */
2813 nlua_loadStandard( env );
2814 nlua_loadGFX( env );
2815 nlua_loadPilotOutfit( env );
2816 nlua_loadCamera( env );
2817 nlua_loadMunition( env );
2818
2819 /* Run code. */
2820 if (nlua_dobufenv( env, dat, sz, o->lua_file ) != 0) {
2821 WARN(_("Outfit '%s' Lua error:\n%s"), o->name, lua_tostring(naevL,-1));
2822 lua_pop(naevL,1);
2823 nlua_freeEnv( o->lua_env );
2824 free( dat );
2825 o->lua_env = LUA_NOREF;
2826 continue;
2827 }
2828 free( dat );
2829
2830 /* Check functions as necessary. */
2831 o->lua_descextra = nlua_refenvtype( env, "descextra",LUA_TFUNCTION );
2832 o->lua_onadd = nlua_refenvtype( env, "onadd", LUA_TFUNCTION );
2833 o->lua_onremove = nlua_refenvtype( env, "onremove", LUA_TFUNCTION );
2834 o->lua_init = nlua_refenvtype( env, "init", LUA_TFUNCTION );
2835 o->lua_cleanup = nlua_refenvtype( env, "cleanup", LUA_TFUNCTION );
2836 o->lua_update = nlua_refenvtype( env, "update", LUA_TFUNCTION );
2837 o->lua_ontoggle = nlua_refenvtype( env, "ontoggle", LUA_TFUNCTION );
2838 o->lua_onshoot = nlua_refenvtype( env, "onshoot", LUA_TFUNCTION );
2839 o->lua_onhit = nlua_refenvtype( env, "onhit", LUA_TFUNCTION );
2840 o->lua_outofenergy= nlua_refenvtype( env, "outofenergy",LUA_TFUNCTION );
2841 o->lua_onshootany = nlua_refenvtype( env, "onshootany",LUA_TFUNCTION );
2842 o->lua_onstealth = nlua_refenvtype( env, "onstealth",LUA_TFUNCTION );
2843 o->lua_onscanned = nlua_refenvtype( env, "onscanned",LUA_TFUNCTION );
2844 o->lua_onscan = nlua_refenvtype( env, "onscan", LUA_TFUNCTION );
2845 o->lua_cooldown = nlua_refenvtype( env, "cooldown", LUA_TFUNCTION );
2846 o->lua_land = nlua_refenvtype( env, "land", LUA_TFUNCTION );
2847 o->lua_takeoff = nlua_refenvtype( env, "takeoff", LUA_TFUNCTION );
2848 o->lua_jumpin = nlua_refenvtype( env, "jumpin", LUA_TFUNCTION );
2849 o->lua_board = nlua_refenvtype( env, "board", LUA_TFUNCTION );
2850 o->lua_keydoubletap= nlua_refenvtype(env, "keydoubletap",LUA_TFUNCTION );
2851 o->lua_keyrelease = nlua_refenvtype( env, "keyrelease",LUA_TFUNCTION );
2852 o->lua_onimpact = nlua_refenvtype( env, "onimpact", LUA_TFUNCTION );
2853 o->lua_onmiss = nlua_refenvtype( env, "onmiss", LUA_TFUNCTION );
2854 o->lua_price = nlua_refenvtype( env, "price", LUA_TFUNCTION );
2855 o->lua_buy = nlua_refenvtype( env, "buy", LUA_TFUNCTION );
2856 o->lua_sell = nlua_refenvtype( env, "sell", LUA_TFUNCTION );
2857
2858 if (outfit_isMod(o)) {
2859 nlua_getenv( naevL, env, "notactive" );
2860 o->u.mod.active = 1;
2861 if (lua_toboolean(naevL,-1)) {
2862 o->u.mod.active = 0;
2863 if (o->lua_ontoggle != LUA_NOREF)
2864 WARN(_("Outfit '%s' has 'ontoggle' Lua function defined, but is set as 'notactive'!"),o->name);
2865 }
2866 lua_pop(naevL,1);
2867
2868 /* Regenerate the description... TODO not duplicate code... */
2869 int l = 0;
2870 SDESC_ADD( l, o, "%s", _(outfit_getType(o)) );
2871 if (o->u.mod.active)
2872 SDESC_ADD( l, o, "\n#o%s#0", _("Activated Outfit") );
2873 if (o->u.mod.active && o->u.mod.cooldown > 0.)
2874 l = os_printD( o->summary_raw, l, o->u.mod.cooldown, &cooldown_opts );
2875 l = os_printD( o->summary_raw, l, o->cpu, &cpu_opts );
2876 l = os_printD( o->summary_raw, l, o->mass, &mass_opts );
2877 }
2878 }
2879
2880 /* Third pass for descriptions. */
2881 for (int i=0; i<noutfits; i++) {
2882 Outfit *o = &outfit_stack[i];
2883 if (outfit_isMod(o)) {
2884 int l = 0;
2885 Outfit *temp = o; /* Needed for SDESC_ADD macro. */
2886 SDESC_ADD( l, temp, "%s", _(outfit_getType(temp)) );
2887 if (temp->u.mod.active) /* Ignore Lua since it's handled later. */
2888 SDESC_ADD( l, temp, "\n#o%s#0", _("Activated Outfit") );
2889 if (temp->u.mod.active && temp->u.mod.cooldown > 0.)
2890 l = os_printD( temp->summary_raw, l, temp->u.mod.cooldown, &cooldown_opts );
2891 l = os_printD( temp->summary_raw, l, temp->cpu, &cpu_opts );
2892 l = os_printD( temp->summary_raw, l, temp->mass, &mass_opts );
2893 }
2894 /* We add the ship stats to the description here. */
2895 if (o->summary_raw != NULL) {
2896 int l = strlen(o->summary_raw);
2897 l += ss_statsListDesc( o->stats, &o->summary_raw[l], OUTFIT_SHORTDESC_MAX-l, 1 );
2898 /* Add extra description task if available. */
2899 //if (o->desc_extra != NULL)
2900 // snprintf( &o->summary_raw[l], OUTFIT_SHORTDESC_MAX-l, "\n%s", _(o->desc_extra) );
2901 }
2902 }
2903
2904#ifdef DEBUGGING
2905 char **outfit_names = malloc( noutfits * sizeof(char*) );
2906 int start;
2907
2908 for (int i=0; i<noutfits; i++)
2909 outfit_names[i] = outfit_stack[i].name;
2910
2911 qsort( outfit_names, noutfits, sizeof(char*), strsort );
2912 for (int i=0; i<(noutfits - 1); i++) {
2913 start = i;
2914 while (strcmp(outfit_names[i], outfit_names[i+1]) == 0)
2915 i++;
2916
2917 if (i == start)
2918 continue;
2919
2920 WARN( n_( "Name collision! %d outfit is named '%s'", "Name collision! %d outfits are named '%s'",
2921 i + 1 - start ),
2922 i + 1 - start, outfit_names[ start ] );
2923 }
2924 free(outfit_names);
2925#endif /* DEBUGGING */
2926
2927#if DEBUGGING
2928 if (conf.devmode) {
2929 time = SDL_GetTicks() - time;
2930 DEBUG( n_( "Loaded %d Outfit in %.3f s", "Loaded %d Outfits in %.3f s", noutfits ), noutfits, time/1000. );
2931 }
2932 else
2933 DEBUG( n_( "Loaded %d Outfit", "Loaded %d Outfits", noutfits ), noutfits );
2934#endif /* DEBUGGING */
2935 return 0;
2936}
2937
2944{
2945 for (int i=0; i<array_size(outfit_stack); i++) {
2946 Outfit *o = &outfit_stack[i];
2947
2948 if (outfit_isFighterBay(o) && (o->u.bay.shipname!=NULL))
2949 o->u.bay.ship = ship_get(o->u.bay.shipname);
2950
2951 /* Add illegality as necessary. */
2952 if (array_size(o->illegaltoS) > 0) {
2954 for (int j=0; j<array_size(o->illegaltoS); j++) {
2955 int f = faction_get( o->illegaltoS[j] );
2956 array_push_back( &o->illegalto, f );
2957 free( o->illegaltoS[j] );
2958 }
2959 array_free( o->illegaltoS );
2960 o->illegaltoS = NULL;
2961
2962 int l = strlen( o->summary_raw );
2963 SDESC_ADD( l, o, "\n#r%s#0", _("Illegal to:") );
2964 for (int j=0; j<array_size(o->illegalto); j++)
2965 SDESC_ADD( l, o, _("\n#r- %s#0"), _(faction_name(o->illegalto[j])) );
2966 }
2967
2968 /* Handle initializing module stuff. */
2969 if (o->lua_env != LUA_NOREF) {
2970 nlua_getenv( naevL, o->lua_env, "onload" );
2971 if (lua_isnil(naevL,-1))
2972 lua_pop(naevL,1);
2973 else {
2974 lua_pushoutfit( naevL, o );
2975 if (nlua_pcall( o->lua_env, 1, 0 )) {
2976 WARN(_("Outfit '%s' lua load error -> 'load':\n%s"), o->name, lua_tostring(naevL,-1));
2977 lua_pop(naevL,1);
2978 }
2979 }
2980 }
2981
2982 /* Make sure licenses are valid. */
2983 if ((o->license!=NULL) && !outfit_licenseExists( o->license ))
2984 WARN(_("Outfit '%s' has inexistent license requirement '%s'!"), o->name, o->license);
2985 }
2986
2987 return 0;
2988}
2989
2995{
2996 for (int i=0; i<array_size(outfit_stack); i++) {
2997 xmlDocPtr doc;
2998 xmlNodePtr node, cur;
2999 Outfit *o = &outfit_stack[i];
3000
3001 if (!outfit_isMap(o))
3002 continue;
3003
3004 doc = xml_parsePhysFS( o->filename );
3005 if (doc == NULL)
3006 continue;
3007
3008 node = doc->xmlChildrenNode; /* first system node */
3009 if (node == NULL) {
3010 WARN( _("Malformed '%s' file: does not contain elements"), o->filename );
3011 xmlFreeDoc(doc);
3012 continue;
3013 }
3014
3015 cur = node->xmlChildrenNode;
3016 do { /* load all the data */
3017 /* Only handle nodes. */
3018 xml_onlyNodes(cur);
3019
3020 if (xml_isNode(cur,"specific"))
3021 outfit_parseSMap(o, cur);
3022
3023 } while (xml_nextNode(cur));
3024
3025 /* Clean up. */
3026 xmlFreeDoc(doc);
3027 }
3028
3029 return 0;
3030}
3031
3036{
3037 char s[PATH_MAX];
3038 snprintf( s, sizeof(s), OVERLAY_GFX_PATH"rarity_%d.webp", rarity );
3039 return gl_newImage( s, OPENGL_TEX_MIPMAPS );
3040}
3041
3045int outfit_checkIllegal( const Outfit* o, int fct )
3046{
3047 for (int i=0; i<array_size(o->illegalto); i++) {
3048 if (o->illegalto[i] == fct)
3049 return 1;
3050 }
3051 return 0;
3052}
3053
3057int outfit_licenseExists( const char *name )
3058{
3059 if (license_stack==NULL)
3060 return 0;
3061 const char *lic = bsearch( &name, license_stack, array_size(license_stack), sizeof(char*), strsort );
3062 return (lic!=NULL);
3063}
3064
3068void outfit_free (void)
3069{
3070 for (int i=0; i < array_size(outfit_stack); i++) {
3071 Outfit *o = &outfit_stack[i];
3072
3073 free( o->filename );
3074
3075 /* Free graphics */
3076 const OutfitGFX *gfx = outfit_gfx(o);
3077 if (gfx != NULL) {
3078 gl_freeTexture( gfx->tex );
3079 gl_freeTexture( gfx->tex_end );
3080 for (int j=0; j<array_size(gfx->polygon); j++)
3081 FreePolygon( &gfx->polygon[j] );
3082 array_free(gfx->polygon);
3083 glDeleteProgram(gfx->program);
3084 }
3085
3086 /* Free slot. */
3087 outfit_freeSlot( &o->slot );
3088
3089 /* Free stats. */
3090 ss_free( o->stats );
3091
3092 /* Free illegality. */
3093 array_free( o->illegalto );
3094
3095 if (outfit_isFighterBay(o))
3096 free(o->u.bay.shipname);
3097 else if (outfit_isGUI(o))
3098 free(o->u.gui.gui);
3099 else if (outfit_isLicense(o))
3100 free(o->u.lic.provides);
3101 else if (outfit_isMap(o)) {
3102 array_free( o->u.map->systems );
3103 array_free( o->u.map->spobs );
3104 array_free( o->u.map->jumps );
3105 free( o->u.map );
3106 }
3107
3108 /* Lua. */
3109 nlua_freeEnv( o->lua_env );
3110 o->lua_env = LUA_NOREF;
3111 free(o->lua_file);
3112
3113 /* strings */
3114 free(o->typename);
3115 free(o->desc_raw);
3116 free(o->desc_extra);
3117 free(o->limit);
3118 free(o->summary_raw);
3119 free(o->license);
3120 free(o->cond);
3121 free(o->condstr);
3122 free(o->name);
3123 free(o->shortname);
3125 for (int j=0; j<array_size(o->gfx_overlays); j++)
3128
3129 /* Free tags. */
3130 for (int j=0; j<array_size(o->tags); j++)
3131 free(o->tags[j]);
3132 array_free(o->tags);
3133 }
3134
3137}
3138
3148static int os_printD( char *buffer, int i, double value, const t_os_stat *opts )
3149{
3150 const int MAXLEN = OUTFIT_SHORTDESC_MAX-i;
3151 int precision;
3152 char buf[NUM2STRLEN];
3153
3154 if (opts->hide_zero && fabs(value) < 1e-2)
3155 return i;
3156
3157 if (value > 1.)
3158 precision = opts->precision;
3159 else
3160 precision = MAX( opts->precision, 2 );
3161
3162 i += scnprintf(buffer + i, MAXLEN, "\n");
3163 if (opts->colour)
3164 i += scnprintf(buffer + i, MAXLEN,
3165 value > opts->colour_threshold ? "#g" :
3166 value < opts->colour_threshold ? "#r" : "");
3167 /* The brochure of the International System of Units declares in chapter 5: "a space separates the number and the symbol %". The ISO 31-0 standard also specifies a space, and the TeX typesetting system encourages using one. */
3168 num2str( buf, value, precision );
3169 i += scnprintf(buffer + i, MAXLEN, p_("outfitstats", "%s: %s %s"), _(opts->name), buf, opts->unit ? _(opts->unit) : "" );
3170 if (opts->colour)
3171 i += scnprintf(buffer + i, MAXLEN, "#0");
3172 return i;
3173}
3174
3185static int os_printD_range( char *buffer, int i, double minValue, double maxValue, const t_os_stat *opts )
3186{
3187 const int MAXLEN = OUTFIT_SHORTDESC_MAX-i;
3188 char buf1[NUM2STRLEN], buf2[NUM2STRLEN];
3189
3190 if (opts->hide_zero && fabs(maxValue) < 1e-2)
3191 return i;
3192
3193 num2str( buf1, minValue, opts->precision );
3194 num2str( buf2, maxValue, opts->precision );
3195
3196 i += scnprintf(buffer + i, MAXLEN, "\n");
3197 if (opts->colour)
3198 i += scnprintf(buffer + i, MAXLEN,
3199 maxValue > opts->colour_threshold ? "#g" :
3200 maxValue < opts->colour_threshold ? "#r" : "");
3201 i += scnprintf(buffer + i, MAXLEN, p_("outfitstats", "%s: %s %s - %s %s"), _(opts->name),
3202 buf1, _(opts->unit), buf2, _(opts->unit) );
3203 if (opts->colour)
3204 i += scnprintf(buffer + i, MAXLEN, "#0");
3205 return i;
3206}
3207
3220static int os_printD_rate( char *buffer, int i, double val, const t_os_stat *val_opts, int multiplier, double rate, const t_os_stat *rate_opts )
3221{
3222 const int MAXLEN = OUTFIT_SHORTDESC_MAX-i;
3223 char mult[128];
3224 char buf1[NUM2STRLEN], buf2[NUM2STRLEN];
3225
3226 if (val_opts->hide_zero && fabs(val) < 1e-2)
3227 return i;
3228
3229 i += scnprintf(buffer + i, MAXLEN, "\n");
3230 if (val_opts->colour)
3231 i += scnprintf(buffer + i, MAXLEN,
3232 val > val_opts->colour_threshold ? "#g" :
3233 val < val_opts->colour_threshold ? "#r" : "");
3234
3235 if (multiplier > 1)
3236 snprintf( mult, sizeof(mult), p_("multiplier", " x %d"), multiplier );
3237 else
3238 mult[0] = '\0';
3239
3240 num2str( buf1, val, val_opts->precision );
3241 num2str( buf2, rate, rate_opts->precision );
3242
3243 i += scnprintf(buffer + i, MAXLEN, p_("outfitstats", "%s: %s%s %s [%s %s]"), _(val_opts->name),
3244 buf1, mult, _(val_opts->unit), buf2, _(rate_opts->unit) );
3245 if (val_opts->colour)
3246 i += scnprintf(buffer + i, MAXLEN, "#0");
3247 return i;
3248}
Provides macros to work with dynamic arrays.
#define array_free(ptr_array)
Frees memory allocated and sets array to NULL.
Definition array.h:158
#define array_create_size(basic_type, capacity)
Creates a new dynamic array of ‘basic_type’ with an initial capacity.
Definition array.h:102
static ALWAYS_INLINE int array_size(const void *array)
Returns number of elements in the array.
Definition array.h:168
#define array_grow(ptr_array)
Increases the number of elements by one and returns the last element.
Definition array.h:119
#define array_shrink(ptr_array)
Shrinks memory to fit only ‘size’ elements.
Definition array.h:149
#define array_push_back(ptr_array, element)
Adds a new element at the end of the array.
Definition array.h:129
#define array_create(basic_type)
Creates a new dynamic array of ‘basic_type’.
Definition array.h:93
void LoadPolygon(CollPoly *polygon, xmlNodePtr node)
Loads a polygon from an xml node.
Definition collision.c:35
int dtype_raw(int type, double *shield, double *armour, double *knockback)
Gets the raw modulation stats of a damage type.
Definition damagetype.c:252
int dtype_get(const char *name)
Gets the id of a dtype based on name.
Definition damagetype.c:159
const char * dtype_damageTypeToStr(int type)
Gets the human readable string from damage type.
Definition damagetype.c:185
const char * faction_name(int f)
Gets a factions "real" (internal) name.
Definition faction.c:306
int faction_get(const char *name)
Gets a faction ID by name.
Definition faction.c:184
void naev_renderLoadscreen(void)
Renders the loadscreen if necessary.
Definition naev.c:597
int naev_shouldRenderLoadscreen(void)
Whether or not we want to render the loadscreen.
Definition naev.c:556
Header file with generic functions and naev-specifics.
#define CLAMP(a, b, x)
Definition naev.h:41
#define pow2(x)
Definition naev.h:46
#define MAX(x, y)
Definition naev.h:39
#define PATH_MAX
Definition naev.h:50
void * ndata_read(const char *path, size_t *filesize)
Reads a file from the ndata (will be NUL terminated).
Definition ndata.c:154
int ndata_matchExt(const char *path, const char *ext)
Sees if a file matches an extension.
Definition ndata.c:365
char ** ndata_listRecursive(const char *path)
Lists all the visible files in a directory, at any depth.
Definition ndata.c:231
int nlua_loadStandard(nlua_env env)
Loads the standard Naev Lua API.
Definition nlua.c:798
int nlua_refenvtype(nlua_env env, const char *name, int type)
Gets the reference of a global in a lua environment if it matches a type.
Definition nlua.c:920
int nlua_loadCamera(nlua_env env)
Loads the camera library.
Definition nlua_camera.c:49
int nlua_loadGFX(nlua_env env)
Loads the graphics library.
Definition nlua_gfx.c:98
int nlua_loadMunition(nlua_env env)
Loads the munition library.
const Outfit ** lua_pushoutfit(lua_State *L, const Outfit *outfit)
Pushes a outfit on the stack.
int nlua_loadPilotOutfit(nlua_env env)
Loads the pilot outfit library.
int num2str(char dest[NUM2STRLEN], double n, int decimals)
Converts a numeric value to a string.
Definition nstring.c:120
int strsort(const void *p1, const void *p2)
Sort function for sorting strings with qsort().
Definition nstring.c:81
int scnprintf(char *text, size_t maxlen, const char *fmt,...)
Like snprintf(), but returns the number of characters ACTUALLY "printed" into the buffer....
Definition nstring.c:99
glTexture * xml_parseTexture(xmlNodePtr node, const char *path, int defsx, int defsy, const unsigned int flags)
Parses a texture handling the sx and sy elements.
Definition nxml.c:29
xmlDocPtr xml_parsePhysFS(const char *filename)
Analogous to xmlParseMemory/xmlParseFile.
Definition nxml.c:75
glInfo gl_screen
Definition opengl.c:51
glTexture * gl_newImage(const char *path, const unsigned int flags)
Loads an image as a texture.
Definition opengl_tex.c:675
void gl_freeTexture(glTexture *texture)
Frees a texture.
Definition opengl_tex.c:862
int outfit_isSecondary(const Outfit *o)
Checks if outfit has the secondary flag set.
Definition outfit.c:662
const char * outfit_description(const Outfit *o)
Gets the description of an outfit.
Definition outfit.c:1021
static int outfit_loadGFX(Outfit *temp, const xmlNodePtr node)
Loads the graphics for an outfit.
Definition outfit.c:1283
static void outfit_parseSMap(Outfit *temp, const xmlNodePtr parent)
Parses the map tidbits of the outfit.
Definition outfit.c:2225
double outfit_speed(const Outfit *o)
Gets the outfit's speed.
Definition outfit.c:796
static Outfit * outfit_stack
Definition outfit.c:68
double outfit_trackmin(const Outfit *o)
Gets the outfit's minimal tracking.
Definition outfit.c:848
static void outfit_parseSLauncher(Outfit *temp, const xmlNodePtr parent)
Parses the specific area for a launcher and loads it into Outfit.
Definition outfit.c:1752
static int outfit_parseDamage(Damage *dmg, xmlNodePtr node)
Parses a damage node.
Definition outfit.c:1174
int outfit_isBeam(const Outfit *o)
Checks if outfit is a beam type weapon.
Definition outfit.c:554
const char * outfit_summary(const Outfit *o, int withname)
Gets the summary of an outfit.
Definition outfit.c:1035
const Outfit * outfit_getAll(void)
Gets the array (array.h) of all outfits.
Definition outfit.c:189
double outfit_cpu(const Outfit *o)
Gets the outfit's cpu usage.
Definition outfit.c:779
static void outfit_parseSAfterburner(Outfit *temp, const xmlNodePtr parent)
Parses the afterburner tidbits of the outfit.
Definition outfit.c:2075
int outfit_isActive(const Outfit *o)
Checks if outfit is an active outfit.
Definition outfit.c:484
int outfit_soundHit(const Outfit *o)
Gets the outfit's hit sound.
Definition outfit.c:895
static int outfit_parse(Outfit *temp, const char *file)
Parses and returns Outfit from parent node.
Definition outfit.c:2430
const Outfit * outfit_get(const char *name)
Gets an outfit by name.
Definition outfit.c:166
int outfit_isLauncher(const Outfit *o)
Checks if outfit is a weapon launcher.
Definition outfit.c:564
int outfit_isSeeker(const Outfit *o)
Checks if outfit is a seeking weapon.
Definition outfit.c:574
static OutfitType outfit_strToOutfitType(char *buf)
Gets the outfit type from a human readable string.
Definition outfit.c:1128
OutfitSlotSize outfit_toSlotSize(const char *s)
Gets the outfit slot size from a human readable string.
Definition outfit.c:461
int outfit_miningRarity(const Outfit *o)
Gets the maximum rarity the outfit can mine up to.
Definition outfit.c:872
static int outfit_loadDir(const char *dir)
Loads all the files in a directory.
Definition outfit.c:2728
static void outfit_parseSLicense(Outfit *temp, const xmlNodePtr parent)
Parses the license tidbits of the outfit.
Definition outfit.c:2393
int outfit_isLocalMap(const Outfit *o)
Checks if outfit is a local space map.
Definition outfit.c:634
int outfit_isWeapon(const Outfit *o)
Checks to see if an outfit is a weapon.
Definition outfit.c:518
static void outfit_parseSFighterBay(Outfit *temp, const xmlNodePtr parent)
Parses the fighter bay tidbits of the outfit.
Definition outfit.c:2167
char outfit_slotTypeColourFont(const OutfitSlot *os)
Gets a font colour character that roughly matches an outfit slot type colour.
Definition outfit.c:425
int outfit_isToggleable(const Outfit *o)
Checks if outfit can be toggled.
Definition outfit.c:500
static void outfit_parseSBolt(Outfit *temp, const xmlNodePtr parent)
Parses the specific area for a bolt weapon and loads it into Outfit.
Definition outfit.c:1364
int outfit_compareTech(const void *outfit1, const void *outfit2)
Function meant for use with C89, C99 algorithm qsort().
Definition outfit.c:243
size_t outfit_getNameWithClass(const Outfit *outfit, char *buf, size_t size)
Gets a brief name/class description suitable for the title section of an outfitter screen.
Definition outfit.c:444
const Outfit * outfit_getW(const char *name)
Gets an outfit by name without warning on no-find.
Definition outfit.c:180
#define O_CMP(s, t)
Definition outfit.c:1120
const char * outfit_getTypeBroad(const Outfit *o)
Gets the outfit's broad type.
Definition outfit.c:976
static void outfit_parseSMod(Outfit *temp, const xmlNodePtr parent)
Parses the modification tidbits of the outfit.
Definition outfit.c:2031
int outfit_fitsSlot(const Outfit *o, const OutfitSlot *s)
Checks to see if an outfit fits a slot.
Definition outfit.c:1047
int outfit_checkIllegal(const Outfit *o, int fct)
Checks illegality of an outfit to a faction.
Definition outfit.c:3045
int outfit_isFighterBay(const Outfit *o)
Checks if outfit is a fighter bay.
Definition outfit.c:616
int outfit_isAfterburner(const Outfit *o)
Checks if outfit is an afterburner.
Definition outfit.c:607
double outfit_range(const Outfit *o)
Gets the outfit's range.
Definition outfit.c:787
int outfit_isMap(const Outfit *o)
Checks if outfit is a space map.
Definition outfit.c:625
double outfit_radius(const Outfit *o)
Gets the outfit's explosion radius.
Definition outfit.c:724
int outfit_spfxShield(const Outfit *o)
Gets the outfit's sound effect.
Definition outfit.c:702
int outfit_isTurret(const Outfit *o)
Checks if outfit is a turret class weapon.
Definition outfit.c:587
double outfit_ammoMass(const Outfit *o)
Gets the outfit's ammunition mass.
Definition outfit.c:906
int outfit_isForward(const Outfit *o)
Checks if outfit is a fixed mounted weapon.
Definition outfit.c:534
const CollPoly * outfit_plg(const Outfit *o)
Gets the outfit's collision polygon.
Definition outfit.c:681
static void sdesc_miningRarity(int *l, Outfit *temp, int rarity)
Adds a small blurb about rarity mining.
Definition outfit.c:1152
int outfit_isLicense(const Outfit *o)
Checks if outfit is a license.
Definition outfit.c:643
static void outfit_parseSGUI(Outfit *temp, const xmlNodePtr parent)
Parses the GUI tidbits of the outfit.
Definition outfit.c:2360
char outfit_slotSizeColourFont(const OutfitSlot *os)
Gets a font colour character that roughly matches an outfit slot size colour.
Definition outfit.c:408
const OutfitGFX * outfit_gfx(const Outfit *o)
Gets the outfit's graphic effect.
Definition outfit.c:671
int outfit_amount(const Outfit *o)
Gets the amount an outfit can hold.
Definition outfit.c:746
int outfit_load(void)
Loads all the outfits.
Definition outfit.c:2773
const char * slotName(const OutfitSlotType type)
Definition outfit.c:335
double outfit_spin(const Outfit *o)
Gets the outfit's animation spin.
Definition outfit.c:837
int outfit_mapParse(void)
Parses all the maps.
Definition outfit.c:2994
const char * outfit_getType(const Outfit *o)
Gets the outfit's specific type.
Definition outfit.c:945
double outfit_heat(const Outfit *o)
Gets the outfit's heat generation.
Definition outfit.c:768
double outfit_trackmax(const Outfit *o)
Gets the outfit's minimal tracking.
Definition outfit.c:860
static void outfit_parseSBeam(Outfit *temp, const xmlNodePtr parent)
Parses the beam weapon specifics of an outfit.
Definition outfit.c:1587
static char ** license_stack
Definition outfit.c:69
int outfit_isMod(const Outfit *o)
Checks if outfit is a ship modification.
Definition outfit.c:598
static int os_printD_range(char *buffer, int i, double minValue, double maxValue, const t_os_stat *opts)
Writes an outfit statistic representing a range between two values to a buffer.
Definition outfit.c:3185
int outfit_isGUI(const Outfit *o)
Checks if outfit is a GUI.
Definition outfit.c:652
int outfit_sound(const Outfit *o)
Gets the outfit's sound.
Definition outfit.c:884
int outfit_spfxArmour(const Outfit *o)
Gets the outfit's sound effect.
Definition outfit.c:691
#define OUTFIT_SHORTDESC_MAX
Definition outfit.c:54
static int outfit_loadPLG(Outfit *temp, const char *buf)
Loads the collision polygon for a bolt outfit.
Definition outfit.c:1218
const char * slotSize(const OutfitSlotSize o)
Gets the slot size as a string.
Definition outfit.c:358
const Damage * outfit_damage(const Outfit *o)
Gets the outfit's damage.
Definition outfit.c:713
char ** outfit_searchFuzzyCase(const char *name, int *n)
Does a fuzzy search of all the outfits. Searches translated names but returns internal names.
Definition outfit.c:208
double outfit_swivel(const Outfit *o)
Gets the swivel of an outfit.
Definition outfit.c:825
static void outfit_parseSLocalMap(Outfit *temp, const xmlNodePtr parent)
Parses the map tidbits of the outfit.
Definition outfit.c:2323
double outfit_duration(const Outfit *o)
Gets the outfit's duration.
Definition outfit.c:917
double outfit_cooldown(const Outfit *o)
Gets the outfit's cooldown.
Definition outfit.c:932
const char * outfit_slotName(const Outfit *o)
Gets the name of the slot type of an outfit.
Definition outfit.c:327
int outfit_isBolt(const Outfit *o)
Checks if outfit is bolt type weapon.
Definition outfit.c:544
const char * outfit_slotSize(const Outfit *o)
Gets the name of the slot size of an outfit.
Definition outfit.c:380
void outfit_free(void)
Frees the outfit stack.
Definition outfit.c:3068
#define outfit_setProp(o, p)
Definition outfit.c:50
const char * outfit_existsCase(const char *name)
Checks to see if an outfit exists matching name (case insensitive).
Definition outfit.c:197
double outfit_energy(const Outfit *o)
Gets the outfit's energy usage.
Definition outfit.c:757
int outfit_licenseExists(const char *name)
Checks to see if a license exists.
Definition outfit.c:3057
void outfit_freeSlot(OutfitSlot *s)
Frees an outfit slot.
Definition outfit.c:1115
static int os_printD(char *buf, int len, double value, const os_opts *opts)
Writes an outfit statistic to a buffer.
Definition outfit.c:3148
const char * outfit_getAmmoAI(const Outfit *o)
Gets a human-readable string describing an ammo outfit's AI.
Definition outfit.c:997
glTexture * rarity_texture(int rarity)
Definition outfit.c:3035
const glColour * outfit_slotSizeColour(const OutfitSlot *os)
Gets the slot size colour for an outfit slot.
Definition outfit.c:391
static int os_printD_rate(char *buffer, int i, double val, const t_os_stat *val_opts, int multiplier, double rate, const t_os_stat *rate_opts)
Writes an outfit statistic representing a "per unit" value and rate of change value.
Definition outfit.c:3220
double outfit_delay(const Outfit *o)
Gets the outfit's delay.
Definition outfit.c:734
int outfit_fitsSlotType(const Outfit *o, const OutfitSlot *s)
Checks to see if an outfit fits a slot type (ignoring size).
Definition outfit.c:1092
int outfit_loadPost(void)
Loads all the outfits legality.
Definition outfit.c:2943
double pilot_heatCalcOutfitC(const Outfit *o)
Calculates the thermal mass of an outfit.
Definition pilot_heat.c:62
double pilot_heatCalcOutfitArea(const Outfit *o)
Calculates the effective transfer area of an outfit.
Definition pilot_heat.c:73
const char * pilot_outfitDescription(const Pilot *p, const Outfit *o)
Gets the description of an outfit for a given pilot.
const char * pilot_outfitSummary(const Pilot *p, const Outfit *o, int withname)
Gets the summary of an outfit for a give pilot.
static cholmod_common C
Definition safelanes.c:95
const Ship * ship_get(const char *name)
Gets a ship based on its name.
Definition ship.c:87
int ss_statsListDesc(const ShipStatList *ll, char *buf, int len, int newline)
Writes the ship statistics description.
Definition shipstats.c:780
void ss_free(ShipStatList *ll)
Frees a list of ship stats.
Definition shipstats.c:887
ShipStatList * ss_listFromXML(xmlNodePtr node)
Creates a shipstat list element from an xml node.
Definition shipstats.c:292
int ss_sort(ShipStatList **ll)
Sorts the ship stats, useful if doing saving stuff.
Definition shipstats.c:381
unsigned int sp_get(const char *name)
Gets the id of a slot property.
Definition slots.c:133
int sp_required(unsigned int spid)
Gets whether or not a slot property is required.
Definition slots.c:179
int sound_disabled
Definition sound.c:133
int sound_get(const char *name)
Gets the buffer to sound of name.
Definition sound.c:756
Spob * spob_get(const char *spobname)
Gets a spob based on its name.
Definition space.c:1051
StarSystem * system_getAll(void)
Gets an array (array.h) of all star systems.
Definition space.c:879
StarSystem * system_get(const char *sysname)
Get the system from its name.
Definition space.c:960
JumpPoint * jump_get(const char *jumpname, const StarSystem *sys)
Gets a jump point based on its target and system.
Definition space.c:1222
const TrailSpec * trailSpec_get(const char *name)
Gets a trail spec by name.
Definition spfx.c:1283
int spfx_get(const char *name)
Gets the id of an spfx based on name.
Definition spfx.c:342
const char * start_dtype_default(void)
Gets the default damage type.
Definition start.c:296
Represents a polygon used for collision detection.
Definition collision.h:13
Core damage that an outfit does.
Definition outfit.h:138
int type
Definition outfit.h:139
double disable
Definition outfit.h:142
double penetration
Definition outfit.h:140
double damage
Definition outfit.h:141
double energy
Definition outfit.h:188
double heatup
Definition outfit.h:190
int sound_warmup
Definition outfit.h:201
double duration
Definition outfit.h:182
int spfx_armour
Definition outfit.h:199
double warmup
Definition outfit.h:181
int mining_rarity
Definition outfit.h:193
Damage dmg
Definition outfit.h:189
GLuint shader
Definition outfit.h:198
double min_duration
Definition outfit.h:183
double turn
Definition outfit.h:187
double heat
Definition outfit.h:191
double range
Definition outfit.h:186
double swivel
Definition outfit.h:192
glColour colour
Definition outfit.h:196
GLfloat width
Definition outfit.h:197
int spfx_shield
Definition outfit.h:200
double delay
Definition outfit.h:180
int spfx_shield
Definition outfit.h:172
double dispersion
Definition outfit.h:162
double heat
Definition outfit.h:158
double trackmin
Definition outfit.h:159
double radius
Definition outfit.h:155
double falloff
Definition outfit.h:152
double range
Definition outfit.h:151
double delay
Definition outfit.h:149
double swivel
Definition outfit.h:161
double heatup
Definition outfit.h:157
Damage dmg
Definition outfit.h:154
double trackmax
Definition outfit.h:160
double speed
Definition outfit.h:150
int mining_rarity
Definition outfit.h:165
double energy
Definition outfit.h:153
OutfitGFX gfx
Definition outfit.h:168
int spfx_armour
Definition outfit.h:171
double speed_dispersion
Definition outfit.h:163
const struct Ship_ * ship
Definition outfit.h:291
double col_size
Definition outfit.h:132
glTexture * tex_end
Definition outfit.h:121
GLuint u_r
Definition outfit.h:128
GLuint program
Definition outfit.h:124
GLuint dimensions
Definition outfit.h:127
double spin
Definition outfit.h:123
GLuint projection
Definition outfit.h:126
GLuint u_time
Definition outfit.h:129
glTexture * tex
Definition outfit.h:120
GLuint u_fade
Definition outfit.h:130
GLuint vertex
Definition outfit.h:125
double size
Definition outfit.h:131
CollPoly * polygon
Definition outfit.h:122
char * gui
Definition outfit.h:315
double dmg_absorb
Definition outfit.h:243
double dispersion
Definition outfit.h:223
double speed_dispersion
Definition outfit.h:224
double trail_x_offset
Definition outfit.h:251
const TrailSpec * trail_spec
Definition outfit.h:250
OutfitGFX gfx
Definition outfit.h:245
double reload_time
Definition outfit.h:214
OutfitAmmoAI ai
Definition outfit.h:231
char * provides
Definition outfit.h:322
double jump_detect
Definition outfit.h:307
double spob_detect
Definition outfit.h:308
Pilot slot that can contain outfits.
Definition outfit.h:109
OutfitSlotSize size
Definition outfit.h:113
int exclusive
Definition outfit.h:111
unsigned int spid
Definition outfit.h:110
OutfitSlotType type
Definition outfit.h:112
For threaded loading of outfits.
Definition outfit.c:59
A ship outfit, depends radically on the type.
Definition outfit.h:328
char ** tags
Definition outfit.h:367
unsigned int properties
Definition outfit.h:360
int lua_ontoggle
Definition outfit.h:378
char * lua_file
Definition outfit.h:370
credits_t price
Definition outfit.h:347
int lua_onscan
Definition outfit.h:385
char * limit
Definition outfit.h:342
int lua_jumpin
Definition outfit.h:389
char * cond
Definition outfit.h:338
double cpu
Definition outfit.h:341
OutfitLauncherData lau
Definition outfit.h:406
int lua_cooldown
Definition outfit.h:386
char * desc_raw
Definition outfit.h:348
OutfitBeamData bem
Definition outfit.h:405
int lua_buy
Definition outfit.h:398
OutfitBoltData blt
Definition outfit.h:404
int lua_keyrelease
Definition outfit.h:392
int lua_takeoff
Definition outfit.h:388
double overheat_max
Definition outfit.h:358
OutfitType type
Definition outfit.h:402
int lua_onimpact
Definition outfit.h:394
int lua_outofenergy
Definition outfit.h:381
int lua_land
Definition outfit.h:387
int lua_onscanned
Definition outfit.h:384
int lua_onshootany
Definition outfit.h:382
int lua_update
Definition outfit.h:377
int lua_onmiss
Definition outfit.h:395
glTexture * gfx_store
Definition outfit.h:353
int rarity
Definition outfit.h:332
int lua_descextra
Definition outfit.h:372
int lua_price
Definition outfit.h:397
OutfitGUIData gui
Definition outfit.h:412
unsigned int group
Definition outfit.h:361
char * condstr
Definition outfit.h:339
char ** illegaltoS
Definition outfit.h:344
char * shortname
Definition outfit.h:331
char * filename
Definition outfit.h:333
OutfitModificationData mod
Definition outfit.h:407
union Outfit::@12 u
OutfitSlot slot
Definition outfit.h:336
int lua_onhit
Definition outfit.h:380
char * summary_raw
Definition outfit.h:349
OutfitAfterburnerData afb
Definition outfit.h:408
int * illegalto
Definition outfit.h:343
char * typename
Definition outfit.h:330
int lua_onshoot
Definition outfit.h:379
OutfitFighterBayData bay
Definition outfit.h:409
int priority
Definition outfit.h:351
int lua_cleanup
Definition outfit.h:376
int lua_sell
Definition outfit.h:399
ShipStatList * stats
Definition outfit.h:364
nlua_env lua_env
Definition outfit.h:371
int lua_onstealth
Definition outfit.h:383
char * license
Definition outfit.h:337
OutfitLocalMapData lmap
Definition outfit.h:411
int lua_onadd
Definition outfit.h:373
glTexture ** gfx_overlays
Definition outfit.h:354
int lua_board
Definition outfit.h:390
int lua_keydoubletap
Definition outfit.h:391
int lua_onremove
Definition outfit.h:374
double overheat_min
Definition outfit.h:357
double mass
Definition outfit.h:340
char * desc_extra
Definition outfit.h:350
OutfitMapData_t * map
Definition outfit.h:410
char * name
Definition outfit.h:329
int lua_init
Definition outfit.h:375
OutfitLicenseData lic
Definition outfit.h:413
int devmode
Definition conf.h:157
Represents relative ship statistics as a linked list.
Definition shipstats.h:167
struct ShipStatList_ * next
Definition shipstats.h:168
Represents a Space Object (SPOB), including and not limited to planets, stations, wormholes,...
Definition space.h:89
SDL_Window * window
Definition opengl.h:68
SDL_GLContext context
Definition opengl.h:69
Abstraction for rendering sprite sheets.
Definition opengl_tex.h:36
double sw
Definition opengl_tex.h:46
double sh
Definition opengl_tex.h:47