FS2_Open
Open source remastering of the Freespace 2 engine
weapons.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) Volition, Inc. 1999. All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 #include "ai/aibig.h"
13 #include "asteroid/asteroid.h"
14 #include "cmdline/cmdline.h"
15 #include "cmeasure/cmeasure.h"
16 #include "debugconsole/console.h"
17 #include "fireball/fireballs.h"
18 #include "freespace2/freespace.h"
19 #include "gamesnd/gamesnd.h"
20 #include "globalincs/linklist.h"
21 #include "graphics/grbatch.h"
22 #include "hud/hud.h"
23 #include "hud/hudartillery.h"
24 #include "iff_defs/iff_defs.h"
25 #include "io/joy_ff.h"
26 #include "io/timer.h"
27 #include "localization/localize.h"
28 #include "math/staticrand.h"
29 #include "mod_table/mod_table.h"
30 #include "model/modelrender.h"
31 #include "network/multi.h"
32 #include "network/multimsgs.h"
33 #include "network/multiutil.h"
34 #include "object/objcollide.h"
35 #include "object/object.h"
36 #include "parse/parselo.h"
37 #include "parse/scripting.h"
38 #include "particle/particle.h"
39 #include "playerman/player.h"
40 #include "radar/radar.h"
41 #include "radar/radarsetup.h"
42 #include "render/3d.h"
43 #include "ship/ship.h"
44 #include "ship/shiphit.h"
45 #include "stats/scoring.h"
46 #include "weapon/beam.h" // for BEAM_TYPE_? definitions
47 #include "weapon/corkscrew.h"
48 #include "weapon/emp.h"
49 #include "weapon/flak.h"
50 #include "weapon/muzzleflash.h"
51 #include "weapon/swarm.h"
52 #include "weapon/weapon.h"
53 
54 // 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.
55 typedef struct delayed_ssm_data {
57  int linenum;
62 
63 typedef struct delayed_ssm_index_data {
65  int linenum;
69 
70 
71 #ifndef NDEBUG
73 DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
74 #endif
75 
76 static int Weapon_flyby_sound_timer;
77 
80 
81 #define MISSILE_OBJ_USED (1<<0) // flag used in missile_obj struct
82 #define MAX_MISSILE_OBJS MAX_WEAPONS // max number of missiles tracked in missile list
83 missile_obj Missile_objs[MAX_MISSILE_OBJS]; // array used to store missile object indexes
84 missile_obj Missile_obj_list; // head of linked list of missile_obj structs
85 
86 //WEAPON SUBTYPE STUFF
88  "Laser",
89  "Missile",
90  "Beam"
91 };
92 int Num_weapon_subtypes = sizeof(Weapon_subtype_names)/sizeof(char *);
93 
95  { "fast firing", WBF_FAST_FIRING, 0 },
96  { "random length", WBF_RANDOM_LENGTH, 0 }
97 };
98 
100 
102 
104 
106 
107 int Num_weapons = 0;
110 
113 
114 int missile_model = -1;
115 
118 
119 static int *used_weapons = NULL;
120 
122 char **Spawn_names = NULL;
123 
124 int Num_player_weapon_precedence; // Number of weapon types in Player_weapon_precedence
125 int Player_weapon_precedence[MAX_WEAPON_TYPES]; // Array of weapon types, precedence list for player weapon selection
126 
127 // Used to avoid playing too many impact sounds in too short a time interval.
128 // This will elimate the odd "stereo" effect that occurs when two weapons impact at
129 // nearly the same time, like from a double laser (also saves sound channels!)
130 #define IMPACT_SOUND_DELTA 50 // in milliseconds
131 int Weapon_impact_timer; // timer, initialized at start of each mission
132 
133 // energy suck defines
134 #define ESUCK_DEFAULT_WEAPON_REDUCE (10.0f)
135 #define ESUCK_DEFAULT_AFTERBURNER_REDUCE (10.0f)
136 
137 // scale factor for supercaps taking damage from weapons which are not "supercap" weapons
138 #define SUPERCAP_DAMAGE_SCALE 0.25f
139 
140 // scale factor for big ships getting hit by flak
141 #define FLAK_DAMAGE_SCALE 0.05f
142 
143 //default time of a homing weapon to not home
144 #define HOMING_DEFAULT_FREE_FLIGHT_TIME 0.5f
145 
146 // time delay between each swarm missile that is fired
147 #define SWARM_MISSILE_DELAY 150
148 
149 // homing missiles have an extended lifetime so they don't appear to run out of gas before they can hit a moving target at extreme
150 // range. Check the comment in weapon_set_tracking_info() for more details
151 #define LOCKED_HOMING_EXTENDED_LIFE_FACTOR 1.2f
152 
153 extern int compute_num_homing_objects(object *target_objp);
154 
155 extern void fs2netd_add_table_validation(const char *tblname);
156 
157 
159 {
160  ExplosionInfo.clear();
161 }
162 
163 int weapon_explosions::GetIndex(char *filename)
164 {
165  if ( filename == NULL ) {
166  Int3();
167  return -1;
168  }
169 
170  for (size_t i = 0; i < ExplosionInfo.size(); i++) {
171  if ( !stricmp(ExplosionInfo[i].lod[0].filename, filename)) {
172  return (int)i;
173  }
174  }
175 
176  return -1;
177 }
178 
179 int weapon_explosions::Load(char *filename, int expected_lods)
180 {
181  char name_tmp[MAX_FILENAME_LEN] = "";
182  int bitmap_id = -1;
183  int nframes, nfps;
184  weapon_expl_info new_wei;
185 
186  Assert( expected_lods <= MAX_WEAPON_EXPL_LOD );
187 
188  //Check if it exists
189  int idx = GetIndex(filename);
190 
191  if (idx != -1)
192  return idx;
193 
194  new_wei.lod_count = 1;
195 
196  strcpy_s(new_wei.lod[0].filename, filename);
197  new_wei.lod[0].bitmap_id = bm_load_animation(filename, &new_wei.lod[0].num_frames, &new_wei.lod[0].fps, NULL, 1);
198 
199  if (new_wei.lod[0].bitmap_id < 0) {
200  Warning(LOCATION, "Weapon explosion '%s' does not have an LOD0 anim!", filename);
201 
202  // if we don't have the first then it's only safe to assume that the rest are missing or not usable
203  return -1;
204  }
205 
206  // 2 chars for the lod, 4 for the extension that gets added automatically
207  if ( (MAX_FILENAME_LEN - strlen(filename)) > 6 ) {
208  for (idx = 1; idx < expected_lods; idx++) {
209  sprintf(name_tmp, "%s_%d", filename, idx);
210 
211  bitmap_id = bm_load_animation(name_tmp, &nframes, &nfps, NULL, 1);
212 
213  if (bitmap_id > 0) {
214  strcpy_s(new_wei.lod[idx].filename, name_tmp);
215  new_wei.lod[idx].bitmap_id = bitmap_id;
216  new_wei.lod[idx].num_frames = nframes;
217  new_wei.lod[idx].fps = nfps;
218 
219  new_wei.lod_count++;
220  } else {
221  break;
222  }
223  }
224 
225  if (new_wei.lod_count != expected_lods)
226  Warning(LOCATION, "For '%s', %i of %i LODs are missing!", filename, expected_lods - new_wei.lod_count, expected_lods);
227  }
228  else {
229  Warning(LOCATION, "Filename '%s' is too long to have any LODs.", filename);
230  }
231 
232  ExplosionInfo.push_back( new_wei );
233 
234  return (int)(ExplosionInfo.size() - 1);
235 }
236 
238 {
239  int i;
240 
241  if ( (idx < 0) || (idx >= (int)ExplosionInfo.size()) )
242  return;
243 
244  weapon_expl_info *wei = &ExplosionInfo[idx];
245 
246  for ( i = 0; i < wei->lod_count; i++ ) {
247  if ( wei->lod[i].bitmap_id >= 0 ) {
249  }
250  }
251 }
252 
253 int weapon_explosions::GetAnim(int weapon_expl_index, vec3d *pos, float size)
254 {
255  if ( (weapon_expl_index < 0) || (weapon_expl_index >= (int)ExplosionInfo.size()) )
256  return -1;
257 
258  //Get our weapon expl for the day
259  weapon_expl_info *wei = &ExplosionInfo[weapon_expl_index];
260 
261  if (wei->lod_count == 1)
262  return wei->lod[0].bitmap_id;
263 
264  // now we have to do some work
265  vertex v;
266  int x, y, w, h, bm_size;
267  int must_stop = 0;
268  int best_lod = 1;
269  int behind = 0;
270 
271  // start the frame
272  extern int G3_count;
273 
274  if(!G3_count){
275  g3_start_frame(1);
276  must_stop = 1;
277  }
279 
280  // get extents of the rotated bitmap
281  g3_rotate_vertex(&v, pos);
282 
283  // if vertex is behind, find size if in front, then drop down 1 LOD
284  if (v.codes & CC_BEHIND) {
285  float dist = vm_vec_dist_quick(&Eye_position, pos);
286  vec3d temp;
287 
288  behind = 1;
289  vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.vec.fvec, dist);
290  g3_rotate_vertex(&v, &temp);
291 
292  // if still behind, bail and go with default
293  if (v.codes & CC_BEHIND) {
294  behind = 0;
295  }
296  }
297 
298  if (!g3_get_bitmap_dims(wei->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
299  if (Detail.hardware_textures == 4) {
300  // straight LOD
301  if(w <= bm_size/8){
302  best_lod = 3;
303  } else if(w <= bm_size/2){
304  best_lod = 2;
305  } else if(w <= 1.3f*bm_size){
306  best_lod = 1;
307  } else {
308  best_lod = 0;
309  }
310  } else {
311  // less aggressive LOD for lower detail settings
312  if(w <= bm_size/8){
313  best_lod = 3;
314  } else if(w <= bm_size/3){
315  best_lod = 2;
316  } else if(w <= (1.15f*bm_size)){
317  best_lod = 1;
318  } else {
319  best_lod = 0;
320  }
321  }
322  }
323 
324  // if it's behind, bump up LOD by 1
325  if (behind)
326  best_lod++;
327 
328  // end the frame
329  if (must_stop)
330  g3_end_frame();
331 
332  best_lod = MIN(best_lod, wei->lod_count - 1);
333  Assert( (best_lod >= 0) && (best_lod < MAX_WEAPON_EXPL_LOD) );
334 
335  return wei->lod[best_lod].bitmap_id;
336 }
337 
338 
339 void parse_weapon_expl_tbl(const char *filename)
340 {
341  uint i;
342  lod_checker lod_check;
343 
344  try
345  {
346  read_file_text(filename, CF_TYPE_TABLES);
347  reset_parse();
348 
349  required_string("#Start");
350  while (required_string_either("#End", "$Name:"))
351  {
352  memset(&lod_check, 0, sizeof(lod_checker));
353 
354  // base filename
355  required_string("$Name:");
357 
358  //Do we have an LOD num
359  if (optional_string("$LOD:"))
360  {
361  stuff_int(&lod_check.num_lods);
362  }
363 
364  // only bother with this if we have 1 or more lods and less than max lods,
365  // otherwise the stardard level loading will take care of the different effects
366  if ((lod_check.num_lods > 0) || (lod_check.num_lods < MAX_WEAPON_EXPL_LOD)) {
367  // name check, update lod count if it already exists
368  for (i = 0; i < LOD_checker.size(); i++) {
369  if (!stricmp(LOD_checker[i].filename, lod_check.filename)) {
370  LOD_checker[i].num_lods = lod_check.num_lods;
371  }
372  }
373 
374  // old entry not found, add new entry
375  if (i == LOD_checker.size()) {
376  LOD_checker.push_back(lod_check);
377  }
378  }
379  }
380  required_string("#End");
381  }
382  catch (const parse::ParseException& e)
383  {
384  mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
385  return;
386  }
387 }
388 
393 {
394  int i;
395 
396  list_init(&Missile_obj_list);
397  for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
398  Missile_objs[i].flags = 0;
399  }
400 }
401 
406 int missile_obj_list_add(int objnum)
407 {
408  int i;
409 
410  for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
411  if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
412  break;
413  }
414  if ( i == MAX_MISSILE_OBJS ) {
415  Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
416  return -1;
417  }
418 
419  Missile_objs[i].flags = 0;
420  Missile_objs[i].objnum = objnum;
421  list_append(&Missile_obj_list, &Missile_objs[i]);
422  Missile_objs[i].flags |= MISSILE_OBJ_USED;
423 
424  return i;
425 }
426 
432 {
433  Assert(index >= 0 && index < MAX_MISSILE_OBJS);
434  list_remove(&Missile_obj_list, &Missile_objs[index]);
435  Missile_objs[index].flags = 0;
436 }
437 
442 {
443  object *objp;
444 
446 
447  for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
448  if ( objp->type == OBJ_WEAPON && Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE ) {
450  }
451  }
452 }
453 
459 {
460  Assert(index >= 0 && index < MAX_MISSILE_OBJS);
461  return &Missile_objs[index];
462 }
463 
467 int weapon_info_lookup(const char *name)
468 {
469  // bogus
470  if (name == NULL)
471  return -1;
472 
473  for (int i=0; i<Num_weapon_types; i++)
474  if (!stricmp(name, Weapon_info[i].name))
475  return i;
476 
477  return -1;
478 }
479 
480 #define DEFAULT_WEAPON_SPAWN_COUNT 10
481 
482 // Parse the weapon flags.
483 void parse_wi_flags(weapon_info *weaponp, int wi_flags, int wi_flags2, int wi_flags3)
484 {
485  const char *spawn_str = NOX("Spawn");
486  const size_t spawn_str_len = strlen(spawn_str);
487 
488  //Make sure we HAVE flags :p
489  if(!optional_string("$Flags:"))
490  return;
491 
492  char weapon_strings[MAX_WEAPON_FLAGS][NAME_LENGTH];
493  int num_strings;
494 
495  num_strings = stuff_string_list(weapon_strings, MAX_WEAPON_FLAGS);
496 
497  if (optional_string("+override")) {
498  // reseting the flag values if set to override the existing flags
499  weaponp->wi_flags = wi_flags;
500  weaponp->wi_flags2 = wi_flags2;
501  weaponp->wi_flags3 = wi_flags3;
502  }
503 
504  bool set_pierce = false;
505  bool set_nopierce = false;
506 
507  for (int i=0; i<num_strings; i++) {
508  if (!strnicmp(spawn_str, weapon_strings[i], 5))
509  {
511  {
512  //We need more spawning slots
513  //allocate in slots of 10
514  if((Num_spawn_types % 10) == 0) {
515  Spawn_names = (char **)vm_realloc(Spawn_names, (Num_spawn_types + 10) * sizeof(*Spawn_names));
516  }
517 
518  int skip_length, name_length;
519  char *temp_string;
520 
521  temp_string = weapon_strings[i];
522 
523  weaponp->wi_flags |= WIF_SPAWN;
524  weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_type = (short)Num_spawn_types;
525  skip_length = spawn_str_len + strspn(&temp_string[spawn_str_len], NOX(" \t"));
526  char *num_start = strchr(&temp_string[skip_length], ',');
527  if (num_start == NULL) {
529  name_length = 999;
530  } else {
531  weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = (short)atoi(num_start+1);
532  name_length = num_start - temp_string - skip_length;
533  }
534 
536 
537  Spawn_names[Num_spawn_types] = vm_strndup( &weapon_strings[i][skip_length], name_length );
538  Num_spawn_types++;
539  weaponp->num_spawn_weapons_defined++;
540  } else {
541  Warning(LOCATION, "Illegal to have more than %d spawn types for one weapon.\nIgnoring weapon %s", MAX_SPAWN_TYPES_PER_WEAPON, weaponp->name);
542  }
543  } else if (!stricmp(NOX("Remote Detonate"), weapon_strings[i]))
544  weaponp->wi_flags |= WIF_REMOTE;
545  else if (!stricmp(NOX("Puncture"), weapon_strings[i]))
546  weaponp->wi_flags |= WIF_PUNCTURE;
547  else if (!stricmp(NOX("Big Ship"), weapon_strings[i]))
548  weaponp->wi_flags |= WIF_BIG_ONLY;
549  else if (!stricmp(NOX("Huge"), weapon_strings[i]))
550  weaponp->wi_flags |= WIF_HUGE;
551  else if (!stricmp(NOX("Bomber+"), weapon_strings[i]))
552  weaponp->wi_flags |= WIF_BOMBER_PLUS;
553  else if (!stricmp(NOX("child"), weapon_strings[i]))
554  weaponp->wi_flags |= WIF_CHILD;
555  else if (!stricmp(NOX("Bomb"), weapon_strings[i]))
556  weaponp->wi_flags |= WIF_BOMB;
557  else if (!stricmp(NOX("No Dumbfire"), weapon_strings[i]))
558  weaponp->wi_flags |= WIF_NO_DUMBFIRE;
559  else if (!stricmp(NOX("In tech database"), weapon_strings[i]))
560  weaponp->wi_flags |= WIF_IN_TECH_DATABASE;
561  else if (!stricmp(NOX("Player allowed"), weapon_strings[i]))
562  weaponp->wi_flags |= WIF_PLAYER_ALLOWED;
563  else if (!stricmp(NOX("Particle Spew"), weapon_strings[i]))
564  weaponp->wi_flags |= WIF_PARTICLE_SPEW;
565  else if (!stricmp(NOX("EMP"), weapon_strings[i]))
566  weaponp->wi_flags |= WIF_EMP;
567  else if (!stricmp(NOX("Esuck"), weapon_strings[i]))
568  weaponp->wi_flags |= WIF_ENERGY_SUCK;
569  else if (!stricmp(NOX("Flak"), weapon_strings[i]))
570  weaponp->wi_flags |= WIF_FLAK;
571  else if (!stricmp(NOX("Corkscrew"), weapon_strings[i]))
572  weaponp->wi_flags |= WIF_CORKSCREW;
573  else if (!stricmp(NOX("Shudder"), weapon_strings[i]))
574  weaponp->wi_flags |= WIF_SHUDDER;
575  else if (!stricmp(NOX("Electronics"), weapon_strings[i]))
576  weaponp->wi_flags |= WIF_ELECTRONICS;
577  else if (!stricmp(NOX("lockarm"), weapon_strings[i]))
578  weaponp->wi_flags |= WIF_LOCKARM;
579  else if (!stricmp(NOX("beam"), weapon_strings[i]))
580  {
581  weaponp->wi_flags |= WIF_BEAM;
582 
583  // IMPORTANT: beams pierce shields by default :rolleyes: :p - Goober5000
584  weaponp->wi_flags2 |= WIF2_PIERCE_SHIELDS;
585  }
586  else if (!stricmp(NOX("stream"), weapon_strings[i]))
587  weaponp->wi_flags |= WIF_STREAM;
588  else if (!stricmp(NOX("supercap"), weapon_strings[i]))
589  weaponp->wi_flags |= WIF_SUPERCAP;
590  else if (!stricmp(NOX("countermeasure"), weapon_strings[i]))
591  weaponp->wi_flags |= WIF_CMEASURE;
592  else if (!stricmp(NOX("ballistic"), weapon_strings[i]))
593  weaponp->wi_flags2 |= WIF2_BALLISTIC;
594  else if (!stricmp(NOX("pierce shields"), weapon_strings[i]))
595  set_pierce = true;
596  else if (!stricmp(NOX("no pierce shields"), weapon_strings[i])) // only for beams
597  set_nopierce = true;
598  else if (!stricmp(NOX("local ssm"), weapon_strings[i]))
599  weaponp->wi_flags2 |= WIF2_LOCAL_SSM;
600  else if (!stricmp(NOX("tagged only"), weapon_strings[i]))
601  weaponp->wi_flags2 |= WIF2_TAGGED_ONLY;
602  else if (!stricmp(NOX("beam no whack"), weapon_strings[i]))
603  {
604  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");
605  weaponp->mass = 0.0f;
606  }
607  else if (!stricmp(NOX("cycle"), weapon_strings[i]))
608  weaponp->wi_flags2 |= WIF2_CYCLE;
609  else if (!stricmp(NOX("small only"), weapon_strings[i]))
610  weaponp->wi_flags2 |= WIF2_SMALL_ONLY;
611  else if (!stricmp(NOX("same turret cooldown"), weapon_strings[i]))
613  else if (!stricmp(NOX("apply no light"), weapon_strings[i]))
614  weaponp->wi_flags2 |= WIF2_MR_NO_LIGHTING;
615  else if (!stricmp(NOX("training"), weapon_strings[i]))
616  weaponp->wi_flags2 |= WIF2_TRAINING;
617  else if (!stricmp(NOX("smart spawn"), weapon_strings[i]))
618  weaponp->wi_flags2 |= WIF2_SMART_SPAWN;
619  else if (!stricmp(NOX("inherit parent target"), weapon_strings[i]))
621  else if (!stricmp(NOX("no emp kill"), weapon_strings[i]))
622  weaponp->wi_flags2 |= WIF2_NO_EMP_KILL;
623  else if (!stricmp(NOX("untargeted heat seeker"), weapon_strings[i]))
625  else if (!stricmp(NOX("no radius doubling"), weapon_strings[i])) {
626  if (weaponp->wi_flags & WIF_BOMB) {
627  weaponp->wi_flags2 |= WIF2_HARD_TARGET_BOMB;
628  }
629  else {
630  Warning(LOCATION, "Weapon %s is not a bomb but has \"no radius doubling\" set. Ignoring this flag", weaponp->name);
631  }
632  }
633  else if (!stricmp(NOX("no subsystem homing"), weapon_strings[i]))
634  weaponp->wi_flags2 |= WIF2_NON_SUBSYS_HOMING;
635  else if (!stricmp(NOX("no lifeleft penalty"), weapon_strings[i]))
637  else if (!stricmp(NOX("can be targeted"), weapon_strings[i]))
638  weaponp->wi_flags2 |= WIF2_CAN_BE_TARGETED;
639  else if (!stricmp(NOX("show on radar"), weapon_strings[i]))
640  weaponp->wi_flags2 |= WIF2_SHOWN_ON_RADAR;
641  else if (!stricmp(NOX("show friendly on radar"), weapon_strings[i]))
642  weaponp->wi_flags2 |= WIF2_SHOW_FRIENDLY;
643  else if (!stricmp(NOX("capital+"), weapon_strings[i]))
644  weaponp->wi_flags2 |= WIF2_CAPITAL_PLUS;
645  else if (!stricmp(NOX("chain external model fps"), weapon_strings[i]))
647  else if (!stricmp(NOX("external model launcher"), weapon_strings[i]))
649  else if (!stricmp(NOX("takes blast damage"), weapon_strings[i]))
651  else if (!stricmp(NOX("takes shockwave damage"), weapon_strings[i]))
653  else if (!stricmp(NOX("hide from radar"), weapon_strings[i]))
655  else if (!stricmp(NOX("render flak"), weapon_strings[i]))
656  weaponp->wi_flags2 |= WIF2_RENDER_FLAK;
657  else if (!stricmp(NOX("ciws"), weapon_strings[i]))
658  weaponp->wi_flags2 |= WIF2_CIWS;
659  else if (!stricmp(NOX("anti-subsystem beam"), weapon_strings[i]))
660  weaponp->wi_flags2 |= WIF2_ANTISUBSYSBEAM;
661  else if (!stricmp(NOX("no primary linking"), weapon_strings[i]))
662  weaponp->wi_flags3 |= WIF3_NOLINK;
663  else if (!stricmp(NOX("same emp time for capships"), weapon_strings[i]))
665  else if (!stricmp(NOX("no primary linked penalty"), weapon_strings[i]))
666  weaponp->wi_flags3 |= WIF3_NO_LINKED_PENALTY;
667  else if (!stricmp(NOX("no homing speed ramp"), weapon_strings[i]))
669  else if (!stricmp(NOX("pulls aspect seekers"), weapon_strings[i]))
670  {
671  if (!(weaponp->wi_flags & WIF_CMEASURE))
672  {
673  Warning(LOCATION, "\"pulls aspect seekers\" may only be used for countermeasures!");
674  }
675  else
676  {
678  }
679  }
680  else if (!stricmp(NOX("interceptable"), weapon_strings[i]))
682  else if (!stricmp(NOX("turret interceptable"), weapon_strings[i]))
684  else if (!stricmp(NOX("fighter interceptable"), weapon_strings[i]))
686  else if (!stricmp(NOX("apply recoil"), weapon_strings[i]))
687  weaponp->wi_flags3 |= WIF3_APPLY_RECOIL;
688  else if (!stricmp(NOX("don't spawn if shot"), weapon_strings[i]))
690  else if (!stricmp(NOX("die on lost lock"), weapon_strings[i])) {
691  if (!(weaponp->wi_flags & WIF_LOCKED_HOMING)) {
692  Warning(LOCATION, "\"die on lost lock\" may only be used for Homing Type ASPECT/JAVELIN!");
693  }
694  else {
695  weaponp->wi_flags3 |= WIF3_DIE_ON_LOST_LOCK;
696  }
697  }
698  else
699  Warning(LOCATION, "Bogus string in weapon flags: %s\n", weapon_strings[i]);
700  }
701 
702  // Goober5000 - fix up pierce/nopierce flags, per Mantis #2442
703  if (set_pierce)
704  weaponp->wi_flags2 |= WIF2_PIERCE_SHIELDS;
705  if (set_nopierce)
706  weaponp->wi_flags2 &= ~WIF2_PIERCE_SHIELDS;
707 
708  // set default tech room status - Goober5000
709  if (weaponp->wi_flags & WIF_IN_TECH_DATABASE)
711 
712  // SWARM, CORKSCREW and FLAK should be mutually exclusive
713  if (weaponp->wi_flags & WIF_FLAK)
714  {
715  if ((weaponp->wi_flags & WIF_SWARM) || (weaponp->wi_flags & WIF_CORKSCREW))
716  {
717  Warning(LOCATION, "Swarm, Corkscrew, and Flak are mutually exclusive! Removing Swarm and Corkscrew attributes.\n");
718  weaponp->wi_flags &= ~WIF_SWARM;
719  weaponp->wi_flags &= ~WIF_CORKSCREW;
720  }
721  }
722  else
723  {
724  if ((weaponp->wi_flags & WIF_SWARM) && (weaponp->wi_flags & WIF_CORKSCREW))
725  {
726  Warning(LOCATION, "Swarm and Corkscrew are mutually exclusive! Defaulting to Swarm.\n");
727  weaponp->wi_flags &= ~WIF_CORKSCREW;
728  }
729  }
730 
731  if (weaponp->wi_flags2 & WIF2_LOCAL_SSM)
732  {
733  if (!(weaponp->wi_flags & WIF_HOMING) || (weaponp->subtype !=WP_MISSILE))
734  {
735  Warning(LOCATION, "local ssm must be guided missile: %s", weaponp->name);
736  }
737  }
738 
739  if ((weaponp->wi_flags2 & WIF2_SMALL_ONLY) && (weaponp->wi_flags & WIF_HUGE))
740  {
741  Warning(LOCATION,"\"small only\" and \"huge\" flags are mutually exclusive.\nThey are used together in %s\nAI will most likely not use this weapon",weaponp->name);
742  }
743 
744  if (!(weaponp->wi_flags & WIF_SPAWN) && (weaponp->wi_flags2 & WIF2_SMART_SPAWN))
745  {
746  Warning(LOCATION,"\"smart spawn\" flag used without \"spawn\" flag in %s\n",weaponp->name);
747  }
748 
749  if ((weaponp->wi_flags2 & WIF2_INHERIT_PARENT_TARGET) && (!(weaponp->wi_flags & WIF_CHILD)))
750  {
751  Warning(LOCATION,"Weapon %s has the \"inherit parent target\" flag, but not the \"child\" flag. No changes in behavior will occur.", weaponp->name);
752  }
753 
754  if (!(weaponp->wi_flags & WIF_HOMING_HEAT) && (weaponp->wi_flags2 & WIF2_UNTARGETED_HEAT_SEEKER))
755  {
756  Warning(LOCATION,"Weapon '%s' has the \"untargeted heat seeker\" flag, but Homing Type is not set to \"HEAT\".", weaponp->name);
757  }
758 }
759 
760 void parse_shockwave_info(shockwave_create_info *sci, char *pre_char)
761 {
762  char buf[NAME_LENGTH];
763 
764  sprintf(buf, "%sShockwave damage:", pre_char);
765  if(optional_string(buf)) {
766  stuff_float(&sci->damage);
767  }
768 
769  sprintf(buf, "%sShockwave damage type:", pre_char);
770  if(optional_string(buf)) {
774  }
775 
776  sprintf(buf, "%sBlast Force:", pre_char);
777  if(optional_string(buf)) {
778  stuff_float(&sci->blast);
779  }
780 
781  sprintf(buf, "%sInner Radius:", pre_char);
782  if(optional_string(buf)) {
783  stuff_float(&sci->inner_rad);
784  }
785 
786  sprintf(buf, "%sOuter Radius:", pre_char);
787  if(optional_string(buf)) {
788  stuff_float(&sci->outer_rad);
789  }
790 
791  if (sci->outer_rad < sci->inner_rad) {
792  Warning(LOCATION, "Shockwave outer radius must be greater than or equal to the inner radius!");
793  sci->outer_rad = sci->inner_rad;
794  }
795 
796  sprintf(buf, "%sShockwave Speed:", pre_char);
797  if(optional_string(buf)) {
798  stuff_float(&sci->speed);
799  }
800 
801  sprintf(buf, "%sShockwave Rotation:", pre_char);
802  if(optional_string(buf)) {
803  float angs[3];
804  stuff_float_list(angs, 3);
805  for(int i = 0; i < 3; i++)
806  {
807  angs[i] = angs[i] * (PI2/180.0f);
808  while(angs[i] < 0)
809  {
810  angs[i] += PI2;
811  }
812  while(angs[i] > PI2)
813  {
814  angs[i] -= PI2;
815  }
816  }
817  sci->rot_angles.p = angs[0];
818  sci->rot_angles.b = angs[1];
819  sci->rot_angles.h = angs[2];
820  }
821 
822  sprintf(buf, "%sShockwave Model:", pre_char);
823  if(optional_string(buf)) {
825  }
826 
827  sprintf(buf, "%sShockwave Name:", pre_char);
828  if(optional_string(buf)) {
830  }
831 }
832 
833 void init_weapon_entry(int weap_info_index)
834 {
835  Assert(weap_info_index > -1 && weap_info_index < MAX_WEAPON_TYPES);
836  weapon_info *wip = &Weapon_info[weap_info_index];
837  int i, j;
838 
841 
842  wip->subtype = WP_UNUSED;
843  wip->render_type = WRT_NONE;
844 
845  memset(wip->name, 0, sizeof(wip->name));
846  memset(wip->title, 0, sizeof(wip->title));
847  wip->desc = NULL;
848 
849  memset(wip->tech_title, 0, sizeof(wip->tech_title));
850  memset(wip->tech_anim_filename, 0, sizeof(wip->tech_anim_filename));
851  wip->tech_desc = NULL;
852  memset(wip->tech_model, 0, sizeof(wip->tech_model));
853 
854  memset(wip->hud_filename, 0, sizeof(wip->hud_filename));
855  wip->hud_image_index = -1;
856 
857  memset(wip->pofbitmap_name, 0, sizeof(wip->pofbitmap_name));
858 
859  wip->model_num = -1;
860  wip->hud_target_lod = -1;
861  wip->num_detail_levels = -1;
862  for ( i = 0; i < MAX_MODEL_DETAIL_LEVELS; i++ )
863  {
864  wip->detail_distance[i] = -1;
865  }
866 
867  vm_vec_zero(&wip->closeup_pos);
868  wip->closeup_zoom = 1.0f;
869 
872 
873  gr_init_color(&wip->laser_color_1, 255, 255, 255);
874  gr_init_color(&wip->laser_color_2, 255, 255, 255);
875 
876  wip->laser_length = 10.0f;
877  wip->laser_head_radius = 1.0f;
878  wip->laser_tail_radius = 1.0f;
879 
880  memset(wip->external_model_name, 0, sizeof(wip->external_model_name));
881  wip->external_model_num = -1;
882 
883  wip->weapon_submodel_rotate_accell = 10.0f;
884  wip->weapon_submodel_rotate_vel = 0.0f;
885 
886  wip->mass = 1.0f;
887  wip->max_speed = 10.0f;
888  wip->acceleration_time = 0.0f;
889  wip->vel_inherit_amount = 1.0f;
890  wip->free_flight_time = 0.0f;
891  wip->fire_wait = 1.0f;
892  wip->max_delay = 0.0f;
893  wip->min_delay = 0.0f;
894  wip->damage = 0.0f;
895  wip->damage_time = -1.0f;
896  wip->atten_damage = -1.0f;
897 
898  wip->damage_type_idx = -1;
899  wip->damage_type_idx_sav = -1;
900 
901  wip->armor_type_idx = -1;
902 
903  wip->arm_time = 0;
904  wip->arm_dist = 0.0f;
905  wip->arm_radius = 0.0f;
906  wip->det_range = 0.0f;
907  wip->det_radius = 0.0f;
908  wip->flak_targeting_accuracy = 60.0f; // Standard value as defined in flak.cpp
909  wip->flak_detonation_accuracy = 65.0f;
910  wip->untargeted_flak_range_penalty = 20.0f;
911 
912  wip->armor_factor = 1.0f;
913  wip->shield_factor = 1.0f;
914  wip->subsystem_factor = 1.0f;
915 
916  wip->life_min = -1.0f;
917  wip->life_max = -1.0f;
918  wip->lifetime = 1.0f;
919  wip->energy_consumed = 0.0f;
920 
921  wip->cargo_size = 1.0f;
922 
923  wip->turn_time = 1.0f;
924  wip->fov = 0; //should be cos(pi), not pi
925 
926  wip->min_lock_time = 0.0f;
927  wip->lock_pixels_per_sec = 50;
928  wip->catchup_pixels_per_sec = 50;
929  wip->catchup_pixel_penalty = 50;
930  wip->seeker_strength = 1.0f;
931 
932  wip->swarm_count = -1;
933  // *Default is 150 -Et1
935 
936  wip->pre_launch_snd = -1;
938 
939  wip->launch_snd = -1;
940  wip->impact_snd = -1;
941  wip->disarmed_impact_snd = -1;
942  wip->flyby_snd = -1;
943 
944  wip->rearm_rate = 1.0f;
945 
946  wip->weapon_range = 999999999.9f;
947  // *Minimum weapon range, default is 0 -Et1
948  wip->WeaponMinRange = 0.0f;
949 
950  wip->num_spawn_weapons_defined = 0;
951 
952  for (i = 0; i < MAX_SPAWN_TYPES_PER_WEAPON; i++)
953  {
954  wip->spawn_info[i].spawn_type = -1;
955  wip->spawn_info[i].spawn_angle = 180;
957  }
958 
959  // Trails
960  wip->tr_info.pt = vmd_zero_vector;
961  wip->tr_info.w_start = 1.0f;
962  wip->tr_info.w_end = 1.0f;
963  wip->tr_info.a_start = 1.0f;
964  wip->tr_info.a_end = 1.0f;
965  wip->tr_info.max_life = 1.0f;
966  wip->tr_info.stamp = 0;
967  generic_bitmap_init(&wip->tr_info.texture, NULL);
968  wip->tr_info.n_fade_out_sections = 0;
969 
970  memset(wip->icon_filename, 0, sizeof(wip->icon_filename));
971 
972  memset(wip->anim_filename, 0, sizeof(wip->anim_filename));
973 
974  wip->impact_explosion_radius = 1.0f;
975  wip->impact_weapon_expl_index = -1;
976 
977  wip->shield_impact_explosion_radius = 1.0f;
978 
979  wip->dinky_impact_explosion_radius = 1.0f;
981 
983  wip->flash_impact_explosion_radius = 0.0f;
984 
987  wip->piercing_impact_particle_life = 0.0f;
992 
993  wip->muzzle_flash = -1;
994 
996  wip->emp_time = EMP_DEFAULT_TIME; // Goober5000: <-- Look! I fixed a Volition bug! Gimme $5, Dave!
997  wip->recoil_modifier = 1.0f;
1000 
1001  //customizeable corkscrew stuff
1002  wip->cs_num_fired=4;
1003  wip->cs_radius=1.25f;
1004  wip->cs_delay=30;
1005  wip->cs_crotate=1;
1006  wip->cs_twist=5.0f;
1007 
1008  wip->elec_time=8000;
1009  wip->elec_eng_mult=1.0f;
1010  wip->elec_weap_mult=1.0f;
1011  wip->elec_beam_mult=1.0f;
1012  wip->elec_sensors_mult=1.0f;
1013  wip->elec_randomness=2000;
1014  wip->elec_use_new_style=0;
1015 
1016  wip->lssm_warpout_delay=0; //delay between launch and warpout (ms)
1017  wip->lssm_warpin_delay=0; //delay between warpout and warpin (ms)
1018  wip->lssm_stage5_vel=0; //velocity during final stage
1019  wip->lssm_warpin_radius=0;
1020  wip->lssm_lock_range=1000000.0f; //local ssm lock range (optional)
1021 
1022  wip->cm_aspect_effectiveness = 1.0f;
1023  wip->cm_heat_effectiveness = 1.0f;
1026  wip->cm_kill_single = false;
1027 
1028  wip->b_info.beam_type = -1;
1029  wip->b_info.beam_life = -1.0f;
1030  wip->b_info.beam_warmup = -1;
1031  wip->b_info.beam_warmdown = -1;
1032  wip->b_info.beam_muzzle_radius = 0.0f;
1033  wip->b_info.beam_particle_count = -1;
1034  wip->b_info.beam_particle_radius = 0.0f;
1035  wip->b_info.beam_particle_angle = 0.0f;
1036  wip->b_info.beam_loop_sound = -1;
1037  wip->b_info.beam_warmup_sound = -1;
1038  wip->b_info.beam_warmdown_sound = -1;
1039  wip->b_info.beam_num_sections = 0;
1040  wip->b_info.glow_length = 0;
1041  wip->b_info.directional_glow = false;
1042  wip->b_info.beam_shots = 1;
1043  wip->b_info.beam_shrink_factor = 0.0f;
1044  wip->b_info.beam_shrink_pct = 0.0f;
1045  wip->b_info.range = BEAM_FAR_LENGTH;
1046  wip->b_info.damage_threshold = 1.0f;
1047  wip->b_info.beam_width = -1.0f;
1048 
1049  generic_anim_init(&wip->b_info.beam_glow, NULL);
1051 
1052  for (i = 0; i < MAX_IFFS; i++)
1053  for (j = 0; j < NUM_SKILL_LEVELS; j++)
1054  wip->b_info.beam_iff_miss_factor[i][j] = 0.00001f;
1055 
1056  //WMC - Okay, so this is needed now
1058  for (i = 0; i < MAX_BEAM_SECTIONS; i++) {
1059  bsip = &wip->b_info.sections[i];
1060 
1061  generic_anim_init(&bsip->texture, NULL);
1062 
1063  bsip->width = 1.0f;
1064  bsip->flicker = 0.1f;
1065  bsip->z_add = i2fl(MAX_BEAM_SECTIONS - i - 1);
1066  bsip->tile_type = 0;
1067  bsip->tile_factor = 1.0f;
1068  bsip->translation = 0.0f;
1069  }
1070 
1071  for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // default values for everything -nuke
1072  wip->particle_spewers[s].particle_spew_type = PSPEW_NONE; // added by nuke
1075  wip->particle_spewers[s].particle_spew_vel = 0.4f;
1078  wip->particle_spewers[s].particle_spew_scale = 0.8f;
1079  wip->particle_spewers[s].particle_spew_z_scale = 1.0f; // added by nuke
1084  }
1085 
1086  wip->tag_level = -1;
1087  wip->tag_time = -1.0f;
1088 
1089  wip->SSM_index =-1; // tag C SSM index, wich entry in the SSM table this weapon calls -Bobboau
1090 
1091  wip->field_of_fire = 0.0f;
1092  wip->fof_spread_rate = 0.0f;
1093  wip->fof_reset_rate = 0.0f;
1094  wip->max_fof_spread = 0.0f;
1095 
1096  wip->shots = 1;
1097 
1098  wip->alpha_max = 1.0f;
1099  wip->alpha_min = 0.0f;
1100  wip->alpha_cycle = 0.0f;
1101 
1104 
1105  wip->weapon_hitpoints = 0;
1106 
1107  wip->burst_delay = 1.0f; // 1 second, just incase its not defined
1108  wip->burst_shots = 0;
1109  wip->burst_flags = 0;
1110 
1113 
1114  wip->thruster_glow_factor = 1.0f;
1115  wip->target_lead_scaler = 0.0f;
1116 
1118 
1119  wip->hud_locked_snd = -1;
1120  wip->hud_tracking_snd = -1;
1121  wip->hud_in_flight_snd = -1;
1122 }
1123 
1124 // function to parse the information for a specific weapon type.
1125 // return 0 if successful, otherwise return -1
1126 #define WEAPONS_MULTITEXT_LENGTH 2048
1127 
1128 int parse_weapon(int subtype, bool replace, const char *filename)
1129 {
1131  weapon_info *wip = NULL;
1132  char fname[NAME_LENGTH];
1133  int iff, idx;
1134  int primary_rearm_rate_specified=0;
1135  bool first_time = false;
1136  bool create_if_not_found = true;
1137  int wi_flags = WIF_DEFAULT_VALUE;
1138  int wi_flags2 = WIF2_DEFAULT_VALUE;
1139  int wi_flags3 = WIF3_DEFAULT_VALUE;
1140 
1141  required_string("$Name:");
1142  stuff_string(fname, F_NAME, NAME_LENGTH);
1143  diag_printf ("Weapon name -- %s\n", fname);
1144 
1145  if(optional_string("+nocreate")) {
1146  if(!replace) {
1147  Warning(LOCATION, "+nocreate flag used for weapon in non-modular table");
1148  }
1149  create_if_not_found = false;
1150  }
1151 
1152  //Remove @ symbol
1153  //these used to be used to denote weapons that would
1154  //only be parsed in demo builds
1155  if ( fname[0] == '@' ) {
1156  backspace(fname);
1157  }
1158 
1159  int w_id = weapon_info_lookup(fname);
1160 
1161  if(w_id != -1)
1162  {
1163  wip = &Weapon_info[w_id];
1164  if(!replace)
1165  {
1166  Warning(LOCATION, "Weapon name %s already exists in weapons.tbl. All weapon names must be unique; the second entry has been skipped", wip->name);
1167  if ( !skip_to_start_of_string_either("$Name:", "#End")) {
1168  Int3();
1169  }
1170  return -1;
1171  }
1172  }
1173  else
1174  {
1175  //Don't create weapon if it has +nocreate and is in a modular table.
1176  if(!create_if_not_found && replace)
1177  {
1178  if ( !skip_to_start_of_string_either("$Name:", "#End")) {
1179  Int3();
1180  }
1181 
1182  return -1;
1183  }
1184 
1185  if(Num_weapon_types >= MAX_WEAPON_TYPES) {
1186  Error(LOCATION, "Too many weapon classes before '%s'; maximum is %d.\n", fname, MAX_WEAPON_TYPES);
1187  }
1188 
1189  wip = &Weapon_info[Num_weapon_types];
1190  init_weapon_entry(Num_weapon_types);
1191  first_time = true;
1192 
1193  strcpy_s(wip->name, fname);
1194  Num_weapon_types++;
1195  }
1196 
1197  if(optional_string("$Alt name:"))
1199 
1200  //Set subtype
1201  if(optional_string("$Subtype:"))
1202  {
1203  stuff_string(fname, F_NAME, NAME_LENGTH);
1204 
1205  if(!stricmp("Primary", fname)) {
1206  wip->subtype = WP_LASER;
1207  } else if(!stricmp("Secondary", fname)) {
1208  wip->subtype = WP_MISSILE;
1209  } else {
1210  Warning(LOCATION, "Unknown subtype on weapon '%s'", wip->name);
1211  }
1212  }
1213  else if(wip->subtype != WP_UNUSED && !first_time)
1214  {
1215  if(wip->subtype != subtype) {
1216  Warning(LOCATION, "Type of weapon %s entry does not agree with original entry type.", wip->name);
1217  }
1218  }
1219  else
1220  {
1221  wip->subtype = subtype;
1222  }
1223 
1224  if (optional_string("+Title:")) {
1226  }
1227 
1228  if (optional_string("+Description:")) {
1229  if (wip->desc != NULL) {
1230  vm_free(wip->desc);
1231  wip->desc = NULL;
1232  }
1233 
1235  }
1236 
1237  if (optional_string("+Tech Title:")) {
1239  }
1240 
1241  if (optional_string("+Tech Anim:")) {
1243  }
1244 
1245  if (optional_string("+Tech Description:")) {
1246  if (wip->tech_desc != NULL) {
1247  vm_free(wip->tech_desc);
1248  wip->tech_desc = NULL;
1249  }
1250 
1252  }
1253 
1254  if (optional_string("$Tech Model:")) {
1256 
1257  if (optional_string("+Closeup_pos:")) {
1258  stuff_vec3d(&wip->closeup_pos);
1259  }
1260 
1261  if (optional_string("+Closeup_zoom:")) {
1262  stuff_float(&wip->closeup_zoom);
1263  }
1264  }
1265 
1266  // Weapon fadein effect, used when no ani is specified or weapon_select_3d is active
1267  wip->selection_effect = Default_weapon_select_effect; // By default, use the FS2 effect
1268  if(optional_string("$Selection Effect:")) {
1269  char effect[NAME_LENGTH];
1270  stuff_string(effect, F_NAME, NAME_LENGTH);
1271  if (!stricmp(effect, "FS2"))
1272  wip->selection_effect = 2;
1273  else if (!stricmp(effect, "FS1"))
1274  wip->selection_effect = 1;
1275  else if (!stricmp(effect, "off"))
1276  wip->selection_effect = 0;
1277  }
1278 
1279  //Check for the HUD image string
1280  if(optional_string("$HUD Image:")) {
1282  }
1283 
1284  // Read the model file. It can be a POF file or none.
1285  // If there is no model file (Model file: = "none") then we use our special
1286  // laser renderer which requires inner, middle and outer information.
1287  if ( optional_string("$Model file:") ) {
1289 
1290  if ( VALID_FNAME(wip->pofbitmap_name) )
1291  wip->render_type = WRT_POF;
1292 
1293  diag_printf("Model pof file -- %s\n", wip->pofbitmap_name );
1294  }
1295 
1296  // a special LOD level to use when rendering the weapon in the hud targetbox
1297  if ( optional_string( "$POF target LOD:" ) )
1298  stuff_int(&wip->hud_target_lod);
1299 
1300  if(optional_string("$Detail distance:")) {
1302  }
1303 
1304  if ( optional_string("$External Model File:") )
1306 
1307  if ( optional_string("$Submodel Rotation Speed:") )
1309 
1310  if ( optional_string("$Submodel Rotation Acceleration:") )
1312 
1313  // No POF or AVI file specified, render as special laser type.(?)
1314  ubyte r,g,b;
1315 
1316  // laser bitmap itself
1317  if ( optional_string("@Laser Bitmap:") ) {
1318  stuff_string(fname, F_NAME, NAME_LENGTH);
1319 
1320  if (wip->render_type == WRT_POF) {
1321  mprintf(("WARNING: Weapon '%s' has both LASER and POF render types! Will only use POF type!\n", wip->name));
1322  generic_anim_init(&wip->laser_bitmap, NULL);
1323  } else {
1324  generic_anim_init(&wip->laser_bitmap, fname);
1325  wip->render_type = WRT_LASER;
1326  }
1327  }
1328 
1329  // optional laser glow
1330  if ( optional_string("@Laser Glow:") ) {
1331  stuff_string(fname, F_NAME, NAME_LENGTH);
1332 
1333  if (wip->render_type != WRT_LASER) {
1334  mprintf(("WARNING: Laser glow specified on non-LASER type weapon (%s)!\n", wip->name));
1335  Int3();
1336  } else {
1337  generic_anim_init(&wip->laser_glow_bitmap, fname);
1338  }
1339  }
1340 
1341  if(optional_string("@Laser Color:"))
1342  {
1343  // This might be confusing at first glance. If we're a modular table (!first_time),
1344  // AND we're providing a new color for the laser (being in this block at all),
1345  // AND the RGB values for laser_color_1 and laser_color_2 match...
1346  // THEN we conclude that laser_color_2 wasn't explicitly defined before, and the modder would probably prefer
1347  // it if the laser didn't suddenly start changing colors from the new to the old over its lifespan. -MageKing17
1348  bool reset = (!first_time && (
1349  (wip->laser_color_1.red == wip->laser_color_2.red) &&
1350  (wip->laser_color_1.green == wip->laser_color_2.green) &&
1351  (wip->laser_color_1.blue == wip->laser_color_2.blue)));
1352  stuff_ubyte(&r);
1353  stuff_ubyte(&g);
1354  stuff_ubyte(&b);
1355  gr_init_color( &wip->laser_color_1, r, g, b );
1356  if (reset) {
1358  }
1359  }
1360 
1361  // optional string for cycling laser colors
1362  if(optional_string("@Laser Color2:")){
1363  stuff_ubyte(&r);
1364  stuff_ubyte(&g);
1365  stuff_ubyte(&b);
1366  gr_init_color( &wip->laser_color_2, r, g, b );
1367  } else if (first_time) {
1369  }
1370 
1371  if(optional_string("@Laser Length:")) {
1372  stuff_float(&wip->laser_length);
1373  }
1374 
1375  if(optional_string("@Laser Head Radius:")) {
1377  }
1378 
1379  if(optional_string("@Laser Tail Radius:")) {
1381  }
1382 
1383  if(optional_string("$Mass:")) {
1384  stuff_float( &(wip->mass) );
1385 
1386  // Goober5000 - hack in order to make the beam whack behavior of these three beams match all other beams
1387  // this relies on Bobboau's beam whack hack in beam_apply_whack()
1388  if ((!strcmp(wip->name, "SAAA") && (wip->mass == 4.0f))
1389  || (!strcmp(wip->name, "MjolnirBeam") && (wip->mass == 1000.0f))
1390  || (!strcmp(wip->name, "MjolnirBeam#home") && (wip->mass == 1000.0f)))
1391  {
1392  wip->mass = 100.0f;
1393  }
1394 
1395  diag_printf ("Weapon mass -- %7.3f\n", wip->mass);
1396  }
1397 
1398  if(optional_string("$Velocity:")) {
1399  stuff_float( &(wip->max_speed) );
1400  diag_printf ("Weapon mass -- %7.3f\n", wip->max_speed);
1401  }
1402 
1403  if(optional_string("$Fire Wait:")) {
1404  stuff_float( &(wip->fire_wait) );
1405  diag_printf ("Weapon fire wait -- %7.3f\n", wip->fire_wait);
1406  // Min and max delay stuff for weapon fire wait randomization
1407  if (optional_string("+Max Delay:")) {
1408  stuff_float(&(wip->max_delay));
1409  diag_printf("Weapon fire max delay -- %7.3f\n", wip->max_delay);
1410  }
1411  if (optional_string("+Min Delay:")) {
1412  stuff_float(&(wip->min_delay));
1413  diag_printf("Weapon fire min delay -- %7.3f\n", wip->min_delay);
1414  }
1415  }
1416 
1417  if(optional_string("$Damage:")) {
1418  stuff_float(&wip->damage);
1419  //WMC - now that shockwave damage can be set for them individually,
1420  //do this automagically
1421  if(first_time) {
1422  wip->shockwave.damage = wip->damage;
1423  }
1424  }
1425 
1426  // Attenuation of non-beam primary weapon damage
1427  if(optional_string("$Damage Time:")) {
1428  stuff_float(&wip->damage_time);
1429  if(optional_string("+Attenuation Damage:")){
1430  stuff_float(&wip->atten_damage);
1431  } else if (optional_string_either("+Min Damage:", "+Max Damage:")) {
1432  Warning(LOCATION, "+Min Damage: and +Max Damage: in %s are deprecated, please change to +Attenuation Damage:.", wip->name);
1433  stuff_float(&wip->atten_damage);
1434  }
1435  }
1436 
1437  if(optional_string("$Damage Type:")) {
1438  //This is checked for validity on every armor type
1439  //If it's invalid (or -1), then armor has no effect
1443  }
1444 
1445  if(optional_string("$Arm time:")) {
1446  float flit;
1447  stuff_float(&flit);
1448  wip->arm_time = fl2f(flit);
1449  }
1450 
1451  if(optional_string("$Arm distance:")) {
1452  stuff_float(&wip->arm_dist);
1453  }
1454 
1455  if(optional_string("$Arm radius:")) {
1456  stuff_float(&wip->arm_radius);
1457  }
1458 
1459  if(optional_string("$Detonation Range:")) {
1460  stuff_float(&wip->det_range);
1461  }
1462 
1463  if(optional_string("$Detonation Radius:")) {
1464  stuff_float(&wip->det_radius);
1465  }
1466 
1467  if(optional_string("$Flak Detonation Accuracy:")) {
1469  }
1470 
1471  if(optional_string("$Flak Targeting Accuracy:")) {
1473  }
1474 
1475  if(optional_string("$Untargeted Flak Range Penalty:")) {
1477  }
1478 
1479  parse_shockwave_info(&wip->shockwave, "$");
1480 
1481  //Retain compatibility
1482  if(first_time)
1483  {
1484  wip->dinky_shockwave = wip->shockwave;
1485  wip->dinky_shockwave.damage /= 4.0f;
1486  }
1487 
1488  if(optional_string("$Dinky shockwave:"))
1489  {
1491  }
1492 
1493  if(optional_string("$Armor Factor:")) {
1494  stuff_float(&wip->armor_factor);
1495  }
1496 
1497  if(optional_string("$Shield Factor:")) {
1498  stuff_float(&wip->shield_factor);
1499  }
1500 
1501  if(optional_string("$Subsystem Factor:")) {
1503  }
1504 
1505  if(optional_string("$Lifetime Min:")) {
1506  stuff_float(&wip->life_min);
1507 
1508  if(wip->life_min < 0.0f) {
1509  wip->life_min = 0.0f;
1510  Warning(LOCATION, "Lifetime min for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
1511  }
1512  }
1513 
1514  if(optional_string("$Lifetime Max:")) {
1515  stuff_float(&wip->life_max);
1516 
1517  if(wip->life_max < 0.0f) {
1518  wip->life_max = 0.0f;
1519  Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
1520  } else if (wip->life_max < wip->life_min) {
1521  wip->life_max = wip->life_min + 0.1f;
1522  Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than its Lifetime Min (%f) value. Setting to %f.\n", wip->name, wip->life_min, wip->life_max);
1523  } else {
1524  wip->lifetime = (wip->life_min+wip->life_max)*0.5f;
1525  }
1526  }
1527 
1528  if(wip->life_min >= 0.0f && wip->life_max < 0.0f) {
1529  wip->lifetime = wip->life_min;
1530  wip->life_min = -1.0f;
1531  Warning(LOCATION, "Lifetime min, but not lifetime max, specified for weapon %s. Assuming static lifetime of %.2f seconds.\n", wip->name, wip->lifetime);
1532  }
1533 
1534  if(optional_string("$Lifetime:")) {
1535  if(wip->life_min >= 0.0f || wip->life_max >= 0.0f) {
1536  Warning(LOCATION, "Lifetime min or max specified, but $Lifetime was also specified; min or max will be used.");
1537  }
1538  stuff_float(&wip->lifetime);
1539  if (wip->damage_time > wip->lifetime) {
1540  Warning(LOCATION, "Lifetime is lower than Damage Time, setting Damage Time to be one half the value of Lifetime.");
1541  wip->damage_time = 0.5f * wip->lifetime;
1542  }
1543  }
1544 
1545  if(optional_string("$Energy Consumed:")) {
1547  }
1548 
1549  // Goober5000: cargo size is checked for div-0 errors... see below (must parse flags first)
1550  if(optional_string("$Cargo Size:"))
1551  {
1552  stuff_float(&wip->cargo_size);
1553  }
1554 
1555  bool is_homing=false;
1556  if(optional_string("$Homing:")) {
1557  stuff_boolean(&is_homing);
1558  }
1559 
1560  if (is_homing || (wip->wi_flags & WIF_HOMING))
1561  {
1562  char temp_type[NAME_LENGTH];
1563 
1564  // the following five items only need to be recorded if the weapon is a homing weapon
1565  if(optional_string("+Type:"))
1566  {
1567  stuff_string(temp_type, F_NAME, NAME_LENGTH);
1568  if (!stricmp(temp_type, NOX("HEAT")))
1569  {
1570  if(wip->wi_flags & WIF_HOMING_ASPECT) {
1571  wip->wi_flags &= ~WIF_HOMING_ASPECT;
1572  }
1573  if(wip->wi_flags & WIF_HOMING_JAVELIN) {
1574  wip->wi_flags &= ~WIF_HOMING_JAVELIN;
1575  }
1576 
1578  wi_flags |= WIF_HOMING_HEAT | WIF_TURNS;
1579  }
1580  else if (!stricmp(temp_type, NOX("ASPECT")))
1581  {
1582  if(wip->wi_flags & WIF_HOMING_HEAT) {
1583  wip->wi_flags &= ~WIF_HOMING_HEAT;
1584  }
1585  if(wip->wi_flags & WIF_HOMING_JAVELIN) {
1586  wip->wi_flags &= ~WIF_HOMING_JAVELIN;
1587  }
1588 
1590  wi_flags |= WIF_HOMING_ASPECT | WIF_TURNS;
1591  }
1592  else if (!stricmp(temp_type, NOX("JAVELIN")))
1593  {
1594  if(wip->wi_flags & WIF_HOMING_HEAT) {
1595  wip->wi_flags &= ~WIF_HOMING_HEAT;
1596  }
1597  if(wip->wi_flags & WIF_HOMING_ASPECT) {
1598  wip->wi_flags &= ~WIF_HOMING_ASPECT;
1599  }
1600 
1602  wi_flags |= (WIF_HOMING_JAVELIN | WIF_TURNS);
1603  }
1604  //If you want to add another weapon, remember you need to reset
1605  //ALL homing flags.
1606  }
1607 
1608  if (wip->wi_flags & WIF_HOMING_HEAT)
1609  {
1610  float view_cone_angle;
1611 
1612  if(optional_string("+Turn Time:")) {
1613  stuff_float(&wip->turn_time);
1614  }
1615 
1616  if(optional_string("+View Cone:")) {
1617  stuff_float(&view_cone_angle);
1618  wip->fov = cosf(fl_radians(view_cone_angle * 0.5f));
1619  }
1620 
1621  if (optional_string("+Seeker Strength:"))
1622  {
1623  //heat default seeker strength is 3
1626  if (wip->seeker_strength <= 0)
1627  {
1628  Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
1629  wip->seeker_strength = 3.0f;
1631  }
1632  }
1633  else
1634  {
1635  if(!(wip->wi_flags2 & WIF2_CUSTOM_SEEKER_STR))
1636  wip->seeker_strength = 3.0f;
1637  }
1638 
1639  if (optional_string("+Target Lead Scaler:"))
1640  {
1642  if (wip->target_lead_scaler == 0.0f)
1644  else {
1646  wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
1647  }
1648  }
1649  }
1650  else if ((wip->wi_flags & WIF_HOMING_ASPECT) || (wip->wi_flags & WIF_HOMING_JAVELIN))
1651  {
1652  if(optional_string("+Turn Time:")) {
1653  stuff_float(&wip->turn_time);
1654  }
1655 
1656  if(optional_string("+View Cone:")) {
1657  float view_cone_angle;
1658  stuff_float(&view_cone_angle);
1659  wip->fov = (float)cos(fl_radians(view_cone_angle * 0.5f));
1660  }
1661 
1662  if(optional_string("+Min Lock Time:")) { // minimum time (in seconds) to achieve lock
1663  stuff_float(&wip->min_lock_time);
1664  }
1665 
1666  if(optional_string("+Lock Pixels/Sec:")) { // pixels/sec moved while locking
1668  }
1669 
1670  if(optional_string("+Catch-up Pixels/Sec:")) { // pixels/sec moved while catching-up for a lock
1672  }
1673 
1674  if(optional_string("+Catch-up Penalty:")) {
1675  // number of extra pixels to move while locking as a penalty for catching up for a lock
1677  }
1678 
1679  if (optional_string("+Seeker Strength:"))
1680  {
1681  //aspect default seeker strength is 2
1684  if (wip->seeker_strength <= 0)
1685  {
1686  Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
1687  wip->seeker_strength = 2.0f;
1689  }
1690  }
1691  else
1692  {
1693  if(!(wip->wi_flags2 & WIF2_CUSTOM_SEEKER_STR))
1694  wip->seeker_strength = 2.0f;
1695  }
1696  if (optional_string("+Target Lead Scaler:"))
1697  {
1699  if (wip->target_lead_scaler == 1.0f)
1701  else {
1703  wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
1704  }
1705  }
1706 
1707  if (wip->wi_flags & WIF_LOCKED_HOMING) {
1708  // locked homing missiles have a much longer lifespan than the AI think they do
1710  }
1711  }
1712  else
1713  {
1714  Error(LOCATION, "Illegal homing type = %s.\nMust be HEAT, ASPECT or JAVELIN.\n", temp_type);
1715  }
1716 
1717  }
1718 
1719  // swarm missiles
1720  int s_count;
1721 
1722  if(optional_string("$Swarm:"))
1723  {
1725  stuff_int(&s_count);
1726  wip->swarm_count = (short)s_count;
1727 
1728  // flag as being a swarm weapon
1729  wip->wi_flags |= WIF_SWARM;
1730  wi_flags |= WIF_SWARM;
1731  }
1732 
1733  // *Swarm wait token -Et1
1734  if((wip->wi_flags & WIF_SWARM) && optional_string( "+SwarmWait:" ))
1735  {
1736  float SwarmWait;
1737  stuff_float( &SwarmWait );
1738  if( SwarmWait > 0.0f && SwarmWait * wip->swarm_count < wip->fire_wait )
1739  {
1740  wip->SwarmWait = int( SwarmWait * 1000 );
1741  }
1742  }
1743 
1744  if(optional_string("$Acceleration Time:")) {
1746  }
1747 
1748  if(optional_string("$Velocity Inherit:")) {
1750  wip->vel_inherit_amount /= 100.0f; // % -> 0..1
1751  }
1752 
1753  if(optional_string("$Free Flight Time:")) {
1754  stuff_float(&(wip->free_flight_time));
1755  } else if(first_time && is_homing) {
1757  }
1758 
1759  if(optional_string("$Free Flight Speed:")) {
1760  float temp;
1761  stuff_float(&temp);
1762  nprintf(("Warning", "Ignoring free flight speed for weapon '%s'\n", wip->name));
1763  }
1764  //Optional one-shot sound to play at the beginning of firing
1765  parse_sound("$PreLaunchSnd:", &wip->pre_launch_snd, wip->name);
1766 
1767  //Optional delay for Pre-Launch sound
1768  if(optional_string("+PreLaunchSnd Min Interval:"))
1769  {
1771  }
1772 
1773  //Launch sound
1774  parse_sound("$LaunchSnd:", &wip->launch_snd, wip->name);
1775 
1776  //Impact sound
1777  parse_sound("$ImpactSnd:", &wip->impact_snd, wip->name);
1778 
1779  //Disarmed impact sound
1780  parse_sound("$Disarmed ImpactSnd:", &wip->impact_snd, wip->name);
1781 
1782  parse_sound("$FlyBySnd:", &wip->flyby_snd, wip->name);
1783 
1784  parse_sound("$TrackingSnd:", &wip->hud_tracking_snd, wip->name);
1785 
1786  parse_sound("$LockedSnd:", &wip->hud_locked_snd, wip->name);
1787 
1788  parse_sound("$InFlightSnd:", &wip->hud_in_flight_snd, wip->name);
1789 
1790  if (optional_string("+Inflight sound type:"))
1791  {
1792  SCP_string type;
1793 
1794  stuff_string(type, F_NAME);
1795 
1796  if (!stricmp(type.c_str(), "TARGETED"))
1797  {
1799  }
1800  else if (!stricmp(type.c_str(), "UNTARGETED"))
1801  {
1803  }
1804  else if (!stricmp(type.c_str(), "ALWAYS"))
1805  {
1806  wip->in_flight_play_type = ALWAYS;
1807  }
1808  else
1809  {
1810  Warning(LOCATION, "Unknown in-flight sound type \"%s\"!", type.c_str());
1811  wip->in_flight_play_type = ALWAYS;
1812  }
1813  }
1814 
1815  if(optional_string("$Model:"))
1816  {
1817  wip->render_type = WRT_POF;
1819  }
1820 
1821  // handle rearm rate - modified by Goober5000
1822  primary_rearm_rate_specified = 0;
1823  float rearm_rate;
1824  // Anticipate rearm rate for ballistic primaries
1825  if (optional_string("$Rearm Rate:"))
1826  {
1827  if (subtype != WP_MISSILE) {
1828  primary_rearm_rate_specified = 1;
1829  }
1830 
1831  stuff_float( &rearm_rate );
1832  if (rearm_rate > 0.0f)
1833  {
1834  wip->rearm_rate = 1.0f/rearm_rate;
1835  }
1836  else
1837  {
1838  Warning(LOCATION, "Rearm wait of less than 0 on weapon %s; setting to 1", wip->name);
1839  }
1840  }
1841 
1842 
1843  if (optional_string("+Weapon Range:")) {
1844  stuff_float(&wip->weapon_range);
1845  }
1846 
1847  if( optional_string( "+Weapon Min Range:" ) )
1848  {
1849  float MinRange;
1850  stuff_float( &MinRange );
1851 
1852  if( MinRange > 0.0f && MinRange < MIN( wip->max_speed * wip->lifetime, wip->weapon_range ) )
1853  {
1854  wip->WeaponMinRange = MinRange;
1855  }
1856  else
1857  {
1858  Warning(LOCATION, "Invalid minimum range on weapon %s; setting to 0", wip->name);
1859  }
1860 
1861  }
1862 
1863  parse_wi_flags(wip, wi_flags, wi_flags2, wi_flags3);
1864 
1865  // be friendly; make sure ballistic flags are synchronized - Goober5000
1866  // primary
1867  if (subtype == WP_LASER)
1868  {
1869  // ballistic
1870  if (wip->wi_flags2 & WIF2_BALLISTIC)
1871  {
1872  // rearm rate not specified
1873  if (!primary_rearm_rate_specified && first_time)
1874  {
1875  Warning(LOCATION, "$Rearm Rate for ballistic primary %s not specified. Defaulting to 100...\n", wip->name);
1876  wip->rearm_rate = 100.0f;
1877  }
1878  }
1879  // not ballistic
1880  else
1881  {
1882  // rearm rate specified
1883  if (primary_rearm_rate_specified)
1884  {
1885  Warning(LOCATION, "$Rearm Rate specified for non-ballistic primary %s\n", wip->name);
1886  }
1887  }
1888 
1889  }
1890  // secondary
1891  else
1892  {
1893  // ballistic
1894  if (wip->wi_flags2 & WIF2_BALLISTIC)
1895  {
1896  Warning(LOCATION, "Secondary weapon %s can't be ballistic. Removing this flag...\n", wip->name);
1897  wip->wi_flags2 &= ~WIF2_BALLISTIC;
1898  }
1899  }
1900 
1901  // also make sure EMP is friendly - Goober5000
1902  if (wip->wi_flags & WIF_EMP)
1903  {
1904  if (!wip->shockwave.outer_rad)
1905  {
1906  Warning(LOCATION, "Outer blast radius of weapon %s is zero - EMP will not work.\nAdd $Outer Radius to weapon table entry.\n", wip->name);
1907  }
1908  }
1909 
1910  // also make sure secondaries and ballistic primaries do not have 0 cargo size
1911  if (subtype == WP_MISSILE || wip->wi_flags2 & WIF2_BALLISTIC)
1912  {
1913  if (wip->cargo_size == 0.0f)
1914  {
1915  Warning(LOCATION, "Cargo size of weapon %s cannot be 0. Setting to 1.\n", wip->name);
1916  wip->cargo_size = 1.0f;
1917  }
1918  }
1919 
1920  if ( optional_string("$Trail:") ) {
1921  trail_info *ti = &wip->tr_info;
1922  wip->wi_flags |= WIF_TRAIL; // missile leaves a trail
1923 
1924  if ( optional_string("+Start Width:") )
1925  stuff_float(&ti->w_start);
1926 
1927  if ( optional_string("+End Width:") )
1928  stuff_float(&ti->w_end);
1929 
1930  if ( optional_string("+Start Alpha:") )
1931  stuff_float(&ti->a_start);
1932 
1933  if ( optional_string("+End Alpha:") )
1934  stuff_float(&ti->a_end);
1935 
1936  if ( optional_string("+Max Life:") ) {
1937  stuff_float(&ti->max_life);
1938  ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
1939  }
1940 
1941  if ( required_string("+Bitmap:") ) {
1942  stuff_string(fname, F_NAME, NAME_LENGTH);
1943  generic_bitmap_init(&ti->texture, fname);
1944  }
1945 
1946  if ( optional_string("+Faded Out Sections:") ) {
1948  }
1949  }
1950 
1951  // read in filename for icon that is used in weapons selection
1952  if ( optional_string("$Icon:") ) {
1954  }
1955 
1956  // read in filename for animation that is used in weapons selection
1957  if ( optional_string("$Anim:") ) {
1959  }
1960 
1961  if ( optional_string("$Impact Explosion:") ) {
1962  stuff_string(fname, F_NAME, NAME_LENGTH);
1963 
1964  if ( VALID_FNAME(fname) )
1965  wip->impact_weapon_expl_index = Weapon_explosions.Load(fname);
1966  }
1967 
1968  if ( optional_string("$Impact Explosion Radius:") )
1970 
1971  if ( optional_string("$Shield Impact Explosion Radius:") ) {
1973  } else if (first_time) {
1975  }
1976 
1977  if ( optional_string("$Dinky Impact Explosion:") ) {
1978  stuff_string(fname, F_NAME, NAME_LENGTH);
1979 
1980  if ( VALID_FNAME(fname) )
1981  wip->dinky_impact_weapon_expl_index = Weapon_explosions.Load(fname);
1982  } else if (first_time) {
1984  }
1985 
1986  if ( optional_string("$Dinky Impact Explosion Radius:") )
1988  else if (first_time)
1990 
1991  if ( optional_string("$Piercing Impact Explosion:") ) {
1992  stuff_string(fname, F_NAME, NAME_LENGTH);
1993 
1994  if ( VALID_FNAME(fname) )
1995  wip->piercing_impact_weapon_expl_index = Weapon_explosions.Load(fname);
1996  }
1997 
1998  if ( optional_string("$Piercing Impact Radius:") )
2000 
2001  if ( optional_string("$Piercing Impact Velocity:") )
2003 
2004  if ( optional_string("$Piercing Impact Splash Velocity:") )
2006 
2007  if ( optional_string("$Piercing Impact Variance:") )
2009 
2010  if ( optional_string("$Piercing Impact Life:") )
2012 
2013  if ( optional_string("$Piercing Impact Particles:") )
2015 
2016  // muzzle flash
2017  if ( optional_string("$Muzzleflash:") ) {
2018  stuff_string(fname, F_NAME, NAME_LENGTH);
2019 
2020  // look it up
2021  wip->muzzle_flash = mflash_lookup(fname);
2022  }
2023 
2024  // EMP optional stuff (if WIF_EMP is not set, none of this matters, anyway)
2025  if( optional_string("$EMP Intensity:") ){
2026  stuff_float(&wip->emp_intensity);
2027  }
2028 
2029  if( optional_string("$EMP Time:") ){
2030  stuff_float(&wip->emp_time);
2031  }
2032 
2033  // This is an optional modifier for a weapon that uses the "apply recoil" flag. recoil_force in ship.cpp line 10445 is multiplied by this if defined.
2034  if (optional_string("$Recoil Modifier:")){
2035  if (!(wip->wi_flags3 & WIF3_APPLY_RECOIL)){
2036  Warning(LOCATION, "$Recoil Modifier specified for weapon %s but this weapon does not have the \"apply recoil\" weapon flag set. Automatically setting the flag", wip->name);
2037  wip->wi_flags3 |= WIF3_APPLY_RECOIL;
2038  }
2040  }
2041 
2042  // Energy suck optional stuff (if WIF_ENERGY_SUCK is not set, none of this matters anyway)
2043  if( optional_string("$Leech Weapon:") ){
2044  stuff_float(&wip->weapon_reduce);
2045  }
2046 
2047  if( optional_string("$Leech Afterburner:") ){
2049  }
2050 
2051  if (optional_string("$Corkscrew:"))
2052  {
2053  if(optional_string("+Num Fired:")) {
2054  stuff_int(&wip->cs_num_fired);
2055  }
2056 
2057  if(optional_string("+Radius:")) {
2058  stuff_float(&wip->cs_radius);
2059  }
2060 
2061  if(optional_string("+Fire Delay:")) {
2062  stuff_int(&wip->cs_delay);
2063  }
2064 
2065  if(optional_string("+Counter rotate:")) {
2066  stuff_boolean(&wip->cs_crotate);
2067  }
2068 
2069  if(optional_string("+Twist:")) {
2070  stuff_float(&wip->cs_twist);
2071  }
2072  }
2073 
2074  //electronics tag optional stuff
2075  //Note that I made all these optional in the interest of modular tables.
2076  //TODO: Possibly add a warning on first_time define?
2077  if (optional_string("$Electronics:"))
2078  {
2079  if(optional_string("+New Style:")) {
2080  wip->elec_use_new_style=1;
2081  }
2082  else if(optional_string("+Old Style:")) {
2083  wip->elec_use_new_style=0;
2084  }
2085 
2086  if(optional_string("+Area Of Effect")) {
2088  }
2089 
2090  //New only -WMC
2091  if(optional_string("+Intensity:")) {
2092  float temp;
2093  stuff_float(&temp);
2094  Warning(LOCATION, "+Intensity is deprecated");
2095  }
2096 
2097  if(optional_string("+Lifetime:")) {
2098  stuff_int(&wip->elec_time);
2099  }
2100 
2101  //New only -WMC
2102  if(optional_string("+Engine Multiplier:")) {
2103  stuff_float(&wip->elec_eng_mult);
2104  if(!wip->elec_use_new_style)Warning(LOCATION, "+Engine multiplier may only be used with new style electronics");
2105  }
2106 
2107  //New only -WMC
2108  if(optional_string("+Weapon Multiplier:")) {
2109  stuff_float(&wip->elec_weap_mult);
2110  if(!wip->elec_use_new_style)Warning(LOCATION, "+Weapon multiplier may only be used with new style electronics");
2111  }
2112 
2113  //New only -WMC
2114  if(optional_string("+Beam Turret Multiplier:")) {
2115  stuff_float(&wip->elec_beam_mult);
2116  if(!wip->elec_use_new_style)Warning(LOCATION, "+Beam turret multiplier may only be used with new style electronics");
2117  }
2118 
2119  //New only -WMC
2120  if(optional_string("+Sensors Multiplier:")) {
2122  if(!wip->elec_use_new_style)Warning(LOCATION, "+Sensors multiplier may only be used with new style electronics");
2123  }
2124 
2125  if(optional_string("+Randomness Time:")) {
2126  stuff_int(&wip->elec_randomness);
2127  }
2128  }
2129 
2130  //read in the spawn angle info
2131  //if the weapon isn't a spawn weapon, then this is not going to be used.
2132  int num_spawn_angs_defined = 0;
2133  float dum_float;
2134 
2135  while (optional_string("$Spawn Angle:"))
2136  {
2137  stuff_float(&dum_float);
2138 
2139  if (num_spawn_angs_defined < MAX_SPAWN_TYPES_PER_WEAPON)
2140  {
2141  wip->spawn_info[num_spawn_angs_defined].spawn_angle = dum_float;
2142  num_spawn_angs_defined++;
2143  }
2144  }
2145 
2146  if (wip->wi_flags2 & WIF2_LOCAL_SSM && optional_string("$Local SSM:"))
2147  {
2148  if(optional_string("+Warpout Delay:")) {
2150  }
2151 
2152  if(optional_string("+Warpin Delay:")) {
2154  }
2155 
2156  if(optional_string("+Stage 5 Velocity:")) {
2158  }
2159 
2160  if(optional_string("+Warpin Radius:")) {
2162  }
2163 
2164  if (optional_string("+Lock Range:")) {
2166  }
2167  }
2168 
2169  if (optional_string("$Countermeasure:"))
2170  {
2171  if (!(wip->wi_flags & WIF_CMEASURE))
2172  {
2173  Warning(LOCATION,"Weapon \'%s\' has countermeasure information defined, but the \"countermeasure\" flag wasn\'t found in the \'$Flags:\' field.\n", wip->name);
2174  }
2175 
2176  if (optional_string("+Heat Effectiveness:"))
2178 
2179  if (optional_string("+Aspect Effectiveness:"))
2181 
2182  if (optional_string("+Effective Radius:"))
2184 
2185  if (optional_string("+Missile Detonation Radius:"))
2187 
2188  if (optional_string("+Single Missile Kill:"))
2190  }
2191 
2192  // beam weapon optional stuff
2193  if ( optional_string("$BeamInfo:") ) {
2194  // beam type
2195  if(optional_string("+Type:")) {
2196  stuff_int(&wip->b_info.beam_type);
2197  }
2198 
2199  // how long it lasts
2200  if(optional_string("+Life:")) {
2201  stuff_float(&wip->b_info.beam_life);
2202  }
2203 
2204  // warmup time
2205  if(optional_string("+Warmup:")) {
2206  stuff_int(&wip->b_info.beam_warmup);
2207  }
2208 
2209  // warmdowm time
2210  if(optional_string("+Warmdown:")) {
2212  }
2213 
2214  // muzzle glow radius
2215  if(optional_string("+Radius:")) {
2217  }
2218 
2219  // particle spew count
2220  if(optional_string("+PCount:")) {
2222  }
2223 
2224  // particle radius
2225  if(optional_string("+PRadius:")) {
2227  }
2228 
2229  // angle off turret normal
2230  if(optional_string("+PAngle:")) {
2232  }
2233 
2234  // particle bitmap/ani
2235  if ( optional_string("+PAni:") ) {
2236  stuff_string(fname, F_NAME, NAME_LENGTH);
2238  }
2239 
2240  // magic miss #
2241  if(optional_string("+Miss Factor:")) {
2242  for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
2243  float temp;
2244  if(!stuff_float_optional(&temp)) {
2245  break;
2246  }
2247  // an unspecified Miss Factor should apply to all IFFs
2248  for(iff=0; iff<Num_iffs; iff++) {
2249  wip->b_info.beam_iff_miss_factor[iff][idx] = temp;
2250  }
2251  }
2252  }
2253  // now check miss factors for each IFF
2254  for(iff=0; iff<Num_iffs; iff++) {
2255  char miss_factor_string[NAME_LENGTH + 15];
2256  sprintf(miss_factor_string, "+%s Miss Factor:", Iff_info[iff].iff_name);
2257  if(optional_string(miss_factor_string)) {
2258  // this Miss Factor applies only to the specified IFF
2259  for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
2260  if(!stuff_float_optional(&wip->b_info.beam_iff_miss_factor[iff][idx])) {
2261  break;
2262  }
2263  }
2264  }
2265  }
2266 
2267  // beam fire sound
2268  parse_sound("+BeamSound:", &wip->b_info.beam_loop_sound, wip->name);
2269 
2270  // warmup sound
2271  parse_sound("+WarmupSound:", &wip->b_info.beam_warmup_sound, wip->name);
2272 
2273  // warmdown sound
2274  parse_sound("+WarmdownSound:", &wip->b_info.beam_warmdown_sound, wip->name);
2275 
2276  // glow bitmap
2277  if (optional_string("+Muzzleglow:") ) {
2278  stuff_string(fname, F_NAME, NAME_LENGTH);
2279  generic_anim_init(&wip->b_info.beam_glow, fname);
2280  }
2281 
2282  if (optional_string("+Directional Glow:")) {
2284  wip->b_info.directional_glow = true;
2285  }
2286 
2287  // # of shots (only used for type D beams)
2288  if(optional_string("+Shots:")) {
2289  stuff_int(&wip->b_info.beam_shots);
2290  }
2291 
2292  // make sure that we have at least one shot so that TYPE_D beams will work
2293  if ( (wip->b_info.beam_type == BEAM_TYPE_D) && (wip->b_info.beam_shots < 1) ) {
2294  Warning( LOCATION, "Type D beam weapon, '%s', has less than one \"+Shots\" specified! It must be set to at least 1!!", wip->name);
2295  wip->b_info.beam_shots = 1;
2296  }
2297 
2298  // shrinkage
2299  if(optional_string("+ShrinkFactor:")) {
2301  }
2302 
2303  if(optional_string("+ShrinkPct:")) {
2305  }
2306 
2307  if (optional_string("+Range:")) {
2308  stuff_float(&wip->b_info.range);
2309  }
2310 
2311  if ( optional_string("+Attenuation:") )
2313 
2314  if ( optional_string("+BeamWidth:") )
2315  stuff_float(&wip->b_info.beam_width);
2316 
2317  if ( optional_string("+Beam Flash Effect:") ) {
2318  stuff_string(fname, F_NAME, NAME_LENGTH);
2319 
2320  if ( VALID_FNAME(fname) )
2321  wip->flash_impact_weapon_expl_index = Weapon_explosions.Load(fname);
2322  }
2323 
2324  if ( optional_string("+Beam Flash Radius:") )
2326 
2327  if ( optional_string("+Beam Piercing Effect:") ) {
2328  stuff_string(fname, F_NAME, NAME_LENGTH);
2329 
2330  if ( VALID_FNAME(fname) )
2331  wip->piercing_impact_weapon_expl_index = Weapon_explosions.Load(fname);
2332  }
2333 
2334  if ( optional_string("+Beam Piercing Radius:") )
2336 
2337  if ( optional_string("+Beam Piercing Effect Velocity:") )
2339 
2340  if ( optional_string("+Beam Piercing Splash Effect Velocity:") )
2342 
2343  if ( optional_string("+Beam Piercing Effect Variance:") )
2345 
2346  // beam sections
2347  while ( optional_string("$Section:") ) {
2348  beam_weapon_section_info *bsip = NULL, tbsw;
2349  bool nocreate = false, remove = false;
2350  int bsw_index_override = -1;
2351 
2352  if ( optional_string("+Index:") ) {
2353  stuff_int(&bsw_index_override);
2354 
2355  if ( optional_string("+remove") ) {
2356  nocreate = true;
2357  remove = true;
2358  }
2359 
2360  if ( (bsw_index_override < 0) || (!remove && (bsw_index_override >= wip->b_info.beam_num_sections)) )
2361  Warning(LOCATION, "Invalid +Index value of %d specified for beam section on weapon '%s'; valid values at this point are %d to %d.", bsw_index_override, wip->name, 0, wip->b_info.beam_num_sections -1);
2362  }
2363 
2364  if ( optional_string("+nocreate") )
2365  nocreate = true;
2366 
2367  // Where are we saving data?
2368  if (bsw_index_override >= 0) {
2369  if (bsw_index_override < wip->b_info.beam_num_sections) {
2370  bsip = &wip->b_info.sections[bsw_index_override];
2371  } else {
2372  if ( !nocreate ) {
2373  if ( (bsw_index_override == wip->b_info.beam_num_sections) && (bsw_index_override < MAX_BEAM_SECTIONS) ) {
2374  bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
2375  } else {
2376  if ( !remove )
2377  Warning(LOCATION, "Invalid index for manually-indexed beam section %d (max %d) on weapon %s.", bsw_index_override, MAX_BEAM_SECTIONS, wip->name);
2378 
2379  bsip = &tbsw;
2380  memset( bsip, 0, sizeof(beam_weapon_section_info) );
2381  generic_anim_init(&bsip->texture, NULL);
2382  }
2383  } else {
2384  if ( !remove )
2385  Warning(LOCATION, "Invalid index for manually-indexed beam section %d, and +nocreate specified, on weapon %s", bsw_index_override, wip->name);
2386 
2387  bsip = &tbsw;
2388  memset( bsip, 0, sizeof(beam_weapon_section_info) );
2389  generic_anim_init(&bsip->texture, NULL);
2390  }
2391 
2392  }
2393  } else {
2395  bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
2396  generic_anim_init(&bsip->texture, NULL);
2397  } else {
2398  Warning(LOCATION, "Too many beam sections for weapon %s - max is %d", wip->name, MAX_BEAM_SECTIONS);
2399  bsip = &tbsw;
2400  memset( bsip, 0, sizeof(beam_weapon_section_info) );
2401  generic_anim_init(&bsip->texture, NULL);
2402  }
2403  }
2404 
2405  // section width
2406  if ( optional_string("+Width:") )
2407  stuff_float(&bsip->width);
2408 
2409  // texture
2410  if ( optional_string("+Texture:") ) {
2411  stuff_string(fname, F_NAME, NAME_LENGTH);
2412 
2413  // invisible textures are okay
2414  if (!stricmp(fname, "invisible")) {
2415  generic_anim_init(&bsip->texture);
2416  } else {
2417  generic_anim_init(&bsip->texture, fname);
2418  }
2419  }
2420 
2421  // The E -- Dummied out due to not being used anywhere
2422  if ( optional_string("+RGBA Inner:") ) {
2423  ubyte dummy;
2424  stuff_ubyte(&dummy);
2425  stuff_ubyte(&dummy);
2426  stuff_ubyte(&dummy);
2427  stuff_ubyte(&dummy);
2428  }
2429 
2430  // The E -- Dummied out due to not being used anywhere
2431  if ( optional_string("+RGBA Outer:") ) {
2432  ubyte dummy;
2433  stuff_ubyte(&dummy);
2434  stuff_ubyte(&dummy);
2435  stuff_ubyte(&dummy);
2436  stuff_ubyte(&dummy);
2437  }
2438 
2439  // flicker
2440  if ( optional_string("+Flicker:") ) {
2441  stuff_float(&bsip->flicker);
2442  //Sanity
2443  if (bsip->flicker < 0.0f || bsip->flicker > 1.0f) {
2444  mprintf(("WARNING: Invalid value found for +Flicker on section %d of beam %s. Valid range is 0.0 to 1.0, values will be adjusted.\n", wip->b_info.beam_num_sections, wip->name));
2445  CLAMP(bsip->flicker, 0.0f, 1.0f);
2446  }
2447  }
2448 
2449  // zadd
2450  if ( optional_string("+Zadd:") )
2451  stuff_float(&bsip->z_add);
2452 
2453  // beam texture tileing factor -Bobboau
2454  if ( optional_string("+Tile Factor:") ) {
2455  stuff_float(&bsip->tile_factor);
2456  stuff_int(&bsip->tile_type);
2457  }
2458 
2459  // beam texture moveing stuff -Bobboau
2460  if ( optional_string("+Translation:") )
2461  stuff_float(&bsip->translation);
2462 
2463  // if we are actually removing this index then reset it and we'll
2464  // clean up the entries later
2465  if (remove) {
2466  memset( bsip, 0, sizeof(beam_weapon_section_info) );
2467  generic_anim_init(&bsip->texture, NULL);
2468  }
2469  }
2470  }
2471 
2472  while ( optional_string("$Pspew:") ) {
2473  int spew_index = -1;
2474  // check for pspew flag
2475  if (!( wip->wi_flags & WIF_PARTICLE_SPEW )) {
2476  Warning(LOCATION, "$Pspew specified for weapon %s but this weapon does not have the \"Particle Spew\" weapon flag set. Automatically setting the flag", wip->name);
2477  wip->wi_flags |= WIF_PARTICLE_SPEW;
2478  }
2479  // index for xmt edit, replace and remove support
2480  if (optional_string("+Index:")) {
2481  stuff_int(&spew_index);
2482  if (spew_index < 0 || spew_index >= MAX_PARTICLE_SPEWERS) {
2483  Warning(LOCATION, "+Index in particle spewer out of range. It must be between 0 and %i. Tag will be ignored.", MAX_PARTICLE_SPEWERS);
2484  spew_index = -1;
2485  }
2486  }
2487  // check for remove flag
2488  if (optional_string("+Remove")) {
2489  if (spew_index < 0) {
2490  Warning(LOCATION, "+Index not specified or is out of range, can not remove spewer.");
2491  } else { // restore defaults
2492  wip->particle_spewers[spew_index].particle_spew_type = PSPEW_NONE;
2493  wip->particle_spewers[spew_index].particle_spew_count = 1;
2494  wip->particle_spewers[spew_index].particle_spew_time = 25;
2495  wip->particle_spewers[spew_index].particle_spew_vel = 0.4f;
2496  wip->particle_spewers[spew_index].particle_spew_radius = 2.0f;
2497  wip->particle_spewers[spew_index].particle_spew_lifetime = 0.15f;
2498  wip->particle_spewers[spew_index].particle_spew_scale = 0.8f;
2499  wip->particle_spewers[spew_index].particle_spew_z_scale = 1.0f;
2500  wip->particle_spewers[spew_index].particle_spew_rotation_rate = 10.0f;
2503  generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, NULL);
2504  }
2505  } else { // were not removing the spewer
2506  if (spew_index < 0) { // index us ether not used or is invalid, so figure out where to put things
2507  //find a free slot in the pspew info array
2508  for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {
2510  spew_index = s;
2511  break;
2512  }
2513  }
2514  }
2515  // no empty spot found, the modder tried to define too many spewers, or screwed up the xmts, or my code sucks
2516  if ( spew_index < 0 ) {
2517  Warning(LOCATION, "Too many particle spewers, max number of spewers is %i.", MAX_PARTICLE_SPEWERS);
2518  } else { // we have a valid index, now parse the spewer already
2519  if (optional_string("+Type:")) { // added type field for pspew types, 0 is the default for reverse compatability -nuke
2520  char temp_pspew_type[NAME_LENGTH];
2521  stuff_string(temp_pspew_type, F_NAME, NAME_LENGTH);
2522 
2523  if (!stricmp(temp_pspew_type, NOX("DEFAULT"))) {
2525  } else if (!stricmp(temp_pspew_type, NOX("HELIX"))) {
2526  wip->particle_spewers[spew_index].particle_spew_type = PSPEW_HELIX;
2527  } else if (!stricmp(temp_pspew_type, NOX("SPARKLER"))) { // new types can be added here
2529  } else if (!stricmp(temp_pspew_type, NOX("RING"))) {
2530  wip->particle_spewers[spew_index].particle_spew_type = PSPEW_RING;
2531  } else if (!stricmp(temp_pspew_type, NOX("PLUME"))) {
2532  wip->particle_spewers[spew_index].particle_spew_type = PSPEW_PLUME;
2533  } else {
2535  }
2536  // for compatibility with existing tables that don't have a type tag
2537  } else if (wip->particle_spewers[spew_index].particle_spew_type == PSPEW_NONE) { // make sure the omission of type wasn't to edit an existing entry
2539  }
2540 
2541  if (optional_string("+Count:")) {
2542  stuff_int(&wip->particle_spewers[spew_index].particle_spew_count);
2543  }
2544 
2545  if (optional_string("+Time:")) {
2546  stuff_int(&wip->particle_spewers[spew_index].particle_spew_time);
2547  }
2548 
2549  if (optional_string("+Vel:")) {
2550  stuff_float(&wip->particle_spewers[spew_index].particle_spew_vel);
2551  }
2552 
2553  if (optional_string("+Radius:")) {
2555  }
2556 
2557  if (optional_string("+Life:")) {
2559  }
2560 
2561  if (optional_string("+Scale:")) {
2563  }
2564 
2565  if (optional_string("+Z Scale:")) {
2567  }
2568 
2569  if (optional_string("+Rotation Rate:")) {
2571  }
2572 
2573  if (optional_string("+Offset:")) {
2575  }
2576 
2577  if (optional_string("+Initial Velocity:")) {
2579  }
2580 
2581  if (optional_string("+Bitmap:")) {
2583  generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, fname);
2584  }
2585  }
2586  }
2587  }
2588  // check to see if the pspew flag was enabled but no pspew tags were given, for compatability with retail tables
2589  if (wip->wi_flags & WIF_PARTICLE_SPEW) {
2590  bool nospew = true;
2591  for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++)
2593  nospew = false;
2594  }
2595  if (nospew) { // set first spewer to default
2597  }
2598  }
2599 
2600  // tag weapon optional stuff
2601  if( optional_string("$Tag:")){
2602  stuff_int(&wip->tag_level);
2603  stuff_float(&wip->tag_time);
2604  wip->wi_flags |= WIF_TAG;
2605  }
2606 
2607  if( optional_string("$SSM:")){
2608  if (stuff_int_optional(&wip->SSM_index) != 2) {
2609  // We can't make an SSM lookup yet, because weapons are parsed first, but we can save the data to process later. -MageKing17
2610  stuff_string(fname, F_NAME, NAME_LENGTH);
2611  delayed_ssm_data temp_data;
2612  temp_data.filename = filename;
2613  temp_data.linenum = get_line_num();
2614  temp_data.ssm_entry = fname;
2615  if (Delayed_SSM_data.find(wip->name) == Delayed_SSM_data.end())
2616  Delayed_SSM_names.push_back(wip->name);
2617  Delayed_SSM_data[wip->name] = temp_data;
2618  } else {
2619  // We'll still want to validate the index later. -MageKing17
2620  delayed_ssm_index_data temp_data;
2621  temp_data.filename = filename;
2622  temp_data.linenum = get_line_num();
2623  if (Delayed_SSM_indices_data.find(wip->name) == Delayed_SSM_indices_data.end())
2624  Delayed_SSM_indices.push_back(wip->name);
2625  Delayed_SSM_indices_data[wip->name] = temp_data;
2626  }
2627  }// SSM index -Bobboau
2628 
2629  if(optional_string("$FOF:")){
2630  stuff_float(&wip->field_of_fire);
2631 
2632  if(optional_string("+FOF Spread Rate:")){
2634  if(required_string("+FOF Reset Rate:")){
2635  stuff_float(&wip->fof_reset_rate);
2636  }
2637 
2638  if(required_string("+Max FOF:")){
2639  float max_fof;
2640  stuff_float(&max_fof);
2641  wip->max_fof_spread = max_fof - wip->field_of_fire;
2642 
2643  if (wip->max_fof_spread <= 0.0f) {
2644  Warning(LOCATION, "WARNING: +Max FOF must be at least as big as $FOF for '%s'! Defaulting to match $FOF, no spread will occur!", wip->name);
2645  wip->max_fof_spread = 0.0f;
2646  }
2647  }
2648  }
2649  }
2650 
2651 
2652  if( optional_string("$Shots:")){
2653  stuff_int(&wip->shots);
2654  }
2655 
2656  //Left in for compatibility
2657  if ( optional_string("$decal:") ) {
2658  mprintf(("WARNING: The decal system has been deactivated in FSO builds. Entries for weapon %s will be discarded.\n", wip->name));
2659  required_string("+texture:");
2660  stuff_string(fname, F_NAME, NAME_LENGTH);
2661 
2662  if ( optional_string("+backface texture:") ) {
2663  stuff_string(fname, F_NAME, NAME_LENGTH);
2664  }
2665 
2666  float bogus;
2667 
2668  required_string("+radius:");
2669  stuff_float(&bogus);
2670 
2671  if ( optional_string("+burn time:") ) {
2672  stuff_float(&bogus);
2673  }
2674  }
2675 
2676 
2677  if (optional_string("$Transparent:")) {
2678  wip->wi_flags2 |= WIF2_TRANSPARENT;
2679 
2680  required_string("+Alpha:");
2681  stuff_float(&wip->alpha_max);
2682 
2683  if (wip->alpha_max > 1.0f)
2684  wip->alpha_max = 1.0f;
2685 
2686  if (wip->alpha_max <= 0.0f) {
2687  Warning(LOCATION, "WARNING: Alpha is set to 0 or a negative value for '%s'! Defaulting to 1.0!", wip->name);
2688  }
2689 
2690  if (optional_string("+Alpha Min:")) {
2691  stuff_float(&wip->alpha_min);
2692  CLAMP(wip->alpha_min, 0.0f, 1.0f);
2693  }
2694 
2695  if (optional_string("+Alpha Cycle:")) {
2696  stuff_float(&wip->alpha_cycle);
2697 
2698  if (wip->alpha_max == wip->alpha_min)
2699  Warning(LOCATION, "WARNING: Alpha is set to cycle for '%s', but max and min values are the same!", wip->name);
2700  }
2701  }
2702 
2703  if (optional_string("$Weapon Hitpoints:")) {
2704  stuff_int(&wip->weapon_hitpoints);
2705  } else if (first_time && (wip->wi_flags3 & (WIF3_TURRET_INTERCEPTABLE | WIF3_FIGHTER_INTERCEPTABLE))) {
2706  wip->weapon_hitpoints = 25;
2707  }
2708 
2709  // making sure bombs get their hitpoints assigned
2710  if ((wip->wi_flags & WIF_BOMB) && (wip->weapon_hitpoints == 0)) {
2711  wip->weapon_hitpoints = 50;
2712  }
2713 
2714  if(optional_string("$Armor Type:")) {
2716  wip->armor_type_idx = armor_type_get_idx(buf);
2717 
2718  if(wip->armor_type_idx == -1)
2719  Warning(LOCATION,"Invalid armor name %s specified for weapon %s", buf, wip->name);
2720  }
2721 
2722  if (optional_string("$Burst Shots:")) {
2723  stuff_int(&wip->burst_shots);
2724  if (wip->burst_shots > 0)
2725  wip->burst_shots--;
2726  }
2727 
2728  if (optional_string("$Burst Delay:")) {
2729  int temp;
2730  stuff_int(&temp);
2731  if (temp > 0) {
2732  wip->burst_delay = ((float) temp) / 1000.0f;
2733  }
2734  }
2735 
2736  if (optional_string("$Burst Flags:")) {
2737  parse_string_flag_list((int*)&wip->burst_flags, Burst_fire_flags, Num_burst_fire_flags);
2738  }
2739 
2740  if (optional_string("$Thruster Flame Effect:")) {
2741  stuff_string(fname, F_NAME, NAME_LENGTH);
2742 
2743  if (VALID_FNAME(fname))
2744  generic_anim_init( &wip->thruster_flame, fname );
2745  }
2746 
2747  if (optional_string("$Thruster Glow Effect:")) {
2748  stuff_string(fname, F_NAME, NAME_LENGTH);
2749 
2750  if (VALID_FNAME(fname))
2751  generic_anim_init( &wip->thruster_glow, fname );
2752  }
2753 
2754  if (optional_string("$Thruster Glow Radius Factor:")) {
2756  }
2757 
2758  //pretty stupid if a target must be tagged to shoot tag missiles at it
2759  if ((wip->wi_flags & WIF_TAG) && (wip->wi_flags2 & WIF2_TAGGED_ONLY))
2760  {
2761  Warning(LOCATION, "%s is a tag missile, but the target must be tagged to shoot it", wip->name);
2762  }
2763 
2764  // if burst delay is longer than firewait skip the whole burst fire option
2765  if (wip->burst_delay >= wip->fire_wait)
2766  wip->burst_shots = 0;
2767 
2768  /* Generate a substitution pattern for this weapon.
2769  This pattern is very naive such that it calculates the lowest common denominator as being all of
2770  the periods multiplied together.
2771  */
2772  while ( optional_string("$substitute:") ) {
2773  char subname[NAME_LENGTH];
2774  int period = 0;
2775  int index = 0;
2776  int offset = 0;
2777  stuff_string(subname, F_NAME, NAME_LENGTH);
2778  if ( optional_string("+period:") ) {
2779  stuff_int(&period);
2780  if ( period <= 0 ) {
2781  Warning(LOCATION, "Substitution '%s' for weapon '%s' requires a period greater than 0. Setting period to 1.", subname, wip->name);
2782  period = 1;
2783  }
2784  if ( optional_string("+offset:") ) {
2785  stuff_int(&offset);
2786  if ( offset <= 0 ) {
2787  Warning(LOCATION, "Period offset for substitution '%s' of weapon '%s' has to be greater than 0. Setting offset to 1.", subname, wip->name);
2788  offset = 1;
2789  }
2790  }
2791  } else if ( optional_string("+index:") ) {
2792  stuff_int(&index);
2793  if ( index < 0 ) {
2794  Warning(LOCATION, "Substitution '%s' for weapon '%s' requires an index greater than 0. Setting index to 0.", subname, wip->name);
2795  index = 0;
2796  }
2797  }
2798 
2799  // we are going to use weapon substition so, make sure that the pattern array has at least one element
2800  if ( wip->num_substitution_patterns == 0 ) {
2801  // pattern is empty, initialize pattern with the weapon being currently parsed.
2804  }
2805 
2806  // if tbler specifies a period then determine if we can fit the resulting pattern
2807  // neatly into the pattern array.
2808  if ( period > 0 ) {
2809  if ( (wip->num_substitution_patterns % period) > 0 ) {
2810  // not neat, need to expand the pattern so that our frequency pattern fits completly.
2811  size_t current_size = wip->num_substitution_patterns;
2812  size_t desired_size = current_size*period;
2813  if (desired_size > MAX_SUBSTITUTION_PATTERNS) {
2814  Warning(LOCATION, "The period is too large for the number of substitution patterns! desired size=" SIZE_T_ARG ", max size=%d", desired_size, MAX_SUBSTITUTION_PATTERNS);
2815  }
2816  else {
2817  wip->num_substitution_patterns = desired_size;
2818 
2819  // now duplicate the current pattern into the new area so the current pattern holds
2820  for ( size_t i = current_size; i < desired_size; i++ ) {
2822  }
2823  }
2824  }
2825 
2826  /* Apply the substituted weapon at the requested period, barrel
2827  shifted by offset if needed.*/
2828  for ( size_t pos = (period + offset - 1) % period;
2829  pos < wip->num_substitution_patterns; pos += period )
2830  {
2832  }
2833  } else {
2834  // assume that tbler wanted to specify a index for the new weapon.
2835 
2836  // make sure that there is enough room
2837  if (index >= MAX_SUBSTITUTION_PATTERNS) {
2838  Warning(LOCATION, "Substitution pattern index exceeds the maximum size! Index=%d, max size=%d", index, MAX_SUBSTITUTION_PATTERNS);
2839  } else {
2840  if ( (size_t)index >= wip->num_substitution_patterns ) {
2841  // need to make the pattern bigger by filling the extra with the current weapon.
2842  for ( size_t i = wip->num_substitution_patterns; i < (size_t)index; i++ ) {
2844  }
2845  wip->num_substitution_patterns = index+1;
2846  }
2847 
2848  strcpy_s(wip->weapon_substitution_pattern_names[index], subname);
2849  }
2850  }
2851  }
2852 
2853  //Optional score for destroying this weapon.
2854  if (optional_string("$Score:")) {
2855  stuff_int(&wip->score);
2856  }
2857 
2858  return WEAPON_INFO_INDEX(wip);
2859 }
2860 
2866 {
2867  int i, j, k;
2868 
2869  for (i = 0; i < Num_weapon_types; i++)
2870  {
2871  for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
2872  {
2873  if ( (Weapon_info[i].spawn_info[j].spawn_type > -1) && (Weapon_info[i].spawn_info[j].spawn_type < Num_spawn_types) )
2874  {
2875  int spawn_type = Weapon_info[i].spawn_info[j].spawn_type;
2876 
2877  Assert( spawn_type < Num_spawn_types );
2878 
2879  for (k = 0; k < Num_weapon_types; k++)
2880  {
2881  if ( !stricmp(Spawn_names[spawn_type], Weapon_info[k].name) )
2882  {
2883  Weapon_info[i].spawn_info[j].spawn_type = (short)k;
2884 
2885  if (i == k)
2886  Warning(LOCATION, "Weapon %s spawns itself. Infinite recursion?\n", Weapon_info[i].name);
2887 
2888  break;
2889  }
2890  }
2891  }
2892  }
2893  }
2894 }
2895 
2896 static char Default_cmeasure_name[NAME_LENGTH] = "";
2897 
2898 void parse_weaponstbl(const char *filename)
2899 {
2900  try
2901  {
2902  read_file_text(filename, CF_TYPE_TABLES);
2903  reset_parse();
2904 
2905  if (optional_string("#Primary Weapons"))
2906  {
2907  while (required_string_either("#End", "$Name:")) {
2908  // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
2909  if (parse_weapon(WP_LASER, Parsing_modular_table, filename) < 0) {
2910  continue;
2911  }
2912  }
2913  required_string("#End");
2914  }
2915 
2916  if (optional_string("#Secondary Weapons"))
2917  {
2918  while (required_string_either("#End", "$Name:")) {
2919  // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
2920  if (parse_weapon(WP_MISSILE, Parsing_modular_table, filename) < 0) {
2921  continue;
2922  }
2923  }
2924  required_string("#End");
2925  }
2926 
2927  if (optional_string("#Beam Weapons"))
2928  {
2929  while (required_string_either("#End", "$Name:")) {
2930  // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
2931  if (parse_weapon(WP_BEAM, Parsing_modular_table, filename) < 0) {
2932  continue;
2933  }
2934  }
2935  required_string("#End");
2936  }
2937 
2938  if (optional_string("#Countermeasures"))
2939  {
2940  while (required_string_either("#End", "$Name:"))
2941  {
2943 
2944  if (idx < 0) {
2945  continue;
2946  }
2947 
2948  //Make sure cmeasure flag is set
2949  Weapon_info[idx].wi_flags |= WIF_CMEASURE;
2950 
2951  //Set cmeasure index
2952  if (!strlen(Default_cmeasure_name)) {
2953  //We can't be sure that index will be the same after sorting, so save the name
2954  strcpy_s(Default_cmeasure_name, Weapon_info[idx].name);
2955  }
2956  }
2957 
2958  required_string("#End");
2959  }
2960 
2961  // Read in a list of weapon_info indicies that are an ordering of the player weapon precedence.
2962  // This list is used to select an alternate weapon when a particular weapon is not available
2963  // during weapon selection.
2964  if ((!Parsing_modular_table && required_string("$Player Weapon Precedence:")) || optional_string("$Player Weapon Precedence:"))
2965  {
2966  Num_player_weapon_precedence = stuff_int_list(Player_weapon_precedence, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
2967  }
2968 
2969  // add tbl/tbm to multiplayer validation list
2970  fs2netd_add_table_validation(filename);
2971  }
2972  catch (const parse::ParseException& e)
2973  {
2974  mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
2975  return;
2976  }
2977 }
2978 
2979 //uses a simple bucket sort to sort weapons, order of importance is:
2980 //Lasers
2981 //Beams
2982 //Child primary weapons
2983 //Fighter missiles and bombs
2984 //Capital missiles and bombs
2985 //Child secondary weapons
2987 {
2988  weapon_info *lasers = NULL, *big_lasers = NULL, *beams = NULL, *missiles = NULL, *big_missiles = NULL, *child_primaries = NULL, *child_secondaries = NULL;
2989  int num_lasers = 0, num_big_lasers = 0, num_beams = 0, num_missiles = 0, num_big_missiles = 0, num_child_primaries = 0, num_child_secondaries = 0;
2990  int i, weapon_index;
2991 
2992  // get the initial count of each weapon type
2993  for (i = 0; i < MAX_WEAPON_TYPES; i++) {
2994  switch (Weapon_info[i].subtype)
2995  {
2996  case WP_UNUSED:
2997  continue;
2998 
2999  case WP_LASER:
3000  if (Weapon_info[i].wi_flags & WIF_CHILD)
3001  num_child_primaries++;
3002  else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
3003  num_big_lasers++;
3004  else
3005  num_lasers++;
3006  break;
3007 
3008  case WP_BEAM:
3009  num_beams++;
3010  break;
3011 
3012  case WP_MISSILE:
3013  if (Weapon_info[i].wi_flags & WIF_CHILD)
3014  num_child_secondaries++;
3015  else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
3016  num_big_missiles++;
3017  else
3018  num_missiles++;
3019  break;
3020 
3021  default:
3022  continue;
3023  }
3024 
3025  }
3026 
3027  // allocate the buckets
3028  if (num_lasers) {
3029  lasers = new weapon_info[num_lasers];
3030  Verify( lasers != NULL );
3031  num_lasers = 0;
3032  }
3033 
3034  if (num_big_lasers) {
3035  big_lasers = new weapon_info[num_big_lasers];
3036  Verify( big_lasers != NULL );
3037  num_big_lasers = 0;
3038  }
3039 
3040  if (num_beams) {
3041  beams = new weapon_info[num_beams];
3042  Verify( beams != NULL );
3043  num_beams = 0;
3044  }
3045 
3046  if (num_missiles) {
3047  missiles = new weapon_info[num_missiles];
3048  Verify( missiles != NULL );
3049  num_missiles = 0;
3050  }
3051 
3052  if (num_big_missiles) {
3053  big_missiles = new weapon_info[num_big_missiles];
3054  Verify( big_missiles != NULL );
3055  num_big_missiles = 0;
3056  }
3057 
3058  if (num_child_primaries) {
3059  child_primaries = new weapon_info[num_child_primaries];
3060  Verify( child_primaries != NULL );
3061  num_child_primaries = 0;
3062  }
3063 
3064  if (num_child_secondaries) {
3065  child_secondaries = new weapon_info[num_child_secondaries];
3066  Verify( child_secondaries != NULL );
3067  num_child_secondaries = 0;
3068  }
3069 
3070  // fill the buckets
3071  for (i = 0; i < MAX_WEAPON_TYPES; i++) {
3072  switch (Weapon_info[i].subtype)
3073  {
3074  case WP_UNUSED:
3075  continue;
3076 
3077  case WP_LASER:
3078  if (Weapon_info[i].wi_flags & WIF_CHILD)
3079  child_primaries[num_child_primaries++] = Weapon_info[i];
3080  else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
3081  big_lasers[num_big_lasers++] = Weapon_info[i];
3082  else
3083  lasers[num_lasers++] = Weapon_info[i];
3084  break;
3085 
3086  case WP_BEAM:
3087  beams[num_beams++] = Weapon_info[i];
3088  break;
3089 
3090  case WP_MISSILE:
3091  if (Weapon_info[i].wi_flags & WIF_CHILD)
3092  child_secondaries[num_child_secondaries++] = Weapon_info[i];
3093  else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
3094  big_missiles[num_big_missiles++] = Weapon_info[i];
3095  else
3096  missiles[num_missiles++]=Weapon_info[i];
3097  break;
3098 
3099  default:
3100  continue;
3101  }
3102  }
3103 
3104  weapon_index = 0;
3105 
3106  // reorder the weapon_info structure according to our rules defined above
3107  for (i = 0; i < num_lasers; i++, weapon_index++)
3108  Weapon_info[weapon_index] = lasers[i];
3109 
3110  for (i = 0; i < num_big_lasers; i++, weapon_index++)
3111  Weapon_info[weapon_index] = big_lasers[i];
3112 
3113  for (i = 0; i < num_beams; i++, weapon_index++)
3114  Weapon_info[weapon_index] = beams[i];
3115 
3116  for (i = 0; i < num_child_primaries; i++, weapon_index++)
3117  Weapon_info[weapon_index] = child_primaries[i];
3118 
3119  // designate start of secondary weapons so that we'll have the correct offset later on
3120  First_secondary_index = weapon_index;
3121 
3122  for (i = 0; i < num_missiles; i++, weapon_index++)
3123  Weapon_info[weapon_index] = missiles[i];
3124 
3125  for (i = 0; i < num_big_missiles; i++, weapon_index++)
3126  Weapon_info[weapon_index] = big_missiles[i];
3127 
3128  for (i = 0; i < num_child_secondaries; i++, weapon_index++)
3129  Weapon_info[weapon_index] = child_secondaries[i];
3130 
3131 
3132  if (lasers) delete [] lasers;
3133  if (big_lasers) delete [] big_lasers;
3134  if (beams) delete [] beams;
3135  if (missiles) delete [] missiles;
3136  if (big_missiles) delete [] big_missiles;
3137  if (child_primaries) delete [] child_primaries;
3138  if (child_secondaries) delete [] child_secondaries;
3139 }
3140 
3145 {
3146  weapon_info *wip;
3147  int i;
3148 
3149  for (i = 0; i < Num_weapon_types; i++) {
3150  wip = &Weapon_info[i];
3151 
3152  if (wip->wi_flags & WIF_BEAM) {
3153  // clean up any beam sections which may have been deleted
3154  int removed = 0;
3155 
3156  for (int s_idx = 0; s_idx < wip->b_info.beam_num_sections; s_idx++) {
3157  if ( !strlen(wip->b_info.sections[s_idx].texture.filename) ) {
3158  int new_idx = s_idx + 1;
3159 
3160  while (new_idx < MAX_BEAM_SECTIONS) {
3161  memcpy( &wip->b_info.sections[new_idx-1], &wip->b_info.sections[new_idx], sizeof(beam_weapon_section_info) );
3162  new_idx++;
3163  }
3164 
3165  removed++;
3166  }
3167  }
3168 
3169  if (removed) {
3170  mprintf(("NOTE: weapon-cleanup is removing %i stale beam sections, out of %i original, from '%s'.\n", removed, wip->b_info.beam_num_sections, wip->name));
3171  wip->b_info.beam_num_sections -= removed;
3172  }
3173  }
3174  }
3175 }
3176 
3178 {
3179  int i, j;
3180  weapon_info *wip;
3181 
3182  // not for FRED...
3183  if (Fred_running)
3184  return;
3185 
3186  // if we are just going to load them all again any, keep everything
3188  return;
3189 
3190  for (i = 0; i < Num_weapon_types; i++) {
3191  wip = &Weapon_info[i];
3192 
3193  // go ahead and clear out models, the model paging code will actually take care of
3194  // releasing this stuff if needed, but we have to keep track of current modelnums ourselves
3195  if (wip->render_type == WRT_POF)
3196  wip->model_num = -1;
3197 
3198  // we are only interested in what we don't need for this mission
3199  if ( used_weapons[i] )
3200  continue;
3201 
3202  if (wip->render_type == WRT_LASER) {
3203  if (wip->laser_bitmap.first_frame >= 0) {
3205  wip->laser_bitmap.first_frame = -1;
3206  }
3207 
3208  // now for the glow
3209  if (wip->laser_glow_bitmap.first_frame >= 0) {
3211  wip->laser_glow_bitmap.first_frame = -1;
3212  }
3213  }
3214 
3215  if (wip->wi_flags & WIF_BEAM) {
3216  // particle animation
3217  if (wip->b_info.beam_particle_ani.first_frame >= 0) {
3220  }
3221 
3222  // muzzle glow
3223  if (wip->b_info.beam_glow.first_frame >= 0) {
3225  wip->b_info.beam_glow.first_frame = -1;
3226  }
3227 
3228  // section textures
3229  for (j = 0; j < wip->b_info.beam_num_sections; j++) {
3230  beam_weapon_section_info *bsi = &wip->b_info.sections[j];
3231 
3232  if (bsi->texture.first_frame >= 0) {
3234  bsi->texture.first_frame = -1;
3235  }
3236  }
3237  }
3238 
3239  if (wip->wi_flags & WIF_TRAIL) {
3240  if (wip->tr_info.texture.bitmap_id >= 0) {
3242  wip->tr_info.texture.bitmap_id = -1;
3243  }
3244  }
3245 
3246  if (wip->wi_flags & WIF_PARTICLE_SPEW) { // tweaked for multiple particle spews -nuke
3247  for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // just bitmaps that got loaded
3252  }
3253  }
3254  }
3255  }
3256 
3257  if (wip->thruster_flame.first_frame >= 0) {
3259  wip->thruster_flame.first_frame = -1;
3260  }
3261 
3262  if (wip->thruster_glow.first_frame >= 0) {
3264  wip->thruster_glow.first_frame = -1;
3265  }
3266  }
3267 }
3268 
3269 bool weapon_is_used(int weapon_index)
3270 {
3271  Assert( (weapon_index >= 0) || (weapon_index < Num_weapon_types) );
3272  return (used_weapons[weapon_index] > 0);
3273 }
3274 
3275 void weapon_load_bitmaps(int weapon_index)
3276 {
3277  weapon_info *wip;
3278 
3279  // not for FRED...
3280  if (Fred_running)
3281  return;
3282 
3283  if ( (weapon_index < 0) || (weapon_index >= Num_weapon_types) ) {
3284  Int3();
3285  return;
3286  }
3287 
3288  wip = &Weapon_info[weapon_index];
3289 
3290  if ( (wip->render_type == WRT_LASER) && (wip->laser_bitmap.first_frame < 0) ) {
3292 
3293  if (wip->laser_bitmap.first_frame >= 0) {
3294  wip->laser_bitmap.num_frames = 1;
3295  wip->laser_bitmap.total_time = 1;
3296  }
3297  // fall back to an animated type
3298  else if ( generic_anim_load(&wip->laser_bitmap) ) {
3299  mprintf(("Could not find a usable bitmap for '%s'!\n", wip->name));
3300  Warning(LOCATION, "Could not find a usable bitmap (%s) for weapon '%s'!\n", wip->laser_bitmap.filename, wip->name);
3301  }
3302 
3303  // now see if we also have a glow
3304  if ( strlen(wip->laser_glow_bitmap.filename) ) {
3306 
3307  if (wip->laser_glow_bitmap.first_frame >= 0) {
3308  wip->laser_glow_bitmap.num_frames = 1;
3309  wip->laser_glow_bitmap.total_time = 1;
3310  }
3311  // fall back to an animated type
3312  else if ( generic_anim_load(&wip->laser_glow_bitmap) ) {
3313  mprintf(("Could not find a usable glow bitmap for '%s'!\n", wip->name));
3314  Warning(LOCATION, "Could not find a usable glow bitmap (%s) for weapon '%s'!\n", wip->laser_glow_bitmap.filename, wip->name);
3315  }
3316  }
3317  }
3318 
3319  if (wip->wi_flags & WIF_BEAM) {
3320  // particle animation
3321  if ( (wip->b_info.beam_particle_ani.first_frame < 0) && strlen(wip->b_info.beam_particle_ani.filename) )
3323 
3324  // muzzle glow
3325  if ( (wip->b_info.beam_glow.first_frame < 0) && strlen(wip->b_info.beam_glow.filename) ) {
3326  if ( generic_anim_load(&wip->b_info.beam_glow) ) {
3327  // animated version failed to load, try static instead
3329 
3330  if (wip->b_info.beam_glow.first_frame >= 0) {
3331  wip->b_info.beam_glow.num_frames = 1;
3332  wip->b_info.beam_glow.total_time = 1;
3333  } else {
3334  mprintf(("Could not find a usable muzzle glow bitmap for '%s'!\n", wip->name));
3335  Warning(LOCATION, "Could not find a usable muzzle glow bitmap (%s) for weapon '%s'!\n", wip->b_info.beam_glow.filename, wip->name);
3336  }
3337  }
3338  }
3339 
3340  // section textures
3341  for (int i = 0; i < wip->b_info.beam_num_sections; i++) {
3342  beam_weapon_section_info *bsi = &wip->b_info.sections[i];
3343 
3344  if ( (bsi->texture.first_frame < 0) && strlen(bsi->texture.filename) ) {
3345  if ( generic_anim_load(&bsi->texture) ) {
3346  // animated version failed to load, try static instead
3348 
3349  if (bsi->texture.first_frame >= 0) {
3350  bsi->texture.num_frames = 1;
3351  bsi->texture.total_time = 1;
3352  } else {
3353  mprintf(("Could not find a usable beam section (%i) bitmap for '%s'!\n", i, wip->name));
3354  Warning(LOCATION, "Could not find a usable beam section (%i) bitmap (%s) for weapon '%s'!\n", i, bsi->texture.filename, wip->name);
3355  }
3356  }
3357  }
3358  }
3359  }
3360 
3361  if ( (wip->wi_flags & WIF_TRAIL) && (wip->tr_info.texture.bitmap_id < 0) )
3363 
3364  //WMC - Don't try to load an anim if no anim is specified, Mmkay?
3365  if (wip->wi_flags & WIF_PARTICLE_SPEW) {
3366  for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // looperfied for multiple pspewers -nuke
3368 
3370  && (wip->particle_spewers[s].particle_spew_anim.filename[0] != '\0') ) {
3371 
3373 
3377  }
3378  // fall back to an animated type
3380  mprintf(("Could not find a usable particle spew bitmap for '%s'!\n", wip->name));
3381  Warning(LOCATION, "Could not find a usable particle spew bitmap (%s) for weapon '%s'!\n", wip->particle_spewers[s].particle_spew_anim.filename, wip->name);
3382  }
3383  }
3384  }
3385  }
3386  }
3387 
3388  // load alternate thruster textures
3389  if (strlen(wip->thruster_flame.filename)) {
3391  }
3392 
3393  if (strlen(wip->thruster_glow.filename)) {
3395  if (wip->thruster_glow.first_frame >= 0) {
3396  wip->thruster_glow.num_frames = 1;
3397  wip->thruster_glow.total_time = 1;
3398  } else {
3400  }
3401  }
3402 
3403  // if this weapon isn't already marked as used, then mark it as such now
3404  // (this should really only happen if the player is cheating)
3405  if ( !used_weapons[weapon_index] )
3406  used_weapons[weapon_index]++;
3407 }
3408 
3413  for (int i = 0; i < MAX_WEAPON_TYPES; i++) {
3414  weapon_info *wip = &(Weapon_info[i]);
3415 
3416  if ( wip->num_substitution_patterns > 0 ) {
3417  for ( size_t j = 0; j < wip->num_substitution_patterns; j++ ) {
3418  int weapon_index = -1;
3419  if ( stricmp("none", wip->weapon_substitution_pattern_names[j]) != 0 ) {
3420  weapon_index = weapon_info_lookup(wip->weapon_substitution_pattern_names[j]);
3421  if ( weapon_index == -1 ) { // invalid sub weapon
3422  Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which does not seem to exist",
3423  wip->name, wip->weapon_substitution_pattern_names[j]);
3424  continue;
3425  }
3426  }
3427 
3428  wip->weapon_substitution_pattern[j] = weapon_index;
3429  }
3430 
3432  }
3433  }
3434 }
3435 
3437 {
3438  weapon_info *wip;
3439  int first_cmeasure_index = -1;
3440  int i;
3441 
3442  weapon_sort_by_type(); // NOTE: This has to be first thing!
3445 
3446  Default_cmeasure_index = -1;
3447 
3448  // run through weapons list and deal with individual issues
3449  for (i = 0; i < Num_weapon_types; i++) {
3450  wip = &Weapon_info[i];
3451 
3452  // set default counter-measure index from the saved name
3453  if ( (Default_cmeasure_index < 0) && strlen(Default_cmeasure_name) ) {
3454  if ( !stricmp(wip->name, Default_cmeasure_name) ) {
3455  Default_cmeasure_index = i;
3456  }
3457  }
3458 
3459  // catch a fall back cmeasure index, just in case
3460  if ( (first_cmeasure_index < 0) && (wip->wi_flags & WIF_CMEASURE) )
3461  first_cmeasure_index = i;
3462  }
3463 
3464  // catch cmeasure fallback
3465  if (Default_cmeasure_index < 0)
3466  Default_cmeasure_index = first_cmeasure_index;
3467 
3468  // now we want to resolve the countermeasures by species
3469  for (SCP_vector<species_info>::iterator ii = Species_info.begin(); ii != Species_info.end(); ++ii)
3470  {
3471  if (*ii->cmeasure_name)
3472  {
3473  int index = weapon_info_lookup(ii->cmeasure_name);
3474  if (index < 0)
3475  Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on species '%s'", ii->cmeasure_name, ii->species_name);
3476  else if (Weapon_info[index].wi_flags & WIF_BEAM)
3477  Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on species '%s'", ii->species_name);
3478  else
3479  ii->cmeasure_index = index;
3480  }
3481  }
3482 
3483  // translate all spawn type weapons to referrnce the appropriate spawned weapon entry
3485 }
3486 
3488 {
3489  int i;
3490 
3491  parse_weapon_expl_tbl("weapon_expl.tbl");
3492 
3493  // check for, and load, modular tables
3495 
3496  // we've got our list so pass it off for final checking and loading
3497  for (i = 0; i < (int)LOD_checker.size(); i++) {
3498  Weapon_explosions.Load( LOD_checker[i].filename, LOD_checker[i].num_lods );
3499  }
3500 
3501  // done
3502  LOD_checker.clear();
3503 }
3504 
3506 {
3507  memset( Weapon_info, 0, sizeof(weapon_info) * MAX_WEAPON_TYPES );
3508 
3509  for (int i = 0; i < MAX_WEAPON_TYPES; i++)
3511 }
3512 
3517 {
3518  if ( !Weapons_inited ) {
3519  //Init weapon explosion info
3521 
3522  // parse weapons.tbl
3524 
3525  Num_weapon_types = 0;
3526  Num_spawn_types = 0;
3527 
3528  parse_weaponstbl("weapons.tbl");
3529 
3530  parse_modular_table(NOX("*-wep.tbm"), parse_weaponstbl);
3531 
3532  // do post-parse cleanup
3534 
3535  Weapons_inited = 1;
3536  }
3537 
3539 }
3540 
3541 
3546 {
3547  int i;
3548 
3549  for (i = 0; i<MAX_WEAPON_TYPES; i++) {
3550  if (Weapon_info[i].desc) {
3551  vm_free(Weapon_info[i].desc);
3552  Weapon_info[i].desc = NULL;
3553  }
3554 
3555  if (Weapon_info[i].tech_desc) {
3556  vm_free(Weapon_info[i].tech_desc);
3557  Weapon_info[i].tech_desc = NULL;
3558  }
3559  }
3560 
3561  if (used_weapons != NULL) {
3562  delete[] used_weapons;
3563  used_weapons = NULL;
3564  }
3565 
3566  if (Spawn_names != NULL) {
3567  for (i=0; i<Num_spawn_types; i++) {
3568  if (Spawn_names[i] != NULL) {
3569  vm_free(Spawn_names[i]);
3570  Spawn_names[i] = NULL;
3571  }
3572  }
3573 
3574  vm_free(Spawn_names);
3575  Spawn_names = NULL;
3576  }
3577 }
3578 
3583 {
3584  int i;
3585 
3586  // Reset everything between levels
3587  Num_weapons = 0;
3588  for (i=0; i<MAX_WEAPONS; i++) {
3589  Weapons[i].objnum = -1;
3590  Weapons[i].weapon_info_index = -1;
3591  }
3592 
3593  for (i=0; i<MAX_WEAPON_TYPES; i++) {
3594  Weapon_info[i].damage_type_idx = Weapon_info[i].damage_type_idx_sav;
3595  Weapon_info[i].shockwave.damage_type_idx = Weapon_info[i].shockwave.damage_type_idx_sav;
3596  }
3597 
3598  trail_level_init(); // reset all missile trails
3599 
3600  swarm_level_init();
3602 
3604 
3605  // emp effect
3606  emp_level_init();
3607 
3608  if (used_weapons == NULL)
3609  used_weapons = new int[Num_weapon_types];
3610 
3611  // clear out used_weapons between missions
3612  memset(used_weapons, 0, Num_weapon_types * sizeof(int));
3613 
3614  Weapon_flyby_sound_timer = timestamp(0);
3615  Weapon_impact_timer = 1; // inited each level, used to reduce impact sounds
3616 }
3617 
3618 MONITOR( NumWeaponsRend )
3619 
3620 const float weapon_glow_scale_f = 2.3f;
3621 const float weapon_glow_scale_r = 2.3f;
3622 const float weapon_glow_scale_l = 1.5f;
3623 const int weapon_glow_alpha = 217; // (0.85 * 255);
3624 
3626 {
3627  int num;
3628  weapon_info *wip;
3629  weapon *wp;
3630  color c;
3631 
3632  MONITOR_INC(NumWeaponsRend, 1);
3633 
3634  Assert(obj->type == OBJ_WEAPON);
3635 
3636  num = obj->instance;
3637  wp = &Weapons[num];
3638  wip = &Weapon_info[Weapons[num].weapon_info_index];
3639 
3640  if (wip->wi_flags2 & WIF2_TRANSPARENT) {
3641  if (wp->alpha_current == -1.0f) {
3642  wp->alpha_current = wip->alpha_max;
3643  } else if (wip->alpha_cycle > 0.0f) {
3644  if (wp->alpha_backward) {
3645  wp->alpha_current += wip->alpha_cycle;
3646 
3647  if (wp->alpha_current > wip->alpha_max) {
3648  wp->alpha_current = wip->alpha_max;
3649  wp->alpha_backward = 0;
3650  }
3651  } else {
3652  wp->alpha_current -= wip->alpha_cycle;
3653 
3654  if (wp->alpha_current < wip->alpha_min) {
3655  wp->alpha_current = wip->alpha_min;
3656  wp->alpha_backward = 1;
3657  }
3658  }
3659  }
3660  }
3661 
3662  switch (wip->render_type)
3663  {
3664  case WRT_LASER:
3665  {
3666  if(wip->laser_length < 0.0001f)
3667  return;
3668  // turn off fogging for good measure
3669  gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
3670  int alpha = 255;
3671  int framenum = 0;
3672 
3673  if (wip->laser_bitmap.first_frame >= 0) {
3675 
3676  if (wip->laser_bitmap.num_frames > 1) {
3678 
3679  // Sanity checks
3680  if (wp->laser_bitmap_frame < 0.0f)
3681  wp->laser_bitmap_frame = 0.0f;
3682  if (wp->laser_bitmap_frame > 100.0f)
3683  wp->laser_bitmap_frame = 0.0f;
3684 
3685  while (wp->laser_bitmap_frame > wip->laser_bitmap.total_time)
3687 
3688  framenum = fl2i( (wp->laser_bitmap_frame * wip->laser_bitmap.num_frames) / wip->laser_bitmap.total_time );
3689 
3690  CLAMP(framenum, 0, wip->laser_bitmap.num_frames-1);
3691  }
3692 
3693  if (wip->wi_flags2 & WIF2_TRANSPARENT)
3694  alpha = fl2i(wp->alpha_current * 255.0f);
3695 
3696  vec3d headp;
3697 
3698  vm_vec_scale_add(&headp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length);
3700 
3701  if ( batch_add_laser(wip->laser_bitmap.first_frame + framenum, &headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius, alpha, alpha, alpha) ) {
3703  }
3704  }
3705 
3706  // maybe draw laser glow bitmap
3707  if (wip->laser_glow_bitmap.first_frame >= 0) {
3708  // get the laser color
3709  weapon_get_laser_color(&c, obj);
3710 
3711  // *Tail point "getting bigger" as well as headpoint isn't being taken into consideration, so
3712  // it caused uneven glow between the head and tail, which really shows in big lasers. So...fixed! -Et1
3713  vec3d headp2, tailp;
3714 
3715  vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * weapon_glow_scale_l);
3716  vm_vec_scale_add(&tailp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * (1 - weapon_glow_scale_l) );
3717 
3718  framenum = 0;
3719 
3720  if (wip->laser_glow_bitmap.num_frames > 1) {
3722 
3723  // Sanity checks
3724  if (wp->laser_glow_bitmap_frame < 0.0f)
3725  wp->laser_glow_bitmap_frame = 0.0f;
3726  if (wp->laser_glow_bitmap_frame > 100.0f)
3727  wp->laser_glow_bitmap_frame = 0.0f;
3728 
3731 
3733 
3734  CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
3735  }
3736 
3737  if (wip->wi_flags2 & WIF2_TRANSPARENT) {
3738  alpha = fl2i(wp->alpha_current * 255.0f);
3739  alpha -= 38; // take 1.5f into account for the normal glow alpha
3740 
3741  if (alpha < 0)
3742  alpha = 0;
3743  } else {
3744  alpha = weapon_glow_alpha;
3745  }
3746 
3747  batch_add_laser(wip->laser_glow_bitmap.first_frame + framenum, &headp2, wip->laser_head_radius * weapon_glow_scale_f, &tailp, wip->laser_tail_radius * weapon_glow_scale_r, (c.red*alpha)/255, (c.green*alpha)/255, (c.blue*alpha)/255);
3748  }
3749 
3750  break;
3751  }
3752 
3753  case WRT_POF:
3754  {
3756 
3758  render_flags &= ~MR_DEPRECATED_NO_LIGHTING;
3759 
3760  if (wip->wi_flags2 & WIF2_TRANSPARENT) {
3762  render_flags |= MR_DEPRECATED_ALL_XPARENT;
3763  }
3764 
3766 
3767  if ( (wip->wi_flags & WIF_THRUSTER) && ((wp->thruster_bitmap > -1) || (wp->thruster_glow_bitmap > -1)) ) {
3768  float ft;
3769  mst_info mst;
3770 
3771  // Add noise to thruster geometry.
3772  ft = 1.0f; // Always use 1.0f for missiles
3773  ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
3774  if (ft > 1.0f)
3775  ft = 1.0f;
3776 
3777  mst.length.xyz.x = ft;
3778  mst.length.xyz.y = ft;
3779  mst.length.xyz.z = ft;
3780 
3781  mst.primary_bitmap = wp->thruster_bitmap;
3784  mst.glow_noise = wp->thruster_glow_noise;
3785 
3786  model_set_thrust(wip->model_num, &mst);
3787 
3788  render_flags |= MR_DEPRECATED_SHOW_THRUSTERS;
3789  }
3790 
3791 
3792  //don't render local ssm's when they are still in subspace
3793  if (wp->lssm_stage==3)
3794  break;
3795 
3796  int clip_plane=0;
3797 
3798  //start a clip plane
3799  if (wp->lssm_stage==2)
3800  {
3801  object *wobj=&Objects[wp->lssm_warp_idx]; //warphole object
3802  clip_plane=1;
3803 
3804  g3_start_user_clip_plane(&wobj->pos,&wobj->orient.vec.fvec);
3805  }
3806 
3807 
3808  model_render_DEPRECATED(wip->model_num, &obj->orient, &obj->pos, render_flags);
3810  if (clip_plane)
3811  {
3813  }
3814  break;
3815  }
3816 
3817  default:
3818  Warning(LOCATION, "Unknown weapon rendering type = %i for weapon %s\n", wip->render_type, wip->name);
3819  }
3820 }
3821 
3822 void weapon_delete(object *obj)
3823 {
3824  weapon *wp;
3825  int num;
3826 
3827  Script_system.SetHookObjects(2, "Weapon", obj, "Self", obj);
3829  Script_system.RemHookVars(2, "Weapon", "Self");
3830 
3831  num = obj->instance;
3832 
3833  Assert( Weapons[num].objnum == OBJ_INDEX(obj));
3834  wp = &Weapons[num];
3835 
3836  Assert(wp->weapon_info_index >= 0);
3837  wp->weapon_info_index = -1;
3838  if (wp->swarm_index >= 0) {
3840  wp->swarm_index = -1;
3841  }
3842 
3843  if(wp->cscrew_index >= 0) {
3845  wp->cscrew_index = -1;
3846  }
3847 
3848  if (wp->missile_list_index >= 0) {
3850  wp->missile_list_index = -1;
3851  }
3852 
3853  if (wp->trail_ptr != NULL) {
3855  wp->trail_ptr = NULL;
3856  }
3857 
3860 
3861  if (wp->model_instance_num >= 0)
3863 
3864  if (wp->cmeasure_ignore_list != nullptr) {
3865  delete wp->cmeasure_ignore_list;
3866  wp->cmeasure_ignore_list = nullptr;
3867  }
3868 
3869  wp->objnum = -1;
3870  Num_weapons--;
3871  Assert(Num_weapons >= 0);
3872 }
3873 
3878 {
3879  if ( wp->homing_object == Player_obj ) {
3880  if ( !(wp->weapon_flags & WF_LOCK_WARNING_PLAYED) ) {
3882  // Use heatlock-warning sound for Heat and Javelin for now
3883  // Possibly add an additional third sound later
3884  if ( (Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_HEAT) ||
3885  (Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_JAVELIN) ) {
3887  } else {
3888  Assert(Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_ASPECT);
3890  }
3891  }
3892  }
3893 }
3894 
3895 
3899 void detonate_nearby_missiles(object* killer_objp, object* missile_objp)
3900 {
3901  if(killer_objp->type != OBJ_WEAPON || missile_objp->type != OBJ_WEAPON) {
3902  Int3();
3903  return;
3904  }
3905 
3906  weapon_info* killer_infop = &Weapon_info[Weapons[killer_objp->instance].weapon_info_index];
3907 
3908  if (killer_infop->cm_kill_single) {
3909  weapon* wp = &Weapons[missile_objp->instance];
3910  if (wp->lifeleft > 0.2f) {
3911  nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
3912  killer_infop->name, killer_objp->signature,
3913  Weapon_info[Weapons[missile_objp->instance].weapon_info_index].name, missile_objp->signature, Framecount));
3914  wp->lifeleft = 0.2f;
3915  }
3916  return;
3917  }
3918 
3919  missile_obj* mop = GET_FIRST(&Missile_obj_list);
3920 
3921  while(mop != END_OF_LIST(&Missile_obj_list)) {
3922  object* objp = &Objects[mop->objnum];
3923  weapon* wp = &Weapons[objp->instance];
3924 
3925  if (iff_x_attacks_y(Weapons[killer_objp->instance].team, wp->team)) {
3926  if ( Missiontime - wp->creation_time > F1_0/2) {
3927  if (vm_vec_dist_quick(&killer_objp->pos, &objp->pos) < killer_infop->cm_detonation_rad) {
3928  if (wp->lifeleft > 0.2f) {
3929  nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
3930  killer_infop->name, killer_objp->signature,
3931  Weapon_info[Weapons[objp->instance].weapon_info_index].name, objp->signature, Framecount));
3932  wp->lifeleft = 0.2f;
3933  }
3934  }
3935  }
3936  }
3937 
3938  mop = mop->next;
3939  }
3940 }
3941 
3945 void find_homing_object(object *weapon_objp, int num)
3946 {
3947  object *objp, *old_homing_objp;
3948  weapon_info *wip;
3949  weapon *wp;
3950  ship *sp;
3951  ship_info *sip;
3952  float best_dist;
3953  int homing_object_team;
3954  float dist;
3955  float dot;
3956  vec3d vec_to_object;
3957  ship_subsys *target_engines = NULL;
3958 
3959  wp = &Weapons[num];
3960 
3961  wip = &Weapon_info[Weapons[num].weapon_info_index];
3962 
3963  best_dist = 99999.9f;
3964 
3965  // save the old homing object so that multiplayer servers can give the right information
3966  // to clients if the object changes
3967  old_homing_objp = wp->homing_object;
3968 
3970 
3971  // Scan all objects, find a weapon to home on.
3972  for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
3973  if ((objp->type == OBJ_SHIP) || ((objp->type == OBJ_WEAPON) && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CMEASURE)))
3974  {
3975  //WMC - Spawn weapons shouldn't go for protected ships
3976  // ditto for untargeted heat seekers - niffiwan
3977  if ( (objp->flags & OF_PROTECTED) &&
3979  continue;
3980 
3981  // Spawned weapons should never home in on their parent - even in multiplayer dogfights where they would pass the iff test below
3982  if ((wp->weapon_flags & WF_SPAWNED) && (objp == &Objects[weapon_objp->parent]))
3983  continue;
3984 
3985  homing_object_team = obj_team(objp);
3986  if (iff_x_attacks_y(wp->team, homing_object_team))
3987  {
3988  if ( objp->type == OBJ_SHIP )
3989  {
3990  sp = &Ships[objp->instance];
3991  sip = &Ship_info[sp->ship_info_index];
3992 
3993  //if the homing weapon is a huge weapon and the ship that is being
3994  //looked at is not huge, then don't home
3995  if ((wip->wi_flags & WIF_HUGE) &&
3997  {
3998  continue;
3999  }
4000 
4001  // AL 2-17-98: If ship is immune to sensors, can't home on it (Sandeep says so)!
4002  if ( sp->flags & SF_HIDDEN_FROM_SENSORS ) {
4003  continue;
4004  }
4005 
4006  // Goober5000: if missiles can't home on sensor-ghosted ships,
4007  // they definitely shouldn't home on stealth ships
4009  continue;
4010  }
4011 
4012  if (wip->wi_flags & WIF_HOMING_JAVELIN)
4013  {
4014  target_engines = ship_get_closest_subsys_in_sight(sp, SUBSYSTEM_ENGINE, &weapon_objp->pos);
4015 
4016  if (!target_engines)
4017  continue;
4018  }
4019 
4020  // MK, 9/4/99.
4021  // If this is a player object, make sure there aren't already too many homers.
4022  // Only in single player. In multiplayer, we don't want to restrict it in dogfight on team vs. team.
4023  // For co-op, it's probably also OK.
4024  if (!( Game_mode & GM_MULTIPLAYER )) {
4025  int num_homers = compute_num_homing_objects(objp);
4027  continue;
4028  }
4029  }
4030  else if (objp->type == OBJ_WEAPON)
4031  {
4032  //don't attempt to home on weapons if the weapon is a huge weapon or is a javelin homing weapon.
4033  if (wip->wi_flags & (WIF_HUGE | WIF_HOMING_JAVELIN))
4034  continue;
4035 
4036  //don't look for local ssms that are gone for the time being
4037  if (Weapons[objp->instance].lssm_stage == 3)
4038  continue;
4039  }
4040 
4041  dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
4042 
4043  if (objp->type == OBJ_WEAPON && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CMEASURE)) {
4044  dist *= 0.5f;
4045  }
4046 
4047  dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
4048 
4049  if (dot > wip->fov) {
4050  if (dist < best_dist) {
4051  best_dist = dist;
4052  wp->homing_object = objp;
4053  wp->target_sig = objp->signature;
4054  wp->homing_subsys = target_engines;
4055 
4057  }
4058  }
4059  }
4060  }
4061  }
4062 
4063  if (wp->homing_object == Player_obj)
4065 
4066  // if the old homing object is different that the new one, send a packet to clients
4067  if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
4068  send_homing_weapon_info( num );
4069  }
4070 }
4071 
4075 void find_homing_object_cmeasures_1(object *weapon_objp)
4076 {
4077  object *objp;
4078  weapon *wp, *cm_wp;
4079  weapon_info *wip, *cm_wip;
4080  float best_dot, dist, dot;
4081 
4082  wp = &Weapons[weapon_objp->instance];
4083  wip = &Weapon_info[wp->weapon_info_index];
4084 
4085  best_dot = wip->fov; // Note, setting to this avoids comparison below.
4086 
4087  for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) )
4088  {
4089  //first check if its a weapon, then setup the pointers
4090  if (objp->type == OBJ_WEAPON)
4091  {
4092  cm_wp = &Weapons[objp->instance];
4093  cm_wip = &Weapon_info[cm_wp->weapon_info_index];
4094 
4095  if (cm_wip->wi_flags & WIF_CMEASURE)
4096  {
4097  //don't have a weapon try to home in on itself
4098  if (objp==weapon_objp)
4099  continue;
4100 
4101  //don't have a weapon try to home in on missiles fired by the same team, unless its the traitor team.
4102  if ((wp->team == cm_wp->team) && (wp->team != Iff_traitor))
4103  continue;
4104 
4105  vec3d vec_to_object;
4106  dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
4107 
4108  if (dist < cm_wip->cm_effective_rad)
4109  {
4110  float chance;
4111 
4112  if (wp->cmeasure_ignore_list == nullptr) {
4114  }
4115  else {
4116  bool found = false;
4117  for (auto ii = wp->cmeasure_ignore_list->cbegin(); ii != wp->cmeasure_ignore_list->cend(); ++ii) {
4118  if (objp->signature == *ii) {
4119  nprintf(("CounterMeasures", "Weapon (%s-%04i) already seen CounterMeasure (%s-%04i) Frame: %i\n",
4120  wip->name, weapon_objp->instance, cm_wip->name, objp->signature, Framecount));
4121  found = true;
4122  break;
4123  }
4124  }
4125  if (found) {
4126  continue;
4127  }
4128  }
4129 
4130  if (wip->wi_flags & WIF_HOMING_ASPECT) {
4131  // aspect seeker this likely to chase a countermeasure
4132  chance = cm_wip->cm_aspect_effectiveness/wip->seeker_strength;
4133  } else {
4134  // heat seeker and javelin HS this likely to chase a countermeasure
4135  chance = cm_wip->cm_heat_effectiveness/wip->seeker_strength;
4136  }
4137 
4138  // remember this cmeasure so it can be ignored in future
4139  wp->cmeasure_ignore_list->push_back(objp->signature);
4140 
4141  if (frand() >= chance) {
4142  // failed to decoy
4143  nprintf(("CounterMeasures", "Weapon (%s-%04i) ignoring CounterMeasure (%s-%04i) Frame: %i\n",
4144  wip->name, weapon_objp->instance, cm_wip->name, objp->signature, Framecount));
4145  }
4146  else {
4147  // successful decoy, maybe chase the new cm
4148  dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
4149 
4150  if (dot > best_dot)
4151  {
4152  best_dot = dot;
4153  wp->homing_object = objp;
4155  nprintf(("CounterMeasures", "Weapon (%s-%04i) chasing CounterMeasure (%s-%04i) Frame: %i\n",
4156  wip->name, weapon_objp->instance, cm_wip->name, objp->signature, Framecount));
4157  }
4158  }
4159  }
4160  }
4161  }
4162  }
4163 }
4164 
4165 
4171 {
4172  object *weapon_objp;
4173 
4174  if (Cmeasures_homing_check == 0)
4175  return;
4176 
4177  if (Cmeasures_homing_check <= 0)
4179 
4181 
4182  for (weapon_objp = GET_FIRST(&obj_used_list); weapon_objp != END_OF_LIST(&obj_used_list); weapon_objp = GET_NEXT(weapon_objp) ) {
4183  if (weapon_objp->type == OBJ_WEAPON) {
4184  weapon_info *wip = &Weapon_info[Weapons[weapon_objp->instance].weapon_info_index];
4185 
4186  if (wip->wi_flags & WIF_HOMING)
4187  find_homing_object_cmeasures_1(weapon_objp);
4188  }
4189  }
4190 
4191 }
4192 
4196 void find_homing_object_by_sig(object *weapon_objp, int sig)
4197 {
4198  ship_obj *sop;
4199  weapon *wp;
4200  object *old_homing_objp;
4201 
4202  wp = &Weapons[weapon_objp->instance];
4203 
4204  // save the old object so that multiplayer masters know whether to send a homing update packet
4205  old_homing_objp = wp->homing_object;
4206 
4207  sop = GET_FIRST(&Ship_obj_list);
4208  while(sop != END_OF_LIST(&Ship_obj_list)) {
4209  object *objp;
4210 
4211  objp = &Objects[sop->objnum];
4212  if (objp->signature == sig) {
4213  wp->homing_object = objp;
4214  wp->target_sig = objp->signature;
4215  break;
4216  }
4217 
4218  sop = sop->next;
4219  }
4220 
4221  // if the old homing object is different that the new one, send a packet to clients
4222  if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
4223  send_homing_weapon_info( weapon_objp->instance );
4224  }
4225 }
4226 
4228 {
4229  Assert(wp != NULL);
4230 
4231  if (wp->homing_object->signature != wp->target_sig) {
4232  if (wp->homing_object->type == OBJ_WEAPON)
4233  {
4234  weapon_info* target_info = &Weapon_info[Weapons[wp->homing_object->instance].weapon_info_index];
4235 
4236  if (target_info->wi_flags & WIF_CMEASURE)
4237  {
4238  // Check if we can home on this countermeasure
4240  || target_info->wi_flags3 & WIF3_CMEASURE_ASPECT_HOME_ON;
4241 
4242  if (!home_on_cmeasure)
4243  {
4244  return true;
4245  }
4246  }
4247  }
4248  }
4249 
4250  return false;
4251 }
4252 
4256 void weapon_home(object *obj, int num, float frame_time)
4257 {
4258  weapon *wp;
4259  weapon_info *wip;
4260  object *hobjp;
4261 
4262  Assert(obj->type == OBJ_WEAPON);
4263  Assert(obj->instance == num);
4264  wp = &Weapons[num];
4265  wip = &Weapon_info[wp->weapon_info_index];
4266  hobjp = Weapons[num].homing_object;
4267 
4268  //local ssms home only in stages 1 and 5
4269  if ( (wp->lssm_stage==2) || (wp->lssm_stage==3) || (wp->lssm_stage==4))
4270  return;
4271 
4272  float max_speed;
4273 
4274  if ((wip->wi_flags2 & WIF2_LOCAL_SSM) && (wp->lssm_stage==5))
4275  max_speed=wip->lssm_stage5_vel;
4276  else
4277  max_speed=wip->max_speed;
4278 
4279  // If not [free-flight-time] gone by, don't home yet.
4280  // Goober5000 - this has been fixed back to more closely follow the original logic. Remember, the retail code
4281  // had 0.5 second of free flight time, the first half of which was spent ramping up to full speed.
4282  if ((hobjp == &obj_used_list) || ( f2fl(Missiontime - wp->creation_time) < (wip->free_flight_time / 2) )) {
4283  if (f2fl(Missiontime - wp->creation_time) > wip->free_flight_time) {
4284  // If this is a heat seeking homing missile and [free-flight-time] has elapsed since firing
4285  // and we don't have a target (else we wouldn't be inside the IF), find a new target.
4286  if (wip->wi_flags & WIF_HOMING_HEAT) {
4287  find_homing_object(obj, num);
4288  }
4289  // modders may want aspect homing missiles to die if they lose their target
4290  else if (wip->wi_flags & WIF_LOCKED_HOMING && wip->wi_flags3 & WIF3_DIE_ON_LOST_LOCK) {
4291  if (wp->lifeleft > 0.5f) {
4292  wp->lifeleft = frand_range(0.1f, 0.5f); // randomise a bit to avoid multiple missiles detonating in one frame
4293  }
4294  return;
4295  }
4296  }
4300  }
4301 
4302  if (wip->acceleration_time > 0.0f) {
4303  if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
4304  float t;
4305 
4306  t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
4307  obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
4308  }
4309  }
4310  // since free_flight_time can now be 0, guard against that
4311  else if (wip->free_flight_time > 0.0f) {
4312  if (obj->phys_info.speed > max_speed) {
4313  obj->phys_info.speed -= frame_time * (2 / wip->free_flight_time);
4314  } else if ((obj->phys_info.speed < max_speed / (2 / wip->free_flight_time)) && (wip->wi_flags & WIF_HOMING_HEAT)) {
4315  obj->phys_info.speed = max_speed / (2 / wip->free_flight_time);
4316  }
4317  }
4318  // no free_flight_time, so immediately set desired speed
4319  else {
4320  obj->phys_info.speed = max_speed;
4321  }
4322 
4323  // set velocity using whatever speed we have
4324  vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
4325 
4326  return;
4327  }
4328 
4329  // if we've got this far, this should be valid
4330  weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index];
4331 
4332  if (wip->acceleration_time > 0.0f) {
4333  if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
4334  float t;
4335 
4336  t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
4337  obj->phys_info.speed = wp->launch_speed + (wp->weapon_max_vel - wp->launch_speed) * t;
4338  vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
4339  }
4340  }
4341 
4342  // AL 4-8-98: If original target for aspect lock missile is lost, stop homing
4343  // WCS - or javelin
4344  if (wip->wi_flags & WIF_LOCKED_HOMING) {
4345  if ( wp->target_sig > 0 ) {
4346  if (aspect_should_lose_target(wp))
4347  {
4349  return;
4350  }
4351  }
4352  }
4353 
4354  // AL 4-13-98: Stop homing on a subsystem if parent ship has changed
4355  if (wip->wi_flags & WIF_HOMING_HEAT) {
4356  if ( wp->target_sig > 0 ) {
4357  if ( wp->homing_object->signature != wp->target_sig ) {
4358  wp->homing_subsys = NULL;
4359  }
4360  }
4361  }
4362 
4363  // If target subsys is dead make missile pick random spot on target as attack point.
4364  if (wp->homing_subsys != NULL) {
4366  if ((wp->homing_subsys->max_hits > 0) && (wp->homing_subsys->current_hits <= 0)) {
4368  return;
4369  }
4370  }
4371  }
4372 
4373  // Make sure Javelin HS missiles always home on engine subsystems if ships
4374  if ((wip->wi_flags & WIF_HOMING_JAVELIN) &&
4375  (hobjp->type == OBJ_SHIP) &&
4376  (wp->target_sig > 0) &&
4377  (wp->homing_subsys != NULL) &&
4379  int sindex = ship_get_by_signature(wp->target_sig);
4380  if (sindex >= 0) {
4381  ship *enemy = &Ships[sindex];
4383  }
4384  }
4385 
4386  // If Javelin HS missile doesn't home in on a subsystem but homing in on a
4387  // ship, lose lock alltogether
4388  // Javelins can only home in one Engines or bombs.
4389  if ((wip->wi_flags & WIF_HOMING_JAVELIN) &&
4390  (hobjp->type == OBJ_SHIP) &&
4391  (wp->target_sig > 0) &&
4392  (wp->homing_subsys == NULL)) {
4394  return;
4395  }
4396 
4397  // TODO maybe add flag to allow WF_LOCKED_HOMING to lose target when target is dead
4398 
4399  switch (hobjp->type) {
4400  case OBJ_NONE:
4401  if (wip->wi_flags & WIF_LOCKED_HOMING) {
4403  }
4404  else {
4405  find_homing_object(obj, num);
4406  }
4407  return;
4408  break;
4409  case OBJ_SHIP:
4410  if (hobjp->signature != wp->target_sig) {
4411  if (wip->wi_flags & WIF_LOCKED_HOMING) {
4413  }
4414  else {
4415  find_homing_object(obj, num);
4416  }
4417  return;
4418  }
4419  break;
4420  case OBJ_WEAPON:
4421  {
4423  || hobj_infop->wi_flags3 & WIF3_CMEASURE_ASPECT_HOME_ON;
4424 
4425  // don't home on countermeasures or non-bombs, that's handled elsewhere
4426  if (((hobj_infop->wi_flags & WIF_CMEASURE) && !home_on_cmeasure))
4427  {
4428  break;
4429  }
4430  else if (!(hobj_infop->wi_flags & WIF_BOMB))
4431  {
4432  break;
4433  }
4434 
4435  if (wip->wi_flags & WIF_LOCKED_HOMING) {
4437  }
4438  else {
4439  find_homing_object(obj, num);
4440  }
4441  break;
4442  }
4443  default:
4444  return;
4445  }
4446 
4447  // See if this weapon is the nearest homing object to the object it is homing on.
4448  // If so, update some fields in the target object's ai_info.
4449  if (hobjp != &obj_used_list) {
4450  float dist;
4451 
4452  dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
4453 
4454  if (hobjp->type == OBJ_SHIP) {
4455  ai_info *aip;
4456 
4457  aip = &Ai_info[Ships[hobjp->instance].ai_index];
4458 
4459  if ((aip->nearest_locked_object == -1) || (dist < aip->nearest_locked_distance)) {
4460  aip->nearest_locked_object = obj-Objects;
4461  aip->nearest_locked_distance = dist;
4462  }
4463  }
4464  }
4465 
4466  // If the object it is homing on is still valid, home some more!
4467  if (hobjp != &obj_used_list) {
4468  float old_dot, vel;
4469  vec3d vec_to_goal;
4470  vec3d target_pos; // position of what the homing missile is seeking
4471 
4472  vm_vec_zero(&target_pos);
4473 
4474  // the homing missile may be seeking a subsystem on a ship. If so, we need to calculate the
4475  // world coordinates of that subsystem so the homing missile can seek it out.
4476  // For now, March 7, 1997, MK, heat seeking homing missiles will be able to home on
4477  // any subsystem. Probably makes sense for them to only home on certain kinds of subsystems.
4478  if ( (wp->homing_subsys != NULL) && !(wip->wi_flags2 & WIF2_NON_SUBSYS_HOMING) && hobjp->type == OBJ_SHIP) {
4479  get_subsystem_world_pos(hobjp, Weapons[num].homing_subsys, &target_pos);
4480  wp->homing_pos = target_pos; // store the homing position in weapon data
4481  Assert( !vm_is_vec_nan(&wp->homing_pos) );
4482  } else {
4483  float fov;
4484  float dist;
4485 
4486  dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
4487  if (hobjp->type == OBJ_WEAPON && (hobj_infop->wi_flags & WIF_CMEASURE))
4488  {
4489  if (dist < hobj_infop->cm_detonation_rad)
4490  {
4491  // Make this missile detonate soon. Not right away, not sure why. Seems better.
4492  if (iff_x_attacks_y(Weapons[hobjp->instance].team, wp->team)) {
4493  detonate_nearby_missiles(hobjp, obj);
4494  return;
4495  }
4496  }
4497  }
4498 
4499  fov = -1.0f;
4500 
4501  int pick_homing_point = 0;
4502  if ( IS_VEC_NULL(&wp->homing_pos) ) {
4503  pick_homing_point = 1;
4504  }
4505 
4506  // Update homing position if it hasn't been set, you're within 500 meters, or every half second, approximately.
4507  // For large objects, don't lead them.
4508  if (hobjp->radius < 40.0f) {
4509  target_pos = hobjp->pos;
4510  wp->homing_pos = target_pos;
4511  } else if ( pick_homing_point || (dist < 500.0f) || (rand_chance(flFrametime, 2.0f)) ) {
4512 
4513  if (hobjp->type == OBJ_SHIP) {
4514  if ( !pick_homing_point ) {
4515  // ensure that current attack point is only updated in world coords (ie not pick a different vertex)
4517  }
4518 
4519  if ( pick_homing_point && !(wip->wi_flags2 & WIF2_NON_SUBSYS_HOMING) ) {
4520  // If *any* player is parent of homing missile, then use position where lock indicator is
4521  if ( Objects[obj->parent].flags & OF_PLAYER_SHIP ) {
4522  player *pp;
4523 
4524  // determine the player
4525  pp = Player;
4526 
4527  if ( Game_mode & GM_MULTIPLAYER ) {
4528  int pnum;
4529 
4530  pnum = multi_find_player_by_object( &Objects[obj->parent] );
4531  if ( pnum != -1 ){
4532  pp = Net_players[pnum].m_player;
4533  }
4534  }
4535 
4536  // If player has apect lock, we don't want to find a homing point on the closest
4537  // octant... setting the timestamp to 0 ensures this.
4538  if (wip->wi_flags & WIF_LOCKED_HOMING) {
4540  } else {
4542  }
4543 
4544  if ( pp && pp->locking_subsys ) {
4546  } else {
4548  }
4549  }
4550  }
4551 
4552  ai_big_pick_attack_point(hobjp, obj, &target_pos, fov);
4553 
4554  } else {
4555  target_pos = hobjp->pos;
4556  }
4557 
4558  wp->homing_pos = target_pos;
4559  Assert( !vm_is_vec_nan(&wp->homing_pos) );
4560  } else
4561  target_pos = wp->homing_pos;
4562  }
4563 
4564  // Couldn't find a lock.
4565  if (IS_VEC_NULL(&target_pos))
4566  return;
4567 
4568  // Cause aspect seeking weapon to home at target's predicted position.
4569  // But don't use predicted position if dot product small or negative.
4570  // If do this, with a ship headed towards missile, could choose a point behind missile.
4571  float dist_to_target, time_to_target;
4572 
4573  dist_to_target = vm_vec_normalized_dir(&vec_to_goal, &target_pos, &obj->pos);
4574  time_to_target = dist_to_target/max_speed;
4575 
4576  vec3d tvec;
4577  tvec = obj->phys_info.vel;
4578  vm_vec_normalize(&tvec);
4579 
4580  old_dot = vm_vec_dot(&tvec, &vec_to_goal);
4581 
4582  // If a weapon has missed its target, detonate it.
4583  // This solves the problem of a weapon circling the center of a subsystem that has been blown away.
4584  // Problem: It does not do impact damage, just proximity damage.
4585  if ((dist_to_target < flFrametime * obj->phys_info.speed * 4.0f + 10.0f) &&
4586  (old_dot < wip->fov) &&
4587  (wp->lifeleft > 0.01f) &&
4588  (wp->homing_object != &obj_used_list) &&
4589  (wp->homing_object->type == OBJ_SHIP))
4590  {
4591  wp->lifeleft = 0.01f;
4592  }
4593 
4594  // Only lead target if more than one second away. Otherwise can miss target. I think this
4595  // is what's causing Harbingers to miss the super destroyer. -- MK, 4/15/98
4596  if ((old_dot > 0.1f) && (time_to_target > 0.1f)) {
4597  if (wip->wi_flags2 & WIF2_VARIABLE_LEAD_HOMING) {
4598  vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, (0.33f * wip->target_lead_scaler * MIN(time_to_target, 6.0f)));
4599  } else if (wip->wi_flags & WIF_LOCKED_HOMING) {
4600  vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, MIN(time_to_target, 2.0f));
4601  }
4602  }
4603 
4604  // If a HEAT seeking (rather than ASPECT seeking) homing missile, verify that target is in viewcone.
4605  if (wip->wi_flags & WIF_HOMING_HEAT) {
4606  if ((old_dot < wip->fov) && (dist_to_target > wip->shockwave.inner_rad*1.1f)) { // Delay finding new target one frame to allow detonation.
4607  find_homing_object(obj, num);
4608  return; // Maybe found a new homing object. Return, process more next frame.
4609  } else // Subtract out life based on how far from target this missile points.
4610  if ((wip->fov < 0.95f) && !(wip->wi_flags2 & WIF2_NO_LIFE_LOST_IF_MISSED)) {
4611  wp->lifeleft -= flFrametime * (0.95f - old_dot);
4612  }
4613  } else if (wip->wi_flags & WIF_LOCKED_HOMING) { // subtract life as if max turn is 90 degrees.
4614  if ((wip->fov < 0.95f) && !(wip->wi_flags2 & WIF2_NO_LIFE_LOST_IF_MISSED))
4615  wp->lifeleft -= flFrametime * (0.95f - old_dot);
4616  } else {
4617  Warning(LOCATION, "Tried to make weapon '%s' home, but found it wasn't aspect-seeking or heat-seeking or a Javelin!", wip->name);
4618  }
4619 
4620 
4621  // Control speed based on dot product to goal. If close to straight ahead, move
4622  // at max speed, else move slower based on how far from ahead.
4623  if (old_dot < 0.90f) {
4624  obj->phys_info.speed = MAX(0.2f, old_dot* (float) fabs(old_dot));
4625  if (obj->phys_info.speed < max_speed*0.75f)
4626  obj->phys_info.speed = max_speed*0.75f;
4627  } else
4628  obj->phys_info.speed = max_speed;
4629 
4630 
4631  if (wip->acceleration_time > 0.0f) {
4632  // Ramp up speed linearly for the given duration
4633  if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
4634  float t;
4635 
4636  t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
4637  obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
4638  }
4639  } else if (!(wip->wi_flags3 & WIF3_NO_HOMING_SPEED_RAMP) && Missiontime - wp->creation_time < i2f(1)) {
4640  // Default behavior:
4641  // For first second of weapon's life, it doesn't fly at top speed. It ramps up.
4642  float t;
4643 
4644  t = f2fl(Missiontime - wp->creation_time);
4645  obj->phys_info.speed *= t*t;
4646  }
4647 
4648  Assert( obj->phys_info.speed > 0.0f );
4649 
4650  vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
4651 
4652  // turn the missile towards the target only if non-swarm. Homing swarm missiles choose
4653  // a different vector to turn towards, this is done in swarm_update_direction().
4654  if ( wp->swarm_index < 0 ) {
4655  ai_turn_towards_vector(&target_pos, obj, frame_time, wip->turn_time, NULL, NULL, 0.0f, 0, NULL);
4656  vel = vm_vec_mag(&obj->phys_info.desired_vel);
4657 
4658  vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, vel);
4659 
4660  }
4661  }
4662 }
4663 
4664 // as Mike K did with ships -- break weapon into process_pre and process_post for code to execute
4665 // before and after physics movement
4666 
4667 void weapon_process_pre( object *obj, float frame_time)
4668 {
4669  if(obj->type != OBJ_WEAPON)
4670  return;
4671 
4672  weapon *wp = &Weapons[obj->instance];
4673  weapon_info *wip = &Weapon_info[wp->weapon_info_index];
4674 
4675  // if the object is a corkscrew style weapon, process it now
4676  if((obj->type == OBJ_WEAPON) && (Weapons[obj->instance].cscrew_index >= 0)){
4677  cscrew_process_pre(obj);
4678  }
4679 
4680  //WMC - Originally flak_maybe_detonate, moved here.
4681  if(wp->det_range > 0.0f)
4682  {
4683  vec3d temp;
4684  vm_vec_sub(&temp, &obj->pos, &wp->start_pos);
4685  if(vm_vec_mag(&temp) >= wp->det_range){
4686  weapon_detonate(obj);
4687  }
4688  }
4689 
4690  //WMC - Maybe detonate weapon anyway!
4691  if(wip->det_radius > 0.0f)
4692  {
4693  if((wp->homing_object != &obj_used_list) && (wp->homing_object->type != 0))
4694  {
4695  if(!IS_VEC_NULL(&wp->homing_pos) && vm_vec_dist(&wp->homing_pos, &obj->pos) <= wip->det_radius)
4696  {
4697  weapon_detonate(obj);
4698  }
4699  } else if(wp->target_num > -1)
4700  {
4701  if(vm_vec_dist(&obj->pos, &Objects[wp->target_num].pos) <= wip->det_radius)
4702  {
4703  weapon_detonate(obj);
4704  }
4705  }
4706  }
4707 }
4708 
4710 
4711 
4712 MONITOR( NumWeapons )
4713 
4714 
4718 {
4719  // don't play flyby sounds too close together
4720  if ( !timestamp_elapsed(Weapon_flyby_sound_timer) ) {
4721  return;
4722  }
4723 
4724  if ( !(wp->weapon_flags & WF_PLAYED_FLYBY_SOUND) && (wp->weapon_flags & WF_CONSIDER_FOR_FLYBY_SOUND) ) {
4725  float dist, dot, radius;
4726 
4727  if ( (Weapon_info[wp->weapon_info_index].wi_flags & WIF_CORKSCREW) ) {
4728  dist = vm_vec_dist_quick(&weapon_objp->last_pos, &Eye_position);
4729  } else {
4730  dist = vm_vec_dist_quick(&weapon_objp->pos, &Eye_position);
4731  }
4732 
4733  if ( Viewer_obj ) {
4734  radius = Viewer_obj->radius;
4735  } else {
4736  radius = 0.0f;
4737  }
4738 
4739  if ( (dist > radius) && (dist < 55) ) {
4740  vec3d vec_to_weapon;
4741 
4742  vm_vec_sub(&vec_to_weapon, &weapon_objp->pos, &Eye_position);
4743  vm_vec_normalize(&vec_to_weapon);
4744 
4745  // ensure laser is in front of eye
4746  dot = vm_vec_dot(&vec_to_weapon, &Eye_matrix.vec.fvec);
4747  if ( dot < 0.1 ) {
4748  return;
4749  }
4750 </