-
Notifications
You must be signed in to change notification settings - Fork 182
Expand file tree
/
Copy pathweapons.cpp
More file actions
10307 lines (8646 loc) · 353 KB
/
weapons.cpp
File metadata and controls
10307 lines (8646 loc) · 353 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include <algorithm>
#include <cstddef>
#include "ai/aibig.h"
#include "asteroid/asteroid.h"
#include "cmdline/cmdline.h"
#include "cmeasure/cmeasure.h"
#include "debugconsole/console.h"
#include "fireball/fireballs.h"
#include "freespace.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/linklist.h"
#include "graphics/color.h"
#include "hud/hud.h"
#include "hud/hudartillery.h"
#include "iff_defs/iff_defs.h"
#include "io/joy_ff.h"
#include "io/timer.h"
#include "math/curve.h"
#include "math/staticrand.h"
#include "missionui/missionweaponchoice.h"
#include "mod_table/mod_table.h"
#include "model/animation/modelanimation_driver.h"
#include "nebula/neb.h"
#include "network/multi.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "object/objcollide.h"
#include "object/objectdock.h"
#include "object/objectshield.h"
#include "object/objectsnd.h"
#include "parse/parsehi.h"
#include "parse/parselo.h"
#include "scripting/global_hooks.h"
#include "particle/particle.h"
#include "playerman/player.h"
#include "radar/radar.h"
#include "render/3d.h"
#include "render/batching.h"
#include "ship/ship.h"
#include "ship/shiphit.h"
#include "weapon/beam.h" // for BeamType definitions
#include "weapon/corkscrew.h"
#include "weapon/emp.h"
#include "weapon/flak.h"
#include "weapon/muzzleflash.h"
#include "weapon/swarm.h"
#include "particle/ParticleEffect.h"
#include "particle/volumes/ConeVolume.h"
#include "particle/volumes/LegacyAACuboidVolume.h"
#include "particle/volumes/SpheroidVolume.h"
#include "particle/volumes/RingVolume.h"
#include "particle/volumes/PointVolume.h"
#include "tracing/Monitor.h"
#include "tracing/tracing.h"
#include "weapon.h"
#include "model/modelrender.h"
// Since SSMs are parsed after weapons, if we want to allow SSM strikes to be specified by name, we need to store those names until after SSMs are parsed.
typedef struct delayed_ssm_data {
SCP_string filename;
int linenum;
SCP_string ssm_entry;
} delayed_ssm_data;
SCP_unordered_map<SCP_string, delayed_ssm_data> Delayed_SSM_data;
SCP_vector<SCP_string> Delayed_SSM_names;
typedef struct delayed_ssm_index_data {
SCP_string filename;
int linenum;
} delayed_ssm_index_data;
SCP_unordered_map<SCP_string, delayed_ssm_index_data> Delayed_SSM_indices_data;
SCP_vector<SCP_string> Delayed_SSM_indices;
#ifndef NDEBUG
int Weapon_flyby_sound_enabled = 1;
DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
#endif
static TIMESTAMP Weapon_flyby_sound_timer;
weapon Weapons[MAX_WEAPONS];
SCP_vector<weapon_info> Weapon_info;
#define MISSILE_OBJ_USED (1<<0) // flag used in missile_obj struct
#define MAX_MISSILE_OBJS MAX_WEAPONS // max number of missiles tracked in missile list
missile_obj Missile_objs[MAX_MISSILE_OBJS]; // array used to store missile object indexes
missile_obj Missile_obj_list; // head of linked list of missile_obj structs
#define DEFAULT_WEAPON_SPAWN_COUNT 10
int Num_spawn_types = 0;
char** Spawn_names = nullptr;
//WEAPON SUBTYPE STUFF
const char *Weapon_subtype_names[] = {
"Laser",
"Missile",
"Beam"
};
int Num_weapon_subtypes = sizeof(Weapon_subtype_names)/sizeof(Weapon_subtype_names[0]);
flag_def_list_new<Weapon::Burst_Flags> Burst_fire_flags[] = {
{ "fast firing", Weapon::Burst_Flags::Fast_firing, true, false },
{ "random length", Weapon::Burst_Flags::Random_length, true, false },
{ "resets", Weapon::Burst_Flags::Resets, true, false },
{ "num firepoints for burst shots", Weapon::Burst_Flags::Num_firepoints_burst_shots, true, false },
{ "burst only loop sounds", Weapon::Burst_Flags::Burst_only_loop_sounds, true, false }
};
const size_t Num_burst_fire_flags = sizeof(Burst_fire_flags)/sizeof(flag_def_list_new<Weapon::Burst_Flags>);
flag_def_list_new<Weapon::Beam_Info_Flags> Beam_info_flags[] = {
{ "burst shares random target", Weapon::Beam_Info_Flags::Burst_share_random, true, false },
{ "track own texture tiling", Weapon::Beam_Info_Flags::Track_own_texture_tiling, true, false },
{ "direct fire lead target", Weapon::Beam_Info_Flags::Direct_fire_lead_target, true, false }
};
const size_t Num_beam_info_flags = sizeof(Beam_info_flags) / sizeof(flag_def_list_new<Weapon::Beam_Info_Flags>);
special_flag_def_list_new<Weapon::Info_Flags, weapon_info*, flagset<Weapon::Info_Flags>&> Weapon_Info_Flags[] = {
{ "spawn", Weapon::Info_Flags::Spawn, true, [](const SCP_string& spawn, weapon_info* weaponp, flagset<Weapon::Info_Flags>& flags) {
if (weaponp->num_spawn_weapons_defined < MAX_SPAWN_TYPES_PER_WEAPON)
{
//We need more spawning slots
//allocate in slots of 10
if ((Num_spawn_types % 10) == 0) {
Spawn_names = (char**)vm_realloc(Spawn_names, (Num_spawn_types + 10) * sizeof(*Spawn_names));
}
flags.set(Weapon::Info_Flags::Spawn);
weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_wep_index = (short)Num_spawn_types;
size_t start_num = spawn.find_first_of(',');
if (start_num == SCP_string::npos) {
weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
start_num = spawn.size();
}
else {
weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = (short)atoi(spawn.substr(start_num + 1).c_str());
}
weaponp->maximum_children_spawned += weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count;
Spawn_names[Num_spawn_types] = vm_strndup(spawn.substr(0, start_num).c_str(), start_num);
Num_spawn_types++;
weaponp->num_spawn_weapons_defined++;
}
else {
Warning(LOCATION, "Illegal to have more than %d spawn types for one weapon.\nIgnoring weapon %s", MAX_SPAWN_TYPES_PER_WEAPON, weaponp->name);
}
}}, //special case
{ "remote detonate", Weapon::Info_Flags::Remote, true },
{ "puncture", Weapon::Info_Flags::Puncture, true },
{ "big ship", Weapon::Info_Flags::Big_only, true },
{ "huge", Weapon::Info_Flags::Huge, true },
{ "bomber+", Weapon::Info_Flags::Bomber_plus, true },
{ "child", Weapon::Info_Flags::Child, true },
{ "bomb", Weapon::Info_Flags::Bomb, true },
{ "no dumbfire", Weapon::Info_Flags::No_dumbfire, true },
{ "no doublefire", Weapon::Info_Flags::No_doublefire, true },
{ "in tech database", Weapon::Info_Flags::In_tech_database, true },
{ "default player weapon", Weapon::Info_Flags::Default_player_weapon, true },
{ "player allowed", Weapon::Info_Flags::Player_allowed, true },
{ "particle spew", Weapon::Info_Flags::Particle_spew, true },
{ "emp", Weapon::Info_Flags::Emp, true },
{ "esuck", Weapon::Info_Flags::Energy_suck, true },
{ "flak", Weapon::Info_Flags::Flak, true },
{ "corkscrew", Weapon::Info_Flags::Corkscrew, true },
{ "shudder", Weapon::Info_Flags::Shudder, true },
{ "electronics", Weapon::Info_Flags::Electronics, true },
{ "lockarm", Weapon::Info_Flags::Lockarm, true },
{ "beam", Weapon::Info_Flags::Beam, true, [](const SCP_string& /*spawn*/, weapon_info* /*weaponp*/, flagset<Weapon::Info_Flags>& flags) {
flags.set(Weapon::Info_Flags::Pierce_shields);
}}, //special case
{ "stream", Weapon::Info_Flags::NUM_VALUES, false, [](const SCP_string& /*spawn*/, weapon_info* weaponp, flagset<Weapon::Info_Flags>& /*flags*/) {
mprintf(("Ignoring \"stream\" flag on weapon %s...\n", weaponp->name)); // dont warn because its not a big deal, but also because retail has several 'stream' weapons
}}, //special case
{ "supercap", Weapon::Info_Flags::Supercap, true },
{ "countermeasure", Weapon::Info_Flags::Cmeasure, true },
{ "ballistic", Weapon::Info_Flags::Ballistic, true },
{ "pierce shields", Weapon::Info_Flags::Pierce_shields, true },
{ "local ssm", Weapon::Info_Flags::Local_ssm, true },
{ "tagged only", Weapon::Info_Flags::Tagged_only, true },
{ "beam no whack", Weapon::Info_Flags::NUM_VALUES, false, [](const SCP_string& /*spawn*/, weapon_info* weaponp, flagset<Weapon::Info_Flags>& /*flags*/) {
Warning(LOCATION, "The \"beam no whack\" flag has been deprecated. Set the beam's mass to 0 instead. This has been done for you.\n");
weaponp->mass = 0.0f;
}}, //special case
{ "cycle", Weapon::Info_Flags::NUM_VALUES, false, [](const SCP_string& /*spawn*/, weapon_info* weaponp, flagset<Weapon::Info_Flags>& /*flags*/) {
weaponp->firing_pattern = FiringPattern::CYCLE_FORWARD;
}}, //special case
{ "small only", Weapon::Info_Flags::Small_only, true },
{ "same turret cooldown", Weapon::Info_Flags::Same_turret_cooldown, true },
{ "apply no light", Weapon::Info_Flags::Mr_no_lighting, true },
{ "training", Weapon::Info_Flags::Training, true },
{ "smart spawn", Weapon::Info_Flags::Smart_spawn, true },
{ "inherit parent target", Weapon::Info_Flags::Inherit_parent_target, true },
{ "no emp kill", Weapon::Info_Flags::No_emp_kill, true },
{ "untargeted heat seeker", Weapon::Info_Flags::Untargeted_heat_seeker, true },
{ "no radius doubling", Weapon::Info_Flags::No_radius_doubling, true },
{ "no subsystem homing", Weapon::Info_Flags::Non_subsys_homing, true },
{ "no lifeleft penalty", Weapon::Info_Flags::No_life_lost_if_missed, true },
{ "can be targeted", Weapon::Info_Flags::Can_be_targeted, true },
{ "show on radar", Weapon::Info_Flags::Shown_on_radar, true },
{ "show friendly on radar", Weapon::Info_Flags::Show_friendly, true },
{ "capital+", Weapon::Info_Flags::Capital_plus, true },
{ "chain external model fps", Weapon::Info_Flags::External_weapon_fp, true },
{ "external model launcher", Weapon::Info_Flags::External_weapon_lnch, true },
{ "takes blast damage", Weapon::Info_Flags::Takes_blast_damage, true },
{ "takes shockwave damage", Weapon::Info_Flags::Takes_shockwave_damage, true },
{ "hide from radar", Weapon::Info_Flags::Dont_show_on_radar, true },
{ "render flak", Weapon::Info_Flags::Render_flak, true },
{ "ciws", Weapon::Info_Flags::Ciws, true },
{ "anti-subsystem beam", Weapon::Info_Flags::Antisubsysbeam, true },
{ "no primary linking", Weapon::Info_Flags::Nolink, true },
{ "same emp time for capships", Weapon::Info_Flags::Use_emp_time_for_capship_turrets, true },
{ "no primary linked penalty", Weapon::Info_Flags::No_linked_penalty, true },
{ "no homing speed ramp", Weapon::Info_Flags::No_homing_speed_ramp, true },
{ "pulls aspect seekers", Weapon::Info_Flags::Cmeasure_aspect_home_on, true },
{ "turret interceptable", Weapon::Info_Flags::Turret_Interceptable, true },
{ "fighter interceptable", Weapon::Info_Flags::Fighter_Interceptable, true },
{ "aoe electronics", Weapon::Info_Flags::Aoe_Electronics, true },
{ "apply recoil", Weapon::Info_Flags::Apply_Recoil, true },
{ "don't spawn if shot", Weapon::Info_Flags::Dont_spawn_if_shot, true },
{ "die on lost lock", Weapon::Info_Flags::Die_on_lost_lock, true, [](const SCP_string& /*spawn*/, weapon_info* weaponp, flagset<Weapon::Info_Flags>& flags) {
if (!(weaponp->is_locked_homing())) {
Warning(LOCATION, "\"die on lost lock\" may only be used for Homing Type ASPECT/JAVELIN!");
flags.remove(Weapon::Info_Flags::Die_on_lost_lock);
}
}}, //special case
{ "no impact spew", Weapon::Info_Flags::No_impact_spew, true },
{ "require exact los", Weapon::Info_Flags::Require_exact_los, true },
{ "can damage shooter", Weapon::Info_Flags::Can_damage_shooter, true },
{ "heals", Weapon::Info_Flags::Heals, true },
{"vampiric", Weapon::Info_Flags::Vampiric, true },
{ "secondary no ammo", Weapon::Info_Flags::SecondaryNoAmmo, true },
{ "no collide", Weapon::Info_Flags::No_collide, true },
{ "multilock target dead subsys", Weapon::Info_Flags::Multilock_target_dead_subsys, true },
{ "no evasion", Weapon::Info_Flags::No_evasion, true },
{ "don't merge lead indicators", Weapon::Info_Flags::Dont_merge_indicators, true },
{ "no_fred", Weapon::Info_Flags::No_fred, true },
{ "detonate on expiration", Weapon::Info_Flags::Detonate_on_expiration, true },
{ "ignores countermeasures", Weapon::Info_Flags::Ignores_countermeasures, true },
{ "freespace 1 missile behavior", Weapon::Info_Flags::Freespace_1_missile_behavior, true, [](const SCP_string& /*spawn*/, weapon_info* weaponp, flagset<Weapon::Info_Flags>& flags) {
if (!(weaponp->is_locked_homing())) {
Warning(LOCATION, "\"freespace 1 missile behavior\" only applies to aspect seekers.");
flags.remove(Weapon::Info_Flags::Freespace_1_missile_behavior);
}
}}, //special case
{"dogfight variant", Weapon::Info_Flags::Dogfight_weapon, true},
};
const size_t num_weapon_info_flags = sizeof(Weapon_Info_Flags) / sizeof(special_flag_def_list_new<Weapon::Info_Flags, weapon_info*, flagset<Weapon::Info_Flags>&>);
int Num_weapons = 0;
bool Weapons_inited = false;
int laser_model_inner = -1;
int laser_model_outer = -1;
int missile_model = -1;
int First_secondary_index = -1;
int Default_cmeasure_index = -1;
static int *used_weapons = NULL;
SCP_vector<int> Player_weapon_precedence; // Vector of weapon types, precedence list for player weapon selection
SCP_vector<SCP_string> Player_weapon_precedence_names; // Vector of weapon names, gets turned into the above after parsing all modular tables and sorting the Weapon_info vector.
SCP_string Player_weapon_precedence_file; // If an invalid name is given, tell the user what file it was specified in.
int Player_weapon_precedence_line; // And this is the line the precedence list was given on.
// Used to avoid playing too many impact sounds in too short a time interval.
// This will elimate the odd "stereo" effect that occurs when two weapons impact at
// nearly the same time, like from a double laser (also saves sound channels!)
constexpr int IMPACT_SOUND_DELTA = 50; // in milliseconds
static TIMESTAMP Weapon_impact_timer; // timer, initialized at start of each mission
// energy suck defines
#define ESUCK_DEFAULT_WEAPON_REDUCE (10.0f)
#define ESUCK_DEFAULT_AFTERBURNER_REDUCE (10.0f)
// scale factor for big ships getting hit by flak
#define FLAK_DAMAGE_SCALE 0.05f
//default time of a homing missile to not home
#define HOMING_DEFAULT_FREE_FLIGHT_TIME 0.5f
// default percentage of max speed to coast at during freeflight
const float HOMING_DEFAULT_FREE_FLIGHT_FACTOR = 0.25f;
//default time of a homing primary to not home
#define HOMING_DEFAULT_PRIMARY_FREE_FLIGHT_TIME 0.0f
// time delay between each swarm missile that is fired
#define SWARM_MISSILE_DELAY 150
// default number of missiles or bullets rearmed per load sound during rearm
#define REARM_NUM_MISSILES_PER_BATCH 4
#define REARM_NUM_BALLISTIC_PRIMARIES_PER_BATCH 100
// most frequently a continuous spawn weapon is allowed to spawn
static const float MINIMUM_SPAWN_INTERVAL = 0.1f;
extern int compute_num_homing_objects(const object *target_objp);
void weapon_spew_stats(WeaponSpewType type);
/*
* Clear out the Missile_obj_list
*/
void missile_obj_list_init()
{
int i;
list_init(&Missile_obj_list);
for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
Missile_objs[i].flags = 0;
}
}
/**
* Add a node from the Missile_obj_list.
* @note Only called from weapon_create()
*/
int missile_obj_list_add(int objnum)
{
int i;
for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
break;
}
if ( i == MAX_MISSILE_OBJS ) {
Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
return -1;
}
Missile_objs[i].flags = 0;
Missile_objs[i].objnum = objnum;
list_append(&Missile_obj_list, &Missile_objs[i]);
Missile_objs[i].flags |= MISSILE_OBJ_USED;
return i;
}
/**
* Remove a node from the Missile_obj_list.
* @note Only called from weapon_delete()
*/
void missle_obj_list_remove(int index)
{
Assert(index >= 0 && index < MAX_MISSILE_OBJS);
list_remove(&Missile_obj_list, &Missile_objs[index]);
Missile_objs[index].flags = 0;
}
/**
* Return the index of Weapon_info[].name that is *name.
*/
int weapon_info_lookup(const char *name)
{
Assertion(name != nullptr, "NULL name passed to weapon_info_lookup");
for (auto it = Weapon_info.cbegin(); it != Weapon_info.cend(); ++it)
if (!stricmp(name, it->name))
return (int)std::distance(Weapon_info.cbegin(), it);
return -1;
}
/**
* Return the index of Weapon_info used by this pointer. Equivalent to the old WEAPON_INFO_INDEX macro:
* #define WEAPON_INFO_INDEX(wip) (int)(wip-Weapon_info)
*/
int weapon_info_get_index(const weapon_info *wip)
{
Assertion(wip != nullptr, "NULL wip passed to weapon_info_get_index");
const weapon_info *data = Weapon_info.data();
return static_cast<int>(std::distance(data, wip));
}
// Parse the weapon flags.
void parse_wi_flags(weapon_info *weaponp)
{
//Make sure we HAVE flags :p
if (!optional_string("$Flags:"))
return;
// have to do this slightly out of order in order to handle spawn properly
// skip OVER flags, check for +override and reset num_spawn_weapons_defined
// otherwise if done afterward, new and old spawn weapons can get mixed up
pause_parse();
SCP_vector<SCP_string> flags;
stuff_string_list(flags);
if (optional_string("+override")) {
// be careful to keep this up to date with any flag that is set while parsing some line other than the Flags line
const Weapon::Info_Flags preset_wi_flag_defs[] = {
Weapon::Info_Flags::Homing_heat,
Weapon::Info_Flags::Homing_aspect,
Weapon::Info_Flags::Cmeasure,
Weapon::Info_Flags::Homing_javelin,
Weapon::Info_Flags::Turns,
Weapon::Info_Flags::Swarm,
Weapon::Info_Flags::Trail,
Weapon::Info_Flags::Particle_spew,
Weapon::Info_Flags::Beam,
Weapon::Info_Flags::Tag,
Weapon::Info_Flags::Shudder,
Weapon::Info_Flags::Transparent,
Weapon::Info_Flags::Variable_lead_homing,
Weapon::Info_Flags::Custom_seeker_str,
Weapon::Info_Flags::Aoe_Electronics,
Weapon::Info_Flags::Apply_Recoil,
Weapon::Info_Flags::Has_display_name,
Weapon::Info_Flags::Vampiric,
Weapon::Info_Flags::Detonate_on_expiration
};
// clear all flags except for the ones that are preset by other fields in the weapon
flagset<Weapon::Info_Flags> cleared_wi_flags;
cleared_wi_flags.set_multiple_from_source(weaponp->wi_flags, std::begin(preset_wi_flag_defs), std::end(preset_wi_flag_defs));
// resetting the flag values if set to override the existing flags
weaponp->wi_flags = cleared_wi_flags;
weaponp->num_spawn_weapons_defined = 0;
}
unpause_parse();
// To make sure +override doesn't overwrite previously parsed values we parse the flags into a separate flagset
SCP_vector<SCP_string> unparsed;
flagset<Weapon::Info_Flags> parsed_flags;
parse_string_flag_list_special(parsed_flags, Weapon_Info_Flags, &unparsed, weaponp, parsed_flags);
optional_string("+override"); // already parsed above
// Now add the parsed flags to the weapon flags
weaponp->wi_flags |= parsed_flags;
bool set_nopierce = false;
for (auto flag = unparsed.begin(); flag != unparsed.end(); ++flag) {
SCP_string flag_text = *flag;
// Parse non-flag strings
if (!stricmp(NOX("no pierce shields"), flag_text.c_str())) {
set_nopierce = true;
}
else if (!stricmp(NOX("interceptable"), flag_text.c_str())) {
weaponp->wi_flags.set(Weapon::Info_Flags::Turret_Interceptable);
weaponp->wi_flags.set(Weapon::Info_Flags::Fighter_Interceptable);
}
else {
Warning(LOCATION, "Unrecognized flag in flag list for weapon %s: \"%s\"", weaponp->name, (*flag).c_str());
}
}
//Do cleanup and sanity checks
if (set_nopierce)
weaponp->wi_flags.remove(Weapon::Info_Flags::Pierce_shields);
if (weaponp->wi_flags[Weapon::Info_Flags::In_tech_database])
weaponp->wi_flags.set(Weapon::Info_Flags::Default_in_tech_database);
if (weaponp->wi_flags[Weapon::Info_Flags::Flak]) {
if (weaponp->wi_flags[Weapon::Info_Flags::Swarm] || weaponp->wi_flags[Weapon::Info_Flags::Corkscrew]) {
weaponp->wi_flags.remove(Weapon::Info_Flags::Swarm);
weaponp->wi_flags.remove(Weapon::Info_Flags::Corkscrew);
Warning(LOCATION, "Swarm, Corkscrew, and Flak are mutually exclusive! Removing Swarm and Corkscrew attributes from weapon %s.\n", weaponp->name);
}
}
if (weaponp->wi_flags[Weapon::Info_Flags::Beam]) {
//If we're not doing damage anyway, then we don't care.. but also this prevents a retail
//warning for the targeting laser
if (weaponp->damage != 0.0f) {
if ((weaponp->armor_factor != 1.0f) && !Beams_use_damage_factors) {
Warning(LOCATION,
"Armor factor defined as %f for beam type weapon %s while damage factors are disabled for beams!",
weaponp->armor_factor, weaponp->name);
}
if ((weaponp->shield_factor != 1.0f) && !Beams_use_damage_factors) {
Warning(LOCATION,
"Shield factor defined as %f for beam type weapon %s while damage factors are disabled for beams!",
weaponp->shield_factor, weaponp->name);
}
if ((weaponp->subsystem_factor != 1.0f) && !Beams_use_damage_factors) {
Warning(LOCATION,
"Subsystem factor defined as %f for beam type weapon %s while damage factors are disabled for beams!",
weaponp->subsystem_factor, weaponp->name);
}
}
}
if (weaponp->wi_flags[Weapon::Info_Flags::Swarm] && weaponp->wi_flags[Weapon::Info_Flags::Corkscrew]) {
weaponp->wi_flags.remove(Weapon::Info_Flags::Corkscrew);
Warning(LOCATION, "Swarm and Corkscrew are mutually exclusive! Defaulting to Swarm on weapon %s.\n", weaponp->name);
}
if (weaponp->wi_flags[Weapon::Info_Flags::Local_ssm]) {
if (!weaponp->is_homing() || weaponp->subtype != WP_MISSILE) {
Warning(LOCATION, "local ssm must be guided missile: %s", weaponp->name);
}
}
if (weaponp->wi_flags[Weapon::Info_Flags::Small_only] && weaponp->wi_flags[Weapon::Info_Flags::Huge])
{
Warning(LOCATION, "\"small only\" and \"huge\" flags are mutually exclusive.\nThey are used together in %s\nThe AI can not use this weapon on any targets", weaponp->name);
}
if (!weaponp->wi_flags[Weapon::Info_Flags::Spawn] && weaponp->wi_flags[Weapon::Info_Flags::Smart_spawn])
{
Warning(LOCATION, "\"smart spawn\" flag used without \"spawn\" flag in %s\n", weaponp->name);
}
if (!weaponp->wi_flags[Weapon::Info_Flags::Homing_heat] && weaponp->wi_flags[Weapon::Info_Flags::Untargeted_heat_seeker])
{
Warning(LOCATION, "Weapon '%s' has the \"untargeted heat seeker\" flag, but Homing Type is not set to \"HEAT\".", weaponp->name);
}
if (!weaponp->wi_flags[Weapon::Info_Flags::Cmeasure] && weaponp->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on])
{
weaponp->wi_flags.remove(Weapon::Info_Flags::Cmeasure_aspect_home_on);
Warning(LOCATION, "Weapon %s has the \"pulls aspect seekers\" flag, but is not a countermeasure.\n", weaponp->name);
}
}
void parse_shockwave_info(shockwave_create_info *sci, const char *pre_char)
{
SCP_string buf;
sprintf(buf, "%sShockwave damage:", pre_char);
if(optional_string(buf.c_str())) {
stuff_float(&sci->damage);
if (sci->damage < 0.0f)
sci->damage = 0.0f;
sci->damage_overridden = true;
}
sprintf(buf, "%sShockwave damage type:", pre_char);
if(optional_string(buf.c_str())) {
stuff_string(buf, F_NAME);
sci->damage_type_idx_sav = damage_type_add(buf.c_str());
sci->damage_type_idx = sci->damage_type_idx_sav;
}
sprintf(buf, "%sBlast Force:", pre_char);
if(optional_string(buf.c_str())) {
stuff_float(&sci->blast);
}
sprintf(buf, "%sInner Radius:", pre_char);
if(optional_string(buf.c_str())) {
stuff_float(&sci->inner_rad);
}
sprintf(buf, "%sOuter Radius:", pre_char);
if(optional_string(buf.c_str())) {
stuff_float(&sci->outer_rad);
}
if (sci->outer_rad < sci->inner_rad) {
Warning(LOCATION, "Shockwave outer radius must be greater than or equal to the inner radius!");
sci->outer_rad = sci->inner_rad;
}
sprintf(buf, "%sShockwave Radius Multiplier over Lifetime Curve:", pre_char);
if (optional_string(buf.c_str())) {
sci->radius_curve_idx = curve_parse(" Shockwave will not use a curve.");
}
sprintf(buf, "%sShockwave Speed:", pre_char);
if(optional_string(buf.c_str())) {
stuff_float(&sci->speed);
}
sprintf(buf, "%sShockwave Rotation:", pre_char);
if(optional_string(buf.c_str())) {
float angs[3];
stuff_float_list(angs, 3);
for(int i = 0; i < 3; i++)
{
angs[i] = fl_radians(angs[i]);
while(angs[i] < 0)
{
angs[i] += PI2;
}
while(angs[i] > PI2)
{
angs[i] -= PI2;
}
}
sci->rot_angles.p = angs[0];
sci->rot_angles.b = angs[1];
sci->rot_angles.h = angs[2];
sci->rot_defined = true;
}
sprintf(buf, "%sShockwave Rotation Is Relative To Parent:", pre_char);
if(optional_string(buf.c_str())) {
stuff_boolean(&sci->rot_parent_relative);
}
sprintf(buf, "%sShockwave Model:", pre_char);
if(optional_string(buf.c_str())) {
stuff_string(sci->pof_name, F_NAME, MAX_FILENAME_LEN);
}
sprintf(buf, "%sShockwave Name:", pre_char);
if(optional_string(buf.c_str())) {
stuff_string(sci->name, F_NAME, MAX_FILENAME_LEN);
}
sprintf(buf, "%sShockwave Sound:", pre_char);
parse_game_sound(buf.c_str(), &sci->blast_sound_id);
}
static SCP_vector<SCP_string> Removed_weapons;
enum Pspew_legacy_type {
PSPEW_NONE, //used to disable a spew, useful for xmts
PSPEW_DEFAULT, //std fs2 pspew
PSPEW_HELIX, //q2 style railgun trail
PSPEW_SPARKLER, //random particles in every direction, can be sperical or ovoid
PSPEW_RING, //outward expanding ring
PSPEW_PLUME, //spewers arrayed within a radius for thruster style effects, may converge or scatter
};
struct pspew_legacy_parse_data {
// particle spew stuff
Pspew_legacy_type particle_spew_type; //added pspew type field -nuke
int particle_spew_count;
int particle_spew_time;
float particle_spew_vel;
float particle_spew_radius;
float particle_spew_lifetime;
float particle_spew_scale;
float particle_spew_z_scale; //length value for some effects -nuke
float particle_spew_rotation_rate; //rotation rate for some particle effects -nuke
vec3d particle_spew_offset; //offsets and normals, yay!
vec3d particle_spew_velocity;
SCP_string particle_spew_anim;
};
static SCP_unordered_map<int, SCP_unordered_map<int, pspew_legacy_parse_data>> pspew_legacy_parse_data_buffer;
static bool pspew_do_warning = false;
static particle::ParticleEffectHandle convertLegacyPspewBuffer(const pspew_legacy_parse_data& pspew_buffer, const weapon_info* wip) {
auto particle_spew_count = static_cast<float>(pspew_buffer.particle_spew_count);
float particle_spew_spawns_per_second = 1000.f / static_cast<float>(pspew_buffer.particle_spew_time);
if (particle_spew_spawns_per_second > 60.f) {
mprintf(("Warning: %s(line %i): PSPEW requested with a spawn frequency of over 60FPS. This used to be capped to spawn once a frame. It will now be artificially capped at 60 spawns per second.\n", Current_filename, get_line_num()));
particle_spew_spawns_per_second = 60.f;
pspew_do_warning = true;
}
bool hasAnim = !pspew_buffer.particle_spew_anim.empty() && bm_validate_filename(pspew_buffer.particle_spew_anim, true, true);
std::unique_ptr<particle::ParticleVolume> velocity_vol, position_vol;
bool absolutePositionVelocityInherit = false;
std::optional<::util::ParsedRandomFloatRange> positionBasedVelocity = std::nullopt;
particle::ParticleEffect::ShapeDirection direction = particle::ParticleEffect::ShapeDirection::ALIGNED;
switch (pspew_buffer.particle_spew_type) {
case PSPEW_DEFAULT:
position_vol = std::make_unique<particle::ConeVolume>(::util::UniformFloatRange(-PI_2, PI_2), powf(pspew_buffer.particle_spew_scale, 3.0f));
direction = particle::ParticleEffect::ShapeDirection::REVERSE;
break;
case PSPEW_HELIX: {
particle_spew_count = 1.f;
particle_spew_spawns_per_second *= pspew_buffer.particle_spew_count;
int curve_id = static_cast<int>(Curves.size());
auto& curve = Curves.emplace_back(SCP_string(";PSPEWHelixCurve;") + wip->name);
curve.keyframes.emplace_back(curve_keyframe{vec2d{0.f, 0.f}, CurveInterpFunction::Linear, 0.f, 0.f});
curve.keyframes.emplace_back(curve_keyframe{vec2d{1.f / pspew_buffer.particle_spew_rotation_rate, PI2}, CurveInterpFunction::Linear, 0.f, 0.f});
auto vel_vol_temp = std::make_unique<particle::PointVolume>();
vel_vol_temp->posOffset = vec3d {{{pspew_buffer.particle_spew_scale, 0.f, 0.f}}};
vel_vol_temp->m_modular_curves.add_curve("Time Running", particle::PointVolume::VolumeModularCurveOutput::OFFSET_ROT, modular_curves_entry{curve_id, ::util::UniformFloatRange(1.f), ::util::UniformFloatRange(0.f, 1.f / pspew_buffer.particle_spew_rotation_rate), true});
velocity_vol = std::move(vel_vol_temp);
}
break;
case PSPEW_SPARKLER: {
//This is really strange behaviour cause the old sparklers (likely accidentally) cumulated the random velocity for each particle.
//This does not do this, but at least tries to emulate the resulting velocity magnitudes in similar chaotic fashion.
int curve_id_dist = static_cast<int>(Curves.size());
auto& curve_dist = Curves.emplace_back(SCP_string(";PSPEWSparklerCurveDist;") + wip->name);
curve_dist.keyframes.emplace_back(curve_keyframe{vec2d{0.f, 1.f / particle_spew_count}, CurveInterpFunction::Linear, 0.f, 0.f});
curve_dist.keyframes.emplace_back(curve_keyframe{vec2d{1.f, 1.f}, CurveInterpFunction::Linear, 0.f, 0.f});
int curve_id_bias = static_cast<int>(Curves.size());
auto& curve_bias = Curves.emplace_back(SCP_string(";PSPEWSparklerCurveBias;") + wip->name);
curve_bias.keyframes.emplace_back(curve_keyframe{vec2d{0.f, 0.f}, CurveInterpFunction::Linear, 0.f, 0.f});
curve_bias.keyframes.emplace_back(curve_keyframe{vec2d{1.f, particle_spew_count}, CurveInterpFunction::Linear, 0.f, 0.f});
auto vel_vol_temp = std::make_unique<particle::SpheroidVolume>(1.f, pspew_buffer.particle_spew_z_scale, pspew_buffer.particle_spew_scale * particle_spew_count);
vel_vol_temp->m_modular_curves.add_curve("Fraction Particles Spawned", particle::SpheroidVolume::VolumeModularCurveOutput::RADIUS, modular_curves_entry{curve_id_dist});
vel_vol_temp->m_modular_curves.add_curve("Fraction Particles Spawned", particle::SpheroidVolume::VolumeModularCurveOutput::BIAS, modular_curves_entry{curve_id_bias});
velocity_vol = std::move(vel_vol_temp);
}
break;
case PSPEW_RING: {
static const int ring_pspew_rot = []() -> int {
int curve_id = static_cast<int>(Curves.size());
auto& curve = Curves.emplace_back(";PSPEWRingCurve");
curve.keyframes.emplace_back(curve_keyframe{vec2d{0.f, 0.f}, CurveInterpFunction::Linear, 0.f, 0.f});
curve.keyframes.emplace_back(curve_keyframe{vec2d{1.f, PI2}, CurveInterpFunction::Linear, 0.f, 0.f});
return curve_id;
}();
auto vel_vol_temp = std::make_unique<particle::PointVolume>();
vel_vol_temp->posOffset = vec3d {{{pspew_buffer.particle_spew_scale, 0.f, 0.f}}};
vel_vol_temp->m_modular_curves.add_curve("Fraction Particles Spawned", particle::PointVolume::VolumeModularCurveOutput::OFFSET_ROT, modular_curves_entry{ring_pspew_rot});
velocity_vol = std::move(vel_vol_temp);
}
break;
case PSPEW_PLUME:
position_vol = std::make_unique<particle::RingVolume>(pspew_buffer.particle_spew_scale, false);
positionBasedVelocity = ::util::UniformFloatRange(pspew_buffer.particle_spew_z_scale);
absolutePositionVelocityInherit = true;
break;
default:
UNREACHABLE("Invalid PSPEW legacy type!");
}
return particle::ParticleManager::get()->addEffect(particle::ParticleEffect(
"", //Name
::util::UniformFloatRange(particle_spew_count), //Particle num
particle::ParticleEffect::Duration::ALWAYS, //permanent Particle Emission
::util::UniformFloatRange(), //No duration
::util::UniformFloatRange (particle_spew_spawns_per_second), //Single particle only
direction, //Particle direction
::util::UniformFloatRange(pspew_buffer.particle_spew_vel), //Velocity Inherit
false, //Velocity Inherit absolute?
std::move(velocity_vol), //Velocity volume
::util::UniformFloatRange(1.f), //Velocity volume multiplier
particle::ParticleEffect::VelocityScaling::NONE, //Velocity directional scaling
std::nullopt, //Orientation-based velocity
positionBasedVelocity, //Position-based velocity
std::move(position_vol), //Position volume
particle::ParticleEffectHandle::invalid(), //Trail
1.f, //Chance
false, //Affected by detail
-1.f, //Culling range multiplier
!hasAnim, //Disregard Animation Length. Must be true for everything using particle::Anim_bitmap_X
false, //Don't reverse animation
false, //parent local
false, //ignore velocity inherit if parented
absolutePositionVelocityInherit, //position velocity inherit absolute?
IS_VEC_NULL(&pspew_buffer.particle_spew_velocity) ? std::nullopt : std::optional(pspew_buffer.particle_spew_velocity), //Local velocity offset
IS_VEC_NULL(&pspew_buffer.particle_spew_offset) ? std::nullopt : std::optional(pspew_buffer.particle_spew_offset), //Local offset
::util::UniformFloatRange(pspew_buffer.particle_spew_lifetime), //Lifetime
::util::UniformFloatRange(pspew_buffer.particle_spew_radius), //Radius
hasAnim ? bm_load_either(pspew_buffer.particle_spew_anim.c_str()) : particle::Anim_bitmap_id_smoke)); //Bitmap or Anim
}
/**
* Parse the information for a specific ship type.
* Return weapon index if successful, otherwise return -1
*/
int parse_weapon(int subtype, bool replace, const char *filename)
{
char buf[NAME_LENGTH];
char fname[NAME_LENGTH];
weapon_info *wip = nullptr;
int iff, idx;
int primary_rearm_rate_specified=0;
bool first_time = false;
bool create_if_not_found = true;
bool remove_weapon = false;
required_string("$Name:");
stuff_string(fname, F_NAME, NAME_LENGTH);
// Remove @ symbol -- these used to denote weapons that would only be parsed in demo builds
if (fname[0] == '@') {
backspace(fname);
}
diag_printf("Weapon name -- %s\n", fname);
if(optional_string("+nocreate")) {
if(!replace) {
Warning(LOCATION, "+nocreate flag used for weapon in non-modular table");
}
create_if_not_found = false;
}
// check first if this is on the remove blacklist
auto it = std::find(Removed_weapons.begin(), Removed_weapons.end(), fname);
if (it != Removed_weapons.end()) {
remove_weapon = true;
}
// we might have a remove tag
if (optional_string("+remove")) {
if (!replace) {
Warning(LOCATION, "+remove flag used for weapon in non-modular table");
}
if (!remove_weapon) {
Removed_weapons.push_back(fname);
remove_weapon = true;
}
}
//Check if weapon exists already
int w_id = weapon_info_lookup(fname);
// maybe remove it
if (remove_weapon)
{
if (w_id >= 0) {
mprintf(("Removing previously parsed weapon '%s'\n", fname));
Weapon_info.erase(Weapon_info.begin() + w_id);
}
if (!skip_to_start_of_string_either("$Name:", "#End")) {
error_display(1, "Missing [#End] or [$Name] after weapon class %s", fname);
}
return -1;
}
// an entry for this weapon exists
if (w_id >= 0)
{
wip = &Weapon_info[w_id];
if (!replace)
{
Warning(LOCATION, "Weapon name %s already exists in weapons.tbl. All weapon names must be unique; the second entry has been skipped", fname);
if (!skip_to_start_of_string_either("$Name:", "#End")) {
error_display(1, "Missing [#End] or [$Name] after duplicate weapon %s", fname);
}
return -1;
}
}
// an entry does not exist
else
{
// Don't create weapon if it has +nocreate and is in a modular table.
if (!create_if_not_found && replace)
{
if (!skip_to_start_of_string_either("$Name:", "#End")) {
error_display(1, "Missing [#End] or [$Name] after weapon %s", fname);
}
return -1;
}
// Check if there are too many weapon classes
if (Weapon_info.size() >= MAX_WEAPON_TYPES) {
Error(LOCATION, "Too many weapon classes before '%s'; maximum is %d.\n", fname, MAX_WEAPON_TYPES);
}
w_id = weapon_info_size();
Weapon_info.push_back(weapon_info());
wip = &Weapon_info.back();
first_time = true;
strcpy_s(wip->name, fname);
// if this name has a hash, create a default display name
if (get_pointer_to_first_hash_symbol(wip->name)) {
wip->display_name = wip->name;
end_string_at_first_hash_symbol(wip->display_name, true);
consolidate_double_characters(wip->display_name, '#');
wip->wi_flags.set(Weapon::Info_Flags::Has_display_name);
}
// do German translation
if (Lcl_gr && !Disable_built_in_translations) {
if (!wip->has_display_name()) {
wip->display_name = wip->name;
lcl_translate_wep_name_gr(wip->display_name);
wip->wi_flags.set(Weapon::Info_Flags::Has_display_name);
}
}
}
if (optional_string("$Alt name:") || optional_string("$Display Name:"))
{
stuff_string(wip->display_name, F_NAME);
wip->wi_flags.set(Weapon::Info_Flags::Has_display_name);
}
//Set subtype
if(optional_string("$Subtype:"))
{
stuff_string(fname, F_NAME, NAME_LENGTH);
if(!stricmp("Primary", fname)) {
wip->subtype = WP_LASER;
} else if(!stricmp("Secondary", fname)) {
wip->subtype = WP_MISSILE;
} else {
Warning(LOCATION, "Unknown subtype on weapon '%s'", wip->name);
}
}
else if(wip->subtype != WP_UNUSED && !first_time)
{
if(wip->subtype != subtype) {
Warning(LOCATION, "Type of weapon %s entry does not agree with original entry type.", wip->name);
}
}
else
{
wip->subtype = subtype;
}
// assign subtype-specific flags
if (first_time)
{
if (wip->subtype == WP_MISSILE)
wip->wi_flags.set(Weapon::Info_Flags::Detonate_on_expiration);
}
if (optional_string("+Title:")) {
stuff_string(wip->title, F_NAME, WEAPON_TITLE_LEN);
}
if (optional_string("+Description:")) {
stuff_string(wip->desc, F_MULTITEXT, true);
// Check if the text exceeds the limits
auto current_line = wip->desc.get();
size_t num_lines = 0;
while (current_line != nullptr) {
auto line_end = strchr(current_line, '\n');
auto line_length = line_end - current_line;
if (line_end == nullptr) {
line_length = strlen(current_line);
}
if (line_length >= WEAPON_DESC_MAX_LENGTH) {
error_display(0, "Weapon description line " SIZE_T_ARG " is too long. Maximum is %d.", num_lines + 1, WEAPON_DESC_MAX_LENGTH);
}
++num_lines;
current_line = line_end == nullptr ? nullptr : line_end + 1; // Skip the \n character if it was a complete line
}
if (num_lines >= WEAPON_DESC_MAX_LINES) {
error_display(0, "Weapon description has too many lines. Maximum is %d.", WEAPON_DESC_MAX_LINES);
}
}
if (optional_string("+Tech Title:")) {
stuff_string(wip->tech_title, F_NAME, NAME_LENGTH);
}
if (optional_string("+Tech Anim:")) {
stuff_string(wip->tech_anim_filename, F_NAME, MAX_FILENAME_LEN);
}
if (optional_string("+Tech Description:")) {
stuff_string(wip->tech_desc, F_MULTITEXT, true);
}
if (optional_string("$Turret Name:")) {
stuff_string(wip->altSubsysName, F_NAME, NAME_LENGTH);
}
if (optional_string("$Tech Model:")) {
stuff_string(wip->tech_model, F_NAME, MAX_FILENAME_LEN);
if (optional_string("+Closeup_pos:")) {
stuff_vec3d(&wip->closeup_pos);
}
if (optional_string("+Closeup_zoom:")) {
stuff_float(&wip->closeup_zoom);
}
}
// Weapon fadein effect, used when no ani is specified or weapon_select_3d is active
if (first_time) {
wip->selection_effect = Default_weapon_select_effect; // By default, use the FS2 effect
wip->fs2_effect_grid_color = Default_fs2_effect_grid_color;
wip->fs2_effect_scanline_color = Default_fs2_effect_scanline_color;
wip->fs2_effect_grid_density = Default_fs2_effect_grid_density;
wip->fs2_effect_wireframe_color = Default_fs2_effect_wireframe_color;
}
if(optional_string("$Selection Effect:")) {
char effect[NAME_LENGTH];
stuff_string(effect, F_NAME, NAME_LENGTH);
if (!stricmp(effect, "FS2"))
wip->selection_effect = 2;