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 <algorithm>
13 
14 #include "ai/aibig.h"
15 #include "asteroid/asteroid.h"
16 #include "cmdline/cmdline.h"
17 #include "cmeasure/cmeasure.h"
18 #include "debugconsole/console.h"
19 #include "fireball/fireballs.h"
20 #include "freespace.h"
21 #include "gamesnd/gamesnd.h"
22 #include "globalincs/linklist.h"
23 #include "hud/hud.h"
24 #include "hud/hudartillery.h"
25 #include "iff_defs/iff_defs.h"
26 #include "io/joy_ff.h"
27 #include "io/timer.h"
28 #include "math/staticrand.h"
29 #include "missionui/missionweaponchoice.h"
30 #include "mod_table/mod_table.h"
31 #include "nebula/neb.h"
32 #include "network/multi.h"
33 #include "network/multimsgs.h"
34 #include "network/multiutil.h"
35 #include "object/objcollide.h"
36 #include "object/objectdock.h"
37 #include "object/objectsnd.h"
38 #include "scripting/scripting.h"
39 #include "particle/particle.h"
40 #include "playerman/player.h"
41 #include "radar/radar.h"
42 #include "render/3d.h"
43 #include "render/batching.h"
44 #include "ship/ship.h"
45 #include "ship/shiphit.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 "particle/effects/SingleParticleEffect.h"
53 #include "particle/effects/BeamPiercingEffect.h"
54 #include "particle/effects/ParticleEmitterEffect.h"
55 #include "tracing/Monitor.h"
56 #include "tracing/tracing.h"
57 #include "weapon.h"
58 
59 
60 // 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.
61 typedef struct delayed_ssm_data {
62 	SCP_string filename;
63 	int linenum;
64 	SCP_string ssm_entry;
65 } delayed_ssm_data;
66 SCP_map<SCP_string, delayed_ssm_data> Delayed_SSM_data;
67 SCP_vector<SCP_string> Delayed_SSM_names;
68 
69 typedef struct delayed_ssm_index_data {
70 	SCP_string filename;
71 	int linenum;
72 } delayed_ssm_index_data;
73 SCP_map<SCP_string, delayed_ssm_index_data> Delayed_SSM_indices_data;
74 SCP_vector<SCP_string> Delayed_SSM_indices;
75 
76 
77 #ifndef NDEBUG
78 int Weapon_flyby_sound_enabled = 1;
79 DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
80 #endif
81 
82 static int Weapon_flyby_sound_timer;
83 
84 weapon Weapons[MAX_WEAPONS];
85 SCP_vector<weapon_info> Weapon_info;
86 
87 #define		MISSILE_OBJ_USED	(1<<0)			// flag used in missile_obj struct
88 #define		MAX_MISSILE_OBJS	MAX_WEAPONS		// max number of missiles tracked in missile list
89 missile_obj Missile_objs[MAX_MISSILE_OBJS];	// array used to store missile object indexes
90 missile_obj Missile_obj_list;						// head of linked list of missile_obj structs
91 
92 //WEAPON SUBTYPE STUFF
93 const char *Weapon_subtype_names[] = {
94 	"Laser",
95 	"Missile",
96 	"Beam"
97 };
98 int Num_weapon_subtypes = sizeof(Weapon_subtype_names)/sizeof(Weapon_subtype_names[0]);
99 
100 flag_def_list_new<Weapon::Burst_Flags> Burst_fire_flags[] = {
101 	{ "fast firing",		Weapon::Burst_Flags::Fast_firing,		true, false },
102 	{ "random length",		Weapon::Burst_Flags::Random_length,		true, false },
103 	{ "resets",		Weapon::Burst_Flags::Resets,		true, false },
104 	{ "num firepoints for burst shots",		Weapon::Burst_Flags::Num_firepoints_burst_shots,		true, false }
105 };
106 
107 const size_t Num_burst_fire_flags = sizeof(Burst_fire_flags)/sizeof(flag_def_list_new<Weapon::Burst_Flags>);
108 
109 flag_def_list_new<Weapon::Beam_Info_Flags> Beam_info_flags[] = {
110 	{ "burst shares random target",		Weapon::Beam_Info_Flags::Burst_share_random,		        true, false },
111 	{ "track own texture tiling",       Weapon::Beam_Info_Flags::Track_own_texture_tiling,          true, false }
112 };
113 
114 const size_t Num_beam_info_flags = sizeof(Beam_info_flags) / sizeof(flag_def_list_new<Weapon::Beam_Info_Flags>);
115 
116 weapon_explosions Weapon_explosions;
117 
118 SCP_vector<lod_checker> LOD_checker;
119 
120 flag_def_list_new<Weapon::Info_Flags> Weapon_Info_Flags[] = {
121     { "spawn",							Weapon::Info_Flags::Spawn,								true, true }, //special case
122     { "remote detonate",				Weapon::Info_Flags::Remote,								true, false },
123     { "puncture",						Weapon::Info_Flags::Puncture,							true, false },
124     { "big ship",						Weapon::Info_Flags::Big_only,							true, false },
125     { "huge",							Weapon::Info_Flags::Huge,								true, false },
126     { "bomber+",						Weapon::Info_Flags::Bomber_plus,						true, false },
127     { "child",							Weapon::Info_Flags::Child,								true, false },
128     { "bomb",							Weapon::Info_Flags::Bomb,								true, false },
129     { "no dumbfire",					Weapon::Info_Flags::No_dumbfire,						true, false },
130 	{ "no doublefire",					Weapon::Info_Flags::No_doublefire,						true, false },
131     { "in tech database",				Weapon::Info_Flags::In_tech_database,					true, false },
132     { "player allowed",					Weapon::Info_Flags::Player_allowed,                     true, false },
133     { "particle spew",					Weapon::Info_Flags::Particle_spew,						true, false },
134     { "emp",							Weapon::Info_Flags::Emp,								true, false },
135     { "esuck",							Weapon::Info_Flags::Energy_suck,						true, false },
136     { "flak",							Weapon::Info_Flags::Flak,								true, false },
137     { "corkscrew",						Weapon::Info_Flags::Corkscrew,							true, false },
138     { "shudder",						Weapon::Info_Flags::Shudder,							true, false },
139     { "electronics",					Weapon::Info_Flags::Electronics,						true, false },
140     { "lockarm",						Weapon::Info_Flags::Lockarm,							true, false },
141     { "beam",							Weapon::Info_Flags::Beam,								true, true }, //special case
142     { "stream",							Weapon::Info_Flags::Stream,								true, false },
143     { "supercap",						Weapon::Info_Flags::Supercap,							true, false },
144     { "countermeasure",					Weapon::Info_Flags::Cmeasure,							true, false },
145     { "ballistic",						Weapon::Info_Flags::Ballistic,							true, false },
146     { "pierce shields",					Weapon::Info_Flags::Pierce_shields,						true, false },
147     { "local ssm",						Weapon::Info_Flags::Local_ssm,							true, false },
148     { "tagged only",					Weapon::Info_Flags::Tagged_only,						true, false },
149     { "beam no whack",					Weapon::Info_Flags::NUM_VALUES,							false, true }, //special case
150     { "cycle",							Weapon::Info_Flags::Cycle,								true, false },
151     { "small only",						Weapon::Info_Flags::Small_only,							true, false },
152     { "same turret cooldown",			Weapon::Info_Flags::Same_turret_cooldown,				true, false },
153     { "apply no light",					Weapon::Info_Flags::Mr_no_lighting,						true, false },
154     { "training",						Weapon::Info_Flags::Training,							true, false },
155     { "smart spawn",					Weapon::Info_Flags::Smart_spawn,						true, false },
156     { "inherit parent target",			Weapon::Info_Flags::Inherit_parent_target,				true, false },
157     { "no emp kill",					Weapon::Info_Flags::No_emp_kill,						true, false },
158     { "untargeted heat seeker",			Weapon::Info_Flags::Untargeted_heat_seeker,				true, false },
159     { "no radius doubling",				Weapon::Info_Flags::No_radius_doubling,					true, false },
160     { "no subsystem homing",			Weapon::Info_Flags::Non_subsys_homing,					true, false },
161     { "no lifeleft penalty",			Weapon::Info_Flags::No_life_lost_if_missed,				true, false },
162     { "can be targeted",				Weapon::Info_Flags::Can_be_targeted,					true, false },
163     { "show on radar",					Weapon::Info_Flags::Shown_on_radar,						true, false },
164     { "show friendly on radar",			Weapon::Info_Flags::Show_friendly,						true, false },
165     { "capital+",						Weapon::Info_Flags::Capital_plus,						true, false },
166     { "chain external model fps",		Weapon::Info_Flags::External_weapon_fp,					true, false },
167     { "external model launcher",		Weapon::Info_Flags::External_weapon_lnch,				true, false },
168     { "takes blast damage",				Weapon::Info_Flags::Takes_blast_damage,					true, false },
169     { "takes shockwave damage",			Weapon::Info_Flags::Takes_shockwave_damage,				true, false },
170     { "hide from radar",				Weapon::Info_Flags::Dont_show_on_radar,					true, false },
171     { "render flak",					Weapon::Info_Flags::Render_flak,						true, false },
172     { "ciws",							Weapon::Info_Flags::Ciws,								true, false },
173     { "anti-subsystem beam",			Weapon::Info_Flags::Antisubsysbeam,						true, false },
174     { "no primary linking",				Weapon::Info_Flags::Nolink,								true, false },
175     { "same emp time for capships",		Weapon::Info_Flags::Use_emp_time_for_capship_turrets,	true, false },
176     { "no primary linked penalty",		Weapon::Info_Flags::No_linked_penalty,					true, false },
177     { "no homing speed ramp",			Weapon::Info_Flags::No_homing_speed_ramp,				true, false },
178     { "pulls aspect seekers",			Weapon::Info_Flags::Cmeasure_aspect_home_on,			true, false },
179     { "turret interceptable",			Weapon::Info_Flags::Turret_Interceptable,				true, false },
180     { "fighter interceptable",			Weapon::Info_Flags::Fighter_Interceptable,				true, false },
181     { "aoe electronics",                Weapon::Info_Flags::Aoe_Electronics,                    true, false },
182     { "apply recoil",                   Weapon::Info_Flags::Apply_Recoil,                       true, false },
183     { "don't spawn if shot",            Weapon::Info_Flags::Dont_spawn_if_shot,                 true, false },
184     { "die on lost lock",               Weapon::Info_Flags::Die_on_lost_lock,                   true, true  }, //special case
185 	{ "no impact spew",					Weapon::Info_Flags::No_impact_spew,						true, false },
186 	{ "require exact los",				Weapon::Info_Flags::Require_exact_los,					true, false },
187 	{ "can damage shooter",				Weapon::Info_Flags::Can_damage_shooter,					true, false },
188 	{ "heals",							Weapon::Info_Flags::Heals,						        true, false },
189 	{ "no collide",						Weapon::Info_Flags::No_collide,						    true, false },
190 	{ "multilock target dead subsys",   Weapon::Info_Flags::Multilock_target_dead_subsys,		true, false },
191 };
192 
193 const size_t num_weapon_info_flags = sizeof(Weapon_Info_Flags) / sizeof(flag_def_list_new<Weapon::Info_Flags>);
194 
195 int Num_weapons = 0;
196 bool Weapons_inited = false;
197 
198 int laser_model_inner = -1;
199 int laser_model_outer = -1;
200 
201 int missile_model = -1;
202 
203 int     First_secondary_index = -1;
204 int		Default_cmeasure_index = -1;
205 
206 static int *used_weapons = NULL;
207 
208 int	Num_spawn_types = 0;
209 char **Spawn_names = NULL;
210 
211 int Num_player_weapon_precedence;				// Number of weapon types in Player_weapon_precedence
212 int Player_weapon_precedence[MAX_WEAPON_TYPES];	// Array of weapon types, precedence list for player weapon selection
213 
214 // Used to avoid playing too many impact sounds in too short a time interval.
215 // This will elimate the odd "stereo" effect that occurs when two weapons impact at
216 // nearly the same time, like from a double laser (also saves sound channels!)
217 #define	IMPACT_SOUND_DELTA	50		// in milliseconds
218 int		Weapon_impact_timer;			// timer, initialized at start of each mission
219 
220 // energy suck defines
221 #define ESUCK_DEFAULT_WEAPON_REDUCE				(10.0f)
222 #define ESUCK_DEFAULT_AFTERBURNER_REDUCE		(10.0f)
223 
224 // scale factor for big ships getting hit by flak
225 #define FLAK_DAMAGE_SCALE				0.05f
226 
227 //default time of a homing missile to not home
228 #define HOMING_DEFAULT_FREE_FLIGHT_TIME	0.5f
229 
230 // default percentage of max speed to coast at during freeflight
231 const float HOMING_DEFAULT_FREE_FLIGHT_FACTOR = 0.25f;
232 
233 //default time of a homing primary to not home
234 #define HOMING_DEFAULT_PRIMARY_FREE_FLIGHT_TIME	0.0f
235 
236 // time delay between each swarm missile that is fired
237 #define SWARM_MISSILE_DELAY				150
238 
239 // 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
240 // range. Check the comment in weapon_set_tracking_info() for more details
241 #define LOCKED_HOMING_EXTENDED_LIFE_FACTOR			1.2f
242 
243 // default number of missiles or bullets rearmed per load sound during rearm
244 #define REARM_NUM_MISSILES_PER_BATCH 4
245 #define REARM_NUM_BALLISTIC_PRIMARIES_PER_BATCH 100
246 
247 // most frequently a continuous spawn weapon is allowed to spawn
248 static const float MINIMUM_SPAWN_INTERVAL = 0.1f;
249 
250 extern int compute_num_homing_objects(object *target_objp);
251 
252 void weapon_spew_stats(WeaponSpewType type);
253 
254 
weapon_explosions()255 weapon_explosions::weapon_explosions()
256 {
257 	ExplosionInfo.clear();
258 }
259 
GetIndex(char * filename)260 int weapon_explosions::GetIndex(char *filename)
261 {
262 	if ( filename == NULL ) {
263 		Int3();
264 		return -1;
265 	}
266 
267 	for (size_t i = 0; i < ExplosionInfo.size(); i++) {
268 		if ( !stricmp(ExplosionInfo[i].lod[0].filename, filename)) {
269 			return (int)i;
270 		}
271 	}
272 
273 	return -1;
274 }
275 
Load(char * filename,int expected_lods)276 int weapon_explosions::Load(char *filename, int expected_lods)
277 {
278 	char name_tmp[MAX_FILENAME_LEN] = "";
279 	int bitmap_id = -1;
280 	int nframes, nfps;
281 	weapon_expl_info new_wei;
282 
283 	Assert( expected_lods <= MAX_WEAPON_EXPL_LOD );
284 
285 	//Check if it exists
286 	int idx = GetIndex(filename);
287 
288 	if (idx != -1)
289 		return idx;
290 
291 	new_wei.lod_count = 1;
292 
293 	strcpy_s(new_wei.lod[0].filename, filename);
294 	new_wei.lod[0].bitmap_id = bm_load_animation(filename, &new_wei.lod[0].num_frames, &new_wei.lod[0].fps, nullptr, nullptr, true);
295 
296 	if (new_wei.lod[0].bitmap_id < 0) {
297 		Warning(LOCATION, "Weapon explosion '%s' does not have an LOD0 anim!", filename);
298 
299 		// if we don't have the first then it's only safe to assume that the rest are missing or not usable
300 		return -1;
301 	}
302 
303 	// 2 chars for the lod, 4 for the extension that gets added automatically
304 	if ( (MAX_FILENAME_LEN - strlen(filename)) > 6 ) {
305 		for (idx = 1; idx < expected_lods; idx++) {
306 			sprintf(name_tmp, "%s_%d", filename, idx);
307 
308 			bitmap_id = bm_load_animation(name_tmp, &nframes, &nfps, nullptr, nullptr, true);
309 
310 			if (bitmap_id > 0) {
311 				strcpy_s(new_wei.lod[idx].filename, name_tmp);
312 				new_wei.lod[idx].bitmap_id = bitmap_id;
313 				new_wei.lod[idx].num_frames = nframes;
314 				new_wei.lod[idx].fps = nfps;
315 
316 				new_wei.lod_count++;
317 			} else {
318 				break;
319 			}
320 		}
321 
322 		if (new_wei.lod_count != expected_lods)
323 			Warning(LOCATION, "For '%s', %i of %i LODs are missing!", filename, expected_lods - new_wei.lod_count, expected_lods);
324 	}
325 	else {
326 		Warning(LOCATION, "Filename '%s' is too long to have any LODs.", filename);
327 	}
328 
329 	ExplosionInfo.push_back( new_wei );
330 
331 	return (int)(ExplosionInfo.size() - 1);
332 }
333 
PageIn(int idx)334 void weapon_explosions::PageIn(int idx)
335 {
336 	int i;
337 
338 	if ( (idx < 0) || (idx >= (int)ExplosionInfo.size()) )
339 		return;
340 
341 	weapon_expl_info *wei = &ExplosionInfo[idx];
342 
343 	for ( i = 0; i < wei->lod_count; i++ ) {
344 		if ( wei->lod[i].bitmap_id >= 0 ) {
345 			bm_page_in_xparent_texture( wei->lod[i].bitmap_id, wei->lod[i].num_frames );
346 		}
347 	}
348 }
349 
GetAnim(int weapon_expl_index,vec3d * pos,float size)350 int weapon_explosions::GetAnim(int weapon_expl_index, vec3d *pos, float size)
351 {
352 	if ( (weapon_expl_index < 0) || (weapon_expl_index >= (int)ExplosionInfo.size()) )
353 		return -1;
354 
355 	//Get our weapon expl for the day
356 	weapon_expl_info *wei = &ExplosionInfo[weapon_expl_index];
357 
358 	if (wei->lod_count == 1)
359 		return wei->lod[0].bitmap_id;
360 
361 	// now we have to do some work
362 	vertex v;
363 	int x, y, w, h, bm_size;
364 	int must_stop = 0;
365 	int best_lod = 1;
366 	int behind = 0;
367 
368 	// start the frame
369 	extern int G3_count;
370 
371 	if(!G3_count){
372 		g3_start_frame(1);
373 		must_stop = 1;
374 	}
375 	g3_set_view_matrix(&Eye_position, &Eye_matrix, Eye_fov);
376 
377 	// get extents of the rotated bitmap
378 	g3_rotate_vertex(&v, pos);
379 
380 	// if vertex is behind, find size if in front, then drop down 1 LOD
381 	if (v.codes & CC_BEHIND) {
382 		float dist = vm_vec_dist_quick(&Eye_position, pos);
383 		vec3d temp;
384 
385 		behind = 1;
386 		vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.vec.fvec, dist);
387 		g3_rotate_vertex(&v, &temp);
388 
389 		// if still behind, bail and go with default
390 		if (v.codes & CC_BEHIND) {
391 			behind = 0;
392 		}
393 	}
394 
395 	if (!g3_get_bitmap_dims(wei->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
396 		if (Detail.hardware_textures == 4) {
397 			// straight LOD
398 			if(w <= bm_size/8){
399 				best_lod = 3;
400 			} else if(w <= bm_size/2){
401 				best_lod = 2;
402 			} else if(w <= 1.3f*bm_size){
403 				best_lod = 1;
404 			} else {
405 				best_lod = 0;
406 			}
407 		} else {
408 			// less aggressive LOD for lower detail settings
409 			if(w <= bm_size/8){
410 				best_lod = 3;
411 			} else if(w <= bm_size/3){
412 				best_lod = 2;
413 			} else if(w <= (1.15f*bm_size)){
414 				best_lod = 1;
415 			} else {
416 				best_lod = 0;
417 			}
418 		}
419 	}
420 
421 	// if it's behind, bump up LOD by 1
422 	if (behind)
423 		best_lod++;
424 
425 	// end the frame
426 	if (must_stop)
427 		g3_end_frame();
428 
429 	best_lod = MIN(best_lod, wei->lod_count - 1);
430 	Assert( (best_lod >= 0) && (best_lod < MAX_WEAPON_EXPL_LOD) );
431 
432 	return wei->lod[best_lod].bitmap_id;
433 }
434 
435 
parse_weapon_expl_tbl(const char * filename)436 void parse_weapon_expl_tbl(const char *filename)
437 {
438 	uint i;
439 	lod_checker lod_check;
440 
441 	try
442 	{
443 		read_file_text(filename, CF_TYPE_TABLES);
444 		reset_parse();
445 
446 		required_string("#Start");
447 		while (required_string_either("#End", "$Name:"))
448 		{
449 			memset(&lod_check, 0, sizeof(lod_checker));
450 
451 			// base filename
452 			required_string("$Name:");
453 			stuff_string(lod_check.filename, F_NAME, MAX_FILENAME_LEN);
454 
455 			//Do we have an LOD num
456 			if (optional_string("$LOD:"))
457 			{
458 				stuff_int(&lod_check.num_lods);
459 			}
460 
461 			// only bother with this if we have 1 or more lods and less than max lods,
462 			// otherwise the stardard level loading will take care of the different effects
463 			if ((lod_check.num_lods > 0) && (lod_check.num_lods < MAX_WEAPON_EXPL_LOD)) {
464 				// name check, update lod count if it already exists
465 				for (i = 0; i < LOD_checker.size(); i++) {
466 					if (!stricmp(LOD_checker[i].filename, lod_check.filename)) {
467 						LOD_checker[i].num_lods = lod_check.num_lods;
468 					}
469 				}
470 
471 				// old entry not found, add new entry
472 				if (i == LOD_checker.size()) {
473 					LOD_checker.push_back(lod_check);
474 				}
475 			}
476 		}
477 		required_string("#End");
478 	}
479 	catch (const parse::ParseException& e)
480 	{
481 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
482 		return;
483 	}
484 }
485 
486 /**
487  * Clear out the Missile_obj_list
488  */
missile_obj_list_init()489 void missile_obj_list_init()
490 {
491 	int i;
492 
493 	list_init(&Missile_obj_list);
494 	for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
495 		Missile_objs[i].flags = 0;
496 	}
497 }
498 
499 /**
500  * Add a node from the Missile_obj_list.
501  * @note Only called from weapon_create()
502  */
missile_obj_list_add(int objnum)503 int missile_obj_list_add(int objnum)
504 {
505 	int i;
506 
507 	for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
508 		if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
509 			break;
510 	}
511 	if ( i == MAX_MISSILE_OBJS ) {
512 		Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
513 		return -1;
514 	}
515 
516 	Missile_objs[i].flags = 0;
517 	Missile_objs[i].objnum = objnum;
518 	list_append(&Missile_obj_list, &Missile_objs[i]);
519 	Missile_objs[i].flags |= MISSILE_OBJ_USED;
520 
521 	return i;
522 }
523 
524 /**
525  * Remove a node from the Missile_obj_list.
526  * @note Only called from weapon_delete()
527  */
missle_obj_list_remove(int index)528 void missle_obj_list_remove(int index)
529 {
530 	Assert(index >= 0 && index < MAX_MISSILE_OBJS);
531 	list_remove(&Missile_obj_list, &Missile_objs[index]);
532 	Missile_objs[index].flags = 0;
533 }
534 
535 /**
536  * Called externally to generate an address from an index into
537  * the Missile_objs[] array
538  */
missile_obj_return_address(int index)539 missile_obj *missile_obj_return_address(int index)
540 {
541 	Assert(index >= 0 && index < MAX_MISSILE_OBJS);
542 	return &Missile_objs[index];
543 }
544 
545 /**
546  * Return the index of Weapon_info[].name that is *name.
547  */
weapon_info_lookup(const char * name)548 int weapon_info_lookup(const char *name)
549 {
550 	Assertion(name != nullptr, "NULL name passed to weapon_info_lookup");
551 
552 	for (auto it = Weapon_info.cbegin(); it != Weapon_info.cend(); ++it)
553 		if (!stricmp(name, it->name))
554 			return (int)std::distance(Weapon_info.cbegin(), it);
555 
556 	return -1;
557 }
558 
559 /**
560  * Return the index of Weapon_info used by this pointer.  Equivalent to the old WEAPON_INFO_INDEX macro:
561  * #define WEAPON_INFO_INDEX(wip)		(int)(wip-Weapon_info)
562  */
weapon_info_get_index(weapon_info * wip)563 int weapon_info_get_index(weapon_info *wip)
564 {
565 	Assertion(wip != nullptr, "NULL wip passed to weapon_info_get_index");
566 	return static_cast<int>(std::distance(Weapon_info.data(), wip));
567 }
568 
569 #define DEFAULT_WEAPON_SPAWN_COUNT	10
570 
571 //	Parse the weapon flags.
parse_wi_flags(weapon_info * weaponp,flagset<Weapon::Info_Flags> preset_wi_flags)572 void parse_wi_flags(weapon_info *weaponp, flagset<Weapon::Info_Flags> preset_wi_flags)
573 {
574     const char *spawn_str = NOX("Spawn");
575     const size_t spawn_str_len = strlen(spawn_str);
576 
577     //Make sure we HAVE flags :p
578     if (!optional_string("$Flags:"))
579         return;
580 
581 	// To make sure +override doesn't overwrite previously parsed values we parse the flags into a separate flagset
582     SCP_vector<SCP_string> unparsed_or_special;
583 	flagset<Weapon::Info_Flags> parsed_flags;
584     parse_string_flag_list(parsed_flags, Weapon_Info_Flags, num_weapon_info_flags, &unparsed_or_special);
585 
586     if (optional_string("+override")) {
587         // resetting the flag values if set to override the existing flags
588         weaponp->wi_flags = preset_wi_flags;
589     }
590 	// Now add the parsed flags to the weapon flags
591 	weaponp->wi_flags |= parsed_flags;
592 
593     bool set_nopierce = false;
594 
595     for (auto flag = unparsed_or_special.begin(); flag != unparsed_or_special.end(); ++flag) {
596         SCP_string flag_text = *flag;
597         //deal with spawn flag
598         if (!strnicmp(spawn_str, flag_text.c_str(), 5))
599         {
600             if (weaponp->num_spawn_weapons_defined < MAX_SPAWN_TYPES_PER_WEAPON)
601             {
602                 //We need more spawning slots
603                 //allocate in slots of 10
604                 if ((Num_spawn_types % 10) == 0) {
605                     Spawn_names = (char **)vm_realloc(Spawn_names, (Num_spawn_types + 10) * sizeof(*Spawn_names));
606                 }
607 
608                 size_t	skip_length, name_length;
609 				std::unique_ptr<char[]> temp_string(new char[flag_text.size() + 1]);
610 
611                 strcpy(temp_string.get(), flag_text.c_str());
612 
613                 weaponp->wi_flags.set(Weapon::Info_Flags::Spawn);
614                 weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_type = (short)Num_spawn_types;
615                 skip_length = spawn_str_len + strspn(&temp_string[spawn_str_len], NOX(" \t"));
616                 char *num_start = strchr(&temp_string[skip_length], ',');
617                 if (num_start == NULL) {
618                     weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
619                     name_length = 999;
620                 }
621                 else {
622                     weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = (short)atoi(num_start + 1);
623                     name_length = num_start - temp_string.get() - skip_length;
624                 }
625 
626                 weaponp->maximum_children_spawned += weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count;
627 
628                 Spawn_names[Num_spawn_types] = vm_strndup(&flag_text[skip_length], name_length);
629                 Num_spawn_types++;
630                 weaponp->num_spawn_weapons_defined++;
631             }
632             else {
633                 Warning(LOCATION, "Illegal to have more than %d spawn types for one weapon.\nIgnoring weapon %s", MAX_SPAWN_TYPES_PER_WEAPON, weaponp->name);
634             }
635         }
636         else if (!stricmp(NOX("beam"), flag_text.c_str())) {
637             weaponp->wi_flags.set(Weapon::Info_Flags::Pierce_shields);
638         }
639         else if (!stricmp(NOX("no pierce shields"), flag_text.c_str())) {
640             set_nopierce = true;
641         }
642         else if (!stricmp(NOX("beam no whack"), flag_text.c_str())) {
643             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");
644             weaponp->mass = 0.0f;
645         }
646         else if (!stricmp(NOX("interceptable"), flag_text.c_str())) {
647             weaponp->wi_flags.set(Weapon::Info_Flags::Turret_Interceptable);
648             weaponp->wi_flags.set(Weapon::Info_Flags::Fighter_Interceptable);
649         }
650         else if (!stricmp(NOX("die on lost lock"), flag_text.c_str())) {
651             if (!(weaponp->is_locked_homing())) {
652                 Warning(LOCATION, "\"die on lost lock\" may only be used for Homing Type ASPECT/JAVELIN!");
653                 weaponp->wi_flags.remove(Weapon::Info_Flags::Die_on_lost_lock);
654             }
655         }
656         else {
657             Warning(LOCATION, "Unrecognized flag in flag list for weapon %s: \"%s\"", weaponp->name, (*flag).c_str());
658         }
659     }
660 
661     //Do cleanup and sanity checks
662 
663 	if (set_nopierce)
664         weaponp->wi_flags.remove(Weapon::Info_Flags::Pierce_shields);
665 
666     if (weaponp->wi_flags[Weapon::Info_Flags::In_tech_database])
667         weaponp->wi_flags.set(Weapon::Info_Flags::Default_in_tech_database);
668 
669     if (weaponp->wi_flags[Weapon::Info_Flags::Flak]) {
670         if (weaponp->wi_flags[Weapon::Info_Flags::Swarm] || weaponp->wi_flags[Weapon::Info_Flags::Corkscrew]) {
671             weaponp->wi_flags.remove(Weapon::Info_Flags::Swarm);
672             weaponp->wi_flags.remove(Weapon::Info_Flags::Corkscrew);
673             Warning(LOCATION, "Swarm, Corkscrew, and Flak are mutually exclusive!  Removing Swarm and Corkscrew attributes from weapon %s.\n", weaponp->name);
674         }
675     }
676 
677     if (weaponp->wi_flags[Weapon::Info_Flags::Swarm] && weaponp->wi_flags[Weapon::Info_Flags::Corkscrew]) {
678         weaponp->wi_flags.remove(Weapon::Info_Flags::Corkscrew);
679         Warning(LOCATION, "Swarm and Corkscrew are mutually exclusive!  Defaulting to Swarm on weapon %s.\n", weaponp->name);
680     }
681 
682     if (weaponp->wi_flags[Weapon::Info_Flags::Local_ssm]) {
683         if (!weaponp->is_homing() || weaponp->subtype != WP_MISSILE) {
684             Warning(LOCATION, "local ssm must be guided missile: %s", weaponp->name);
685         }
686     }
687 
688     if (weaponp->wi_flags[Weapon::Info_Flags::Small_only] && weaponp->wi_flags[Weapon::Info_Flags::Huge])
689     {
690         Warning(LOCATION, "\"small only\" and \"huge\" flags are mutually exclusive.\nThey are used together in %s\nThe AI can not use this weapon on any targets", weaponp->name);
691     }
692 
693     if (!weaponp->wi_flags[Weapon::Info_Flags::Spawn] && weaponp->wi_flags[Weapon::Info_Flags::Smart_spawn])
694     {
695         Warning(LOCATION, "\"smart spawn\" flag used without \"spawn\" flag in %s\n", weaponp->name);
696     }
697 
698     if (!weaponp->wi_flags[Weapon::Info_Flags::Homing_heat] && weaponp->wi_flags[Weapon::Info_Flags::Untargeted_heat_seeker])
699     {
700         Warning(LOCATION, "Weapon '%s' has the \"untargeted heat seeker\" flag, but Homing Type is not set to \"HEAT\".", weaponp->name);
701     }
702 
703     if (!weaponp->wi_flags[Weapon::Info_Flags::Cmeasure] && weaponp->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on])
704     {
705         weaponp->wi_flags.remove(Weapon::Info_Flags::Cmeasure_aspect_home_on);
706         Warning(LOCATION, "Weapon %s has the \"pulls aspect seekers\" flag, but is not a countermeasure.\n", weaponp->name);
707     }
708 }
709 
parse_shockwave_info(shockwave_create_info * sci,const char * pre_char)710 void parse_shockwave_info(shockwave_create_info *sci, const char *pre_char)
711 {
712 	char buf[NAME_LENGTH];
713 
714 	sprintf(buf, "%sShockwave damage:", pre_char);
715 	if(optional_string(buf)) {
716 		stuff_float(&sci->damage);
717 		sci->damage_overidden = true;
718 	}
719 
720 	sprintf(buf, "%sShockwave damage type:", pre_char);
721 	if(optional_string(buf)) {
722 		stuff_string(buf, F_NAME, NAME_LENGTH);
723 		sci->damage_type_idx_sav = damage_type_add(buf);
724 		sci->damage_type_idx = sci->damage_type_idx_sav;
725 	}
726 
727 	sprintf(buf, "%sBlast Force:", pre_char);
728 	if(optional_string(buf)) {
729 		stuff_float(&sci->blast);
730 	}
731 
732 	sprintf(buf, "%sInner Radius:", pre_char);
733 	if(optional_string(buf)) {
734 		stuff_float(&sci->inner_rad);
735 	}
736 
737 	sprintf(buf, "%sOuter Radius:", pre_char);
738 	if(optional_string(buf)) {
739 		stuff_float(&sci->outer_rad);
740 	}
741 
742 	if (sci->outer_rad < sci->inner_rad) {
743 		Warning(LOCATION, "Shockwave outer radius must be greater than or equal to the inner radius!");
744 		sci->outer_rad = sci->inner_rad;
745 	}
746 
747 	sprintf(buf, "%sShockwave Speed:", pre_char);
748 	if(optional_string(buf)) {
749 		stuff_float(&sci->speed);
750 	}
751 
752 	sprintf(buf, "%sShockwave Rotation:", pre_char);
753 	if(optional_string(buf)) {
754 		float angs[3];
755 		stuff_float_list(angs, 3);
756 		for(int i = 0; i < 3; i++)
757 		{
758 			angs[i] = fl_radians(angs[i]);
759 			while(angs[i] < 0)
760 			{
761 				angs[i] += PI2;
762 			}
763 			while(angs[i] > PI2)
764 			{
765 				angs[i] -= PI2;
766 			}
767 		}
768 		sci->rot_angles.p = angs[0];
769 		sci->rot_angles.b = angs[1];
770 		sci->rot_angles.h = angs[2];
771 
772 		sci->rot_defined = true;
773 	}
774 
775 	sprintf(buf, "%sShockwave Model:", pre_char);
776 	if(optional_string(buf)) {
777 		stuff_string(sci->pof_name, F_NAME, MAX_FILENAME_LEN);
778 	}
779 
780 	sprintf(buf, "%sShockwave Name:", pre_char);
781 	if(optional_string(buf)) {
782 		stuff_string(sci->name, F_NAME, MAX_FILENAME_LEN);
783 	}
784 }
785 
786 static SCP_vector<SCP_string> Removed_weapons;
787 
788 /**
789  * Parse the information for a specific ship type.
790  * Return weapon index if successful, otherwise return -1
791  */
parse_weapon(int subtype,bool replace,const char * filename)792 int parse_weapon(int subtype, bool replace, const char *filename)
793 {
794 	char buf[NAME_LENGTH];
795 	char fname[NAME_LENGTH];
796 	weapon_info *wip = nullptr;
797 	int iff, idx;
798 	int primary_rearm_rate_specified=0;
799 	bool first_time = false;
800 	bool create_if_not_found = true;
801 	bool remove_weapon = false;
802 	// be careful to keep this up to date because modular tables can clear flags
803     flagset<Weapon::Info_Flags> preset_wi_flags;
804 
805 	required_string("$Name:");
806 	stuff_string(fname, F_NAME, NAME_LENGTH);
807 	diag_printf("Weapon name -- %s\n", fname);
808 
809 	if(optional_string("+nocreate")) {
810 		if(!replace) {
811 			Warning(LOCATION, "+nocreate flag used for weapon in non-modular table");
812 		}
813 		create_if_not_found = false;
814 	}
815 
816 	// check first if this is on the remove blacklist
817 	auto it = std::find(Removed_weapons.begin(), Removed_weapons.end(), fname);
818 	if (it != Removed_weapons.end()) {
819 		remove_weapon = true;
820 	}
821 
822 	// we might have a remove tag
823 	if (optional_string("+remove")) {
824 		if (!replace) {
825 			Warning(LOCATION, "+remove flag used for weapon in non-modular table");
826 		}
827 		if (!remove_weapon) {
828 			Removed_weapons.push_back(fname);
829 			remove_weapon = true;
830 		}
831 	}
832 
833 	//Remove @ symbol
834 	//these used to denote weapons that would
835 	//only be parsed in demo builds
836 	if ( fname[0] == '@' ) {
837 		backspace(fname);
838 	}
839 
840 	//Check if weapon exists already
841 	int w_id = weapon_info_lookup(fname);
842 
843 	// maybe remove it
844 	if (remove_weapon)
845 	{
846 		if (w_id >= 0) {
847 			mprintf(("Removing previously parsed weapon '%s'\n", fname));
848 			Weapon_info.erase(Weapon_info.begin() + w_id);
849 		}
850 
851 		if (!skip_to_start_of_string_either("$Name:", "#End")) {
852 			error_display(1, "Missing [#End] or [$Name] after weapon class %s", fname);
853 		}
854 		return -1;
855 	}
856 
857 	// an entry for this weapon exists
858 	if (w_id >= 0)
859 	{
860 		wip = &Weapon_info[w_id];
861 		if (!replace)
862 		{
863 			Warning(LOCATION, "Weapon name %s already exists in weapons.tbl.  All weapon names must be unique; the second entry has been skipped", fname);
864 			if (!skip_to_start_of_string_either("$Name:", "#End")) {
865 				error_display(1, "Missing [#End] or [$Name] after duplicate weapon %s", fname);
866 			}
867 			return -1;
868 		}
869 	}
870 	// an entry does not exist
871 	else
872 	{
873 		// Don't create weapon if it has +nocreate and is in a modular table.
874 		if (!create_if_not_found && replace)
875 		{
876 			if (!skip_to_start_of_string_either("$Name:", "#End")) {
877 				error_display(1, "Missing [#End] or [$Name] after weapon %s", fname);
878 			}
879 
880 			return -1;
881 		}
882 
883 		// Check if there are too many weapon classes
884 		if (Weapon_info.size() >= MAX_WEAPON_TYPES) {
885 			Error(LOCATION, "Too many weapon classes before '%s'; maximum is %d.\n", fname, MAX_WEAPON_TYPES);
886 		}
887 
888 		w_id = weapon_info_size();
889 		Weapon_info.push_back(weapon_info());
890 		wip = &Weapon_info.back();
891 		first_time = true;
892 
893 		strcpy_s(wip->name, fname);
894 
895 		// if this name has a hash, create a default display name
896 		if (get_pointer_to_first_hash_symbol(wip->name)) {
897 			strcpy_s(wip->display_name, wip->name);
898 			end_string_at_first_hash_symbol(wip->display_name, true);
899 			consolidate_double_characters(wip->display_name, '#');
900 			wip->wi_flags.set(Weapon::Info_Flags::Has_display_name);
901 			preset_wi_flags.set(Weapon::Info_Flags::Has_display_name);
902 		}
903 
904 		// do German translation
905 		if (Lcl_gr && !Disable_built_in_translations) {
906 			if (!wip->display_name[0]) {
907 				strcpy_s(wip->display_name, wip->name);
908 			}
909 			lcl_translate_wep_name_gr(wip->display_name);
910 		}
911 	}
912 
913 	if (optional_string("$Alt name:") || optional_string("$Display Name:"))
914 	{
915 		stuff_string(wip->display_name, F_NAME, NAME_LENGTH);
916 		wip->wi_flags.set(Weapon::Info_Flags::Has_display_name);
917 		preset_wi_flags.set(Weapon::Info_Flags::Has_display_name);
918 	}
919 
920 	//Set subtype
921 	if(optional_string("$Subtype:"))
922 	{
923 		stuff_string(fname, F_NAME, NAME_LENGTH);
924 
925 		if(!stricmp("Primary", fname)) {
926 			wip->subtype = WP_LASER;
927 		} else if(!stricmp("Secondary", fname)) {
928 			wip->subtype = WP_MISSILE;
929 		} else {
930 			Warning(LOCATION, "Unknown subtype on weapon '%s'", wip->name);
931 		}
932 	}
933 	else if(wip->subtype != WP_UNUSED && !first_time)
934 	{
935 		if(wip->subtype != subtype) {
936 			Warning(LOCATION, "Type of weapon %s entry does not agree with original entry type.", wip->name);
937 		}
938 	}
939 	else
940 	{
941 		wip->subtype = subtype;
942 	}
943 
944 	if (optional_string("+Title:")) {
945 		stuff_string(wip->title, F_NAME, WEAPON_TITLE_LEN);
946 	}
947 
948 	if (optional_string("+Description:")) {
949 		if (wip->desc != NULL) {
950 			vm_free(wip->desc);
951 			wip->desc = NULL;
952 		}
953 
954 		stuff_malloc_string(&wip->desc, F_MULTITEXT);
955 
956 		// Check if the text exceeds the limits
957 		auto current_line = wip->desc;
958 		size_t num_lines = 0;
959 		while (current_line != nullptr) {
960 			auto line_end = strchr(current_line, '\n');
961 			auto line_length = line_end - current_line;
962 			if (line_end == nullptr) {
963 				line_length = strlen(current_line);
964 			}
965 
966 			if (line_length >= WEAPON_DESC_MAX_LENGTH) {
967 				error_display(0, "Weapon description line " SIZE_T_ARG " is too long. Maximum is %d.", num_lines + 1, WEAPON_DESC_MAX_LENGTH);
968 			}
969 
970 			++num_lines;
971 			current_line = line_end == nullptr ? nullptr : line_end + 1; // Skip the \n character if it was a complete line
972 		}
973 		if (num_lines >= WEAPON_DESC_MAX_LINES) {
974 			error_display(0, "Weapon description has too many lines. Maximum is %d.", WEAPON_DESC_MAX_LINES);
975 		}
976 	}
977 
978 	if (optional_string("+Tech Title:")) {
979 		stuff_string(wip->tech_title, F_NAME, NAME_LENGTH);
980 	}
981 
982 	if (optional_string("+Tech Anim:")) {
983 		stuff_string(wip->tech_anim_filename, F_NAME, MAX_FILENAME_LEN);
984 	}
985 
986 	if (optional_string("+Tech Description:")) {
987 		if (wip->tech_desc != NULL) {
988 			vm_free(wip->tech_desc);
989 			wip->tech_desc = NULL;
990 		}
991 
992 		stuff_malloc_string(&wip->tech_desc, F_MULTITEXT);
993 	}
994 
995 	if (optional_string("$Tech Model:")) {
996 		stuff_string(wip->tech_model, F_NAME, MAX_FILENAME_LEN);
997 
998 		if (optional_string("+Closeup_pos:")) {
999 			stuff_vec3d(&wip->closeup_pos);
1000 		}
1001 
1002 		if (optional_string("+Closeup_zoom:")) {
1003 			stuff_float(&wip->closeup_zoom);
1004 		}
1005 	}
1006 
1007 	// Weapon fadein effect, used when no ani is specified or weapon_select_3d is active
1008 	wip->selection_effect = Default_weapon_select_effect; // By default, use the FS2 effect
1009 	if(optional_string("$Selection Effect:")) {
1010 		char effect[NAME_LENGTH];
1011 		stuff_string(effect, F_NAME, NAME_LENGTH);
1012 		if (!stricmp(effect, "FS2"))
1013 			wip->selection_effect = 2;
1014 		else if (!stricmp(effect, "FS1"))
1015 			wip->selection_effect = 1;
1016 		else if (!stricmp(effect, "off"))
1017 			wip->selection_effect = 0;
1018 	}
1019 
1020 	//Check for the HUD image string
1021 	if(optional_string("$HUD Image:")) {
1022 		stuff_string(wip->hud_filename, F_NAME, MAX_FILENAME_LEN);
1023 	}
1024 
1025 	//	Read the model file.  It can be a POF file or none.
1026 	//	If there is no model file (Model file: = "none") then we use our special
1027 	//	laser renderer which requires inner, middle and outer information.
1028 	if ( optional_string("$Model file:") ) {
1029 		stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);
1030 
1031 		if (VALID_FNAME(wip->pofbitmap_name))
1032 			wip->render_type = WRT_POF;
1033 		else
1034 			wip->render_type = WRT_NONE;
1035 
1036 		diag_printf("Model pof file -- %s\n", wip->pofbitmap_name );
1037 	}
1038 
1039 	// a special LOD level to use when rendering the weapon in the hud targetbox
1040 	if ( optional_string( "$POF target LOD:" ) )
1041 		stuff_int(&wip->hud_target_lod);
1042 
1043 	if(optional_string("$Detail distance:")) {
1044 		wip->num_detail_levels = (int)stuff_int_list(wip->detail_distance, MAX_MODEL_DETAIL_LEVELS, RAW_INTEGER_TYPE);
1045 	}
1046 
1047 	if ( optional_string("$External Model File:") )
1048 		stuff_string(wip->external_model_name, F_NAME, MAX_FILENAME_LEN);
1049 
1050 	if ( optional_string("$Submodel Rotation Speed:") )
1051 		stuff_float(&wip->weapon_submodel_rotate_vel);
1052 
1053 	if ( optional_string("$Submodel Rotation Acceleration:") )
1054 		stuff_float(&wip->weapon_submodel_rotate_accell);
1055 
1056 	//	No POF or AVI file specified, render as special laser type.(?)
1057 	ubyte r,g,b;
1058 
1059 	// laser bitmap itself
1060 	if ( optional_string("@Laser Bitmap:") ) {
1061 		stuff_string(fname, F_NAME, NAME_LENGTH);
1062 
1063 		if (wip->render_type == WRT_POF) {
1064 			mprintf(("WARNING:  Weapon '%s' has both LASER and POF render types!  Will only use POF type!\n", wip->name));
1065 			generic_anim_init(&wip->laser_bitmap, nullptr);
1066 		} else {
1067 			generic_anim_init(&wip->laser_bitmap, fname);
1068 			wip->render_type = WRT_LASER;
1069 		}
1070 	}
1071 
1072 	// optional laser glow
1073 	if ( optional_string("@Laser Glow:") ) {
1074 		stuff_string(fname, F_NAME, NAME_LENGTH);
1075 
1076 		if (wip->render_type != WRT_LASER) {
1077 			mprintf(("WARNING:  Laser glow specified on non-LASER type weapon (%s)!\n", wip->name));
1078 			Int3();
1079 		} else {
1080 			generic_anim_init(&wip->laser_glow_bitmap, fname);
1081 		}
1082 	}
1083 
1084 	if(optional_string("@Laser Color:"))
1085 	{
1086 		// This might be confusing at first glance. If we're a modular table (!first_time),
1087 		// AND we're providing a new color for the laser (being in this block at all),
1088 		// AND the RGB values for laser_color_1 and laser_color_2 match...
1089 		// THEN we conclude that laser_color_2 wasn't explicitly defined before, and the modder would probably prefer
1090 		// it if the laser didn't suddenly start changing colors from the new to the old over its lifespan. -MageKing17
1091 		bool reset = (!first_time && (
1092 			(wip->laser_color_1.red == wip->laser_color_2.red) &&
1093 			(wip->laser_color_1.green == wip->laser_color_2.green) &&
1094 			(wip->laser_color_1.blue == wip->laser_color_2.blue)));
1095 		stuff_ubyte(&r);
1096 		stuff_ubyte(&g);
1097 		stuff_ubyte(&b);
1098 		gr_init_color( &wip->laser_color_1, r, g, b );
1099 		if (reset) {
1100 			gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
1101 		}
1102 	}
1103 
1104 	// optional string for cycling laser colors
1105 	if(optional_string("@Laser Color2:")){
1106 		stuff_ubyte(&r);
1107 		stuff_ubyte(&g);
1108 		stuff_ubyte(&b);
1109 		gr_init_color( &wip->laser_color_2, r, g, b );
1110 	} else if (first_time) {
1111 		gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
1112 	}
1113 
1114 	if(optional_string("@Laser Length:")) {
1115 		stuff_float(&wip->laser_length);
1116 	}
1117 
1118 	if(optional_string("@Laser Head Radius:")) {
1119 		stuff_float(&wip->laser_head_radius);
1120 	}
1121 
1122 	if(optional_string("@Laser Tail Radius:")) {
1123 		stuff_float(&wip->laser_tail_radius );
1124 	}
1125 
1126 	if (optional_string("$Collision Radius Override:")) {
1127 		stuff_float(&wip->collision_radius_override);
1128 	}
1129 
1130 	if(optional_string("$Mass:")) {
1131 		stuff_float( &(wip->mass) );
1132 
1133 		// Goober5000 - hack in order to make the beam whack behavior of these three beams match all other beams
1134 		// this relies on Bobboau's beam whack hack in beam_apply_whack()
1135 		if ((!strcmp(wip->name, "SAAA") && (wip->mass == 4.0f))
1136 			|| (!strcmp(wip->name, "MjolnirBeam") && (wip->mass == 1000.0f))
1137 			|| (!strcmp(wip->name, "MjolnirBeam#home") && (wip->mass == 1000.0f)))
1138 		{
1139 			wip->mass = 100.0f;
1140 		}
1141 
1142 		diag_printf ("Weapon mass -- %7.3f\n", wip->mass);
1143 	}
1144 
1145 	if(optional_string("$Velocity:")) {
1146 		stuff_float( &(wip->max_speed) );
1147 		diag_printf ("Weapon mass -- %7.3f\n", wip->max_speed);
1148 	}
1149 
1150 	if(optional_string("$Fire Wait:")) {
1151 		stuff_float( &(wip->fire_wait) );
1152 		diag_printf ("Weapon fire wait -- %7.3f\n", wip->fire_wait);
1153 		// Min and max delay stuff for weapon fire wait randomization
1154 		if (optional_string("+Max Delay:")) {
1155 			stuff_float(&(wip->max_delay));
1156 			diag_printf("Weapon fire max delay -- %7.3f\n", wip->max_delay);
1157 		}
1158 		if (optional_string("+Min Delay:")) {
1159 			stuff_float(&(wip->min_delay));
1160 			diag_printf("Weapon fire min delay -- %7.3f\n", wip->min_delay);
1161 		}
1162 	}
1163 
1164 	if(optional_string("$Damage:")) {
1165 		stuff_float(&wip->damage);
1166 		//WMC - now that shockwave damage can be set for them individually,
1167 		//do this automagically
1168 		if(!wip->shockwave.damage_overidden) {
1169 			wip->shockwave.damage = wip->damage;
1170 		}
1171 	}
1172 
1173 	// Attenuation of non-beam primary weapon damage
1174 	if(optional_string("$Damage Time:")) {
1175 		stuff_float(&wip->damage_time);
1176 		if(optional_string("+Attenuation Damage:")){
1177 			stuff_float(&wip->atten_damage);
1178 		} else if (optional_string_either("+Min Damage:", "+Max Damage:")) {
1179 			Warning(LOCATION, "+Min Damage: and +Max Damage: in %s are deprecated, please change to +Attenuation Damage:.", wip->name);
1180 			stuff_float(&wip->atten_damage);
1181 		}
1182 	}
1183 
1184 	// angle-based damage multiplier
1185 	if (optional_string("$Angle of Incidence Damage Multiplier:")) {
1186 		if (optional_string("+Min:")) {
1187 			stuff_float(&wip->damage_incidence_min);
1188 		}
1189 		if (optional_string("+Max:")) {
1190 			stuff_float(&wip->damage_incidence_max);
1191 		}
1192 	}
1193 
1194 	if(optional_string("$Damage Type:")) {
1195 		//This is checked for validity on every armor type
1196 		//If it's invalid (or -1), then armor has no effect
1197 		stuff_string(buf, F_NAME, NAME_LENGTH);
1198 		wip->damage_type_idx_sav = damage_type_add(buf);
1199 		wip->damage_type_idx = wip->damage_type_idx_sav;
1200 	}
1201 
1202 	if(optional_string("$Arm time:")) {
1203 		float flit;
1204 		stuff_float(&flit);
1205 		wip->arm_time = fl2f(flit);
1206 	}
1207 
1208 	if(optional_string("$Arm distance:")) {
1209 		stuff_float(&wip->arm_dist);
1210 	}
1211 
1212 	if(optional_string("$Arm radius:")) {
1213 		stuff_float(&wip->arm_radius);
1214 	}
1215 
1216 	if(optional_string("$Detonation Range:")) {
1217 		stuff_float(&wip->det_range);
1218 	}
1219 
1220 	if(optional_string("$Detonation Radius:")) {
1221 		stuff_float(&wip->det_radius);
1222 	}
1223 
1224 	if(optional_string("$Flak Detonation Accuracy:")) {
1225 		stuff_float(&wip->flak_detonation_accuracy);
1226 	}
1227 
1228 	if(optional_string("$Flak Targeting Accuracy:")) {
1229 		stuff_float(&wip->flak_targeting_accuracy);
1230 	}
1231 
1232 	if(optional_string("$Untargeted Flak Range Penalty:")) {
1233 		stuff_float(&wip->untargeted_flak_range_penalty);
1234 	}
1235 
1236 	parse_shockwave_info(&wip->shockwave, "$");
1237 
1238 	//Retain compatibility
1239 	if(first_time)
1240 	{
1241 		wip->dinky_shockwave = wip->shockwave;
1242 		wip->dinky_shockwave.damage *= Dinky_shockwave_default_multiplier;
1243 	}
1244 
1245 	if(optional_string("$Dinky shockwave:"))
1246 	{
1247 		parse_shockwave_info(&wip->dinky_shockwave, "+");
1248 	}
1249 
1250 	if(optional_string("$Armor Factor:")) {
1251 		stuff_float(&wip->armor_factor);
1252 	}
1253 
1254 	if(optional_string("$Shield Factor:")) {
1255 		stuff_float(&wip->shield_factor);
1256 	}
1257 
1258 	if(optional_string("$Subsystem Factor:")) {
1259 		stuff_float(&wip->subsystem_factor);
1260 	}
1261 
1262 	if(optional_string("$Lifetime Min:")) {
1263 		stuff_float(&wip->life_min);
1264 
1265 		if(wip->life_min < 0.0f) {
1266 			wip->life_min = 0.0f;
1267 			Warning(LOCATION, "Lifetime min for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
1268 		}
1269 	}
1270 
1271 	if(optional_string("$Lifetime Max:")) {
1272 		stuff_float(&wip->life_max);
1273 
1274 		if(wip->life_max < 0.0f) {
1275 			wip->life_max = 0.0f;
1276 			Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
1277 		} else if (wip->life_max < wip->life_min) {
1278 			wip->life_max = wip->life_min + 0.1f;
1279 			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);
1280 		} else {
1281 			wip->lifetime = (wip->life_min+wip->life_max)*0.5f;
1282 		}
1283 	}
1284 
1285 	if(wip->life_min >= 0.0f && wip->life_max < 0.0f) {
1286 		wip->lifetime = wip->life_min;
1287 		wip->life_min = -1.0f;
1288 		Warning(LOCATION, "Lifetime min, but not lifetime max, specified for weapon %s. Assuming static lifetime of %.2f seconds.\n", wip->name, wip->lifetime);
1289 	}
1290 
1291 	if(optional_string("$Lifetime:")) {
1292 		if(wip->life_min >= 0.0f || wip->life_max >= 0.0f) {
1293 			Warning(LOCATION, "Lifetime min or max specified, but $Lifetime was also specified; min or max will be used.");
1294 		}
1295 		stuff_float(&wip->lifetime);
1296 		if (wip->damage_time > wip->lifetime) {
1297 			Warning(LOCATION, "Lifetime is lower than Damage Time, setting Damage Time to be one half the value of Lifetime.");
1298 			wip->damage_time = 0.5f * wip->lifetime;
1299 		}
1300 	}
1301 
1302 	if(optional_string("$Energy Consumed:")) {
1303 		stuff_float(&wip->energy_consumed);
1304 	}
1305 
1306 	// Goober5000: cargo size is checked for div-0 errors... see below (must parse flags first)
1307 	if(optional_string("$Cargo Size:"))
1308 	{
1309 		stuff_float(&wip->cargo_size);
1310 	}
1311 
1312 	bool is_homing=false;
1313 	if(optional_string("$Homing:")) {
1314 		stuff_boolean(&is_homing);
1315 	}
1316 
1317 	if (is_homing || (wip->is_homing()))
1318 	{
1319 		char	temp_type[NAME_LENGTH];
1320 
1321 		// the following five items only need to be recorded if the weapon is a homing weapon
1322 		if(optional_string("+Type:"))
1323 		{
1324 			stuff_string(temp_type, F_NAME, NAME_LENGTH);
1325 			if (!stricmp(temp_type, NOX("HEAT")))
1326 			{
1327                 wip->wi_flags.remove(Weapon::Info_Flags::Homing_aspect);
1328                 wip->wi_flags.remove(Weapon::Info_Flags::Homing_javelin);
1329 
1330                 wip->wi_flags.set(Weapon::Info_Flags::Homing_heat);
1331                 preset_wi_flags.set(Weapon::Info_Flags::Homing_heat);
1332 			}
1333 			else if (!stricmp(temp_type, NOX("ASPECT")))
1334 			{
1335                 wip->wi_flags.remove(Weapon::Info_Flags::Homing_heat);
1336                 wip->wi_flags.remove(Weapon::Info_Flags::Homing_javelin);
1337 
1338                 wip->wi_flags.set(Weapon::Info_Flags::Homing_aspect);
1339                 preset_wi_flags.set(Weapon::Info_Flags::Homing_aspect);
1340 			}
1341 			else if (!stricmp(temp_type, NOX("JAVELIN")))
1342 			{
1343                 wip->wi_flags.remove(Weapon::Info_Flags::Homing_heat);
1344                 wip->wi_flags.remove(Weapon::Info_Flags::Homing_aspect);
1345 
1346                 wip->wi_flags.set(Weapon::Info_Flags::Homing_javelin);
1347                 preset_wi_flags.set(Weapon::Info_Flags::Homing_javelin);
1348 			}
1349 
1350             wip->wi_flags.set(Weapon::Info_Flags::Turns);
1351             preset_wi_flags.set(Weapon::Info_Flags::Turns);
1352 			//If you want to add another weapon, remember you need to reset
1353 			//ALL homing flags.
1354 		}
1355 
1356 		if (wip->wi_flags[Weapon::Info_Flags::Homing_heat])
1357 		{
1358 			float	view_cone_angle;
1359 
1360 			if(optional_string("+Turn Time:")) {
1361 				stuff_float(&wip->turn_time);
1362 			}
1363 
1364 			if (optional_string("+Turning Acceleration Time:")) {
1365 				stuff_float(&wip->turn_accel_time);
1366 				if (!Framerate_independent_turning) {
1367 					static bool No_flag_turn_accel_warned = false;
1368 					if (!No_flag_turn_accel_warned) {
1369 						No_flag_turn_accel_warned = true;
1370 						Warning(LOCATION, "One or more weapons are using Turning Acceleration Time; this requires \"AI use framerate independent turning\" in game_settings.tbl ");
1371 					}
1372 					wip->turn_accel_time = 0.f;
1373 				}
1374 			}
1375 
1376 			if(optional_string("+View Cone:")) {
1377 				stuff_float(&view_cone_angle);
1378 				wip->fov = cosf(fl_radians(view_cone_angle * 0.5f));
1379 			}
1380 
1381 			if (optional_string("+Seeker Strength:"))
1382 			{
1383 				//heat default seeker strength is 3
1384 				stuff_float(&wip->seeker_strength);
1385 				if (wip->seeker_strength > 0)
1386 				{
1387 					wip->wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1388 					preset_wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1389 				}
1390 				else
1391 				{
1392 					Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nResetting value to default.", wip->name);
1393 					wip->seeker_strength = 3.0f;
1394 				}
1395 			}
1396 			else
1397 			{
1398 				if(!(wip->wi_flags[Weapon::Info_Flags::Custom_seeker_str]))
1399 					wip->seeker_strength = 3.0f;
1400 			}
1401 
1402 			if (optional_string("+Target Lead Scaler:"))
1403 			{
1404 				stuff_float(&wip->target_lead_scaler);
1405                 if (wip->target_lead_scaler == 0.0f)
1406                     wip->wi_flags.remove(Weapon::Info_Flags::Variable_lead_homing);
1407 				else {
1408                     wip->wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1409                     preset_wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1410 				}
1411 			}
1412 		}
1413 		else if ((wip->wi_flags[Weapon::Info_Flags::Homing_aspect]) || (wip->wi_flags[Weapon::Info_Flags::Homing_javelin]))
1414 		{
1415 			if(optional_string("+Turn Time:")) {
1416 				stuff_float(&wip->turn_time);
1417 			}
1418 
1419 			if (optional_string("+Turning Acceleration Time:")) {
1420 				stuff_float(&wip->turn_accel_time);
1421 				if (!Framerate_independent_turning) {
1422 					static bool No_flag_turn_accel_warned = false;
1423 					if (!No_flag_turn_accel_warned) {
1424 						No_flag_turn_accel_warned = true;
1425 						Warning(LOCATION, "One or more weapons are using Turning Acceleration Time; this requires \"AI use framerate independent turning\" in game_settings.tbl ");
1426 					}
1427 					wip->turn_accel_time = 0.f;
1428 				}
1429 			}
1430 
1431 			if(optional_string("+View Cone:")) {
1432 				float	view_cone_angle;
1433 				stuff_float(&view_cone_angle);
1434 				wip->fov = (float)cos(fl_radians(view_cone_angle * 0.5f));
1435 			}
1436 
1437 			if(optional_string("+Min Lock Time:")) {			// minimum time (in seconds) to achieve lock
1438 				stuff_float(&wip->min_lock_time);
1439 			}
1440 
1441 			if(optional_string("+Lock Pixels/Sec:")) {		// pixels/sec moved while locking
1442 				stuff_int(&wip->lock_pixels_per_sec);
1443 			}
1444 
1445 			if(optional_string("+Catch-up Pixels/Sec:")) {	// pixels/sec moved while catching-up for a lock
1446 				stuff_int(&wip->catchup_pixels_per_sec);
1447 			}
1448 
1449 			if(optional_string("+Catch-up Penalty:")) {
1450 				// number of extra pixels to move while locking as a penalty for catching up for a lock
1451 				stuff_int(&wip->catchup_pixel_penalty);
1452 			}
1453 
1454 			if (optional_string("+Seeker Strength:"))
1455 			{
1456 				//aspect default seeker strength is 2
1457 				stuff_float(&wip->seeker_strength);
1458 				if (wip->seeker_strength > 0)
1459 				{
1460 					wip->wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1461 					preset_wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1462 				}
1463 				else
1464 				{
1465 					Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
1466 					wip->seeker_strength = 2.0f;
1467 				}
1468 			}
1469 			else
1470 			{
1471 				if(!(wip->wi_flags[Weapon::Info_Flags::Custom_seeker_str]))
1472 					wip->seeker_strength = 2.0f;
1473 			}
1474 
1475 			if (optional_string("+Target Lead Scaler:"))
1476 			{
1477 				stuff_float(&wip->target_lead_scaler);
1478 				if (wip->target_lead_scaler == 1.0f)
1479 					wip->wi_flags.remove(Weapon::Info_Flags::Variable_lead_homing);
1480 				else {
1481 					wip->wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1482 					preset_wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1483 				}
1484 			}
1485 
1486 			if (optional_string("+Target Lock Restriction:")) {
1487 				if (optional_string("current target, any subsystem")) {
1488 					wip->target_restrict = LR_CURRENT_TARGET_SUBSYS;
1489 
1490 				}
1491 				else if (optional_string("any target")) {
1492 					wip->target_restrict = LR_ANY_TARGETS;
1493 
1494 				}
1495 				else if (optional_string("current target only")) {
1496 					wip->target_restrict = LR_CURRENT_TARGET;
1497 
1498 				}
1499 				else {
1500 					wip->target_restrict = LR_CURRENT_TARGET;
1501 
1502 				}
1503 
1504 			}
1505 
1506 				if (optional_string("+Independent Seekers:")) {
1507 				stuff_boolean(&wip->multi_lock);
1508 			}
1509 
1510 			if (optional_string("+Trigger Hold:")) {
1511 				stuff_boolean(&wip->trigger_lock);
1512 			}
1513 
1514 			if (optional_string("+Reset On Launch:")) {
1515 				stuff_boolean(&wip->launch_reset_locks);
1516 			}
1517 
1518 			if (optional_string("+Max Seekers Per Target:")) {
1519 				stuff_int(&wip->max_seekers_per_target);
1520 			}
1521 
1522 			if (optional_string("+Max Active Seekers:")) {
1523 				stuff_int(&wip->max_seeking);
1524 			}
1525 
1526 			if (wip->is_locked_homing()) {
1527 				// locked homing missiles have a much longer lifespan than the AI think they do
1528 				wip->max_lifetime = wip->lifetime * LOCKED_HOMING_EXTENDED_LIFE_FACTOR;
1529 			}
1530 		}
1531 		else
1532 		{
1533 			Error(LOCATION, "Illegal homing type = %s.\nMust be HEAT, ASPECT or JAVELIN.\n", temp_type);
1534 		}
1535 
1536 		// handle homing restrictions
1537 		if (optional_string("+Ship Types:")) {
1538 			SCP_vector<SCP_string> temp_names;
1539 			stuff_string_list(temp_names);
1540 
1541 			for (auto& name : temp_names)
1542 				wip->ship_restrict_strings.emplace_back(LockRestrictionType::TYPE, name);
1543 		}
1544 
1545 		if (optional_string("+Ship Classes:")) {
1546 			SCP_vector<SCP_string> temp_names;
1547 			stuff_string_list(temp_names);
1548 
1549 			for (auto& name : temp_names)
1550 				wip->ship_restrict_strings.emplace_back(LockRestrictionType::CLASS, name);
1551 		}
1552 
1553 		if (optional_string("+Species:")) {
1554 			SCP_vector<SCP_string> temp_names;
1555 			stuff_string_list(temp_names);
1556 
1557 			for (auto& name : temp_names)
1558 				wip->ship_restrict_strings.emplace_back(LockRestrictionType::SPECIES, name);
1559 		}
1560 
1561 		if (optional_string("+IFFs:")) {
1562 			SCP_vector<SCP_string> temp_names;
1563 			stuff_string_list(temp_names);
1564 
1565 			for (auto& name : temp_names)
1566 				wip->ship_restrict_strings.emplace_back(LockRestrictionType::IFF, name);
1567 		}
1568 
1569 		if (subtype == WP_LASER && !wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
1570 			Warning(LOCATION, "Homing primary %s must be a heat seeker. Setting to heat", wip->name);
1571 			wip->wi_flags.remove(Weapon::Info_Flags::Homing_aspect);
1572 			wip->wi_flags.remove(Weapon::Info_Flags::Homing_javelin);
1573 			wip->wi_flags.set(Weapon::Info_Flags::Homing_heat);
1574 		}
1575 	}
1576 
1577 	if (optional_string("$Homing Auto-Target Method:"))
1578 	{
1579 		char	temp[NAME_LENGTH];
1580 		stuff_string(temp, F_NAME, NAME_LENGTH);
1581 		if (!stricmp(temp, NOX("CLOSEST")))
1582 			wip->auto_target_method = HomingAcquisitionType::CLOSEST;
1583 		else if (!stricmp(temp, NOX("RANDOM")))
1584 			wip->auto_target_method = HomingAcquisitionType::RANDOM;
1585 		else
1586 			Warning(LOCATION, "Homing weapon %s has an unrecognized Homing Auto-Target Method %s", wip->name, temp);
1587 	}
1588 
1589 	// swarm missiles
1590 	int s_count;
1591 
1592 	if(optional_string("$Swarm:"))
1593 	{
1594 		wip->swarm_count = SWARM_DEFAULT_NUM_MISSILES_FIRED;
1595 		stuff_int(&s_count);
1596 		wip->swarm_count = (short)s_count;
1597 
1598 		// flag as being a swarm weapon
1599 		wip->wi_flags.set(Weapon::Info_Flags::Swarm);
1600 		preset_wi_flags.set(Weapon::Info_Flags::Swarm);
1601 	}
1602 
1603 	// *Swarm wait token    -Et1
1604 	if((wip->wi_flags[Weapon::Info_Flags::Swarm]) && optional_string( "+SwarmWait:" ))
1605 	{
1606 		float SwarmWait;
1607 		stuff_float( &SwarmWait );
1608 		if( SwarmWait > 0.0f && SwarmWait * wip->swarm_count < wip->fire_wait )
1609 		{
1610 			wip->SwarmWait = int( SwarmWait * 1000 );
1611 		}
1612 	}
1613 
1614 	if(optional_string("$Acceleration Time:")) {
1615 		stuff_float(&wip->acceleration_time);
1616 	}
1617 
1618 	if(optional_string("$Velocity Inherit:")) {
1619 		stuff_float(&wip->vel_inherit_amount);
1620 		wip->vel_inherit_amount /= 100.0f; // % -> 0..1
1621 	}
1622 
1623 	if(optional_string("$Free Flight Time:")) {
1624 		stuff_float(&(wip->free_flight_time));
1625 	} else if(first_time && is_homing) {
1626 		if (subtype == WP_LASER) {
1627 			wip->free_flight_time = HOMING_DEFAULT_PRIMARY_FREE_FLIGHT_TIME;
1628 		}
1629 		else {
1630 			wip->free_flight_time = HOMING_DEFAULT_FREE_FLIGHT_TIME;
1631 		}
1632 	}
1633 
1634 	if (optional_string("$Free Flight Speed:")) {
1635 		error_display(0, "$Free Flight Speed: used for weapon '%s' is depreciated and ignored, use $Free Flight Speed Factor: instead. ", wip->name);
1636 		float temp;
1637 		stuff_float(&temp);
1638 	}
1639 
1640 	if(optional_string("$Free Flight Speed Factor:")) {
1641 		stuff_float(&(wip->free_flight_speed_factor));
1642 		if (wip->free_flight_speed_factor < 0.01f) {
1643 			error_display(0, "Free Flight Speed Factor value is too low for weapon '%s'. Resetting to default (0.25 times maximum)", wip->name);
1644 			wip->free_flight_speed_factor = HOMING_DEFAULT_FREE_FLIGHT_FACTOR;
1645 		}
1646 	}
1647 	else if (first_time && is_homing) {
1648 		wip->free_flight_speed_factor = HOMING_DEFAULT_FREE_FLIGHT_FACTOR;
1649 	}
1650 	//Optional one-shot sound to play at the beginning of firing
1651 	parse_game_sound("$PreLaunchSnd:", &wip->pre_launch_snd);
1652 
1653 	//Optional delay for Pre-Launch sound
1654 	if(optional_string("+PreLaunchSnd Min Interval:"))
1655 	{
1656 		stuff_int(&wip->pre_launch_snd_min_interval);
1657 	}
1658 
1659 	//Launch sound
1660 	parse_game_sound("$LaunchSnd:", &wip->launch_snd);
1661 
1662 	//Impact sound
1663 	parse_game_sound("$ImpactSnd:", &wip->impact_snd);
1664 
1665 	//Disarmed impact sound
1666 	parse_game_sound("$Disarmed ImpactSnd:", &wip->disarmed_impact_snd);
1667 
1668 	parse_game_sound("$FlyBySnd:", &wip->flyby_snd);
1669 
1670 	parse_game_sound("$AmbientSnd:", &wip->ambient_snd);
1671 
1672 	parse_game_sound("$TrackingSnd:", &wip->hud_tracking_snd);
1673 
1674 	parse_game_sound("$LockedSnd:", &wip->hud_locked_snd);
1675 
1676 	parse_game_sound("$InFlightSnd:", &wip->hud_in_flight_snd);
1677 
1678 	if (optional_string("+Inflight sound type:"))
1679 	{
1680 		SCP_string type;
1681 
1682 		stuff_string(type, F_NAME);
1683 
1684 		if (!stricmp(type.c_str(), "TARGETED"))
1685 		{
1686 			wip->in_flight_play_type = TARGETED;
1687 		}
1688 		else if (!stricmp(type.c_str(), "UNTARGETED"))
1689 		{
1690 			wip->in_flight_play_type = UNTARGETED;
1691 		}
1692 		else if (!stricmp(type.c_str(), "ALWAYS"))
1693 		{
1694 			wip->in_flight_play_type = ALWAYS;
1695 		}
1696 		else
1697 		{
1698 			Warning(LOCATION, "Unknown in-flight sound type \"%s\"!", type.c_str());
1699 			wip->in_flight_play_type = ALWAYS;
1700 		}
1701 	}
1702 
1703 	if(optional_string("$Model:"))
1704 	{
1705 		wip->render_type = WRT_POF;
1706 		stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);
1707 	}
1708 
1709 	// handle rearm rate - modified by Goober5000
1710 	primary_rearm_rate_specified = 0;
1711 	float rearm_rate;
1712 	// Anticipate rearm rate for ballistic primaries
1713 	if (optional_string("$Rearm Rate:"))
1714 	{
1715 		if (subtype != WP_MISSILE) {
1716 			primary_rearm_rate_specified = 1;
1717 		}
1718 
1719 		stuff_float( &rearm_rate );
1720 		if (rearm_rate > 0.0f)
1721 		{
1722 			wip->rearm_rate = 1.0f/rearm_rate;
1723 		}
1724 		else
1725 		{
1726 			Warning(LOCATION, "Rearm wait of less than 0 on weapon %s; setting to 1", wip->name);
1727 		}
1728 	}
1729 
1730 		// set the number of missles or ballistic ammo reloaded per batch
1731 	int reloaded_each_batch;
1732 
1733 	if (optional_string("$Rearm Ammo Increment:")) {
1734 		stuff_int(&reloaded_each_batch);
1735 
1736 		if (reloaded_each_batch > 0) {
1737 			wip->reloaded_per_batch = reloaded_each_batch;
1738 		} else if (wip->reloaded_per_batch != -1) {
1739 			Warning(LOCATION, "Rearm increment amount of 0 or less in weapon %s, keeping at previously defined value.",
1740 			        wip->name);
1741 		} else {
1742 			Warning(LOCATION, "Rearm increment amount of 0 or less in weapon %s, setting to the default, 4 for missiles or 100 for ballistic primaries.", wip->name);
1743 		}
1744 	}
1745 	// This setting requires a default if not specified, otherwise, rearming will break. - Cyborg17
1746 	if (wip->reloaded_per_batch == -1) {
1747 		if (subtype == WP_MISSILE) {
1748 			wip->reloaded_per_batch = REARM_NUM_MISSILES_PER_BATCH;
1749 		} else {
1750 			wip->reloaded_per_batch = REARM_NUM_BALLISTIC_PRIMARIES_PER_BATCH;
1751 		}
1752 	}
1753 
1754 	if (optional_string("+Weapon Range:")) {
1755 		stuff_float(&wip->weapon_range);
1756 	}
1757 
1758 	if( optional_string( "+Weapon Min Range:" ) )
1759 	{
1760 		float min_range;
1761 		stuff_float( &min_range);
1762 
1763 		if(min_range > 0.0f && min_range < MIN( wip->max_speed * wip->lifetime, wip->weapon_range ) )
1764 		{
1765 			wip->weapon_min_range = min_range;
1766 		}
1767 		else
1768 		{
1769 			Warning(LOCATION, "Invalid minimum range on weapon %s; setting to 0", wip->name);
1770 		}
1771 	}
1772 
1773 	if (optional_string("+Weapon Optimum Range:")) {
1774 		stuff_float(&wip->optimum_range);
1775 		if (wip->optimum_range < wip->weapon_min_range || wip->optimum_range > MIN(wip->max_speed * wip->lifetime, wip->weapon_range)) {
1776 			Warning(LOCATION, "Optimum range on weapon %s must be within its min range and max range", wip->name);
1777 			wip->optimum_range = 0.0f;
1778 		}
1779 	}
1780 
1781 	if (optional_string("$Pierce Objects:")) {
1782 		stuff_boolean(&wip->pierce_objects);
1783 
1784 		if (optional_string("+Spawn Children On Pierce:")) {
1785 			stuff_boolean(&wip->spawn_children_on_pierce);
1786 		}
1787 	}
1788 
1789 	parse_wi_flags(wip, preset_wi_flags);
1790 
1791 	// preset_wi_flags does not need to be maintained after the above function is called
1792 
1793 	// be friendly; make sure ballistic flags are synchronized - Goober5000
1794 	// primary
1795 	if (subtype == WP_LASER)
1796 	{
1797 		// ballistic
1798 		if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
1799 		{
1800 			// rearm rate not specified
1801 			if (!primary_rearm_rate_specified && first_time)
1802 			{
1803 				Warning(LOCATION, "$Rearm Rate for ballistic primary %s not specified.  Defaulting to 100...\n", wip->name);
1804 				wip->rearm_rate = 100.0f;
1805 			}
1806 		}
1807 		// not ballistic
1808 		else
1809 		{
1810 			// rearm rate specified
1811 			if (primary_rearm_rate_specified)
1812 			{
1813 				Warning(LOCATION, "$Rearm Rate specified for non-ballistic primary %s\n", wip->name);
1814 			}
1815 		}
1816 
1817 	}
1818 	// secondary
1819 	else
1820 	{
1821 		// ballistic
1822 		if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
1823 		{
1824 			Warning(LOCATION, "Secondary weapon %s can't be ballistic.  Removing this flag...\n", wip->name);
1825 			wip->wi_flags.remove(Weapon::Info_Flags::Ballistic);
1826 		}
1827 	}
1828 
1829 	// also make sure EMP is friendly - Goober5000
1830 	if (wip->wi_flags[Weapon::Info_Flags::Emp])
1831 	{
1832 		if (!wip->shockwave.outer_rad)
1833 		{
1834 			Warning(LOCATION, "Outer blast radius of weapon %s is zero - EMP will not work.\nAdd $Outer Radius to weapon table entry.\n", wip->name);
1835 		}
1836 	}
1837 
1838 	// also make sure secondaries and ballistic primaries do not have 0 cargo size
1839 	if (subtype == WP_MISSILE || wip->wi_flags[Weapon::Info_Flags::Ballistic])
1840 	{
1841 		if (wip->cargo_size == 0.0f)
1842 		{
1843 			Warning(LOCATION, "Cargo size of weapon %s cannot be 0.  Setting to 1.\n", wip->name);
1844 			wip->cargo_size = 1.0f;
1845 		}
1846 	}
1847 
1848 	if ( optional_string("$Trail:") ) {
1849 		trail_info *ti = &wip->tr_info;
1850         wip->wi_flags.set(Weapon::Info_Flags::Trail);		// missile leaves a trail
1851 
1852 		if ( optional_string("+Start Width:") )
1853 			stuff_float(&ti->w_start);
1854 
1855 		if ( optional_string("+End Width:") )
1856 			stuff_float(&ti->w_end);
1857 
1858 		if ( optional_string("+Start Alpha:") )
1859 			stuff_float(&ti->a_start);
1860 
1861 		if ( optional_string("+End Alpha:") )
1862 			stuff_float(&ti->a_end);
1863 
1864 		if (optional_string("+Alpha Decay Exponent:")) {
1865 			stuff_float(&ti->a_decay_exponent);
1866 			if (ti->a_decay_exponent < 0.0f) {
1867 				Warning(LOCATION, "Trail Alpha Decay Exponent of weapon %s cannot be negative. Reseting to 1.\n", wip->name);
1868 				ti->a_decay_exponent = 1.0f;
1869 			}
1870 		}
1871 
1872 		if ( optional_string("+Max Life:") ) {
1873 			stuff_float(&ti->max_life);
1874 			ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
1875 		}
1876 
1877 		if (optional_string("+Spread:"))
1878 			stuff_float(&ti->spread);
1879 
1880 		if ( required_string("+Bitmap:") ) {
1881 			stuff_string(fname, F_NAME, NAME_LENGTH);
1882 			generic_bitmap_init(&ti->texture, fname);
1883 		}
1884 
1885 		if (optional_string("+Bitmap Stretch:")) {
1886 			stuff_float(&ti->texture_stretch);
1887 			if (ti->texture_stretch == 0.0f) {
1888 				Warning(LOCATION, "Trail bitmap stretch of weapon %s cannot be 0.  Setting to 1.\n", wip->name);
1889 				ti->texture_stretch = 1.0f;
1890 			}
1891 		}
1892 
1893 		if ( optional_string("+Faded Out Sections:") ) {
1894 			stuff_int(&ti->n_fade_out_sections);
1895 		}
1896 	}
1897 
1898 	// read in filename for icon that is used in weapons selection
1899 	if ( optional_string("$Icon:") ) {
1900 		stuff_string(wip->icon_filename, F_NAME, MAX_FILENAME_LEN);
1901 	}
1902 
1903 	// read in filename for animation that is used in weapons selection
1904 	if ( optional_string("$Anim:") ) {
1905 		stuff_string(wip->anim_filename, F_NAME, MAX_FILENAME_LEN);
1906 	}
1907 
1908 	if (optional_string("$Impact Effect:")) {
1909 		wip->impact_weapon_expl_effect = particle::util::parseEffect(wip->name);
1910 	} else {
1911 		// This is for compatibility with old tables
1912 		// Do not add features here!
1913 
1914 		float size = 1.0f;
1915 		int bitmapIndex = -1;
1916 
1917 		// $Impact Effect was not found, parse the old values
1918 		if (optional_string("$Impact Explosion:")) {
1919 			stuff_string(fname, F_NAME, NAME_LENGTH);
1920 
1921 			if (VALID_FNAME(fname))
1922 			{
1923 				bitmapIndex = bm_load_animation(fname);
1924 
1925 				if (bitmapIndex < 0)
1926 				{
1927 					Warning(LOCATION, "Couldn't load effect '%s' for weapon '%s'.", fname, wip->name);
1928 				}
1929 			}
1930 		}
1931 
1932 		if (optional_string("$Impact Explosion Radius:"))
1933 			stuff_float(&size);
1934 
1935 		if (bitmapIndex >= 0 && size > 0.0f)
1936 		{
1937 			using namespace particle;
1938 
1939 			// Only beams do this randomization
1940 			if (subtype == WP_BEAM)
1941 			{
1942 				// The original formula is (1.2f + 0.007f * (float)(rand() % 100)) which generates values within [1.2, 1.9)
1943 				auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size * 1.2f, size * 1.9f);
1944 				wip->impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
1945 			}
1946 			else
1947 			{
1948 				auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size, size);
1949 				wip->impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
1950 			}
1951 		}
1952 	}
1953 
1954 	if ( optional_string("$Shield Impact Explosion Radius:") ) {
1955 		stuff_float(&wip->shield_impact_explosion_radius);
1956 	} else if (first_time) {
1957 		using namespace particle;
1958 
1959 		// Default value
1960 		wip->shield_impact_explosion_radius = 1.0f;
1961 		if (wip->impact_weapon_expl_effect.isValid()) {
1962 			auto singleEffect = dynamic_cast<effects::SingleParticleEffect*>(ParticleManager::get()->getEffect(wip->impact_weapon_expl_effect));
1963 
1964 			if (singleEffect)
1965 			{
1966 				// Initialize with value of the previously created single particle effect
1967 				wip->shield_impact_explosion_radius = singleEffect->getProperties().m_radius.next();
1968 			}
1969 		}
1970 	}
1971 
1972 	if (optional_string("$Dinky Impact Effect:")) {
1973 		wip->dinky_impact_weapon_expl_effect = particle::util::parseEffect(wip->name);
1974 	} else {
1975 		// This is for compatibility with old tables
1976 		// Do not add features here!
1977 
1978 		if (optional_string("$Dinky Impact Explosion:")) {
1979 			stuff_string(fname, F_NAME, NAME_LENGTH);
1980 
1981 			int bitmapID = -1;
1982 			float size = 1.0f;
1983 
1984 			if (VALID_FNAME(fname))
1985 			{
1986 				bitmapID = bm_load_animation(fname);
1987 
1988 				if (bitmapID < 0)
1989 				{
1990 					Warning(LOCATION, "Couldn't load effect '%s' for weapon '%s'.", fname, wip->name);
1991 				}
1992 			}
1993 
1994 			if (optional_string("$Dinky Impact Explosion Radius:"))
1995 			{
1996 				stuff_float(&size);
1997 			}
1998 
1999 			if (bitmapID >= 0 && size > 0.0f)
2000 			{
2001 				using namespace particle;
2002 
2003 				// Only beams do this randomization
2004 				if (subtype == WP_BEAM)
2005 				{
2006 					// The original formula is (1.2f + 0.007f * (float)(rand() % 100)) which generates values within [1.2, 1.9)
2007 					auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapID, size * 1.2f, size * 1.9f);
2008 					wip->dinky_impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
2009 				}
2010 				else
2011 				{
2012 					auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapID, size, size);
2013 					wip->dinky_impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
2014 				}
2015 			}
2016 		}
2017 		else if (first_time) {
2018 			wip->dinky_impact_weapon_expl_effect = wip->impact_weapon_expl_effect;
2019 		}
2020 
2021 		// Slight variation from original, as the original size is not known anymore the whole effect is copied
2022 		// If the radius was stull specified it needs to be parsed to not mess up something else
2023 		if (optional_string("$Dinky Impact Explosion Radius:"))
2024 		{
2025 			float temp;
2026 			stuff_float(&temp);
2027 		}
2028 	}
2029 
2030 	if (optional_string("$Piercing Impact Effect:")) {
2031 		wip->piercing_impact_effect = particle::util::parseEffect(wip->name);
2032 		if (first_time)
2033 		{
2034 			// The secondary effect is only needed if the old effect got parsed
2035 			wip->piercing_impact_secondary_effect = particle::ParticleEffectHandle::invalid();
2036 		}
2037 	}
2038 	else
2039 	{
2040 		// This is for compatibility with old tables
2041 		// Do not add features here!
2042 
2043 		using namespace particle;
2044 		using namespace effects;
2045 
2046 		int effectIndex = -1;
2047 		float radius = 0.0f;
2048 		int count = 0;
2049 		float life = 0.0f;
2050 		float velocity = 0.0f;
2051 		float back_velocity = 0.0f;
2052 		float variance = 0.0f;
2053 
2054 		particle_emitter emitter;
2055 		memset(&emitter, 0, sizeof(emitter));
2056 
2057 		if (optional_string("$Piercing Impact Explosion:"))
2058 		{
2059 			stuff_string(fname, F_NAME, NAME_LENGTH);
2060 
2061 			effectIndex = bm_load_animation(fname);
2062 
2063 			if (effectIndex < 0)
2064 			{
2065 				Warning(LOCATION, "Failed to load effect '%s' for weapon %s!", fname, wip->name);
2066 			}
2067 		}
2068 
2069 		if (optional_string("$Piercing Impact Radius:"))
2070 			stuff_float(&radius);
2071 
2072 		if (optional_string("$Piercing Impact Velocity:"))
2073 			stuff_float(&velocity);
2074 
2075 		if (optional_string("$Piercing Impact Splash Velocity:"))
2076 			stuff_float(&back_velocity);
2077 
2078 		if (optional_string("$Piercing Impact Variance:"))
2079 			stuff_float(&variance);
2080 
2081 		if (optional_string("$Piercing Impact Life:"))
2082 			stuff_float(&life);
2083 
2084 		if (optional_string("$Piercing Impact Particles:"))
2085 			stuff_int(&count);
2086 
2087 		if (effectIndex >= 0 && radius != 0.0f)
2088 		{
2089 			emitter.max_vel = 2.0f * velocity;
2090 			emitter.min_vel = 0.5f * velocity;
2091 			emitter.max_life = 2.0f * life;
2092 			emitter.min_life = 0.25f * life;
2093 			emitter.num_high = 2 * count;
2094 			emitter.num_low = count / 2;
2095 			emitter.normal_variance = variance;
2096 			emitter.max_rad = 2.0f * radius;
2097 			emitter.min_rad = 0.5f * radius;
2098 			emitter.vel = vmd_zero_vector;
2099 
2100 			auto emitterEffect = new ParticleEmitterEffect();
2101 			emitterEffect->setValues(emitter, effectIndex, 10.0f);
2102 			wip->piercing_impact_effect = ParticleManager::get()->addEffect(emitterEffect);
2103 
2104 			if (back_velocity != 0.0f)
2105 			{
2106 				emitter.max_vel = 2.0f * back_velocity;
2107 				emitter.min_vel = 0.5f * back_velocity;
2108 				emitter.num_high /= 2;
2109 				emitter.num_low /= 2;
2110 
2111 				auto secondaryEffect = new ParticleEmitterEffect();
2112 				secondaryEffect->setValues(emitter, effectIndex, 10.0f);
2113 				wip->piercing_impact_secondary_effect = ParticleManager::get()->addEffect(secondaryEffect);
2114 			}
2115 		}
2116 	}
2117 
2118 	if (optional_string("$Inflight Effect:")) {
2119 		auto effetIdx = particle::util::parseEffect(wip->name);
2120 		wip->state_effects.insert(std::make_pair(WeaponState::NORMAL, effetIdx));
2121 	}
2122 
2123 	if (wip->subtype == WP_MISSILE)
2124 	{
2125 		if (optional_string("$Freeflight Effect:")) {
2126 			auto effetIdx = particle::util::parseEffect(wip->name);
2127 			wip->state_effects.insert(std::make_pair(WeaponState::FREEFLIGHT, effetIdx));
2128 		}
2129 		if (optional_string("$Ignition Effect:")) {
2130 			auto effetIdx = particle::util::parseEffect(wip->name);
2131 			wip->state_effects.insert(std::make_pair(WeaponState::IGNITION, effetIdx));
2132 		}
2133 		if (optional_string("$Homed Flight Effect:")) {
2134 			auto effetIdx = particle::util::parseEffect(wip->name);
2135 			wip->state_effects.insert(std::make_pair(WeaponState::HOMED_FLIGHT, effetIdx));
2136 		}
2137 		if (optional_string("$Unhomed Flight Effect:")) {
2138 			auto effetIdx = particle::util::parseEffect(wip->name);
2139 			wip->state_effects.insert(std::make_pair(WeaponState::UNHOMED_FLIGHT, effetIdx));
2140 		}
2141 	}
2142 
2143 	// muzzle flash
2144 	if ( optional_string("$Muzzleflash:") ) {
2145 		stuff_string(fname, F_NAME, NAME_LENGTH);
2146 
2147 		// look it up
2148 		wip->muzzle_flash = mflash_lookup(fname);
2149 	}
2150 
2151 	// EMP optional stuff (if WIF_EMP is not set, none of this matters, anyway)
2152 	if( optional_string("$EMP Intensity:") ){
2153 		stuff_float(&wip->emp_intensity);
2154 	}
2155 
2156 	if( optional_string("$EMP Time:") ){
2157 		stuff_float(&wip->emp_time);
2158 	}
2159 
2160 	// 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.
2161 	if (optional_string("$Recoil Modifier:")){
2162 		if (!(wip->wi_flags[Weapon::Info_Flags::Apply_Recoil])){
2163 			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);
2164             wip->wi_flags.set(Weapon::Info_Flags::Apply_Recoil);
2165 		}
2166 		stuff_float(&wip->recoil_modifier);
2167 	}
2168 
2169 	// Energy suck optional stuff (if WIF_ENERGY_SUCK is not set, none of this matters anyway)
2170 	if( optional_string("$Leech Weapon:") ){
2171 		stuff_float(&wip->weapon_reduce);
2172 	}
2173 
2174 	if( optional_string("$Leech Afterburner:") ){
2175 		stuff_float(&wip->afterburner_reduce);
2176 	}
2177 
2178 	if (optional_string("$Corkscrew:"))
2179 	{
2180 		if(optional_string("+Num Fired:")) {
2181 			stuff_int(&wip->cs_num_fired);
2182 		}
2183 
2184 		if(optional_string("+Radius:")) {
2185 			stuff_float(&wip->cs_radius);
2186 		}
2187 
2188 		if(optional_string("+Fire Delay:")) {
2189 			stuff_int(&wip->cs_delay);
2190 		}
2191 
2192 		if(optional_string("+Counter rotate:")) {
2193 			stuff_boolean(&wip->cs_crotate);
2194 		}
2195 
2196 		if(optional_string("+Twist:")) {
2197 			stuff_float(&wip->cs_twist);
2198 		}
2199 	}
2200 
2201 	//electronics tag optional stuff
2202 	//Note that I made all these optional in the interest of modular tables.
2203 	//TODO: Possibly add a warning on first_time define?
2204 	if (optional_string("$Electronics:"))
2205 	{
2206 		if(optional_string("+New Style:")) {
2207 			wip->elec_use_new_style=1;
2208 		}
2209 		else if(optional_string("+Old Style:")) {
2210 			wip->elec_use_new_style=0;
2211 		}
2212 
2213 		if(optional_string("+Area Of Effect")) {
2214             wip->wi_flags.set(Weapon::Info_Flags::Aoe_Electronics);
2215 		}
2216 
2217 		//New only -WMC
2218 		if(optional_string("+Intensity:")) {
2219 			float temp;
2220 			stuff_float(&temp);
2221 			Warning(LOCATION, "+Intensity is deprecated");
2222 		}
2223 
2224 		if(optional_string("+Lifetime:")) {
2225 			stuff_int(&wip->elec_time);
2226 		}
2227 
2228 		//New only -WMC
2229 		if(optional_string("+Engine Multiplier:")) {
2230 			stuff_float(&wip->elec_eng_mult);
2231 			if(!wip->elec_use_new_style)Warning(LOCATION, "+Engine multiplier may only be used with new style electronics");
2232 		}
2233 
2234 		//New only -WMC
2235 		if(optional_string("+Weapon Multiplier:")) {
2236 			stuff_float(&wip->elec_weap_mult);
2237 			if(!wip->elec_use_new_style)Warning(LOCATION, "+Weapon multiplier may only be used with new style electronics");
2238 		}
2239 
2240 		//New only -WMC
2241 		if(optional_string("+Beam Turret Multiplier:")) {
2242 			stuff_float(&wip->elec_beam_mult);
2243 			if(!wip->elec_use_new_style)Warning(LOCATION, "+Beam turret multiplier may only be used with new style electronics");
2244 		}
2245 
2246 		//New only -WMC
2247 		if(optional_string("+Sensors Multiplier:")) {
2248 			stuff_float(&wip->elec_sensors_mult);
2249 			if(!wip->elec_use_new_style)Warning(LOCATION, "+Sensors multiplier may only be used with new style electronics");
2250 		}
2251 
2252 		if(optional_string("+Randomness Time:")) {
2253 			stuff_int(&wip->elec_randomness);
2254 		}
2255 	}
2256 
2257 	//read in the spawn angle info
2258 	//if the weapon isn't a spawn weapon, then this is not going to be used.
2259     int spawn_weap = 0;
2260     float dum_float;
2261 
2262 	while (optional_string("$Spawn Angle:"))
2263     {
2264         stuff_float(&dum_float);
2265 
2266         if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2267         {
2268             wip->spawn_info[spawn_weap].spawn_angle = dum_float;
2269 			spawn_weap++;
2270         }
2271 	}
2272 
2273 	spawn_weap = 0;
2274 
2275 	while (optional_string("$Spawn Minimum Angle:"))
2276 	{
2277 		stuff_float(&dum_float);
2278 
2279 		if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2280 		{
2281 			wip->spawn_info[spawn_weap].spawn_min_angle = dum_float;
2282 			spawn_weap++;
2283 		}
2284 	}
2285 
2286 	spawn_weap = 0;
2287 
2288 	while (optional_string("$Spawn Interval:"))
2289 	{
2290 		float temp_interval;
2291 		stuff_float(&temp_interval);
2292 
2293 		if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2294 		{
2295 			// Get delay out of the way before handling interval so we can do adjusted lifetime
2296 			if (optional_string("+Delay:")) {
2297 				stuff_float(&wip->spawn_info[spawn_weap].spawn_interval_delay);
2298 			}
2299 
2300 			float adjusted_lifetime = wip->lifetime - wip->spawn_info[spawn_weap].spawn_interval_delay;
2301 
2302 			if (temp_interval >= MINIMUM_SPAWN_INTERVAL) {
2303 				wip->spawn_info[spawn_weap].spawn_interval = temp_interval;
2304 				wip->maximum_children_spawned +=
2305 					(int)(wip->spawn_info[spawn_weap].spawn_count *
2306 						(adjusted_lifetime / wip->spawn_info[spawn_weap].spawn_interval));
2307 			}
2308 			else if (temp_interval > 0.f) {
2309 				// only warn if they tried to put it in between 0 and minimum, to make it clear that it won't be used despite being positive
2310 				Warning(LOCATION, "Weapon \'%s\' spawn interval must be greater than %1.1f seconds.\n", wip->name, MINIMUM_SPAWN_INTERVAL);
2311 			}
2312 			spawn_weap++;
2313 		}
2314 	}
2315 
2316 	spawn_weap = 0;
2317 
2318 	while (optional_string("$Spawn Chance:"))
2319 	{
2320 		stuff_float(&dum_float);
2321 
2322 		if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2323 		{
2324 			if (dum_float < 0.f || dum_float > 1.f) {
2325 				Warning(LOCATION, "Weapon \'%s\' spawn chance is invalid. Only 0 - 1 is valid.\n", wip->name);
2326 			}
2327 			else
2328 				wip->spawn_info[spawn_weap].spawn_chance = dum_float;
2329 			spawn_weap++;
2330 		}
2331 	}
2332 
2333 	spawn_weap = 0;
2334 
2335 	while (optional_string("$Spawn Effect:"))
2336 	{
2337 		if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2338 		{
2339 			wip->spawn_info[spawn_weap].spawn_effect = particle::util::parseEffect(wip->name);
2340 			spawn_weap++;
2341 		}
2342 	}
2343 
2344 	if (wip->wi_flags[Weapon::Info_Flags::Local_ssm] && optional_string("$Local SSM:"))
2345 	{
2346 		if(optional_string("+Warpout Delay:")) {
2347 			stuff_int(&wip->lssm_warpout_delay);
2348 		}
2349 
2350 		if(optional_string("+Warpin Delay:")) {
2351 			stuff_int(&wip->lssm_warpin_delay);
2352 		}
2353 
2354 		if(optional_string("+Stage 5 Velocity:")) {
2355 			stuff_float(&wip->lssm_stage5_vel);
2356 		}
2357 
2358 		if(optional_string("+Warpin Radius:")) {
2359 			stuff_float(&wip->lssm_warpin_radius);
2360 		}
2361 
2362 		if (optional_string("+Lock Range:")) {
2363 			stuff_float(&wip->lssm_lock_range);
2364 		}
2365 
2366 		if (optional_string("+Warp Effect:")) {
2367 			int temp;
2368 			if (stuff_int_optional(&temp) != 2) {
2369 				// We have a string to parse instead.
2370 				char unique_id[NAME_LENGTH];
2371 				memset(unique_id, 0, NAME_LENGTH);
2372 				stuff_string(unique_id, F_NAME, NAME_LENGTH);
2373 				int fireball_type = fireball_info_lookup(unique_id);
2374 				if (fireball_type < 0) {
2375 					error_display(0, "Unknown fireball [%s] to use as warp effect for LSSM weapon [%s].", unique_id, wip->name);
2376 					wip->lssm_warpeffect = FIREBALL_WARP;
2377 				}
2378 				else {
2379 					wip->lssm_warpeffect = fireball_type;
2380 				}
2381 			}
2382 			else {
2383 				if ((temp < 0) || (temp >= Num_fireball_types)) {
2384 					error_display(0, "Fireball index [%d] out of range (should be 0-%d) for LSSM weapon [%s].", temp, Num_fireball_types - 1, wip->name);
2385 					wip->lssm_warpeffect = FIREBALL_WARP;
2386 				}
2387 				else {
2388 					wip->lssm_warpeffect = temp;
2389 				}
2390 			}
2391 		}
2392 	}
2393 
2394 	if (optional_string("$Countermeasure:"))
2395 	{
2396 		if (!(wip->wi_flags[Weapon::Info_Flags::Cmeasure]))
2397 		{
2398 			Warning(LOCATION,"Weapon \'%s\' has countermeasure information defined, but the \"countermeasure\" flag wasn\'t found in the \'$Flags:\' field.\n", wip->name);
2399 		}
2400 
2401 		if (optional_string("+Heat Effectiveness:"))
2402 			stuff_float(&wip->cm_heat_effectiveness);
2403 
2404 		if (optional_string("+Aspect Effectiveness:"))
2405 			stuff_float(&wip->cm_aspect_effectiveness);
2406 
2407 		if (optional_string("+Effective Radius:"))
2408 			stuff_float(&wip->cm_effective_rad);
2409 
2410 		if (optional_string("+Missile Detonation Radius:"))
2411 			stuff_float(&wip->cm_detonation_rad);
2412 
2413 		if (optional_string("+Single Missile Kill:"))
2414 			stuff_boolean(&wip->cm_kill_single);
2415 
2416 		if (optional_string("+Pulse Interval:")) {
2417 			stuff_int(&wip->cmeasure_timer_interval);
2418 		}
2419 
2420 		if (optional_string("+Use Fire Wait:")) {
2421 			wip->cmeasure_firewait = (int) (wip->fire_wait*1000.0f);
2422 		}
2423 
2424 		if (optional_string("+Failure Launch Delay Multiplier for AI:")) {
2425 			int delay_multiplier;
2426 			stuff_int(&delay_multiplier);
2427 			if (delay_multiplier > 0) {
2428 				wip->cmeasure_failure_delay_multiplier_ai = delay_multiplier;
2429 			} else {
2430 				Warning(LOCATION,"\"+Failure Launch Delay Multiplier for AI:\" should be >= 0 (read %i). Value will not be used. ", delay_multiplier);
2431 			}
2432 		}
2433 
2434 		if (optional_string("+Successful Launch Delay Multiplier for AI:")) {
2435 			int delay_multiplier;
2436 			stuff_int(&delay_multiplier);
2437 			if (delay_multiplier > 0) {
2438 				wip->cmeasure_sucess_delay_multiplier_ai = delay_multiplier;
2439 			} else {
2440 				Warning(LOCATION, "\"+Successful Launch Delay Multiplier for AI:\" should be >= 0 (read %i). Value will not be used. ", delay_multiplier);
2441 			}
2442 		}
2443 
2444 	}
2445 
2446 	// beam weapon optional stuff
2447 	if ( optional_string("$BeamInfo:") ) {
2448 		// beam type
2449 		if(optional_string("+Type:")) {
2450 			stuff_int(&wip->b_info.beam_type);
2451 		}
2452 
2453 		// how long it lasts
2454 		if(optional_string("+Life:")) {
2455 			stuff_float(&wip->b_info.beam_life);
2456 		}
2457 
2458 		// warmup time
2459 		if(optional_string("+Warmup:")) {
2460 			stuff_int(&wip->b_info.beam_warmup);
2461 		}
2462 
2463 		// warmdowm time
2464 		if(optional_string("+Warmdown:")) {
2465 			stuff_int(&wip->b_info.beam_warmdown);
2466 		}
2467 
2468 		// muzzle glow radius
2469 		if(optional_string("+Radius:")) {
2470 			stuff_float(&wip->b_info.beam_muzzle_radius);
2471 		}
2472 
2473 		// particle spew count
2474 		if(optional_string("+PCount:")) {
2475 			stuff_int(&wip->b_info.beam_particle_count);
2476 		}
2477 
2478 		// particle radius
2479 		if(optional_string("+PRadius:")) {
2480 			stuff_float(&wip->b_info.beam_particle_radius);
2481 		}
2482 
2483 		// angle off turret normal
2484 		if(optional_string("+PAngle:")) {
2485 			stuff_float(&wip->b_info.beam_particle_angle);
2486 		}
2487 
2488 		// particle bitmap/ani
2489 		if ( optional_string("+PAni:") ) {
2490 			stuff_string(fname, F_NAME, NAME_LENGTH);
2491 			generic_anim_init(&wip->b_info.beam_particle_ani, fname);
2492 		}
2493 
2494 		// magic miss #
2495 		if(optional_string("+Miss Factor:")) {
2496 			for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
2497 				float temp;
2498 				if(!stuff_float_optional(&temp)) {
2499 					break;
2500 				}
2501 				// an unspecified Miss Factor should apply to all IFFs
2502 				for(iff=0; iff<Num_iffs; iff++) {
2503 					wip->b_info.beam_iff_miss_factor[iff][idx] = temp;
2504 				}
2505 			}
2506 		}
2507 		// now check miss factors for each IFF
2508 		for(iff=0; iff<Num_iffs; iff++) {
2509 			SCP_string miss_factor_string;
2510 			sprintf(miss_factor_string, "+%s Miss Factor:", Iff_info[iff].iff_name);
2511 			if(optional_string(miss_factor_string.c_str())) {
2512 				// this Miss Factor applies only to the specified IFF
2513 				for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
2514 					if(!stuff_float_optional(&wip->b_info.beam_iff_miss_factor[iff][idx])) {
2515 						break;
2516 					}
2517 				}
2518 			}
2519 		}
2520 
2521 		// beam fire sound
2522 		parse_game_sound("+BeamSound:", &wip->b_info.beam_loop_sound);
2523 
2524 		// warmup sound
2525 		parse_game_sound("+WarmupSound:", &wip->b_info.beam_warmup_sound);
2526 
2527 		// warmdown sound
2528 		parse_game_sound("+WarmdownSound:", &wip->b_info.beam_warmdown_sound);
2529 
2530 		// glow bitmap
2531 		if (optional_string("+Muzzleglow:") ) {
2532 			stuff_string(fname, F_NAME, NAME_LENGTH);
2533 			generic_anim_init(&wip->b_info.beam_glow, fname);
2534 		}
2535 
2536 		if (optional_string("+Directional Glow:")) {
2537 			stuff_float(&wip->b_info.glow_length);
2538 			wip->b_info.directional_glow = true;
2539 		}
2540 
2541 		// # of shots (only used for type D beams)
2542 		if(optional_string("+Shots:")) {
2543 			stuff_int(&wip->b_info.beam_shots);
2544 		}
2545 
2546 		// make sure that we have at least one shot so that TYPE_D beams will work
2547 		if ( (wip->b_info.beam_type == BEAM_TYPE_D) && (wip->b_info.beam_shots < 1) ) {
2548 			Warning( LOCATION, "Type D beam weapon, '%s', has less than one \"+Shots\" specified!  It must be set to at least 1!!",  wip->name);
2549 			wip->b_info.beam_shots = 1;
2550 		}
2551 
2552 		// shrinkage
2553 		if(optional_string("+ShrinkFactor:")) {
2554 			stuff_float(&wip->b_info.beam_shrink_factor);
2555 			if (wip->b_info.beam_shrink_factor > 1.f || wip->b_info.beam_shrink_factor < 0.f) {
2556 				Warning(LOCATION, "Beam '%s' ShrinkFactor must be between 0 and 1", wip->name);
2557 				CLAMP(wip->b_info.beam_shrink_factor, 0.f, 1.f);
2558 			}
2559 		}
2560 
2561 		if(optional_string("+ShrinkPct:")) {
2562 			stuff_float(&wip->b_info.beam_shrink_pct);
2563 			if (wip->b_info.beam_shrink_pct < 0.f) {
2564 				Warning(LOCATION, "Beam '%s' ShrinkPct must be positive.", wip->name);
2565 				wip->b_info.beam_shrink_pct = 0.f;
2566 			}
2567 		}
2568 
2569 		// grow.. age?
2570 		if (optional_string("+GrowFactor:")) {
2571 			stuff_float(&wip->b_info.beam_grow_factor);
2572 			if (wip->b_info.beam_grow_factor > 1.f || wip->b_info.beam_grow_factor < 0.f) {
2573 				Warning(LOCATION, "Beam '%s' GrowFactor must be between 0 and 1", wip->name);
2574 				CLAMP(wip->b_info.beam_grow_factor, 0.f, 1.f);
2575 			}
2576 		}
2577 
2578 		if (optional_string("+GrowPct:")) {
2579 			stuff_float(&wip->b_info.beam_grow_pct);
2580 			if (wip->b_info.beam_grow_pct < 0.f) {
2581 				Warning(LOCATION, "Beam '%s' GrowPct must be positive.", wip->name);
2582 				wip->b_info.beam_grow_pct = 0.f;
2583 			}
2584 		}
2585 
2586 		if (optional_string("+Initial Width Factor:")) {
2587 			stuff_float(&wip->b_info.beam_initial_width);
2588 			if (wip->b_info.beam_initial_width > 1.f || wip->b_info.beam_initial_width < 0.f) {
2589 				Warning(LOCATION, "Beam '%s' Initial Width Factor must be between 0 and 1", wip->name);
2590 				CLAMP(wip->b_info.beam_initial_width, 0.f, 1.f);
2591 			}
2592 		}
2593 
2594 		if (optional_string("+Range:")) {
2595 			stuff_float(&wip->b_info.range);
2596 		}
2597 
2598 		if ( optional_string("+Attenuation:") )
2599 			stuff_float(&wip->b_info.damage_threshold);
2600 
2601 		if ( optional_string("+BeamWidth:") )
2602 			stuff_float(&wip->b_info.beam_width);
2603 
2604 		if (optional_string("+Beam Flash Particle Effect:")) {
2605 			wip->flash_impact_weapon_expl_effect = particle::util::parseEffect(wip->name);
2606 		} else {
2607 			// This is for compatibility with old tables
2608 			// Do not add features here!
2609 
2610 			int bitmapIndex = -1;
2611 			float size = 0.0f;
2612 			bool defaultEffect = false;
2613 
2614 			if (optional_string("+Beam Flash Effect:")) {
2615 				stuff_string(fname, F_NAME, NAME_LENGTH);
2616 
2617 				if (VALID_FNAME(fname))
2618 				{
2619 					bitmapIndex = bm_load_animation(fname);
2620 
2621 					if (bitmapIndex < 0)
2622 					{
2623 						Warning(LOCATION, "Failed to load effect '%s' for weapon '%s'!", fname, wip->name);
2624 					}
2625 				}
2626 			}
2627 
2628 			if (bitmapIndex < 0)
2629 			{
2630 				// Default to the smoke particle effect
2631 				bitmapIndex = bm_load_animation("particlesmoke01");
2632 				defaultEffect = true;
2633 			}
2634 
2635 			if (optional_string("+Beam Flash Radius:"))
2636 				stuff_float(&size);
2637 
2638 			if (bitmapIndex >= 0 && size > 0.0f)
2639 			{
2640 				using namespace particle;
2641 
2642 				if (defaultEffect)
2643 				{
2644 					// 'size * 1.5f * 0.005f' is another weird thing, the original code scales the lifetime of the flash particles based on size
2645 					// so the new effects have to simulate that, but that onyl applies to the default effect, not a custom effect
2646 					// seriously, who though that would be a good idea?
2647 					auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size * 1.2f, size * 1.9f, size * 1.5f * 0.005f);
2648 					wip->flash_impact_weapon_expl_effect = ParticleManager::get()->
2649 						addEffect(singleEffect);
2650 				}
2651 				else
2652 				{
2653 					auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size * 1.2f, size * 1.9f);
2654 					wip->flash_impact_weapon_expl_effect = ParticleManager::get()->
2655 						addEffect(singleEffect);
2656 				}
2657 			}
2658 		}
2659 
2660 		if (optional_string("+Beam Piercing Particle Effect:")) {
2661 			wip->piercing_impact_effect = particle::util::parseEffect(wip->name);
2662 		} else {
2663 			// This is for compatibility with old tables
2664 			// Do not add features here!
2665 
2666 			int bitmapIndex = -1;
2667 			float radius = 0.0f;
2668 			float velocity = 0.0f;
2669 			float back_velocity = 0.0f;
2670 			float variance = 0.0f;
2671 
2672 			if (optional_string("+Beam Piercing Effect:"))
2673 			{
2674 				stuff_string(fname, F_NAME, NAME_LENGTH);
2675 
2676 				if (VALID_FNAME(fname))
2677 				{
2678 					bitmapIndex = bm_load_animation(fname);
2679 
2680 					if (bitmapIndex < 0)
2681 					{
2682 						Warning(LOCATION, "Failed to load effect '%s' for weapon %s!", fname, wip->name);
2683 					}
2684 				}
2685 			}
2686 
2687 			if (optional_string("+Beam Piercing Radius:"))
2688 				stuff_float(&radius);
2689 
2690 			if (optional_string("+Beam Piercing Effect Velocity:"))
2691 				stuff_float(&velocity);
2692 
2693 			if (optional_string("+Beam Piercing Splash Effect Velocity:"))
2694 				stuff_float(&back_velocity);
2695 
2696 			if (optional_string("+Beam Piercing Effect Variance:"))
2697 				stuff_float(&variance);
2698 
2699 			if (bitmapIndex >= 0 && radius > 0.0f)
2700 			{
2701 				using namespace particle;
2702 				using namespace effects;
2703 
2704 				auto piercingEffect = new BeamPiercingEffect();
2705 				piercingEffect->setValues(bitmapIndex, radius, velocity, back_velocity, variance);
2706 
2707 				wip->piercing_impact_effect = ParticleManager::get()->
2708 					addEffect(piercingEffect);
2709 			}
2710 		}
2711 
2712 		if (optional_string("$Type 5 Beam Options:")) {
2713 
2714 			char temp_type[NAME_LENGTH];
2715 			type5_beam_info* t5info = &wip->b_info.t5info;
2716 
2717 			if (optional_string("+Start Position:")) {
2718 				stuff_string(temp_type, F_NAME, NAME_LENGTH);
2719 					if (!stricmp(temp_type, NOX("RANDOM ON SHIP"))) {
2720 						t5info->start_pos = Type5BeamPos::RANDOM_INSIDE;
2721 					}
2722 					else if (!stricmp(temp_type, NOX("RANDOM OFF SHIP"))) {
2723 						t5info->start_pos = Type5BeamPos::RANDOM_OUTSIDE;
2724 					}
2725 					else if (!stricmp(temp_type, NOX("CENTER"))) {
2726 						t5info->start_pos = Type5BeamPos::CENTER;
2727 					}
2728 					else if (!stricmp(temp_type, NOX("SAME RANDOM"))) {
2729 						Warning(LOCATION, "'SAME RANDOM' is not applicable for start position on beam %s!", wip->name);
2730 					}
2731 					else {
2732 						Warning(LOCATION, "Invalid start position on beam %s!\n Options are: 'RANDOM ON SHIP', 'RANDOM OFF SHIP', 'CENTER''", wip->name);
2733 					}
2734 			}
2735 
2736 			if (optional_string("+Start Position Offset:")) {
2737 				stuff_vec3d(&t5info->start_pos_offset);
2738 			}
2739 
2740 			if (optional_string("+Start Position Randomness:")) {
2741 				stuff_vec3d(&t5info->start_pos_rand);
2742 			}
2743 
2744 			if (optional_string("+End Position:")) {
2745 				stuff_string(temp_type, F_NAME, NAME_LENGTH);
2746 				if (!stricmp(temp_type, NOX("RANDOM ON SHIP"))) {
2747 					t5info->end_pos = Type5BeamPos::RANDOM_INSIDE;
2748 					t5info->no_translate = false;
2749 				}
2750 				else if (!stricmp(temp_type, NOX("RANDOM OFF SHIP"))) {
2751 					t5info->end_pos = Type5BeamPos::RANDOM_OUTSIDE;
2752 					t5info->no_translate = false;
2753 				}
2754 				else if (!stricmp(temp_type, NOX("CENTER"))) {
2755 					t5info->end_pos = Type5BeamPos::CENTER;
2756 					t5info->no_translate = false;
2757 				}
2758 				else if (!stricmp(temp_type, NOX("SAME RANDOM"))) {
2759 					t5info->end_pos = Type5BeamPos::SAME_RANDOM;
2760 					// offset could still be different so this counts as translating
2761 					t5info->no_translate = false;
2762 				}
2763 				else {
2764 					Warning(LOCATION, "Invalid end position on beam %s!\n Options are: 'RANDOM ON SHIP', 'RANDOM OFF SHIP', 'CENTER', 'SAME RANDOM'", wip->name);
2765 				}
2766 			}
2767 
2768 			if (optional_string("+End Position Offset:")) {
2769 				stuff_vec3d(&t5info->end_pos_offset);
2770 			}
2771 
2772 			if (optional_string("+End Position Randomness:")) {
2773 				stuff_vec3d(&t5info->end_pos_rand);
2774 			}
2775 
2776 			if (optional_string("+Orient Offsets to Target:")) {
2777 				stuff_boolean(&t5info->target_orient_positions);
2778 			}
2779 
2780 			if (optional_string("+Scale Offsets to Target:")) {
2781 				stuff_boolean(&t5info->target_scale_positions);
2782 			}
2783 
2784 			if (optional_string("+Continuous Rotation:")) {
2785 				stuff_float(&t5info->continuous_rot);
2786 				t5info->continuous_rot *= (PI / 180.f);
2787 			}
2788 
2789 			if (optional_string("+Continuous Rotation Axis:")) {
2790 				stuff_string(temp_type, F_NAME, NAME_LENGTH);
2791 				if (!stricmp(temp_type, NOX("CENTER"))) {
2792 					t5info->continuous_rot_axis = Type5BeamRotAxis::CENTER;
2793 				}
2794 				else if (!stricmp(temp_type, NOX("END POSITION BEFORE OFFSET"))) {
2795 					t5info->continuous_rot_axis = Type5BeamRotAxis::ENDPOS_NO_OFFSET;
2796 				}
2797 				else if (!stricmp(temp_type, NOX("START POSITION BEFORE OFFSET"))) {
2798 					t5info->continuous_rot_axis = Type5BeamRotAxis::STARTPOS_NO_OFFSET;
2799 				}
2800 				else if (!stricmp(temp_type, NOX("END POSITION AFTER OFFSET"))) {
2801 					t5info->continuous_rot_axis = Type5BeamRotAxis::ENDPOS_OFFSET;
2802 				}
2803 				else if (!stricmp(temp_type, NOX("START POSITION AFTER OFFSET"))) {
2804 					t5info->continuous_rot_axis = Type5BeamRotAxis::STARTPOS_OFFSET;
2805 				}
2806 				else {
2807 					Warning(LOCATION, "Invalid continuous rotation axis on beam %s!\n Options are: 'CENTER', 'END POSITION BEFORE OFFSET', 'START POSITION BEFORE OFFSET', 'END POSITION AFTER OFFSET', 'START POSITION AFTER OFFSET'", wip->name);
2808 				}
2809 			}
2810 
2811 			if (optional_string("+Burst Rotation Pattern:")) {
2812 				stuff_float_list(t5info->burst_rot_pattern);
2813 				for (float &rot : t5info->burst_rot_pattern) {
2814 					rot = fl_radians(rot);
2815 				}
2816 			}
2817 
2818 			if (optional_string("+Burst Rotation Axis:")) {
2819 				stuff_string(temp_type, F_NAME, NAME_LENGTH);
2820 				if (!stricmp(temp_type, NOX("CENTER"))) {
2821 					t5info->burst_rot_axis = Type5BeamRotAxis::CENTER;
2822 				}
2823 				else if (!stricmp(temp_type, NOX("END POSITION BEFORE OFFSET"))) {
2824 					t5info->burst_rot_axis = Type5BeamRotAxis::ENDPOS_NO_OFFSET;
2825 				}
2826 				else if (!stricmp(temp_type, NOX("START POSITION BEFORE OFFSET"))) {
2827 					t5info->burst_rot_axis = Type5BeamRotAxis::STARTPOS_NO_OFFSET;
2828 				}
2829 				else if (!stricmp(temp_type, NOX("END POSITION AFTER OFFSET"))) {
2830 					t5info->burst_rot_axis = Type5BeamRotAxis::ENDPOS_OFFSET;
2831 				}
2832 				else if (!stricmp(temp_type, NOX("START POSITION AFTER OFFSET"))) {
2833 					t5info->burst_rot_axis = Type5BeamRotAxis::STARTPOS_OFFSET;
2834 				}
2835 				else {
2836 					Warning(LOCATION, "Invalid burst rotation axis on beam %s!\n Options are: 'CENTER', 'END POSITION BEFORE OFFSET', 'START POSITION BEFORE OFFSET', 'END POSITION AFTER OFFSET', 'START POSITION AFTER OFFSET'", wip->name);
2837 				}
2838 			}
2839 
2840 			if (optional_string("+Per Burst Rotation:")) {
2841 				stuff_float(&t5info->per_burst_rot);
2842 				t5info->per_burst_rot = fl_radians(t5info->per_burst_rot);
2843 				if (t5info->per_burst_rot < -PI2 || t5info->per_burst_rot > PI2) {
2844 					Warning(LOCATION, "Per Burst Rotation on beam '%s' must not exceed 360 degrees.", wip->name);
2845 					t5info->per_burst_rot = 0.0f;
2846 				}
2847 			}
2848 
2849 			if (optional_string("+Per Burst Rotation Axis:")) {
2850 				stuff_string(temp_type, F_NAME, NAME_LENGTH);
2851 				if (!stricmp(temp_type, NOX("CENTER"))) {
2852 					t5info->per_burst_rot_axis = Type5BeamRotAxis::CENTER;
2853 				}
2854 				else if (!stricmp(temp_type, NOX("END POSITION BEFORE OFFSET"))) {
2855 					t5info->per_burst_rot_axis = Type5BeamRotAxis::ENDPOS_NO_OFFSET;
2856 				}
2857 				else if (!stricmp(temp_type, NOX("START POSITION BEFORE OFFSET"))) {
2858 					t5info->per_burst_rot_axis = Type5BeamRotAxis::STARTPOS_NO_OFFSET;
2859 				}
2860 				else if (!stricmp(temp_type, NOX("END POSITION AFTER OFFSET"))) {
2861 					t5info->per_burst_rot_axis = Type5BeamRotAxis::ENDPOS_OFFSET;
2862 				}
2863 				else if (!stricmp(temp_type, NOX("START POSITION AFTER OFFSET"))) {
2864 					t5info->per_burst_rot_axis = Type5BeamRotAxis::STARTPOS_OFFSET;
2865 				}
2866 				else {
2867 					Warning(LOCATION, "Invalid per burst rotation axis on beam %s!\n Options are: 'CENTER', 'END POSITION BEFORE OFFSET', 'START POSITION BEFORE OFFSET', 'END POSITION AFTER OFFSET', 'START POSITION AFTER OFFSET'", wip->name);
2868 				}
2869 			}
2870 		}
2871 
2872 		if (optional_string("+Beam Flags:")) {
2873 			parse_string_flag_list(wip->b_info.flags, Beam_info_flags, Num_beam_info_flags, nullptr);
2874 		}
2875 
2876 		// beam sections
2877 		while ( optional_string("$Section:") ) {
2878 			beam_weapon_section_info *bsip = NULL, tbsw;
2879 			bool nocreate = false, remove = false;
2880 			int bsw_index_override = -1;
2881 
2882 			if ( optional_string("+Index:") ) {
2883 				stuff_int(&bsw_index_override);
2884 				if (first_time) {
2885 					Warning(LOCATION, "+Index should not be used on weapon '%s' in its initial definition. +Index is only for modification of an existing weapon.", wip->name);
2886 					bsw_index_override = -1;
2887 				} else {
2888 
2889 					if (optional_string("+remove")) {
2890 						nocreate = true;
2891 						remove = true;
2892 					}
2893 
2894 					if ((bsw_index_override < 0) || (!remove && (bsw_index_override >= wip->b_info.beam_num_sections)))
2895 						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);
2896 				}
2897 			}
2898 
2899 			if ( optional_string("+nocreate") )
2900 				nocreate = true;
2901 
2902 			// Where are we saving data?
2903 			if (bsw_index_override >= 0) {
2904 				if (bsw_index_override < wip->b_info.beam_num_sections) {
2905 					bsip = &wip->b_info.sections[bsw_index_override];
2906 				} else {
2907 					if ( !nocreate ) {
2908 						if ( (bsw_index_override == wip->b_info.beam_num_sections) && (bsw_index_override < MAX_BEAM_SECTIONS) ) {
2909 							bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
2910 						} else {
2911 							if ( !remove )
2912 								Warning(LOCATION, "Invalid index for manually-indexed beam section %d (max %d) on weapon %s.", bsw_index_override, MAX_BEAM_SECTIONS, wip->name);
2913 
2914 							bsip = &tbsw;
2915 							memset( bsip, 0, sizeof(beam_weapon_section_info) );
2916 							generic_anim_init(&bsip->texture, NULL);
2917 						}
2918 					} else {
2919 						if ( !remove )
2920 							Warning(LOCATION, "Invalid index for manually-indexed beam section %d, and +nocreate specified, on weapon %s", bsw_index_override, wip->name);
2921 
2922 						bsip = &tbsw;
2923 						memset( bsip, 0, sizeof(beam_weapon_section_info) );
2924 						generic_anim_init(&bsip->texture, NULL);
2925 					}
2926 
2927 				}
2928 			} else {
2929 				if (wip->b_info.beam_num_sections < MAX_BEAM_SECTIONS) {
2930 					bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
2931 					generic_anim_init(&bsip->texture, NULL);
2932 				} else {
2933 					Warning(LOCATION, "Too many beam sections for weapon %s - max is %d", wip->name, MAX_BEAM_SECTIONS);
2934 					bsip = &tbsw;
2935 					memset( bsip, 0, sizeof(beam_weapon_section_info) );
2936 					generic_anim_init(&bsip->texture, NULL);
2937 				}
2938 			}
2939 
2940 			// section width
2941 			if ( optional_string("+Width:") )
2942 				stuff_float(&bsip->width);
2943 
2944 			// texture
2945 			if ( optional_string("+Texture:") ) {
2946 				stuff_string(fname, F_NAME, NAME_LENGTH);
2947 
2948 				// invisible textures are okay - see weapon_clean_entries()
2949 				generic_anim_init(&bsip->texture, fname);
2950 			}
2951 
2952 			// The E -- Dummied out due to not being used anywhere
2953 			if ( optional_string("+RGBA Inner:") ) {
2954 				ubyte dummy;
2955 				stuff_ubyte(&dummy);
2956 				stuff_ubyte(&dummy);
2957 				stuff_ubyte(&dummy);
2958 				stuff_ubyte(&dummy);
2959 			}
2960 
2961 			// The E -- Dummied out due to not being used anywhere
2962 			if ( optional_string("+RGBA Outer:") ) {
2963 				ubyte dummy;
2964 				stuff_ubyte(&dummy);
2965 				stuff_ubyte(&dummy);
2966 				stuff_ubyte(&dummy);
2967 				stuff_ubyte(&dummy);
2968 			}
2969 
2970 			// flicker
2971 			if ( optional_string("+Flicker:") ) {
2972 				stuff_float(&bsip->flicker);
2973 				//Sanity
2974 				if (bsip->flicker < 0.0f || bsip->flicker > 1.0f) {
2975 					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));
2976 					CLAMP(bsip->flicker, 0.0f, 1.0f);
2977 				}
2978 			}
2979 
2980 			// zadd
2981 			if ( optional_string("+Zadd:") )
2982 				stuff_float(&bsip->z_add);
2983 
2984  			// beam texture tileing factor -Bobboau
2985 			if ( optional_string("+Tile Factor:") ) {
2986 				stuff_float(&bsip->tile_factor);
2987 				stuff_int(&bsip->tile_type);
2988 			}
2989 
2990 			// beam texture moveing stuff -Bobboau
2991 			if ( optional_string("+Translation:") )
2992 				stuff_float(&bsip->translation);
2993 
2994 			// if we are actually removing this index then reset it and we'll
2995 			// clean up the entries later
2996 			if (remove) {
2997 				memset( bsip, 0, sizeof(beam_weapon_section_info) );
2998 				generic_anim_init(&bsip->texture, NULL);
2999 			}
3000 		}
3001 	}
3002 
3003 	while ( optional_string("$Pspew:") ) {
3004 		int spew_index = -1;
3005 		// check for pspew flag
3006 		if (!( wip->wi_flags[Weapon::Info_Flags::Particle_spew] )) {
3007 			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);
3008             wip->wi_flags.set(Weapon::Info_Flags::Particle_spew);
3009 		}
3010 		// index for xmt edit, replace and remove support
3011 		if (optional_string("+Index:")) {
3012 			stuff_int(&spew_index);
3013 			if (spew_index < 0 || spew_index >= MAX_PARTICLE_SPEWERS) {
3014 				Warning(LOCATION, "+Index in particle spewer out of range. It must be between 0 and %i. Tag will be ignored.", MAX_PARTICLE_SPEWERS);
3015 				spew_index = -1;
3016 			}
3017 		}
3018 		// check for remove flag
3019 		if (optional_string("+Remove")) {
3020 			if (spew_index < 0) {
3021 				Warning(LOCATION, "+Index not specified or is out of range, can not remove spewer.");
3022 			} else { // restore defaults
3023 				wip->particle_spewers[spew_index].particle_spew_type = PSPEW_NONE;
3024 				wip->particle_spewers[spew_index].particle_spew_count = 1;
3025 				wip->particle_spewers[spew_index].particle_spew_time = 25;
3026 				wip->particle_spewers[spew_index].particle_spew_vel = 0.4f;
3027 				wip->particle_spewers[spew_index].particle_spew_radius = 2.0f;
3028 				wip->particle_spewers[spew_index].particle_spew_lifetime = 0.15f;
3029 				wip->particle_spewers[spew_index].particle_spew_scale = 0.8f;
3030 				wip->particle_spewers[spew_index].particle_spew_z_scale = 1.0f;
3031 				wip->particle_spewers[spew_index].particle_spew_rotation_rate = 10.0f;
3032 				wip->particle_spewers[spew_index].particle_spew_offset = vmd_zero_vector;
3033 				wip->particle_spewers[spew_index].particle_spew_velocity = vmd_zero_vector;
3034 				generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, NULL);
3035 			}
3036 		} else { // were not removing the spewer
3037 			if (spew_index < 0) { // index us ether not used or is invalid, so figure out where to put things
3038 				//find a free slot in the pspew info array
3039 				for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {
3040 					if (wip->particle_spewers[s].particle_spew_type == PSPEW_NONE) {
3041 						spew_index = (int)s;
3042 						break;
3043 					}
3044 				}
3045 			}
3046 			// no empty spot found, the modder tried to define too many spewers, or screwed up the xmts, or my code sucks
3047 			if ( spew_index < 0 ) {
3048 				Warning(LOCATION, "Too many particle spewers, max number of spewers is %i.", MAX_PARTICLE_SPEWERS);
3049 			} else { // we have a valid index, now parse the spewer already
3050 				if (optional_string("+Type:")) { // added type field for pspew types, 0 is the default for reverse compatability -nuke
3051 					char temp_pspew_type[NAME_LENGTH];
3052 					stuff_string(temp_pspew_type, F_NAME, NAME_LENGTH);
3053 
3054 					if (!stricmp(temp_pspew_type, NOX("DEFAULT"))) {
3055 						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
3056 					} else if (!stricmp(temp_pspew_type, NOX("HELIX"))) {
3057 						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_HELIX;
3058 					} else if (!stricmp(temp_pspew_type, NOX("SPARKLER"))) {	// new types can be added here
3059 						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_SPARKLER;
3060 					} else if (!stricmp(temp_pspew_type, NOX("RING"))) {
3061 						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_RING;
3062 					} else if (!stricmp(temp_pspew_type, NOX("PLUME"))) {
3063 						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_PLUME;
3064 					} else {
3065 						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
3066 					}
3067 				// for compatibility with existing tables that don't have a type tag
3068 				} 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
3069 					wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
3070 				}
3071 
3072 				if (optional_string("+Count:")) {
3073 					stuff_int(&wip->particle_spewers[spew_index].particle_spew_count);
3074 				}
3075 
3076 				if (optional_string("+Time:")) {
3077 					stuff_int(&wip->particle_spewers[spew_index].particle_spew_time);
3078 				}
3079 
3080 				if (optional_string("+Vel:")) {
3081 					stuff_float(&wip->particle_spewers[spew_index].particle_spew_vel);
3082 				}
3083 
3084 				if (optional_string("+Radius:")) {
3085 					stuff_float(&wip->particle_spewers[spew_index].particle_spew_radius);
3086 				}
3087 
3088 				if (optional_string("+Life:")) {
3089 					stuff_float(&wip->particle_spewers[spew_index].particle_spew_lifetime);
3090 				}
3091 
3092 				if (optional_string("+Scale:")) {
3093 					stuff_float(&wip->particle_spewers[spew_index].particle_spew_scale);
3094 				}
3095 
3096 				if (optional_string("+Z Scale:")) {
3097 					stuff_float(&wip->particle_spewers[spew_index].particle_spew_z_scale);
3098 				}
3099 
3100 				if (optional_string("+Rotation Rate:")) {
3101 					stuff_float(&wip->particle_spewers[spew_index].particle_spew_rotation_rate);
3102 				}
3103 
3104 				if (optional_string("+Offset:")) {
3105 					stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_offset);
3106 				}
3107 
3108 				if (optional_string("+Initial Velocity:")) {
3109 					stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_velocity);
3110 				}
3111 
3112 				if (optional_string("+Bitmap:")) {
3113 					stuff_string(fname, F_NAME, MAX_FILENAME_LEN);
3114 					generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, fname);
3115 				}
3116 			}
3117 		}
3118 	}
3119 	// check to see if the pspew flag was enabled but no pspew tags were given, for compatability with retail tables
3120 	if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
3121 		bool nospew = true;
3122 		for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++)
3123 			if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
3124 				nospew = false;
3125 			}
3126 		if (nospew) { // set first spewer to default
3127 			wip->particle_spewers[0].particle_spew_type = PSPEW_DEFAULT;
3128 		}
3129 	}
3130 
3131 	// tag weapon optional stuff
3132 	if( optional_string("$Tag:")){
3133 		stuff_int(&wip->tag_level);
3134 		stuff_float(&wip->tag_time);
3135         wip->wi_flags.set(Weapon::Info_Flags::Tag);
3136 	}
3137 
3138 	if( optional_string("$SSM:")){
3139 		if (stuff_int_optional(&wip->SSM_index) != 2) {
3140 			// We can't make an SSM lookup yet, because weapons are parsed first, but we can save the data to process later. -MageKing17
3141 			stuff_string(fname, F_NAME, NAME_LENGTH);
3142 			delayed_ssm_data temp_data;
3143 			temp_data.filename = filename;
3144 			temp_data.linenum = get_line_num();
3145 			temp_data.ssm_entry = fname;
3146 			if (Delayed_SSM_data.find(wip->name) == Delayed_SSM_data.end())
3147 				Delayed_SSM_names.push_back(wip->name);
3148 			Delayed_SSM_data[wip->name] = temp_data;
3149 		} else {
3150 			// We'll still want to validate the index later. -MageKing17
3151 			delayed_ssm_index_data temp_data;
3152 			temp_data.filename = filename;
3153 			temp_data.linenum = get_line_num();
3154 			if (Delayed_SSM_indices_data.find(wip->name) == Delayed_SSM_indices_data.end())
3155 				Delayed_SSM_indices.push_back(wip->name);
3156 			Delayed_SSM_indices_data[wip->name] = temp_data;
3157 		}
3158 	}// SSM index -Bobboau
3159 
3160 	if(optional_string("$FOF:")){
3161 		stuff_float(&wip->field_of_fire);
3162 
3163 		if(optional_string("+FOF Spread Rate:")){
3164 			stuff_float(&wip->fof_spread_rate);
3165 			if(required_string("+FOF Reset Rate:")){
3166 				stuff_float(&wip->fof_reset_rate);
3167 			}
3168 
3169 			if(required_string("+Max FOF:")){
3170 				float max_fof;
3171 				stuff_float(&max_fof);
3172 				wip->max_fof_spread = max_fof - wip->field_of_fire;
3173 
3174 				if (wip->max_fof_spread <= 0.0f) {
3175 					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);
3176 					wip->max_fof_spread = 0.0f;
3177 				}
3178 			}
3179 		}
3180 	}
3181 
3182 
3183 	if( optional_string("$Shots:")){
3184 		stuff_int(&wip->shots);
3185 	}
3186 
3187 	//Left in for compatibility
3188 	if ( optional_string("$decal:") ) {
3189 		mprintf(("WARNING: The decal system has been deactivated in FSO builds. Entries for weapon %s will be discarded.\n", wip->name));
3190 		required_string("+texture:");
3191 		stuff_string(fname, F_NAME, NAME_LENGTH);
3192 
3193 		if ( optional_string("+backface texture:") ) {
3194 			stuff_string(fname, F_NAME, NAME_LENGTH);
3195 		}
3196 
3197 		float bogus;
3198 
3199 		required_string("+radius:");
3200 		stuff_float(&bogus);
3201 
3202 		if ( optional_string("+burn time:") ) {
3203 			stuff_float(&bogus);
3204 		}
3205 	}
3206 
3207 	// New decal system parsing
3208 	if (optional_string("$Impact Decal:")) {
3209 		decals::parseDecalReference(wip->impact_decal, create_if_not_found);
3210 	}
3211 
3212 	if (optional_string("$Transparent:")) {
3213         wip->wi_flags.set(Weapon::Info_Flags::Transparent);
3214 
3215 		required_string("+Alpha:");
3216 		stuff_float(&wip->alpha_max);
3217 
3218 		if (wip->alpha_max > 1.0f)
3219 			wip->alpha_max = 1.0f;
3220 
3221 		if (wip->alpha_max <= 0.0f) {
3222 			Warning(LOCATION, "WARNING:  Alpha is set to 0 or a negative value for '%s'!  Defaulting to 1.0!", wip->name);
3223 		}
3224 
3225 		if (optional_string("+Alpha Min:")) {
3226 			stuff_float(&wip->alpha_min);
3227             CLAMP(wip->alpha_min, 0.0f, 1.0f);
3228 		}
3229 
3230 		if (optional_string("+Alpha Cycle:")) {
3231 			stuff_float(&wip->alpha_cycle);
3232 
3233 			if (wip->alpha_max == wip->alpha_min)
3234 				Warning(LOCATION, "WARNING:  Alpha is set to cycle for '%s', but max and min values are the same!", wip->name);
3235 		}
3236 	}
3237 
3238 	if (optional_string("$Weapon Hitpoints:")) {
3239 		stuff_int(&wip->weapon_hitpoints);
3240 	} else if (first_time && (wip->wi_flags[Weapon::Info_Flags::Turret_Interceptable, Weapon::Info_Flags::Fighter_Interceptable])) {
3241 		wip->weapon_hitpoints = 25;
3242 	}
3243 
3244 	// making sure bombs get their hitpoints assigned
3245 	if ((wip->wi_flags[Weapon::Info_Flags::Bomb]) && (wip->weapon_hitpoints == 0)) {
3246 		wip->weapon_hitpoints = 50;
3247 	}
3248 
3249 	if (wip->weapon_hitpoints <= 0.0f && (wip->wi_flags[Weapon::Info_Flags::No_radius_doubling])) {
3250 		Warning(LOCATION, "Weapon \'%s\' is not interceptable but has \"no radius doubling\" set. Ignoring the flag", wip->name);
3251 		wip->wi_flags.set(Weapon::Info_Flags::No_radius_doubling, false);
3252 	}
3253 
3254 	if(optional_string("$Armor Type:")) {
3255 		stuff_string(buf, F_NAME, NAME_LENGTH);
3256 		wip->armor_type_idx = armor_type_get_idx(buf);
3257 
3258 		if(wip->armor_type_idx == -1)
3259 			Warning(LOCATION,"Invalid armor name %s specified for weapon %s", buf, wip->name);
3260 	}
3261 
3262 	if (optional_string("$Burst Shots:")) {
3263 		stuff_int(&wip->burst_shots);
3264 		if (wip->burst_shots > 0)
3265 			wip->burst_shots--;
3266 	}
3267 
3268 	if (optional_string("$Burst Delay:")) {
3269 		int temp;
3270 		stuff_int(&temp);
3271 		if (temp > 0) {
3272 			wip->burst_delay = ((float) temp) / 1000.0f;
3273 		}
3274 	}
3275 
3276 	if (optional_string("$Burst Flags:")) {
3277 		parse_string_flag_list(wip->burst_flags, Burst_fire_flags, Num_burst_fire_flags, NULL);
3278 	}
3279 
3280 	if (optional_string("$Thruster Flame Effect:")) {
3281 		stuff_string(fname, F_NAME, NAME_LENGTH);
3282 
3283 		if (VALID_FNAME(fname))
3284 			generic_anim_init( &wip->thruster_flame, fname );
3285 	}
3286 
3287 	if (optional_string("$Thruster Glow Effect:")) {
3288 		stuff_string(fname, F_NAME, NAME_LENGTH);
3289 
3290 		if (VALID_FNAME(fname))
3291 			generic_anim_init( &wip->thruster_glow, fname );
3292 	}
3293 
3294 	if (optional_string("$Thruster Glow Radius Factor:")) {
3295 		stuff_float(&wip->thruster_glow_factor);
3296 	}
3297 
3298 	//pretty stupid if a target must be tagged to shoot tag missiles at it
3299 	if ((wip->wi_flags[Weapon::Info_Flags::Tag]) && (wip->wi_flags[Weapon::Info_Flags::Tagged_only]))
3300 	{
3301 		Warning(LOCATION, "%s is a tag missile, but the target must be tagged to shoot it", wip->name);
3302 	}
3303 
3304 	// if burst delay is longer than firewait skip the whole burst fire option
3305 	if (wip->burst_delay >= wip->fire_wait)
3306 		wip->burst_shots = 0;
3307 
3308 	// Set up weapon failure
3309 	if (optional_string("$Failure Rate:")) {
3310 		stuff_float(&wip->failure_rate);
3311 		if (optional_string("+Failure Substitute:")) {
3312 			stuff_string(wip->failure_sub_name, F_NAME);
3313 		}
3314 	}
3315 
3316 	// This only works with lasters and missiles for now
3317 	if (wip->subtype == WP_MISSILE || wip->subtype == WP_LASER) {
3318 		// We always have an object we are attached to
3319 		flagset<actions::ProgramContextFlags> contexts{actions::ProgramContextFlags::HasObject};
3320 
3321 		if (wip->render_type == WRT_POF) {
3322 			// We will render with a model so we can have subobejcts
3323 			contexts.set(actions::ProgramContextFlags::HasSubobject, true);
3324 		}
3325 
3326 		wip->on_create_program = actions::ProgramSet::parseProgramSet("$On Create:", contexts);
3327 	}
3328 
3329 	/* Generate a substitution pattern for this weapon.
3330 	This pattern is very naive such that it calculates the lowest common denominator as being all of
3331 	the periods multiplied together.
3332 	*/
3333 	while ( optional_string("$substitute:") ) {
3334 		char subname[NAME_LENGTH];
3335 		int period = 0;
3336 		int index = 0;
3337 		int offset = 0;
3338 		stuff_string(subname, F_NAME, NAME_LENGTH);
3339 		if ( optional_string("+period:") ) {
3340 			stuff_int(&period);
3341 			if ( period <= 0 ) {
3342 				Warning(LOCATION, "Substitution '%s' for weapon '%s' requires a period greater than 0. Setting period to 1.", subname, wip->name);
3343 				period = 1;
3344 			}
3345 			if ( optional_string("+offset:") ) {
3346 				stuff_int(&offset);
3347 				if ( offset <= 0 ) {
3348 					Warning(LOCATION, "Period offset for substitution '%s' of weapon '%s' has to be greater than 0. Setting offset to 1.", subname, wip->name);
3349 					offset = 1;
3350 				}
3351 			}
3352 		} else if ( optional_string("+index:") ) {
3353 			stuff_int(&index);
3354 			if ( index < 0 ) {
3355 				Warning(LOCATION, "Substitution '%s' for weapon '%s' requires an index greater than 0. Setting index to 0.", subname, wip->name);
3356 				index = 0;
3357 			}
3358 		}
3359 
3360 		// we are going to use weapon substition so, make sure that the pattern array has at least one element
3361 		if ( wip->num_substitution_patterns == 0 ) {
3362 			// pattern is empty, initialize pattern with the weapon being currently parsed.
3363 			strcpy_s(wip->weapon_substitution_pattern_names[0], wip->name);
3364 			wip->num_substitution_patterns++;
3365 		}
3366 
3367 		// if tbler specifies a period then determine if we can fit the resulting pattern
3368 		// neatly into the pattern array.
3369 		if ( period > 0 ) {
3370 			if ( (wip->num_substitution_patterns % period) > 0 ) {
3371 				// not neat, need to expand the pattern so that our frequency pattern fits completly.
3372 				size_t current_size = wip->num_substitution_patterns;
3373 				size_t desired_size = current_size*period;
3374 				if (desired_size > MAX_SUBSTITUTION_PATTERNS) {
3375 					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);
3376 				}
3377 				else {
3378 					wip->num_substitution_patterns = desired_size;
3379 
3380 					// now duplicate the current pattern into the new area so the current pattern holds
3381 					for ( size_t i = current_size; i < desired_size; i++ ) {
3382 						strcpy_s(wip->weapon_substitution_pattern_names[i], wip->weapon_substitution_pattern_names[i%current_size]);
3383 					}
3384 				}
3385 			}
3386 
3387 			/* Apply the substituted weapon at the requested period, barrel
3388 			shifted by offset if needed.*/
3389 			for ( size_t pos = (period + offset - 1) % period;
3390 				pos < wip->num_substitution_patterns; pos += period )
3391 			{
3392 				strcpy_s(wip->weapon_substitution_pattern_names[pos], subname);
3393 			}
3394 		} else {
3395 			// assume that tbler wanted to specify a index for the new weapon.
3396 
3397 			// make sure that there is enough room
3398 			if (index >= MAX_SUBSTITUTION_PATTERNS) {
3399 				Warning(LOCATION, "Substitution pattern index exceeds the maximum size!  Index=%d, max size=%d", index, MAX_SUBSTITUTION_PATTERNS);
3400 			} else {
3401 				if ( (size_t)index >= wip->num_substitution_patterns ) {
3402 					// need to make the pattern bigger by filling the extra with the current weapon.
3403 					for ( size_t i = wip->num_substitution_patterns; i < (size_t)index; i++ ) {
3404 						strcpy_s(wip->weapon_substitution_pattern_names[i], subname);
3405 					}
3406 					wip->num_substitution_patterns = index+1;
3407 				}
3408 
3409 				strcpy_s(wip->weapon_substitution_pattern_names[index], subname);
3410 			}
3411 		}
3412 	}
3413 
3414 	//Optional score for destroying this weapon.
3415 	if (optional_string("$Score:")) {
3416 		stuff_int(&wip->score);
3417 	}
3418 
3419 	return w_id;
3420 }
3421 
3422 /**
3423  * For all weapons that spawn weapons, given an index at weaponp->spawn_type,
3424  * convert the strings in Spawn_names to indices in the Weapon_types array.
3425  */
translate_spawn_types()3426 void translate_spawn_types()
3427 {
3428     int	i, j, k;
3429 
3430     for (i = 0; i < weapon_info_size(); i++)
3431     {
3432         for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
3433         {
3434             if ( (Weapon_info[i].spawn_info[j].spawn_type > -1) && (Weapon_info[i].spawn_info[j].spawn_type < Num_spawn_types) )
3435             {
3436                 int	spawn_type = Weapon_info[i].spawn_info[j].spawn_type;
3437 
3438                 Assert( spawn_type < Num_spawn_types );
3439 
3440                 for (k = 0; k < weapon_info_size(); k++)
3441                 {
3442                     if ( !stricmp(Spawn_names[spawn_type], Weapon_info[k].name) )
3443                     {
3444                         Weapon_info[i].spawn_info[j].spawn_type = (short)k;
3445 
3446                         if (i == k)
3447                             Warning(LOCATION, "Weapon %s spawns itself.  Infinite recursion?\n", Weapon_info[i].name);
3448 
3449                         break;
3450                     }
3451                 }
3452             }
3453         }
3454     }
3455 }
3456 
3457 static char Default_cmeasure_name[NAME_LENGTH] = "";
3458 
parse_weaponstbl(const char * filename)3459 void parse_weaponstbl(const char *filename)
3460 {
3461 	try
3462 	{
3463 		read_file_text(filename, CF_TYPE_TABLES);
3464 		reset_parse();
3465 
3466 		if (optional_string("#Primary Weapons"))
3467 		{
3468 			while (required_string_either("#End", "$Name:")) {
3469 				// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
3470 				if (parse_weapon(WP_LASER, Parsing_modular_table, filename) < 0) {
3471 					continue;
3472 				}
3473 			}
3474 			required_string("#End");
3475 		}
3476 
3477 		if (optional_string("#Secondary Weapons"))
3478 		{
3479 			while (required_string_either("#End", "$Name:")) {
3480 				// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
3481 				if (parse_weapon(WP_MISSILE, Parsing_modular_table, filename) < 0) {
3482 					continue;
3483 				}
3484 			}
3485 			required_string("#End");
3486 		}
3487 
3488 		if (optional_string("#Beam Weapons"))
3489 		{
3490 			while (required_string_either("#End", "$Name:")) {
3491 				// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
3492 				if (parse_weapon(WP_BEAM, Parsing_modular_table, filename) < 0) {
3493 					continue;
3494 				}
3495 			}
3496 			required_string("#End");
3497 		}
3498 
3499 		if (optional_string("#Countermeasures"))
3500 		{
3501 			while (required_string_either("#End", "$Name:"))
3502 			{
3503 				int idx = parse_weapon(WP_MISSILE, Parsing_modular_table, filename);
3504 
3505 				if (idx < 0) {
3506 					continue;
3507 				}
3508 
3509 				//Make sure cmeasure flag is set
3510                 Weapon_info[idx].wi_flags.set(Weapon::Info_Flags::Cmeasure);
3511 
3512 				//Set cmeasure index
3513 				if (!strlen(Default_cmeasure_name)) {
3514 					//We can't be sure that index will be the same after sorting, so save the name
3515 					strcpy_s(Default_cmeasure_name, Weapon_info[idx].name);
3516 				}
3517 			}
3518 
3519 			required_string("#End");
3520 		}
3521 
3522 		// Read in a list of weapon_info indicies that are an ordering of the player weapon precedence.
3523 		// This list is used to select an alternate weapon when a particular weapon is not available
3524 		// during weapon selection.
3525 		if ((!Parsing_modular_table && required_string("$Player Weapon Precedence:")) || optional_string("$Player Weapon Precedence:"))
3526 		{
3527 			Num_player_weapon_precedence = (int)stuff_int_list(Player_weapon_precedence, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
3528 		}
3529 	}
3530 	catch (const parse::ParseException& e)
3531 	{
3532 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
3533 		return;
3534 	}
3535 }
3536 
3537 //uses a simple bucket sort to sort weapons, order of importance is:
3538 //Lasers
3539 //Beams
3540 //Child primary weapons
3541 //Fighter missiles and bombs
3542 //Capital missiles and bombs
3543 //Child secondary weapons
weapon_sort_by_type()3544 void weapon_sort_by_type()
3545 {
3546 	weapon_info *lasers = NULL, *big_lasers = NULL, *beams = NULL, *missiles = NULL, *big_missiles = NULL, *child_primaries = NULL, *child_secondaries = NULL;
3547 	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;
3548 	int i, weapon_index;
3549 
3550 	// get the initial count of each weapon type
3551 	for (i = 0; i < weapon_info_size(); i++) {
3552 		switch (Weapon_info[i].subtype)
3553 		{
3554 			case WP_UNUSED:
3555 				continue;
3556 
3557 			case WP_LASER:
3558 				if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3559 					num_child_primaries++;
3560 				else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3561 					num_big_lasers++;
3562 				else
3563 					num_lasers++;
3564 				break;
3565 
3566 			case WP_BEAM:
3567 				num_beams++;
3568 				break;
3569 
3570 			case WP_MISSILE:
3571 				if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3572 					num_child_secondaries++;
3573 				else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3574 					num_big_missiles++;
3575 				else
3576 					num_missiles++;
3577 				break;
3578 
3579 			default:
3580 				continue;
3581 		}
3582 
3583 	}
3584 
3585 	// allocate the buckets
3586 	if (num_lasers) {
3587 		lasers = new weapon_info[num_lasers];
3588 		Verify( lasers != NULL );
3589 		num_lasers = 0;
3590 	}
3591 
3592 	if (num_big_lasers) {
3593 		big_lasers = new weapon_info[num_big_lasers];
3594 		Verify( big_lasers != NULL );
3595 		num_big_lasers = 0;
3596 	}
3597 
3598 	if (num_beams) {
3599 		beams = new weapon_info[num_beams];
3600 		Verify( beams != NULL );
3601 		num_beams = 0;
3602 	}
3603 
3604 	if (num_missiles) {
3605 		missiles = new weapon_info[num_missiles];
3606 		Verify( missiles != NULL );
3607 		num_missiles = 0;
3608 	}
3609 
3610 	if (num_big_missiles) {
3611 		big_missiles = new weapon_info[num_big_missiles];
3612 		Verify( big_missiles != NULL );
3613 		num_big_missiles = 0;
3614 	}
3615 
3616 	if (num_child_primaries) {
3617 		child_primaries = new weapon_info[num_child_primaries];
3618 		Verify( child_primaries != NULL );
3619 		num_child_primaries = 0;
3620 	}
3621 
3622 	if (num_child_secondaries) {
3623 		child_secondaries = new weapon_info[num_child_secondaries];
3624 		Verify( child_secondaries != NULL );
3625 		num_child_secondaries = 0;
3626 	}
3627 
3628 	// fill the buckets
3629 	for (i = 0; i < weapon_info_size(); i++) {
3630 		switch (Weapon_info[i].subtype)
3631 		{
3632 			case WP_UNUSED:
3633 				continue;
3634 
3635 			case WP_LASER:
3636 				if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3637 					child_primaries[num_child_primaries++] = Weapon_info[i];
3638 				else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3639 					big_lasers[num_big_lasers++] = Weapon_info[i];
3640 				else
3641 					lasers[num_lasers++] = Weapon_info[i];
3642 				break;
3643 
3644 			case WP_BEAM:
3645 				beams[num_beams++] = Weapon_info[i];
3646 				break;
3647 
3648 			case WP_MISSILE:
3649 				if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3650 					child_secondaries[num_child_secondaries++] = Weapon_info[i];
3651 				else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3652 					big_missiles[num_big_missiles++] = Weapon_info[i];
3653 				else
3654 					missiles[num_missiles++]=Weapon_info[i];
3655 				break;
3656 
3657 			default:
3658 				continue;
3659 		}
3660 	}
3661 
3662 	weapon_index = 0;
3663 
3664 	// reorder the weapon_info structure according to our rules defined above
3665 	for (i = 0; i < num_lasers; i++, weapon_index++)
3666 		Weapon_info[weapon_index] = lasers[i];
3667 
3668 	for (i = 0; i < num_big_lasers; i++, weapon_index++)
3669 		Weapon_info[weapon_index] = big_lasers[i];
3670 
3671 	for (i = 0; i < num_beams; i++, weapon_index++)
3672 		Weapon_info[weapon_index] = beams[i];
3673 
3674 	for (i = 0; i < num_child_primaries; i++, weapon_index++)
3675 		Weapon_info[weapon_index] = child_primaries[i];
3676 
3677 	// designate start of secondary weapons so that we'll have the correct offset later on
3678 	First_secondary_index = weapon_index;
3679 
3680 	for (i = 0; i < num_missiles; i++, weapon_index++)
3681 		Weapon_info[weapon_index] = missiles[i];
3682 
3683 	for (i = 0; i < num_big_missiles; i++, weapon_index++)
3684 		Weapon_info[weapon_index] = big_missiles[i];
3685 
3686 	for (i = 0; i < num_child_secondaries; i++, weapon_index++)
3687 		Weapon_info[weapon_index] = child_secondaries[i];
3688 
3689 
3690 	if (lasers)			delete [] lasers;
3691 	if (big_lasers)		delete [] big_lasers;
3692 	if (beams)			delete [] beams;
3693 	if (missiles)		delete [] missiles;
3694 	if (big_missiles)	delete [] big_missiles;
3695 	if (child_primaries)	delete [] child_primaries;
3696 	if (child_secondaries)	delete [] child_secondaries;
3697 }
3698 
3699 /**
3700  * Do any post-parse cleaning on weapon entries
3701  */
weapon_clean_entries()3702 void weapon_clean_entries()
3703 {
3704 	for (auto &wi : Weapon_info) {
3705 		if (wi.wi_flags[Weapon::Info_Flags::Beam]) {
3706 			// clean up any beam sections which may have been deleted
3707 			int removed = 0;
3708 
3709 			for (int s_idx = 0; s_idx < wi.b_info.beam_num_sections; s_idx++) {
3710 				beam_weapon_section_info *bsip = &wi.b_info.sections[s_idx];
3711 
3712 				// If this is an invisible beam section, we want to keep it.  Originally invisible sections were initialized as they were parsed,
3713 				// but then they were inadvertently cleaned up in this function.  So let's properly set the filename here while not removing the section.
3714 				if ( !stricmp(bsip->texture.filename, "invisible") ) {
3715 					memset(bsip->texture.filename, 0, MAX_FILENAME_LEN);
3716 				}
3717 				// Now remove empty beam sections as before
3718 				else if ( !strlen(bsip->texture.filename) ) {
3719 					int new_idx = s_idx + 1;
3720 
3721 					while (new_idx < MAX_BEAM_SECTIONS) {
3722 						memcpy( &wi.b_info.sections[new_idx-1], &wi.b_info.sections[new_idx], sizeof(beam_weapon_section_info) );
3723 						new_idx++;
3724 					}
3725 
3726 					removed++;
3727 				}
3728 			}
3729 
3730 			if (removed) {
3731 				mprintf(("NOTE: weapon-cleanup is removing %i stale beam sections, out of %i original, from '%s'.\n", removed, wi.b_info.beam_num_sections, wi.name));
3732 				wi.b_info.beam_num_sections -= removed;
3733 			}
3734 
3735 			if (wi.b_info.beam_num_sections == 0) {
3736 				Warning(LOCATION, "The beam '%s' has 0 usable sections!", wi.name);
3737 			}
3738 		}
3739 	}
3740 }
3741 
weapon_release_bitmaps()3742 void weapon_release_bitmaps()
3743 {
3744 	// not for FRED...
3745 	if (Fred_running)
3746 		return;
3747 
3748 	// if we are just going to load them all again any, keep everything
3749 	if (Cmdline_load_all_weapons)
3750 		return;
3751 
3752 	for (int i = 0; i < weapon_info_size(); ++i) {
3753 		weapon_info *wip = &Weapon_info[i];
3754 
3755 		// go ahead and clear out models, the model paging code will actually take care of
3756 		// releasing this stuff if needed, but we have to keep track of current modelnums ourselves
3757 		if (wip->render_type == WRT_POF)
3758 			wip->model_num = -1;
3759 
3760 		// we are only interested in what we don't need for this mission
3761 		if ( used_weapons[i] )
3762 			continue;
3763 
3764 		if (wip->render_type == WRT_LASER) {
3765 			if (wip->laser_bitmap.first_frame >= 0) {
3766 				bm_release(wip->laser_bitmap.first_frame);
3767 				wip->laser_bitmap.first_frame = -1;
3768 			}
3769 
3770 			// now for the glow
3771 			if (wip->laser_glow_bitmap.first_frame >= 0) {
3772 				bm_release(wip->laser_glow_bitmap.first_frame);
3773 				wip->laser_glow_bitmap.first_frame = -1;
3774 			}
3775 		}
3776 
3777 		if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
3778 			// particle animation
3779 			if (wip->b_info.beam_particle_ani.first_frame >= 0) {
3780 				bm_release(wip->b_info.beam_particle_ani.first_frame);
3781 				wip->b_info.beam_particle_ani.first_frame = -1;
3782 			}
3783 
3784 			// muzzle glow
3785 			if (wip->b_info.beam_glow.first_frame >= 0) {
3786 				bm_release(wip->b_info.beam_glow.first_frame);
3787 				wip->b_info.beam_glow.first_frame = -1;
3788 			}
3789 
3790 			// section textures
3791 			for (int j = 0; j < wip->b_info.beam_num_sections; ++j) {
3792 				beam_weapon_section_info *bsi = &wip->b_info.sections[j];
3793 
3794 				if (bsi->texture.first_frame >= 0) {
3795 					bm_release(bsi->texture.first_frame);
3796 					bsi->texture.first_frame = -1;
3797 				}
3798 			}
3799 		}
3800 
3801 		if (wip->wi_flags[Weapon::Info_Flags::Trail]) {
3802 			if (wip->tr_info.texture.bitmap_id >= 0) {
3803 				bm_release(wip->tr_info.texture.bitmap_id);
3804 				wip->tr_info.texture.bitmap_id = -1;
3805 			}
3806 		}
3807 
3808 		if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) { // tweaked for multiple particle spews -nuke
3809 			for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++)  { // just bitmaps that got loaded
3810 				if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){
3811 					if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
3812 						bm_release(wip->particle_spewers[s].particle_spew_anim.first_frame);
3813 						wip->particle_spewers[s].particle_spew_anim.first_frame = -1;
3814 					}
3815 				}
3816 			}
3817 		}
3818 
3819 		if (wip->thruster_flame.first_frame >= 0) {
3820 			bm_release(wip->thruster_flame.first_frame);
3821 			wip->thruster_flame.first_frame = -1;
3822 		}
3823 
3824 		if (wip->thruster_glow.first_frame >= 0) {
3825 			bm_release(wip->thruster_glow.first_frame);
3826 			wip->thruster_glow.first_frame = -1;
3827 		}
3828 	}
3829 }
3830 
weapon_is_used(int weapon_index)3831 bool weapon_is_used(int weapon_index)
3832 {
3833 	Assert( (weapon_index >= 0) || (weapon_index < weapon_info_size()) );
3834 	return (used_weapons[weapon_index] > 0);
3835 }
3836 
weapon_load_bitmaps(int weapon_index)3837 void weapon_load_bitmaps(int weapon_index)
3838 {
3839 	weapon_info *wip;
3840 
3841 	// not for FRED...
3842 	if (Fred_running)
3843 		return;
3844 
3845 	if ( (weapon_index < 0) || (weapon_index >= weapon_info_size()) ) {
3846 		Int3();
3847 		return;
3848 	}
3849 
3850 	wip = &Weapon_info[weapon_index];
3851 
3852 	if ( (wip->render_type == WRT_LASER) && (wip->laser_bitmap.first_frame < 0) ) {
3853 		wip->laser_bitmap.first_frame = bm_load(wip->laser_bitmap.filename);
3854 
3855 		if (wip->laser_bitmap.first_frame >= 0) {
3856 			wip->laser_bitmap.num_frames = 1;
3857 			wip->laser_bitmap.total_time = 1;
3858 		}
3859 		// fall back to an animated type
3860 		else if ( generic_anim_load(&wip->laser_bitmap) ) {
3861 			mprintf(("Could not find a usable bitmap for '%s'!\n", wip->name));
3862 			Warning(LOCATION, "Could not find a usable bitmap (%s) for weapon '%s'!\n", wip->laser_bitmap.filename, wip->name);
3863 		}
3864 
3865 		// now see if we also have a glow
3866 		if ( strlen(wip->laser_glow_bitmap.filename) ) {
3867 			wip->laser_glow_bitmap.first_frame = bm_load(wip->laser_glow_bitmap.filename);
3868 
3869 			if (wip->laser_glow_bitmap.first_frame >= 0) {
3870 				wip->laser_glow_bitmap.num_frames = 1;
3871 				wip->laser_glow_bitmap.total_time = 1;
3872 			}
3873 			// fall back to an animated type
3874 			else if ( generic_anim_load(&wip->laser_glow_bitmap) ) {
3875 				mprintf(("Could not find a usable glow bitmap for '%s'!\n", wip->name));
3876 				Warning(LOCATION, "Could not find a usable glow bitmap (%s) for weapon '%s'!\n", wip->laser_glow_bitmap.filename, wip->name);
3877 			}
3878 		}
3879 	}
3880 
3881 	if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
3882 		// particle animation
3883 		if ( (wip->b_info.beam_particle_ani.first_frame < 0) && strlen(wip->b_info.beam_particle_ani.filename) )
3884 			generic_anim_load(&wip->b_info.beam_particle_ani);
3885 
3886 		// muzzle glow
3887 		if ( (wip->b_info.beam_glow.first_frame < 0) && strlen(wip->b_info.beam_glow.filename) ) {
3888 			if ( generic_anim_load(&wip->b_info.beam_glow) ) {
3889 				// animated version failed to load, try static instead
3890 				wip->b_info.beam_glow.first_frame = bm_load(wip->b_info.beam_glow.filename);
3891 
3892 				if (wip->b_info.beam_glow.first_frame >= 0) {
3893 					wip->b_info.beam_glow.num_frames = 1;
3894 					wip->b_info.beam_glow.total_time = 1;
3895 				} else {
3896 					mprintf(("Could not find a usable muzzle glow bitmap for '%s'!\n", wip->name));
3897 					Warning(LOCATION, "Could not find a usable muzzle glow bitmap (%s) for weapon '%s'!\n", wip->b_info.beam_glow.filename, wip->name);
3898 				}
3899 			}
3900 		}
3901 
3902 		// section textures
3903 		for (int i = 0; i < wip->b_info.beam_num_sections; i++) {
3904 			beam_weapon_section_info *bsi = &wip->b_info.sections[i];
3905 
3906 			if ( (bsi->texture.first_frame < 0) && strlen(bsi->texture.filename) ) {
3907 				if ( generic_anim_load(&bsi->texture) ) {
3908 					// animated version failed to load, try static instead
3909 					bsi->texture.first_frame = bm_load(bsi->texture.filename);
3910 
3911 					if (bsi->texture.first_frame >= 0) {
3912 						bsi->texture.num_frames = 1;
3913 						bsi->texture.total_time = 1;
3914 					} else {
3915 						mprintf(("Could not find a usable beam section (%i) bitmap for '%s'!\n", i, wip->name));
3916 						Warning(LOCATION, "Could not find a usable beam section (%i) bitmap (%s) for weapon '%s'!\n", i, bsi->texture.filename, wip->name);
3917 					}
3918 				}
3919 			}
3920 		}
3921 	}
3922 
3923 	if ( (wip->wi_flags[Weapon::Info_Flags::Trail]) && (wip->tr_info.texture.bitmap_id < 0) )
3924 		generic_bitmap_load(&wip->tr_info.texture);
3925 
3926 	//WMC - Don't try to load an anim if no anim is specified, Mmkay?
3927 	if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
3928 		for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {	// looperfied for multiple pspewers -nuke
3929 			if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){
3930 
3931 				if ((wip->particle_spewers[s].particle_spew_anim.first_frame < 0)
3932 					&& (wip->particle_spewers[s].particle_spew_anim.filename[0] != '\0') ) {
3933 
3934 					wip->particle_spewers[s].particle_spew_anim.first_frame = bm_load(wip->particle_spewers[s].particle_spew_anim.filename);
3935 
3936 					if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
3937 						wip->particle_spewers[s].particle_spew_anim.num_frames = 1;
3938 						wip->particle_spewers[s].particle_spew_anim.total_time = 1;
3939 					}
3940 					// fall back to an animated type
3941 					else if ( generic_anim_load(&wip->particle_spewers[s].particle_spew_anim) ) {
3942 						mprintf(("Could not find a usable particle spew bitmap for '%s'!\n", wip->name));
3943 						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);
3944 					}
3945 				}
3946 			}
3947 		}
3948 	}
3949 
3950 	// load alternate thruster textures
3951 	if (strlen(wip->thruster_flame.filename)) {
3952 		generic_anim_load(&wip->thruster_flame);
3953 	}
3954 
3955 	if (strlen(wip->thruster_glow.filename)) {
3956 		wip->thruster_glow.first_frame = bm_load(wip->thruster_glow.filename);
3957 		if (wip->thruster_glow.first_frame >= 0) {
3958 			wip->thruster_glow.num_frames = 1;
3959 			wip->thruster_glow.total_time = 1;
3960 		} else {
3961 			generic_anim_load(&wip->thruster_glow);
3962 		}
3963 	}
3964 
3965 	decals::loadBitmaps(wip->impact_decal);
3966 
3967 	// if this weapon isn't already marked as used, then mark it as such now
3968 	// (this should really only happen if the player is cheating)
3969 	if ( !used_weapons[weapon_index] )
3970 		used_weapons[weapon_index]++;
3971 }
3972 
3973 /**
3974  * Checks all of the weapon infos for substitution patterns and caches the weapon_index of any that it finds.
3975  */
weapon_generate_indexes_for_substitution()3976 void weapon_generate_indexes_for_substitution() {
3977 	for (int i = 0; i < weapon_info_size(); i++) {
3978 		weapon_info *wip = &(Weapon_info[i]);
3979 
3980 		if ( wip->num_substitution_patterns > 0 ) {
3981 			for ( size_t j = 0; j < wip->num_substitution_patterns; j++ ) {
3982 				int weapon_index = -1;
3983 				if ( stricmp("none", wip->weapon_substitution_pattern_names[j]) != 0 ) {
3984 					weapon_index = weapon_info_lookup(wip->weapon_substitution_pattern_names[j]);
3985 
3986 					if ( weapon_index == -1 ) { // invalid sub weapon
3987 						Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which does not seem to exist",
3988 							wip->name, wip->weapon_substitution_pattern_names[j]);
3989 						continue;
3990 					}
3991 
3992 					if (Weapon_info[weapon_index].subtype != wip->subtype) {
3993 						// Check to make sure secondaries can't be launched by primaries and vice versa
3994 						Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which is of a different subtype.",
3995 							wip->name, wip->weapon_substitution_pattern_names[j]);
3996 						wip->num_substitution_patterns = 0;
3997 						std::fill(std::begin(wip->weapon_substitution_pattern),
3998 								  std::end(wip->weapon_substitution_pattern),
3999 								  -1);
4000 						break;
4001 					}
4002 
4003 					if (Weapon_info[weapon_index].wi_flags[Weapon::Info_Flags::Beam] != wip->wi_flags[Weapon::Info_Flags::Beam]) {
4004 						// Check to make sure beams and non-beams aren't being mixed
4005 						Warning(LOCATION, "Beams and non-beams cannot be mixed in substitution for weapon '%s'.", wip->name);
4006 						wip->num_substitution_patterns = 0;
4007 						std::fill(std::begin(wip->weapon_substitution_pattern),
4008 							std::end(wip->weapon_substitution_pattern),
4009 							-1);
4010 						break;
4011 					}
4012 				}
4013 
4014 				wip->weapon_substitution_pattern[j] = weapon_index;
4015 			}
4016 
4017 			memset(wip->weapon_substitution_pattern_names, 0, sizeof(char) * MAX_SUBSTITUTION_PATTERNS * NAME_LENGTH);
4018 		}
4019 
4020 		if (wip->failure_rate > 0.0f) {
4021 			if (VALID_FNAME(wip->failure_sub_name)) {
4022 				wip->failure_sub = weapon_info_lookup(wip->failure_sub_name.c_str());
4023 
4024 				if (wip->failure_sub == -1) { // invalid sub weapon
4025 					Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which does not seem to exist",
4026 						wip->name, wip->failure_sub_name.c_str());
4027 					wip->failure_rate = 0.0f;
4028 				}
4029 
4030 				if (Weapon_info[wip->failure_sub].subtype != wip->subtype) {
4031 					// Check to make sure secondaries can't be launched by primaries and vice versa
4032 					Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which is of a different subtype.",
4033 						wip->name, wip->failure_sub_name.c_str());
4034 					wip->failure_sub = -1;
4035 					wip->failure_rate = 0.0f;
4036 				}
4037 			}
4038 
4039 			wip->failure_sub_name.clear();
4040 		}
4041 	}
4042 }
4043 
weapon_do_post_parse()4044 void weapon_do_post_parse()
4045 {
4046 	weapon_info *wip;
4047 	int first_cmeasure_index = -1;
4048 
4049 	weapon_sort_by_type();	// NOTE: This has to be first thing!
4050 	weapon_clean_entries();
4051 	weapon_generate_indexes_for_substitution();
4052 
4053 	Default_cmeasure_index = -1;
4054 
4055 	// run through weapons list and deal with individual issues
4056 	for (int i = 0; i < weapon_info_size(); ++i) {
4057 		wip = &Weapon_info[i];
4058 
4059 		// set default counter-measure index from the saved name
4060 		if ( (Default_cmeasure_index < 0) && strlen(Default_cmeasure_name) ) {
4061 			if ( !stricmp(wip->name, Default_cmeasure_name) ) {
4062 				Default_cmeasure_index = i;
4063 			}
4064 		}
4065 
4066 		// catch a fall back cmeasure index, just in case
4067 		if ( (first_cmeasure_index < 0) && (wip->wi_flags[Weapon::Info_Flags::Cmeasure]) )
4068 			first_cmeasure_index = i;
4069 	}
4070 
4071 	// catch cmeasure fallback
4072 	if (Default_cmeasure_index < 0)
4073 		Default_cmeasure_index = first_cmeasure_index;
4074 
4075 	// now we want to resolve the countermeasures by species
4076 	for (SCP_vector<species_info>::iterator ii = Species_info.begin(); ii != Species_info.end(); ++ii)
4077 	{
4078 		if (*ii->cmeasure_name)
4079 		{
4080 			int index = weapon_info_lookup(ii->cmeasure_name);
4081 			if (index < 0)
4082 				Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on species '%s'", ii->cmeasure_name, ii->species_name);
4083 			else if (Weapon_info[index].wi_flags[Weapon::Info_Flags::Beam])
4084 				Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on species '%s'", ii->species_name);
4085 			else
4086 				ii->cmeasure_index = index;
4087 		}
4088 	}
4089 
4090 	// translate all spawn type weapons to referrnce the appropriate spawned weapon entry
4091 	translate_spawn_types();
4092 }
4093 
weapon_expl_info_init()4094 void weapon_expl_info_init()
4095 {
4096 	int i;
4097 
4098 	parse_weapon_expl_tbl("weapon_expl.tbl");
4099 
4100 	// check for, and load, modular tables
4101 	parse_modular_table(NOX("*-wxp.tbm"), parse_weapon_expl_tbl);
4102 
4103 	// we've got our list so pass it off for final checking and loading
4104 	for (i = 0; i < (int)LOD_checker.size(); i++) {
4105 		Weapon_explosions.Load( LOD_checker[i].filename, LOD_checker[i].num_lods );
4106 	}
4107 
4108 	// done
4109 	LOD_checker.clear();
4110 }
4111 
4112 /**
4113  * This will get called once at game startup
4114  */
weapon_init()4115 void weapon_init()
4116 {
4117 	if ( !Weapons_inited ) {
4118 		//Init weapon explosion info
4119 		weapon_expl_info_init();
4120 
4121 		Num_spawn_types = 0;
4122 
4123 		// parse weapons.tbl
4124 		Removed_weapons.clear();
4125 		Weapon_info.clear();
4126 		parse_weaponstbl("weapons.tbl");
4127 
4128 		parse_modular_table(NOX("*-wep.tbm"), parse_weaponstbl);
4129 
4130 		// do post-parse cleanup
4131 		weapon_do_post_parse();
4132 
4133 		Weapons_inited = true;
4134 	}
4135 
4136 	weapon_level_init();
4137 
4138 	if (Cmdline_spew_weapon_stats != WeaponSpewType::NONE)
4139 		weapon_spew_stats(Cmdline_spew_weapon_stats);
4140 }
4141 
4142 
4143 /**
4144  * Call from game_shutdown() only!!
4145  */
weapon_close()4146 void weapon_close()
4147 {
4148 	int i;
4149 
4150 	for (i = 0; i < weapon_info_size(); i++) {
4151 		if (Weapon_info[i].desc) {
4152 			vm_free(Weapon_info[i].desc);
4153 			Weapon_info[i].desc = NULL;
4154 		}
4155 
4156 		if (Weapon_info[i].tech_desc) {
4157 			vm_free(Weapon_info[i].tech_desc);
4158 			Weapon_info[i].tech_desc = NULL;
4159 		}
4160 	}
4161 
4162 	if (used_weapons != NULL) {
4163 		delete[] used_weapons;
4164 		used_weapons = NULL;
4165 	}
4166 
4167 	if (Spawn_names != NULL) {
4168 		for (i=0; i<Num_spawn_types; i++) {
4169 			if (Spawn_names[i] != NULL) {
4170 				vm_free(Spawn_names[i]);
4171 				Spawn_names[i] = NULL;
4172 			}
4173 		}
4174 
4175 		vm_free(Spawn_names);
4176 		Spawn_names = NULL;
4177 	}
4178 }
4179 
4180 /**
4181  * This will get called at the start of each level.
4182  */
weapon_level_init()4183 void weapon_level_init()
4184 {
4185 	int i;
4186 	extern bool Ships_inited;
4187 
4188 	// Reset everything between levels
4189 	Num_weapons = 0;
4190 	for (i=0; i<MAX_WEAPONS; i++)	{
4191 		Weapons[i].objnum = -1;
4192 		Weapons[i].weapon_info_index = -1;
4193 	}
4194 
4195 	for (i = 0; i < weapon_info_size(); i++) {
4196 		Weapon_info[i].damage_type_idx = Weapon_info[i].damage_type_idx_sav;
4197 		Weapon_info[i].shockwave.damage_type_idx = Weapon_info[i].shockwave.damage_type_idx_sav;
4198 
4199 		if ( Ships_inited ) {
4200 			// populate ship type lock restrictions
4201 			for (auto& pair : Weapon_info[i].ship_restrict_strings) {
4202 				const char* name = pair.second.c_str();
4203 				int idx;
4204 				switch (pair.first)
4205 				{
4206 					case LockRestrictionType::TYPE: idx = ship_type_name_lookup(name); break;
4207 					case LockRestrictionType::CLASS: idx = ship_info_lookup(name); break;
4208 					case LockRestrictionType::SPECIES: idx = species_info_lookup(name); break;
4209 					case LockRestrictionType::IFF: idx = iff_lookup(name); break;
4210 					default: Assertion(false, "Unknown multi lock restriction type %d", (int)pair.first);
4211 						idx = -1;
4212 				}
4213 				if ( idx >= 0 ) {
4214 					Weapon_info[i].ship_restrict.emplace_back(pair.first, idx);
4215 				}
4216 				else {
4217 					Warning(LOCATION, "Couldn't find multi lock restriction type '%s' for weapon '%s'", name, Weapon_info[i].name);
4218 				}
4219 			}
4220 			Weapon_info[i].ship_restrict_strings.clear();
4221 		}
4222 	}
4223 
4224 	trail_level_init();		// reset all missile trails
4225 
4226 	swarm_level_init();
4227 	missile_obj_list_init();
4228 
4229 	cscrew_level_init();
4230 
4231 	// emp effect
4232 	emp_level_init();
4233 
4234 	if (used_weapons == nullptr)
4235 		used_weapons = new int[Weapon_info.size()];
4236 
4237 	// clear out used_weapons between missions
4238 	memset(used_weapons, 0, Weapon_info.size() * sizeof(int));
4239 
4240 	Weapon_flyby_sound_timer = timestamp(0);
4241 	Weapon_impact_timer = 1;	// inited each level, used to reduce impact sounds
4242 }
4243 
4244 MONITOR( NumWeaponsRend )
4245 
4246 const float weapon_glow_scale_f = 2.3f;
4247 const float weapon_glow_scale_r = 2.3f;
4248 const float weapon_glow_scale_l = 1.5f;
4249 const int weapon_glow_alpha = 217; // (0.85 * 255);
4250 
weapon_delete(object * obj)4251 void weapon_delete(object *obj)
4252 {
4253 	weapon *wp;
4254 	int num;
4255 
4256 	if (Script_system.IsActiveAction(CHA_ONWEAPONDELETE)) {
4257 		Script_system.SetHookObjects(2, "Weapon", obj, "Self", obj);
4258 		Script_system.RunCondition(CHA_ONWEAPONDELETE);
4259 		Script_system.RemHookVars({"Weapon", "Self"});
4260 	}
4261 
4262 	num = obj->instance;
4263 
4264 	Assert( Weapons[num].objnum == OBJ_INDEX(obj));
4265 	wp = &Weapons[num];
4266 
4267 	Assert(wp->weapon_info_index >= 0);
4268 	wp->weapon_info_index = -1;
4269 	if (wp->swarm_info_ptr != nullptr)
4270 		wp->swarm_info_ptr.reset();
4271 
4272 	if(wp->cscrew_index >= 0) {
4273 		cscrew_delete(wp->cscrew_index);
4274 		wp->cscrew_index = -1;
4275 	}
4276 
4277 	if (wp->missile_list_index >= 0) {
4278 		missle_obj_list_remove(wp->missile_list_index);
4279 		wp->missile_list_index = -1;
4280 	}
4281 
4282 	if (wp->trail_ptr != NULL) {
4283 		trail_object_died(wp->trail_ptr);
4284 		wp->trail_ptr = NULL;
4285 	}
4286 
4287 	if (wp->hud_in_flight_snd_sig.isValid() && snd_is_playing(wp->hud_in_flight_snd_sig))
4288 		snd_stop(wp->hud_in_flight_snd_sig);
4289 
4290 	if (wp->model_instance_num >= 0)
4291 		model_delete_instance(wp->model_instance_num);
4292 
4293 	if (wp->cmeasure_ignore_list != nullptr) {
4294 		delete wp->cmeasure_ignore_list;
4295 		wp->cmeasure_ignore_list = nullptr;
4296 	}
4297 
4298 	if (wp->collisionInfo != nullptr) {
4299 		delete wp->collisionInfo;
4300 		wp->collisionInfo = nullptr;
4301 	}
4302 
4303 	wp->objnum = -1;
4304 	Num_weapons--;
4305 	Assert(Num_weapons >= 0);
4306 }
4307 
4308 /**
4309  * Check if missile is newly locked onto the Player, maybe play a launch warning
4310  */
weapon_maybe_play_warning(weapon * wp)4311 void weapon_maybe_play_warning(weapon *wp)
4312 {
4313 	if ( wp->homing_object == Player_obj ) {
4314 		if ( !(wp->weapon_flags[Weapon::Weapon_Flags::Lock_warning_played]) ) {
4315             wp->weapon_flags.set(Weapon::Weapon_Flags::Lock_warning_played);
4316 			// Use heatlock-warning sound for Heat and Javelin for now
4317 			// Possibly add an additional third sound later
4318 			if ( (Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Homing_heat]) ||
4319 				 (Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Homing_javelin]) ) {
4320 				snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::HEATLOCK_WARN)));
4321 			} else {
4322 				Assert(Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Homing_aspect]);
4323 				snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::ASPECTLOCK_WARN)));
4324 			}
4325 		}
4326 	}
4327 }
4328 
4329 
4330 /**
4331  * Detonate all missiles near this countermeasure.
4332  */
detonate_nearby_missiles(object * killer_objp,object * missile_objp)4333 void detonate_nearby_missiles(object* killer_objp, object* missile_objp)
4334 {
4335 	if(killer_objp->type != OBJ_WEAPON || missile_objp->type != OBJ_WEAPON) {
4336 		Int3();
4337 		return;
4338 	}
4339 
4340 	weapon_info* killer_infop = &Weapon_info[Weapons[killer_objp->instance].weapon_info_index];
4341 
4342 	if (killer_infop->cm_kill_single) {
4343 		weapon* wp = &Weapons[missile_objp->instance];
4344 		if (wp->lifeleft > 0.2f) {
4345 			nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
4346 						killer_infop->name, killer_objp->signature,
4347 						Weapon_info[Weapons[missile_objp->instance].weapon_info_index].name, missile_objp->signature, Framecount));
4348 			wp->lifeleft = 0.2f;
4349 		}
4350 		return;
4351 	}
4352 
4353 	missile_obj* mop = GET_FIRST(&Missile_obj_list);
4354 
4355 	while(mop != END_OF_LIST(&Missile_obj_list)) {
4356 		object* objp = &Objects[mop->objnum];
4357 		weapon* wp = &Weapons[objp->instance];
4358 
4359 		if (iff_x_attacks_y(Weapons[killer_objp->instance].team, wp->team)) {
4360 			if ( Missiontime - wp->creation_time > F1_0/2) {
4361 				if (vm_vec_dist_quick(&killer_objp->pos, &objp->pos) < killer_infop->cm_detonation_rad) {
4362 					if (wp->lifeleft > 0.2f) {
4363 						nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
4364 									killer_infop->name, killer_objp->signature,
4365 									Weapon_info[Weapons[objp->instance].weapon_info_index].name, objp->signature, Framecount));
4366 						wp->lifeleft = 0.2f;
4367 					}
4368 				}
4369 			}
4370 		}
4371 
4372 		mop = mop->next;
4373 	}
4374 }
4375 
4376 /**
4377  * Find an object for weapon #num (object *weapon_objp) to home on due to heat.
4378  */
find_homing_object(object * weapon_objp,int num)4379 void find_homing_object(object *weapon_objp, int num)
4380 {
4381     ship_subsys *target_engines = NULL;
4382 
4383 	weapon* wp = &Weapons[num];
4384 
4385 	weapon_info* wip = &Weapon_info[Weapons[num].weapon_info_index];
4386 
4387 	float best_dist = 99999.9f;
4388 
4389 	// save the old homing object so that multiplayer servers can give the right information
4390 	// to clients if the object changes
4391 	object* old_homing_objp = wp->homing_object;
4392 
4393 	wp->homing_object = &obj_used_list;
4394 
4395 	// only for random acquisition, accrue targets to later pick from randomly
4396 	SCP_vector<object*> prospective_targets;
4397 
4398 	//	Scan all objects, find a weapon to home on.
4399 	for ( object* objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
4400 		if ((objp->type == OBJ_SHIP) || ((objp->type == OBJ_WEAPON) && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Cmeasure])))
4401 		{
4402 			//WMC - Spawn weapons shouldn't go for protected ships
4403 			// ditto for untargeted heat seekers - niffiwan
4404 			if ( (objp->flags[Object::Object_Flags::Protected]) &&
4405 				((wp->weapon_flags[Weapon::Weapon_Flags::Spawned]) || (wip->wi_flags[Weapon::Info_Flags::Untargeted_heat_seeker])) )
4406 				continue;
4407 
4408 			// Spawned weapons should never home in on their parent - even in multiplayer dogfights where they would pass the iff test below
4409 			if ((wp->weapon_flags[Weapon::Weapon_Flags::Spawned]) && (objp == &Objects[weapon_objp->parent]))
4410 				continue;
4411 
4412 			int homing_object_team = obj_team(objp);
4413 			bool can_attack = weapon_has_iff_restrictions(wip) || iff_x_attacks_y(wp->team, homing_object_team);
4414 			if (weapon_target_satisfies_lock_restrictions(wip, objp) && can_attack)
4415 			{
4416 				if ( objp->type == OBJ_SHIP )
4417                 {
4418                     ship* sp  = &Ships[objp->instance];
4419                     ship_info* sip = &Ship_info[sp->ship_info_index];
4420 
4421                     //if the homing weapon is a huge weapon and the ship that is being
4422                     //looked at is not huge, then don't home
4423                     if ((wip->wi_flags[Weapon::Info_Flags::Huge]) &&
4424                         !(sip->is_huge_ship()))
4425                     {
4426                         continue;
4427                     }
4428 
4429 					// AL 2-17-98: If ship is immune to sensors, can't home on it (Sandeep says so)!
4430 					if ( sp->flags[Ship::Ship_Flags::Hidden_from_sensors] ) {
4431 						continue;
4432 					}
4433 
4434 					// Goober5000: if missiles can't home on sensor-ghosted ships,
4435 					// they definitely shouldn't home on stealth ships
4436 					if ( sp->flags[Ship::Ship_Flags::Stealth] && (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_heat_seeker_stealth_bug]) ) {
4437 						continue;
4438 					}
4439 
4440                     if (wip->wi_flags[Weapon::Info_Flags::Homing_javelin])
4441                     {
4442                         target_engines = ship_get_closest_subsys_in_sight(sp, SUBSYSTEM_ENGINE, &weapon_objp->pos);
4443 
4444                         if (!target_engines)
4445                             continue;
4446                     }
4447 
4448 					//	MK, 9/4/99.
4449 					//	If this is a player object, make sure there aren't already too many homers.
4450 					//	Only in single player.  In multiplayer, we don't want to restrict it in dogfight on team vs. team.
4451 					//	For co-op, it's probably also OK.
4452 					if (!( Game_mode & GM_MULTIPLAYER ) && objp == Player_obj) {
4453 						int	num_homers = compute_num_homing_objects(objp);
4454 						if (The_mission.ai_profile->max_allowed_player_homers[Game_skill_level] < num_homers)
4455 							continue;
4456 					}
4457 				}
4458                 else if (objp->type == OBJ_WEAPON)
4459 				{
4460                     //don't attempt to home on weapons if the weapon is a huge weapon or is a javelin homing weapon.
4461                     if (wip->wi_flags[Weapon::Info_Flags::Huge, Weapon::Info_Flags::Homing_javelin])
4462                         continue;
4463 
4464                     //don't look for local ssms that are gone for the time being
4465 					if (Weapons[objp->instance].lssm_stage == 3)
4466 						continue;
4467 				}
4468 
4469 				vec3d vec_to_object;
4470 				float dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
4471 
4472 				if (objp->type == OBJ_WEAPON && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Cmeasure])) {
4473 					dist *= 0.5f;
4474 				}
4475 
4476 				float dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
4477 
4478 				if (dot > wip->fov) {
4479 					if (wip->auto_target_method == HomingAcquisitionType::CLOSEST && dist < best_dist) {
4480 						best_dist = dist;
4481 						wp->homing_object	= objp;
4482 						wp->target_sig		= objp->signature;
4483 						wp->homing_subsys	= target_engines;
4484 
4485 						cmeasure_maybe_alert_success(objp);
4486 					} else { // HomingAcquisitionType::RANDOM
4487 						prospective_targets.push_back(objp);
4488 					}
4489 				}
4490 			}
4491 		}
4492 	}
4493 
4494 	if (wip->auto_target_method == HomingAcquisitionType::RANDOM && prospective_targets.size() > 0) {
4495 		// pick a random target from the valid ones
4496 		object* target = prospective_targets[Random::next((int)prospective_targets.size())];
4497 
4498 		wp->homing_object = target;
4499 		wp->target_sig = target->signature;
4500 		wp->homing_subsys = nullptr;
4501 
4502 		if (wip->wi_flags[Weapon::Info_Flags::Homing_javelin] && target->type == OBJ_SHIP) {
4503 			wp->homing_subsys = ship_get_closest_subsys_in_sight(&Ships[target->instance], SUBSYSTEM_ENGINE, &weapon_objp->pos);
4504 		}
4505 	}
4506 
4507 	if (wp->homing_object == Player_obj)
4508 		weapon_maybe_play_warning(wp);
4509 
4510 	// if the old homing object is different that the new one, send a packet to clients
4511 	if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
4512 		send_homing_weapon_info( num );
4513 	}
4514 }
4515 
4516 /**
4517  * For all homing weapons, see if they should be decoyed by a countermeasure.
4518  */
find_homing_object_cmeasures(const SCP_vector<object * > & cmeasure_list)4519 void find_homing_object_cmeasures(const SCP_vector<object*> &cmeasure_list)
4520 {
4521 	for (object *weapon_objp = GET_FIRST(&obj_used_list); weapon_objp != END_OF_LIST(&obj_used_list); weapon_objp = GET_NEXT(weapon_objp) ) {
4522 		if (weapon_objp->type == OBJ_WEAPON) {
4523 			weapon *wp = &Weapons[weapon_objp->instance];
4524 			weapon_info	*wip = &Weapon_info[wp->weapon_info_index];
4525 
4526 			if (wip->is_homing()) {
4527 				float best_dot = wip->fov;
4528 				for (auto cit = cmeasure_list.cbegin(); cit != cmeasure_list.cend(); ++cit) {
4529 					//don't have a weapon try to home in on itself
4530 					if (*cit == weapon_objp)
4531 						continue;
4532 
4533 					weapon *cm_wp = &Weapons[(*cit)->instance];
4534 					weapon_info *cm_wip = &Weapon_info[cm_wp->weapon_info_index];
4535 
4536 					//don't have a weapon try to home in on missiles fired by the same team, unless its the traitor team.
4537 					if ((wp->team == cm_wp->team) && (wp->team != Iff_traitor))
4538 						continue;
4539 
4540 					vec3d	vec_to_object;
4541 					float dist = vm_vec_normalized_dir(&vec_to_object, &(*cit)->pos, &weapon_objp->pos);
4542 
4543 					if (dist < cm_wip->cm_effective_rad)
4544 					{
4545 						float chance;
4546 
4547 						if (wp->cmeasure_ignore_list == nullptr) {
4548 							wp->cmeasure_ignore_list = new SCP_vector<int>;
4549 						}
4550 						else {
4551 							bool found = false;
4552 							for (auto ii = wp->cmeasure_ignore_list->cbegin(); ii != wp->cmeasure_ignore_list->cend(); ++ii) {
4553 								if ((*cit)->signature == *ii) {
4554 									nprintf(("CounterMeasures", "Weapon (%s-%04i) already seen CounterMeasure (%s-%04i) Frame: %i\n",
4555 												wip->name, weapon_objp->instance, cm_wip->name, (*cit)->signature, Framecount));
4556 									found = true;
4557 									break;
4558 								}
4559 							}
4560 							if (found) {
4561 								continue;
4562 							}
4563 						}
4564 
4565 						if (wip->wi_flags[Weapon::Info_Flags::Homing_aspect]) {
4566 							// aspect seeker this likely to chase a countermeasure
4567 							chance = cm_wip->cm_aspect_effectiveness/wip->seeker_strength;
4568 						} else {
4569 							// heat seeker and javelin HS this likely to chase a countermeasure
4570 							chance = cm_wip->cm_heat_effectiveness/wip->seeker_strength;
4571 						}
4572 
4573 						// remember this cmeasure so it can be ignored in future
4574 						wp->cmeasure_ignore_list->push_back((*cit)->signature);
4575 
4576 						if (frand() >= chance) {
4577 							// failed to decoy
4578 							nprintf(("CounterMeasures", "Weapon (%s-%04i) ignoring CounterMeasure (%s-%04i) Frame: %i\n",
4579 										wip->name, weapon_objp->instance, cm_wip->name, (*cit)->signature, Framecount));
4580 						}
4581 						else {
4582 							// successful decoy, maybe chase the new cm
4583 							float dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
4584 
4585 							if (dot > best_dot)
4586 							{
4587 								best_dot = dot;
4588 								wp->homing_object = (*cit);
4589 								cmeasure_maybe_alert_success((*cit));
4590 								nprintf(("CounterMeasures", "Weapon (%s-%04i) chasing CounterMeasure (%s-%04i) Frame: %i\n",
4591 											wip->name, weapon_objp->instance, cm_wip->name, (*cit)->signature, Framecount));
4592 							}
4593 						}
4594 					}
4595 				}
4596 			}
4597 		}
4598 	}
4599 }
4600 
4601 /**
4602  * Find object with signature "sig" and make weapon home on it.
4603  */
find_homing_object_by_sig(object * weapon_objp,int sig)4604 void find_homing_object_by_sig(object *weapon_objp, int sig)
4605 {
4606 	ship_obj		*sop;
4607 	weapon		*wp;
4608 	object		*old_homing_objp;
4609 
4610 	wp = &Weapons[weapon_objp->instance];
4611 
4612 	// save the old object so that multiplayer masters know whether to send a homing update packet
4613 	old_homing_objp = wp->homing_object;
4614 
4615 	sop = GET_FIRST(&Ship_obj_list);
4616 	while(sop != END_OF_LIST(&Ship_obj_list)) {
4617 		object	*objp;
4618 
4619 		objp = &Objects[sop->objnum];
4620 		if (objp->signature == sig) {
4621 			wp->homing_object = objp;
4622 			wp->target_sig = objp->signature;
4623 			break;
4624 		}
4625 
4626 		sop = sop->next;
4627 	}
4628 
4629 	// if the old homing object is different that the new one, send a packet to clients
4630 	if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
4631 		send_homing_weapon_info( weapon_objp->instance );
4632 	}
4633 }
4634 
aspect_should_lose_target(weapon * wp)4635 bool aspect_should_lose_target(weapon* wp)
4636 {
4637 	Assert(wp != NULL);
4638 
4639 	if (wp->homing_object->signature != wp->target_sig) {
4640 		if (wp->homing_object->type == OBJ_WEAPON)
4641 		{
4642 			weapon_info* target_info = &Weapon_info[Weapons[wp->homing_object->instance].weapon_info_index];
4643 
4644 			if (target_info->wi_flags[Weapon::Info_Flags::Cmeasure])
4645 			{
4646 				// Check if we can home on this countermeasure
4647 				bool home_on_cmeasure = The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_lock_countermeasure]
4648 					|| target_info->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on];
4649 
4650 				if (!home_on_cmeasure)
4651 				{
4652 					return true;
4653 				}
4654 			}
4655 		}
4656 	}
4657 
4658 	return false;
4659 }
4660 
4661 /**
4662  * Make weapon num home.  It's also object *obj.
4663  */
weapon_home(object * obj,int num,float frame_time)4664 void weapon_home(object *obj, int num, float frame_time)
4665 {
4666 	weapon		*wp;
4667 	weapon_info	*wip;
4668 	object		*hobjp;
4669 
4670 	Assert(obj->type == OBJ_WEAPON);
4671 	Assert(obj->instance == num);
4672 	wp = &Weapons[num];
4673 	wip = &Weapon_info[wp->weapon_info_index];
4674 	hobjp = Weapons[num].homing_object;
4675 
4676 	//local ssms home only in stages 1 and 5
4677 	if ( (wp->lssm_stage==2) || (wp->lssm_stage==3) || (wp->lssm_stage==4))
4678 		return;
4679 
4680 	float max_speed;
4681 
4682 	if ((wip->wi_flags[Weapon::Info_Flags::Local_ssm]) && (wp->lssm_stage==5))
4683 		max_speed=wip->lssm_stage5_vel;
4684 	else
4685 		max_speed=wip->max_speed;
4686 
4687 	//	If not [free-flight-time] gone by, don't home yet.
4688 	// Goober5000 - this has been fixed back to more closely follow the original logic.  Remember, the retail code
4689 	// had 0.5 second of free flight time, the first half of which was spent ramping up to full speed.
4690 	if ((hobjp == &obj_used_list) || ( f2fl(Missiontime - wp->creation_time) < (wip->free_flight_time / 2) )) {
4691 		if (f2fl(Missiontime - wp->creation_time) > wip->free_flight_time) {
4692 			// If this is a heat seeking homing missile and [free-flight-time] has elapsed since firing
4693 			// and we don't have a target (else we wouldn't be inside the IF), find a new target.
4694 			if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
4695 				find_homing_object(obj, num);
4696 			}
4697 			// modders may want aspect homing missiles to die if they lose their target
4698 			else if (wip->is_locked_homing() && wip->wi_flags[Weapon::Info_Flags::Die_on_lost_lock]) {
4699 				if (wp->lifeleft > 0.5f) {
4700 					wp->lifeleft = frand_range(0.1f, 0.5f); // randomise a bit to avoid multiple missiles detonating in one frame
4701 				}
4702 				return;
4703 			}
4704 		}
4705 
4706 		if (wip->acceleration_time > 0.0f) {
4707 			if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
4708 				float t;
4709 
4710 				t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
4711 				obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
4712 			}
4713 		}
4714 		// since free_flight_time can now be 0, guard against that
4715 		else if (wip->free_flight_time > 0.0f) {
4716 			if (obj->phys_info.speed > max_speed) {
4717 				obj->phys_info.speed -= frame_time * 4;
4718 			} else if ((obj->phys_info.speed < max_speed * wip->free_flight_speed_factor) && (wip->wi_flags[Weapon::Info_Flags::Homing_heat])) {
4719 				obj->phys_info.speed = max_speed * wip->free_flight_speed_factor;
4720 			}
4721 		}
4722 		// no free_flight_time, so immediately set desired speed
4723 		else {
4724 			obj->phys_info.speed = max_speed;
4725 		}
4726 
4727 		// set velocity using whatever speed we have
4728 		vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
4729 
4730 		return;
4731 	}
4732 
4733 	if (wip->acceleration_time > 0.0f) {
4734 		if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
4735 			float t;
4736 
4737 			t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
4738 			obj->phys_info.speed = wp->launch_speed + (wp->weapon_max_vel - wp->launch_speed) * t;
4739 			vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
4740 		}
4741 	}
4742 
4743 	// AL 4-8-98: If original target for aspect lock missile is lost, stop homing
4744 	// WCS - or javelin
4745 	if (wip->is_locked_homing()) {
4746 		if ( wp->target_sig > 0 ) {
4747 			if (aspect_should_lose_target(wp))
4748 			{
4749 				wp->homing_object = &obj_used_list;
4750 				return;
4751 			}
4752 		}
4753 	}
4754 
4755   	// AL 4-13-98: Stop homing on a subsystem if parent ship has changed
4756 	if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
4757 		if ( wp->target_sig > 0 ) {
4758 			if ( wp->homing_object->signature != wp->target_sig ) {
4759 				wp->homing_subsys = NULL;
4760 			}
4761 		}
4762 	}
4763 
4764 	// If target subsys is dead make missile pick random spot on target as attack point.
4765 	if (wp->homing_subsys != NULL) {
4766 		if (wp->homing_subsys->flags[Ship::Subsystem_Flags::Missiles_ignore_if_dead]) {
4767 			if ((wp->homing_subsys->max_hits > 0) && (wp->homing_subsys->current_hits <= 0)) {
4768 				wp->homing_object = &obj_used_list;
4769 				return;
4770 			}
4771 		}
4772 	}
4773 
4774 	// Make sure Javelin HS missiles always home on engine subsystems if ships
4775 	if ((wip->wi_flags[Weapon::Info_Flags::Homing_javelin]) &&
4776 		(hobjp->type == OBJ_SHIP) &&
4777 		(wp->target_sig > 0) &&
4778 		(wp->homing_subsys != NULL) &&
4779 		(wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE)) {
4780 			int sindex = ship_get_by_signature(wp->target_sig);
4781 			if (sindex >= 0) {
4782 				ship *enemy = &Ships[sindex];
4783 				wp->homing_subsys = ship_get_closest_subsys_in_sight(enemy, SUBSYSTEM_ENGINE, &Objects[wp->objnum].pos);
4784 			}
4785 	}
4786 
4787 	// If Javelin HS missile doesn't home in on a subsystem but homing in on a
4788 	// ship, lose lock alltogether
4789 	// Javelins can only home in one Engines or bombs.
4790 	if ((wip->wi_flags[Weapon::Info_Flags::Homing_javelin]) &&
4791 		(hobjp->type == OBJ_SHIP) &&
4792 		(wp->target_sig > 0) &&
4793 		(wp->homing_subsys == NULL)) {
4794 			wp->homing_object = &obj_used_list;
4795 			return;
4796 	}
4797 
4798 	// TODO maybe add flag to allow WF_LOCKED_HOMING to lose target when target is dead
4799 
4800 	switch (hobjp->type) {
4801 	case OBJ_NONE:
4802 		if (wip->is_locked_homing()) {
4803 			find_homing_object_by_sig(obj, wp->target_sig);
4804 		}
4805 		else {
4806 			find_homing_object(obj, num);
4807 		}
4808 		return;
4809 		break;
4810 	case OBJ_SHIP:
4811 		if (hobjp->signature != wp->target_sig) {
4812 			if (wip->is_locked_homing()) {
4813 				find_homing_object_by_sig(obj, wp->target_sig);
4814 			}
4815 			else {
4816 				find_homing_object(obj, num);
4817 			}
4818 			return;
4819 		}
4820 		break;
4821 	case OBJ_WEAPON:
4822 	{
4823 		weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index];
4824 
4825 		bool home_on_cmeasure = The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_lock_countermeasure]
4826 			|| hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on];
4827 
4828 		// don't home on countermeasures or non-bombs, that's handled elsewhere
4829 		if (((hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure]) && !home_on_cmeasure))
4830 		{
4831 			break;
4832 		}
4833 		else if (!(hobj_infop->wi_flags[Weapon::Info_Flags::Bomb]))
4834 		{
4835 			break;
4836 		}
4837 
4838 		if (wip->is_locked_homing()) {
4839 			find_homing_object_by_sig(obj, wp->target_sig);
4840 		}
4841 		else {
4842 			find_homing_object(obj, num);
4843 		}
4844 		break;
4845 	}
4846 	default:
4847 		return;
4848 	}
4849 
4850 	//	See if this weapon is the nearest homing object to the object it is homing on.
4851 	//	If so, update some fields in the target object's ai_info.
4852 	if (hobjp != &obj_used_list) {
4853 
4854 		if (hobjp->type == OBJ_SHIP) {
4855 			ai_info	*aip;
4856 
4857 			aip = &Ai_info[Ships[hobjp->instance].ai_index];
4858 
4859 			vec3d target_vector;
4860 			float dist = vm_vec_normalized_dir(&target_vector, &hobjp->pos, &obj->pos);
4861 
4862 			// add this missile to nearest_locked_object if its the closest
4863 			// with the flag, only do it if its also mostly pointed at its target
4864 			if (((aip->nearest_locked_object == -1) || (dist < aip->nearest_locked_distance)) &&
4865 				(!(The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]) || vm_vec_dot(&target_vector, &obj->orient.vec.fvec) > 0.5f)) {
4866 				aip->nearest_locked_object = OBJ_INDEX(obj);
4867 				aip->nearest_locked_distance = dist;
4868 			}
4869 		}
4870 	}
4871 
4872 	//	If the object it is homing on is still valid, home some more!
4873 	if (hobjp != &obj_used_list) {
4874 		float		old_dot, vel;
4875 		vec3d	vec_to_goal;
4876 		vec3d	target_pos;	// position of what the homing missile is seeking
4877 
4878 		vm_vec_zero(&target_pos);
4879 
4880 		if (wp->weapon_flags[Weapon::Weapon_Flags::Overridden_homing]) {
4881 			target_pos = wp->homing_pos;
4882 		} else {
4883 			// the homing missile may be seeking a subsystem on a ship.  If so, we need to calculate the
4884 			// world coordinates of that subsystem so the homing missile can seek it out.
4885 			//	For now, March 7, 1997, MK, heat seeking homing missiles will be able to home on
4886 			//	any subsystem.  Probably makes sense for them to only home on certain kinds of subsystems.
4887 			if ((wp->homing_subsys != nullptr) && !(wip->wi_flags[Weapon::Info_Flags::Non_subsys_homing]) &&
4888 			    hobjp->type == OBJ_SHIP) {
4889 				get_subsystem_world_pos(hobjp, Weapons[num].homing_subsys, &target_pos);
4890 				wp->homing_pos = target_pos; // store the homing position in weapon data
4891 				Assert(!vm_is_vec_nan(&wp->homing_pos));
4892 			} else {
4893 				float fov;
4894 				float dist;
4895 
4896 				dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
4897 				if (hobjp->type == OBJ_WEAPON) {
4898 					weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index];
4899 					if (hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure] && dist < hobj_infop->cm_detonation_rad) {
4900 						//	Make this missile detonate soon.  Not right away, not sure why.  Seems better.
4901 						if (iff_x_attacks_y(Weapons[hobjp->instance].team, wp->team)) {
4902 							detonate_nearby_missiles(hobjp, obj);
4903 							return;
4904 						}
4905 					}
4906 				}
4907 
4908 				fov = -1.0f;
4909 
4910 				int pick_homing_point = 0;
4911 				if (IS_VEC_NULL(&wp->homing_pos)) {
4912 					pick_homing_point = 1;
4913 				}
4914 
4915 				//	Update homing position if it hasn't been set, you're within 500 meters, or every half second,
4916 				//approximately. 	For large objects, don't lead them.
4917 				if (hobjp->radius < 40.0f) {
4918 					target_pos     = hobjp->pos;
4919 					wp->homing_pos = target_pos;
4920 					// only recalculate a homing point if you are not a client.  You will get the new point from the server.
4921 				} else if (pick_homing_point || (!MULTIPLAYER_CLIENT && ((dist < 500.0f) || (rand_chance(flFrametime, 2.0f))))) {
4922 
4923 					if (hobjp->type == OBJ_SHIP) {
4924 						if (!pick_homing_point) {
4925 							// ensure that current attack point is only updated in world coords (ie not pick a different
4926 							// vertex)
4927 							wp->pick_big_attack_point_timestamp = 0;
4928 						}
4929 
4930 						if (pick_homing_point && !(wip->wi_flags[Weapon::Info_Flags::Non_subsys_homing])) {
4931 							// If *any* player is parent of homing missile, then use position where lock indicator is
4932 							if (Objects[obj->parent].flags[Object::Object_Flags::Player_ship]) {
4933 								player* pp;
4934 
4935 								// determine the player
4936 								pp = Player;
4937 
4938 								if (Game_mode & GM_MULTIPLAYER) {
4939 									int pnum;
4940 
4941 									pnum = multi_find_player_by_object(&Objects[obj->parent]);
4942 									if (pnum != -1) {
4943 										pp = Net_players[pnum].m_player;
4944 									}
4945 								}
4946 
4947 								// If player has apect lock, we don't want to find a homing point on the closest
4948 								// octant... setting the timestamp to 0 ensures this.
4949 								if (wip->is_locked_homing()) {
4950 									wp->pick_big_attack_point_timestamp = 0;
4951 								} else {
4952 									wp->pick_big_attack_point_timestamp = 1;
4953 								}
4954 
4955 								if (pp && pp->locking_subsys) {
4956 									wp->big_attack_point = pp->locking_subsys->system_info->pnt;
4957 								} else {
4958 									vm_vec_zero(&wp->big_attack_point);
4959 								}
4960 							}
4961 						}
4962 
4963 						ai_big_pick_attack_point(hobjp, obj, &target_pos, fov);
4964 
4965 					} else {
4966 						target_pos = hobjp->pos;
4967 					}
4968 
4969 					wp->homing_pos = target_pos;
4970 					Assert(!vm_is_vec_nan(&wp->homing_pos));
4971 				} else
4972 					target_pos = wp->homing_pos;
4973 			}
4974 		}
4975 
4976 		//	Couldn't find a lock.
4977 		if (IS_VEC_NULL(&target_pos))
4978 			return;
4979 
4980 		//	Cause aspect seeking weapon to home at target's predicted position.
4981 		//	But don't use predicted position if dot product small or negative.
4982 		//	If do this, with a ship headed towards missile, could choose a point behind missile.
4983 		float	dist_to_target, time_to_target;
4984 
4985 		dist_to_target = vm_vec_normalized_dir(&vec_to_goal, &target_pos, &obj->pos);
4986 		time_to_target = dist_to_target/max_speed;
4987 
4988 		vec3d	tvec;
4989 		tvec = obj->phys_info.vel;
4990 		vm_vec_normalize(&tvec);
4991 
4992 		old_dot = vm_vec_dot(&tvec, &vec_to_goal);
4993 
4994 		//	If a weapon has missed its target, detonate it.
4995 		//	This solves the problem of a weapon circling the center of a subsystem that has been blown away.
4996 		//	Problem: It does not do impact damage, just proximity damage.
4997 		if ((dist_to_target < flFrametime * obj->phys_info.speed * 4.0f + 10.0f) &&
4998             (old_dot < wip->fov) &&
4999             (wp->lifeleft > 0.01f) &&
5000             (wp->homing_object != &obj_used_list) &&
5001             (wp->homing_object->type == OBJ_SHIP) &&
5002             (wip->subtype != WP_LASER))
5003         {
5004             wp->lifeleft = 0.01f;
5005         }
5006 
5007 		//	Only lead target if more than one second away.  Otherwise can miss target.  I think this
5008 		//	is what's causing Harbingers to miss the super destroyer. -- MK, 4/15/98
5009 		if ((old_dot > 0.1f) && (time_to_target > 0.1f)) {
5010 			if (wip->wi_flags[Weapon::Info_Flags::Variable_lead_homing]) {
5011 				vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, (0.33f * wip->target_lead_scaler * MIN(time_to_target, 6.0f)));
5012 			} else if (wip->is_locked_homing()) {
5013 				vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, MIN(time_to_target, 2.0f));
5014 			}
5015 		}
5016 
5017 		//	If a HEAT seeking (rather than ASPECT seeking) homing missile, verify that target is in viewcone.
5018 		if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
5019 			if ((old_dot < wip->fov) && (dist_to_target > wip->shockwave.inner_rad*1.1f)) {	//	Delay finding new target one frame to allow detonation.
5020 				find_homing_object(obj, num);
5021 				return;			//	Maybe found a new homing object.  Return, process more next frame.
5022 			} else	//	Subtract out life based on how far from target this missile points.
5023 				if ((wip->fov < 0.95f) && !(wip->wi_flags[Weapon::Info_Flags::No_life_lost_if_missed])) {
5024 					wp->lifeleft -= flFrametime * (0.95f - old_dot);
5025 				}
5026 		} else if (wip->is_locked_homing()) {	//	subtract life as if max turn is 90 degrees.
5027 			if ((wip->fov < 0.95f) && !(wip->wi_flags[Weapon::Info_Flags::No_life_lost_if_missed]))
5028 				wp->lifeleft -= flFrametime * (0.95f - old_dot);
5029 		} else {
5030 			Warning(LOCATION, "Tried to make weapon '%s' home, but found it wasn't aspect-seeking or heat-seeking or a Javelin!", wip->name);
5031 		}
5032 
5033 
5034 		//	Control speed based on dot product to goal.  If close to straight ahead, move
5035 		//	at max speed, else move slower based on how far from ahead.
5036 		//	Asteroth - but not for homing primaries
5037 		if (old_dot < 0.90f && wip->subtype != WP_LASER) {
5038 			obj->phys_info.speed = MAX(0.2f, old_dot* (float) fabs(old_dot));
5039 			if (obj->phys_info.speed < max_speed*0.75f)
5040 				obj->phys_info.speed = max_speed*0.75f;
5041 		} else
5042 			obj->phys_info.speed = max_speed;
5043 
5044 
5045 		if (wip->acceleration_time > 0.0f) {
5046 			// Ramp up speed linearly for the given duration
5047 			if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
5048 				float t;
5049 
5050 				t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
5051 				obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
5052 			}
5053 		} else if (!(wip->wi_flags[Weapon::Info_Flags::No_homing_speed_ramp]) && Missiontime - wp->creation_time < i2f(1)) {
5054 			// Default behavior:
5055 			// For first second of weapon's life, it doesn't fly at top speed.  It ramps up.
5056 			float	t;
5057 
5058 			t = f2fl(Missiontime - wp->creation_time);
5059 			obj->phys_info.speed *= t*t;
5060 		}
5061 
5062 		Assert( obj->phys_info.speed > 0.0f );
5063 
5064 		vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
5065 
5066 		// turn the missile towards the target only if non-swarm.  Homing swarm missiles choose
5067 		// a different vector to turn towards, this is done in swarm_update_direction().
5068 		if ( wp->swarm_info_ptr == nullptr ) {
5069 			ai_turn_towards_vector(&target_pos, obj, nullptr, nullptr, 0.0f, 0, nullptr);
5070 			vel = vm_vec_mag(&obj->phys_info.desired_vel);
5071 
5072 			vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, vel);
5073 
5074 		}
5075 	}
5076 }
5077 
5078 
5079 // moved out of weapon_process_post() so it can be called from either -post() or -pre() depending on Framerate_independent_turning
weapon_update_missiles(object * obj,float frame_time)5080 void weapon_update_missiles(object* obj, float  frame_time) {
5081 
5082 	Assertion(obj->type == OBJ_WEAPON, "weapon_update_missiles called on a non-weapon object");
5083 
5084 	weapon* wp = &Weapons[obj->instance];
5085 	weapon_info* wip = &Weapon_info[wp->weapon_info_index];
5086 
5087 	// a single player or multiplayer server function -- it affects actual weapon movement.
5088 	if (wip->is_homing() && !(wp->weapon_flags[Weapon::Weapon_Flags::No_homing])) {
5089 		vec3d pos_hold = wp->homing_pos;
5090 		int target_hold = wp->target_sig;
5091 
5092 		weapon_home(obj, obj->instance, frame_time);
5093 
5094 		// tell the server to send an update of the missile.
5095 		if (MULTIPLAYER_MASTER && !IS_VEC_NULL(&wp->homing_pos)) {
5096 			wp->weapon_flags.set(Weapon::Weapon_Flags::Multi_homing_update_needed);
5097 			// if the first update for a weapon has already been sent, then we do not need to send any others unless the homing_pos
5098 			// drastically changes.
5099 			if (wp->weapon_flags[Weapon::Weapon_Flags::Multi_Update_Sent]) {
5100 				vm_vec_sub2(&pos_hold, &wp->homing_pos);
5101 				if ((vm_vec_mag(&pos_hold) < 1.0f) && (wp->target_sig == target_hold)) {
5102 					wp->weapon_flags.remove(Weapon::Weapon_Flags::Multi_homing_update_needed);
5103 				}
5104 			}
5105 		}
5106 
5107 		// If this is a swarm type missile,
5108 		if (wp->swarm_info_ptr != nullptr) {
5109 			swarm_update_direction(obj, wp->swarm_info_ptr.get());
5110 		}
5111 	}
5112 	else if (wip->acceleration_time > 0.0f) {
5113 		if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
5114 			float t;
5115 
5116 			t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
5117 			obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
5118 		}
5119 		else {
5120 			obj->phys_info.speed = wip->max_speed;
5121 			obj->phys_info.flags |= PF_CONST_VEL; // Max speed reached, can use simpler physics calculations now
5122 		}
5123 
5124 		vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
5125 	}
5126 }
5127 
5128 // as Mike K did with ships -- break weapon into process_pre and process_post for code to execute
5129 // before and after physics movement
5130 
weapon_process_pre(object * obj,float frame_time)5131 void weapon_process_pre( object *obj, float  frame_time)
5132 {
5133 	if(obj->type != OBJ_WEAPON)
5134 		return;
5135 
5136 	weapon *wp = &Weapons[obj->instance];
5137 	weapon_info *wip = &Weapon_info[wp->weapon_info_index];
5138 
5139 	// if the object is a corkscrew style weapon, process it now
5140 	if((obj->type == OBJ_WEAPON) && (Weapons[obj->instance].cscrew_index >= 0)){
5141 		cscrew_process_pre(obj);
5142 	}
5143 
5144 	//WMC - Originally flak_maybe_detonate, moved here.
5145 	if(wp->det_range > 0.0f)
5146 	{
5147 		vec3d temp;
5148 		vm_vec_sub(&temp, &obj->pos, &wp->start_pos);
5149 		if(vm_vec_mag(&temp) >= wp->det_range){
5150 			weapon_detonate(obj);
5151 		}
5152 	}
5153 
5154 	//WMC - Maybe detonate weapon anyway!
5155 	if(wip->det_radius > 0.0f)
5156 	{
5157 		if((wp->homing_object != &obj_used_list) && (wp->homing_object->type != 0))
5158 		{
5159 			if(!IS_VEC_NULL(&wp->homing_pos) && vm_vec_dist(&wp->homing_pos, &obj->pos) <= wip->det_radius)
5160 			{
5161 				weapon_detonate(obj);
5162 			}
5163 		} else if(wp->target_num > -1)
5164 		{
5165 			if(vm_vec_dist(&obj->pos, &Objects[wp->target_num].pos) <= wip->det_radius)
5166 			{
5167 				weapon_detonate(obj);
5168 			}
5169 		}
5170 	}
5171 
5172 	// If this flag is false missile turning is evaluated in weapon_process_post()
5173 	if (Framerate_independent_turning) {
5174 		weapon_update_missiles(obj, frame_time);
5175 	}
5176 }
5177 
5178 int	Homing_hits = 0, Homing_misses = 0;
5179 
5180 
MONITOR(NumWeapons)5181 MONITOR( NumWeapons )
5182 
5183 /**
5184  * Maybe play a "whizz sound" if close enough to view position
5185  */
5186 void weapon_maybe_play_flyby_sound(object *weapon_objp, weapon *wp)
5187 {
5188 	// don't play flyby sounds too close together
5189 	if ( !timestamp_elapsed(Weapon_flyby_sound_timer) ) {
5190 		return;
5191 	}
5192 
5193 	if ( !(wp->weapon_flags[Weapon::Weapon_Flags::Played_flyby_sound]) ) {
5194 		float		dist, dot, radius;
5195 
5196 		if ( (Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Corkscrew]) ) {
5197 			dist = vm_vec_dist_quick(&weapon_objp->last_pos, &Eye_position);
5198 		} else {
5199 			dist = vm_vec_dist_quick(&weapon_objp->pos, &Eye_position);
5200 		}
5201 
5202 		if ( Viewer_obj ) {
5203 			radius = Viewer_obj->radius;
5204 		} else {
5205 			radius = 0.0f;
5206 		}
5207 
5208 		if ( (dist > radius) && (dist < 55) ) {
5209 			vec3d	vec_to_weapon;
5210 
5211 			vm_vec_sub(&vec_to_weapon, &weapon_objp->pos, &Eye_position);
5212 			vm_vec_normalize(&vec_to_weapon);
5213 
5214 			// ensure laser is in front of eye
5215 			dot = vm_vec_dot(&vec_to_weapon, &Eye_matrix.vec.fvec);
5216 			if ( dot < 0.1 ) {
5217 				return;
5218 			}
5219 
5220 			// ensure that laser is moving in similar direction to fvec
5221 			dot = vm_vec_dot(&vec_to_weapon, &weapon_objp->orient.vec.fvec);
5222 
5223 			if ( (dot < -0.80) && (dot > -0.98) ) {
5224 				if(Weapon_info[wp->weapon_info_index].flyby_snd.isValid()) {
5225 					snd_play_3d( gamesnd_get_game_sound(Weapon_info[wp->weapon_info_index].flyby_snd), &weapon_objp->pos, &Eye_position );
5226 				} else {
5227 					if ( Weapon_info[wp->weapon_info_index].subtype == WP_LASER ) {
5228 						snd_play_3d( gamesnd_get_game_sound(GameSounds::WEAPON_FLYBY), &weapon_objp->pos, &Eye_position );
5229 					}
5230 				}
5231 				Weapon_flyby_sound_timer = timestamp(200);
5232                 wp->weapon_flags.set(Weapon::Weapon_Flags::Played_flyby_sound);
5233 			}
5234 		}
5235 	}
5236 }
5237 
weapon_set_state(weapon_info * wip,weapon * wp,WeaponState state)5238 static void weapon_set_state(weapon_info* wip, weapon* wp, WeaponState state)
5239 {
5240 	if (wp->weapon_state == state)
5241 	{
5242 		// No change
5243 		return;
5244 	}
5245 
5246 	wp->weapon_state = state;
5247 
5248 	auto map_entry = wip->state_effects.find(wp->weapon_state);
5249 
5250 	if (map_entry != wip->state_effects.end())
5251 	{
5252 		auto source = particle::ParticleManager::get()->createSource(map_entry->second);
5253 
5254 		source.moveToObject(&Objects[wp->objnum], &vmd_zero_vector);
5255 		source.setWeaponState(wp->weapon_state);
5256 
5257 		source.finish();
5258 	}
5259 }
5260 
weapon_update_state(weapon * wp)5261 static void weapon_update_state(weapon* wp)
5262 {
5263 	weapon_info* wip = &Weapon_info[wp->weapon_info_index];
5264 
5265 	if (wip->subtype == WP_LASER)
5266 	{
5267 		weapon_set_state(wip, wp, WeaponState::NORMAL);
5268 		return;
5269 	}
5270 
5271 	auto infree_flight = false;
5272 	if (wip->free_flight_time)
5273 	{
5274 		fix lifetime = Missiontime - wp->creation_time;
5275 		if (lifetime < fl2f(wip->free_flight_time))
5276 		{
5277 			weapon_set_state(wip, wp, WeaponState::FREEFLIGHT);
5278 			infree_flight = true;
5279 		}
5280 		else if (lifetime >= fl2f(wip->free_flight_time) &&        // V indicates a valid homing_objp V
5281 			(lifetime - Frametime) <= fl2f(wip->free_flight_time) && wp->homing_object != &obj_used_list)
5282 		{
5283 			weapon_set_state(wip, wp, WeaponState::IGNITION);
5284 			infree_flight = true;
5285 		}
5286 	}
5287 
5288 	if (!infree_flight)
5289 	{    // V same here; means no homing_objp V
5290 		if (wp->homing_object == &obj_used_list)
5291 		{
5292 			weapon_set_state(wip, wp, WeaponState::UNHOMED_FLIGHT);
5293 		}
5294 		else
5295 		{
5296 			weapon_set_state(wip, wp, WeaponState::HOMED_FLIGHT);
5297 		}
5298 	}
5299 }
5300 
5301 // process a weapon after physics movement.  MWA reorders some of the code on 8/13 for multiplayer.  When
5302 // adding something to this function, decide whether or not a client in a multiplayer game needs to do
5303 // what is normally done in a single player game.  Things like plotting an object on a radar, effect
5304 // for exhaust are things that are done on all machines.  Things which calculate weapon targets, new
5305 // velocities, etc, are server only functions and should go after the if ( !MULTIPLAYER_MASTER ) statement
5306 // See Allender if you cannot decide what to do.
weapon_process_post(object * obj,float frame_time)5307 void weapon_process_post(object * obj, float frame_time)
5308 {
5309 	int			num;
5310 	weapon_info	*wip;
5311 	weapon		*wp;
5312 
5313 	MONITOR_INC( NumWeapons, 1 );
5314 
5315 	Assert(obj->type == OBJ_WEAPON);
5316 
5317 	num = obj->instance;
5318 
5319 #ifndef NDEBUG
5320 	int objnum;
5321 	objnum = OBJ_INDEX(obj);
5322 	Assert( Weapons[num].objnum == objnum );
5323 #endif
5324 
5325 	wp = &Weapons[num];
5326 
5327 	wp->lifeleft -= frame_time;
5328 
5329 	wip = &Weapon_info[wp->weapon_info_index];
5330 
5331 	// do continuous spawns
5332 	if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
5333 		for (int i = 0; i < wip->num_spawn_weapons_defined; i++) {
5334 			if (wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL) {
5335 				if (timestamp_elapsed(wp->next_spawn_time[i])) {
5336 					spawn_child_weapons(obj, i);
5337 
5338 					// do the spawn effect
5339 					if (wip->spawn_info[i].spawn_effect.isValid()) {
5340 						auto particleSource = particle::ParticleManager::get()->createSource(wip->spawn_info[i].spawn_effect);
5341 						particleSource.moveTo(&obj->pos);
5342 						particleSource.setOrientationFromVec(&obj->phys_info.vel);
5343 						particleSource.finish();
5344 					}
5345 
5346 					// update next_spawn_time
5347 					wp->next_spawn_time[i] = timestamp() + (int)(wip->spawn_info[i].spawn_interval * 1000.f);
5348 				}
5349 			}
5350 		}
5351 	}
5352 
5353 	if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
5354 	{
5355 		if ((wp->lssm_stage != 5) && (wp->lssm_stage != 0))
5356 		{
5357 			wp->lifeleft += frame_time;
5358 		}
5359 	}
5360 
5361 
5362 	// check life left.  Multiplayer client code will go through here as well.  We must be careful in weapon_hit
5363 	// when killing a missile that spawn child weapons!!!!
5364 	if ( wp->lifeleft < 0.0f ) {
5365 		if ( wip->subtype & WP_MISSILE ) {
5366 			if(Game_mode & GM_MULTIPLAYER){
5367 				if ( !MULTIPLAYER_CLIENT || (MULTIPLAYER_CLIENT && (wip->wi_flags[Weapon::Info_Flags::Child]))) {					// don't call this function multiplayer client -- host will send this packet to us
5368 					weapon_detonate(obj);
5369 				}
5370 
5371 				if (MULTIPLAYER_MASTER) {
5372 					send_missile_kill_packet(obj);
5373 				}
5374 
5375 			} else {
5376 				weapon_detonate(obj);
5377 			}
5378 			if (wip->is_homing()) {
5379 				Homing_misses++;
5380 			}
5381 		} else {
5382             obj->flags.set(Object::Object_Flags::Should_be_dead);
5383 		}
5384 		return;
5385 	}
5386 	else if ((MULTIPLAYER_MASTER) && (wip->is_locked_homing() && wp->weapon_flags[Weapon::Weapon_Flags::Multi_homing_update_needed])) {
5387 		send_homing_weapon_info(obj->instance);
5388 		wp->weapon_flags.remove(Weapon::Weapon_Flags::Multi_homing_update_needed);
5389 	}
5390 
5391 	// plot homing missiles on the radar
5392 	if (((wip->wi_flags[Weapon::Info_Flags::Bomb]) || (wip->wi_flags[Weapon::Info_Flags::Shown_on_radar])) && !(wip->wi_flags[Weapon::Info_Flags::Dont_show_on_radar])) {
5393 		if ( hud_gauge_active(HUD_RADAR) ) {
5394 			radar_plot_object( obj );
5395 		}
5396 	}
5397 
5398 	// trail missiles
5399 	if ((wip->wi_flags[Weapon::Info_Flags::Trail]) && !(wip->wi_flags[Weapon::Info_Flags::Corkscrew])) {
5400 		if ( (wp->trail_ptr != NULL ) && (wp->lssm_stage!=3))	{
5401 			vec3d pos;
5402 
5403 			if (wip->render_type == WRT_LASER) {
5404 				// place tail origin in center of the bolt
5405 				vm_vec_scale_add(&pos, &obj->pos, &obj->orient.vec.fvec, (wip->laser_length / 2));
5406 			} else {
5407 				pos = obj->pos;
5408 			}
5409 
5410 			if (trail_stamp_elapsed(wp->trail_ptr)) {
5411 
5412 				trail_add_segment( wp->trail_ptr, &pos, &obj->orient);
5413 
5414 				trail_set_stamp(wp->trail_ptr);
5415 			} else {
5416 				trail_set_segment( wp->trail_ptr, &pos );
5417 			}
5418 
5419 		}
5420 	}
5421 
5422 	if ( wip->wi_flags[Weapon::Info_Flags::Thruster] )	{
5423 		ship_do_weapon_thruster_frame( wp, obj, flFrametime );
5424 	}
5425 
5426 	// maybe play a "whizz sound" if close enough to view position
5427 	#ifndef NDEBUG
5428 	if ( Weapon_flyby_sound_enabled ) {
5429 		weapon_maybe_play_flyby_sound(obj, wp);
5430 	}
5431 	#else
5432 		weapon_maybe_play_flyby_sound(obj, wp);
5433 	#endif
5434 
5435 	if(wip->wi_flags[Weapon::Info_Flags::Particle_spew] && wp->lssm_stage != 3 ){
5436 		weapon_maybe_spew_particle(obj);
5437 	}
5438 
5439 	// If this flag is true this is evaluated in weapon_process_pre()
5440 	if (!Framerate_independent_turning) {
5441 		weapon_update_missiles(obj, frame_time);
5442 	}
5443 
5444 	//handle corkscrew missiles
5445 	if (wip->is_homing() && !(wp->weapon_flags[Weapon::Weapon_Flags::No_homing]) && wp->cscrew_index >= 0) {
5446 		cscrew_process_post(obj);
5447 	}
5448 
5449 	//local ssm stuff
5450 	if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
5451 	{
5452 		//go into subspace if the missile is locked and its time to warpout
5453 		if ((wp->lssm_stage==1) && (timestamp_elapsed(wp->lssm_warpout_time)))
5454 		{
5455 			//if we don't have a lock at this point, just stay in normal space
5456 			if (wp->homing_object == &obj_used_list)
5457 			{
5458 				wp->lssm_stage=0;
5459 				return;
5460 			}
5461 
5462 			//point where to warpout
5463 			vec3d warpout;
5464 
5465 			//create a warp effect
5466 			vm_vec_copy_scale(&warpout,&obj->phys_info.vel,3.0f);
5467 
5468 			//get the size of the warp, directly using the model if possible
5469 			float warp_size = obj->radius;
5470 			if (wip->model_num >= 0)
5471 				warp_size = model_get_radius(wip->model_num);
5472 
5473 			//set the time the warphole stays open, minimum of 7 seconds
5474 			wp->lssm_warp_time = ((warp_size * 2) / (obj->phys_info.speed)) +1.5f;
5475 			wp->lssm_warp_time = MAX(wp->lssm_warp_time,7.0f);
5476 
5477 			//calculate the percerentage of the warpholes life at which the missile is fully in subspace.
5478 			wp->lssm_warp_pct = 1.0f - (3.0f/wp->lssm_warp_time);
5479 
5480 			//create the warphole
5481 			vm_vec_add2(&warpout,&obj->pos);
5482 			wp->lssm_warp_idx = fireball_create(&warpout, wip->lssm_warpeffect, FIREBALL_WARP_EFFECT, -1, warp_size * 1.5f, true, &vmd_zero_vector, wp->lssm_warp_time, 0, &obj->orient);
5483 
5484 			if (wp->lssm_warp_idx < 0) {
5485 				mprintf(("LSSM: Failed to create warp effect! Please report if this happens frequently.\n"));
5486 				// Abort warping
5487 				wp->lssm_stage = 0;
5488 			} else {
5489 				wp->lssm_stage = 2;
5490 			}
5491 		}
5492 
5493 		//its just entered subspace subspace. don't collide or render
5494 		if ((wp->lssm_stage==2) && (fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <= wp->lssm_warp_pct))
5495 		{
5496             auto flags = obj->flags;
5497             flags.remove(Object::Object_Flags::Renders);
5498             flags.remove(Object::Object_Flags::Collides);
5499 
5500 			obj_set_flags(obj, flags);
5501 
5502 			// stop its trail here
5503 			if (wp->trail_ptr != nullptr) {
5504 				trail_object_died(wp->trail_ptr);
5505 				wp->trail_ptr = nullptr;
5506 			}
5507 
5508 			//get the position of the target, and estimate its position when it warps out
5509 			//so we have an idea of where it will be.
5510 			auto target_objp = wp->homing_object;
5511 			if (target_objp == nullptr && wp->target_num != -1) {
5512 				target_objp = &Objects[wp->target_num];
5513 			}
5514 
5515 			if (target_objp != nullptr)
5516 			{
5517 				vm_vec_scale_add(&wp->lssm_target_pos, &target_objp->pos, &target_objp->phys_info.vel, (float)wip->lssm_warpin_delay / 1000.0f);
5518 			}
5519 			else
5520 			{
5521 				// Our target is invalid, just jump to our position
5522 				wp->lssm_target_pos = obj->pos;
5523 			}
5524 
5525 			wp->lssm_stage=3;
5526 
5527 		}
5528 
5529 		//time to warp in.
5530 		if ((wp->lssm_stage==3) && (timestamp_elapsed(wp->lssm_warpin_time)))
5531 		{
5532 
5533 			vec3d warpin;
5534 			object* target_objp=wp->homing_object;
5535 			vec3d fvec;
5536 			matrix orient;
5537 
5538 			//spawn the ssm at a random point in a circle around the target
5539 			vm_vec_random_in_circle(&warpin, &wp->lssm_target_pos, &target_objp->orient, wip->lssm_warpin_radius + target_objp->radius, true);
5540 
5541 			//orient the missile properly
5542 			vm_vec_sub(&fvec,&wp->lssm_target_pos, &warpin);
5543 			vm_vector_2_matrix(&orient,&fvec,NULL,NULL);
5544 
5545 			//get the size of the warp, directly using the model if possible
5546 			float warp_size = obj->radius;
5547 			if (wip->model_num >= 0)
5548 				warp_size = model_get_radius(wip->model_num);
5549 
5550 			//create a warpin effect
5551 			wp->lssm_warp_idx = fireball_create(&warpin, wip->lssm_warpeffect, FIREBALL_WARP_EFFECT, -1, warp_size * 1.5f, false, &vmd_zero_vector, wp->lssm_warp_time, 0, &orient);
5552 
5553 			if (wp->lssm_warp_idx < 0) {
5554 				mprintf(("LSSM: Failed to create warp effect! Please report if this happens frequently.\n"));
5555 			}
5556 
5557 			obj->orient=orient;
5558 			obj->pos=warpin;
5559 			obj->phys_info.speed=0;
5560 			obj->phys_info.desired_vel = vmd_zero_vector;
5561 			obj->phys_info.vel = obj->phys_info.desired_vel;
5562 
5563 			wp->lssm_stage = 4;
5564 		}
5565 
5566 		//done warping in.  render and collide it. let the fun begin
5567 		// If the previous fireball creation failed just put it into normal space now
5568 		if ((wp->lssm_stage==4) && (wp->lssm_warp_idx < 0 || fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <=0.5f))
5569 		{
5570 			vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, wip->lssm_stage5_vel );
5571 			obj->phys_info.vel = obj->phys_info.desired_vel;
5572 			obj->phys_info.speed = vm_vec_mag(&obj->phys_info.desired_vel);
5573 
5574 			wp->lssm_stage=5;
5575 
5576             auto flags = obj->flags;
5577             flags.set(Object::Object_Flags::Renders);
5578             flags.set(Object::Object_Flags::Collides);
5579 
5580 			obj_set_flags(obj,flags);
5581 
5582 			// start the trail back up if applicable
5583 			if (wip->wi_flags[Weapon::Info_Flags::Trail]) {
5584 				wp->trail_ptr = trail_create(&wip->tr_info);
5585 
5586 				if (wp->trail_ptr != nullptr) {
5587 					// Add two segments.  One to stay at launch pos, one to move.
5588 					trail_add_segment(wp->trail_ptr, &obj->pos, &obj->orient);
5589 					trail_add_segment(wp->trail_ptr, &obj->pos, &obj->orient);
5590 				}
5591 			}
5592 		}
5593 	}
5594 
5595 	if (wip->hud_in_flight_snd.isValid() && obj->parent_sig == Player_obj->signature)
5596 	{
5597 		bool play_sound = false;
5598 		switch (wip->in_flight_play_type)
5599 		{
5600 		case TARGETED:
5601 			play_sound = wp->homing_object != &obj_used_list;
5602 			break;
5603 		case UNTARGETED:
5604 			play_sound = wp->homing_object == &obj_used_list;
5605 			break;
5606 		case ALWAYS:
5607 			play_sound = true;
5608 			break;
5609 		default:
5610 			Error(LOCATION, "Unknown in-flight sound status %d!", (int) wip->in_flight_play_type);
5611 			break;
5612 		}
5613 
5614 		if (play_sound)
5615 		{
5616 			if (!wp->hud_in_flight_snd_sig.isValid() || !snd_is_playing(wp->hud_in_flight_snd_sig)) {
5617 				wp->hud_in_flight_snd_sig = snd_play_looping(gamesnd_get_game_sound(wip->hud_in_flight_snd));
5618 			}
5619 		}
5620 	}
5621 
5622 	weapon_update_state(wp);
5623 }
5624 
5625 /**
5626  * Update weapon tracking information.
5627  */
weapon_set_tracking_info(int weapon_objnum,int parent_objnum,int target_objnum,int target_is_locked,ship_subsys * target_subsys)5628 void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_objnum, int target_is_locked, ship_subsys *target_subsys)
5629 {
5630 	object		*parent_objp;
5631 	weapon		*wp;
5632 	weapon_info	*wip;
5633 	int targeting_same = 0;
5634 
5635 	if ( weapon_objnum < 0 ) {
5636 		return;
5637 	}
5638 
5639 	Assert(Objects[weapon_objnum].type == OBJ_WEAPON);
5640 
5641 	wp = &Weapons[Objects[weapon_objnum].instance];
5642 	wip = &Weapon_info[wp->weapon_info_index];
5643 
5644 	if (wp->weapon_flags[Weapon::Weapon_Flags::No_homing]) {
5645 		return;
5646 	}
5647 
5648 	if (parent_objnum >= 0) {
5649 		parent_objp = &Objects[parent_objnum];
5650 		Assert(parent_objp->type == OBJ_SHIP);
5651 	} else {
5652 		parent_objp = NULL;
5653 	}
5654 
5655 	if (parent_objp != NULL && (Ships[parent_objp->instance].flags[Ship::Ship_Flags::No_secondary_lockon])) {
5656         wp->weapon_flags.set(Weapon::Weapon_Flags::No_homing);
5657 		wp->homing_object = &obj_used_list;
5658 		wp->homing_subsys = NULL;
5659 		wp->target_num = -1;
5660 		wp->target_sig = -1;
5661 
5662 		return;
5663 	}
5664 
5665 	if ( parent_objp == NULL || Ships[parent_objp->instance].ai_index >= 0 ) {
5666 		int target_team = -1;
5667 		if ( target_objnum >= 0 ) {
5668 			int obj_type = Objects[target_objnum].type;
5669 
5670 			if ( (obj_type == OBJ_SHIP) || (obj_type == OBJ_WEAPON) ) {
5671 				target_team = obj_team(&Objects[target_objnum]);
5672 
5673 			}
5674 		}
5675 
5676 		// determining if we're targeting the same team
5677 		if (parent_objp != NULL && Ships[parent_objp->instance].team == target_team){
5678 			targeting_same = 1;
5679 
5680 			// Goober5000 - if we're going bonkers, pretend we're not targeting our own team
5681 			ai_info *parent_aip = &Ai_info[Ships[parent_objp->instance].ai_index];
5682 			if (parent_aip->active_goal != AI_GOAL_NONE && parent_aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) {
5683 				if (parent_aip->goals[parent_aip->active_goal].flags[AI::Goal_Flags::Target_own_team]) {
5684 					targeting_same = 0;
5685 				}
5686 			}
5687 		} else {
5688 			targeting_same = 0;
5689 		}
5690 
5691 		bool can_lock = (weapon_has_iff_restrictions(wip) && target_objnum >= 0) ? weapon_target_satisfies_lock_restrictions(wip, &Objects[target_objnum]) :
5692 			(!targeting_same || (MULTI_DOGFIGHT && (target_team == Iff_traitor)));
5693 
5694 		// Cyborg17 - exclude all invalid object numbers here since in multi, the lock slots can get out of sync.
5695 		if ((target_objnum > -1) && (target_objnum < (MAX_OBJECTS)) && can_lock) {
5696 			wp->target_num = target_objnum;
5697 			wp->target_sig = Objects[target_objnum].signature;
5698 			if ( (wip->wi_flags[Weapon::Info_Flags::Homing_aspect]) && target_is_locked) {
5699 				wp->homing_object = &Objects[target_objnum];
5700 				wp->homing_subsys = target_subsys;
5701 				weapon_maybe_play_warning(wp);
5702 			} else if ( (wip->wi_flags[Weapon::Info_Flags::Homing_javelin]) && target_is_locked) {
5703 				if ((Objects[target_objnum].type == OBJ_SHIP) &&
5704 					( (wp->homing_subsys == NULL) ||
5705 					  (wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE) )) {
5706 						ship *target_ship = &Ships[Objects[target_objnum].instance];
5707 						wp->homing_subsys = ship_get_closest_subsys_in_sight(target_ship, SUBSYSTEM_ENGINE, &Objects[weapon_objnum].pos);
5708 						if (wp->homing_subsys == NULL) {
5709 							wp->homing_object = &obj_used_list;
5710 						} else {
5711 							Assert(wp->homing_subsys->parent_objnum == target_objnum);
5712 							wp->homing_object = &Objects[target_objnum];
5713 							weapon_maybe_play_warning(wp);
5714 						}
5715 				} else {
5716 					wp->homing_object = &Objects[target_objnum];
5717 					wp->homing_subsys = target_subsys;
5718 					weapon_maybe_play_warning(wp);
5719 				}
5720 			} else if ( wip->wi_flags[Weapon::Info_Flags::Homing_heat] ) {
5721 				//	Make a heat seeking missile try to home.  If the target is outside the view cone, it will
5722 				//	immediately drop it and try to find one in its view cone.
5723 				if ((target_objnum != -1) && !(wip->wi_flags[Weapon::Info_Flags::Untargeted_heat_seeker])) {
5724 					wp->homing_object = &Objects[target_objnum];
5725 					wp->homing_subsys = target_subsys;
5726 					weapon_maybe_play_warning(wp);
5727 				} else {
5728 					wp->homing_object = &obj_used_list;
5729 					wp->homing_subsys = NULL;
5730 				}
5731 			}
5732 		} else {
5733 			wp->target_num = -1;
5734 			wp->target_sig = -1;
5735 		}
5736 
5737 		//	If missile is locked on target, increase its lifetime by 20% since missiles can be fired at limit of range
5738 		//	as defined by velocity*lifeleft, but missiles often slow down a bit, plus can be fired at a moving away target.
5739 		//	Confusing to many players when their missiles run out of gas before getting to target.
5740 		// DB - removed 7:14 pm 9/6/99. was totally messing up lifetimes for all weapons.
5741 		//	MK, 7:11 am, 9/7/99.  Put it back in, but with a proper check here to make sure it's an aspect seeker and
5742 		//	put a sanity check in the color changing laser code that was broken by this code.
5743 		if (target_is_locked && (wp->target_num != -1) && (wip->is_locked_homing()) ) {
5744 			wp->lifeleft *= LOCKED_HOMING_EXTENDED_LIFE_FACTOR;
5745 		}
5746 
5747 		ai_update_danger_weapon(target_objnum, weapon_objnum);
5748 	}
5749 }
5750 
get_pointer_to_weapon_fire_pattern_index(int weapon_type,int ship_idx,ship_subsys * src_turret)5751 size_t* get_pointer_to_weapon_fire_pattern_index(int weapon_type, int ship_idx, ship_subsys * src_turret)
5752 {
5753 	Assertion(ship_idx >= 0 && ship_idx < MAX_SHIPS, "Invalid ship index in get_pointer_to_weapon_fire_pattern_index()");
5754 	ship* shipp = &Ships[ship_idx];
5755 	ship_weapon* ship_weapon_p = &(shipp->weapons);
5756 	if(src_turret)
5757 	{
5758 		ship_weapon_p = &src_turret->weapons;
5759 	}
5760 	Assert( ship_weapon_p != NULL );
5761 
5762 	// search for the corresponding bank pattern index for the weapon_type that is being fired.
5763 	// Note: Because a weapon_type may not be unique to a weapon bank per ship this search may attribute
5764 	// the weapon to the wrong bank.  Hopefully this isn't a problem.
5765 	for ( int pi = 0; pi < MAX_SHIP_PRIMARY_BANKS; pi++ ) {
5766 		if ( ship_weapon_p->primary_bank_weapons[pi] == weapon_type ) {
5767 			return &(ship_weapon_p->primary_bank_pattern_index[pi]);
5768 		}
5769 	}
5770 	for ( int si = 0; si < MAX_SHIP_SECONDARY_BANKS; si++ ) {
5771 		if ( ship_weapon_p->secondary_bank_weapons[si] == weapon_type ) {
5772 			return &(ship_weapon_p->secondary_bank_pattern_index[si]);
5773 		}
5774 	}
5775 	return NULL;
5776 }
5777 
5778 /**
5779  * Create a weapon object
5780  *
5781  * @return Index of weapon in the Objects[] array, -1 if the weapon object was not created
5782  */
5783 int Weapons_created = 0;
weapon_create(vec3d * pos,matrix * porient,int weapon_type,int parent_objnum,int group_id,int is_locked,int is_spawned,float fof_cooldown,ship_subsys * src_turret)5784 int weapon_create( vec3d * pos, matrix * porient, int weapon_type, int parent_objnum, int group_id, int is_locked, int is_spawned, float fof_cooldown, ship_subsys * src_turret)
5785 {
5786 	int			n, objnum;
5787 	int num_deleted;
5788 	object		*objp, *parent_objp=NULL;
5789 	weapon		*wp;
5790 	weapon_info	*wip;
5791 
5792 	Assert(weapon_type >= 0 && weapon_type < weapon_info_size());
5793 
5794 	wip = &Weapon_info[weapon_type];
5795 
5796 	// beam weapons should never come through here!
5797 	if(wip->wi_flags[Weapon::Info_Flags::Beam])
5798 	{
5799 		Warning(LOCATION, "An attempt to fire a beam ('%s') through weapon_create() was made.\n", wip->name);
5800 		return -1;
5801 	}
5802 
5803 	parent_objp = NULL;
5804 	if(parent_objnum >= 0){
5805 		parent_objp = &Objects[parent_objnum];
5806 	}
5807 
5808 	if ( (wip->num_substitution_patterns > 0) && (parent_objp != NULL)) {
5809 		// using substitution
5810 
5811 		// get to the instance of the gun
5812 		Assertion( parent_objp->type == OBJ_SHIP, "Expected type OBJ_SHIP, got %d", parent_objp->type );
5813 		Assertion( (parent_objp->instance < MAX_SHIPS) && (parent_objp->instance >= 0),
5814 			"Ship index is %d, which is out of range [%d,%d)", parent_objp->instance, 0, MAX_SHIPS);
5815 		ship* parent_shipp = &(Ships[parent_objp->instance]);
5816 		Assert( parent_shipp != NULL );
5817 
5818 		size_t *position = get_pointer_to_weapon_fire_pattern_index(weapon_type, parent_objp->instance, src_turret);
5819 		Assertion( position != NULL, "'%s' is trying to fire a weapon that is not selected", Ships[parent_objp->instance].ship_name );
5820 
5821 		size_t curr_pos = *position;
5822 		if (((Weapon_info[weapon_type].subtype == WP_LASER && parent_shipp->flags[Ship::Ship_Flags::Primary_linked]) ||
5823 			 (Weapon_info[weapon_type].subtype == WP_MISSILE && parent_shipp->flags[Ship::Ship_Flags::Secondary_dual_fire])) &&
5824 			 (curr_pos > 0)) {
5825 			curr_pos--;
5826 		}
5827 		++(*position);
5828 		*position = (*position) % wip->num_substitution_patterns;
5829 
5830 		if ( wip->weapon_substitution_pattern[curr_pos] == -1 ) {
5831 			// weapon doesn't want any sub
5832 			return -1;
5833 		} else if ( wip->weapon_substitution_pattern[curr_pos] != weapon_type ) {
5834 			// weapon wants to sub with weapon other than me
5835 			return weapon_create(pos, porient, wip->weapon_substitution_pattern[curr_pos], parent_objnum, group_id, is_locked, is_spawned, fof_cooldown);
5836 		}
5837 	}
5838 
5839 	// Let's setup a fast failure check with a uniform distribution.
5840 	if (wip->failure_rate > 0.0f) {
5841 		util::UniformFloatRange rng(0.0f, 1.0f);
5842 		float test = rng.next();
5843 		if (test < wip->failure_rate) {
5844 			if (wip->failure_sub != -1) {
5845 				return weapon_create(pos, porient, wip->failure_sub, parent_objnum, group_id, is_locked, is_spawned, fof_cooldown);
5846 			} else {
5847 				return -1;
5848 			}
5849 		}
5850 	}
5851 
5852 	num_deleted = 0;
5853 	if (Num_weapons >= MAX_WEAPONS-5) {
5854 
5855 		num_deleted = collide_remove_weapons();
5856 
5857 		mprintf(("Deleted %d weapons because of lack of slots.\n", num_deleted));
5858 		if (num_deleted == 0){
5859 			return -1;
5860 		}
5861 	}
5862 
5863 	for (n=0; n<MAX_WEAPONS; n++ ){
5864 		if (Weapons[n].weapon_info_index < 0){
5865 			break;
5866 		}
5867 	}
5868 
5869 	if (n == MAX_WEAPONS) {
5870 		// if we supposedly deleted weapons above, what happened here!!!!
5871 		if (num_deleted){
5872 			Int3();				// get allender -- something funny is going on!!!
5873 		}
5874 
5875 		return -1;
5876 	}
5877 
5878 	// make sure we are loaded and useable
5879 	if ( (wip->render_type == WRT_POF) && (wip->model_num < 0) ) {
5880 		wip->model_num = model_load(wip->pofbitmap_name, 0, NULL);
5881 
5882 		if (wip->model_num < 0) {
5883 			Int3();
5884 			return -1;
5885 		}
5886 	}
5887 
5888 	// make sure that our textures are loaded as well
5889 	if ( !used_weapons[weapon_type] )
5890 		weapon_load_bitmaps(weapon_type);
5891 
5892 	//I am hopeing that this way does not alter the input orient matrix
5893 	//Feild of Fire code -Bobboau
5894 	matrix morient;
5895 	matrix *orient;
5896 
5897 	if(porient != NULL) {
5898 		morient = *porient;
5899 	} else {
5900 		morient = vmd_identity_matrix;
5901 	}
5902 
5903 	orient = &morient;
5904 
5905 	float combined_fof = wip->field_of_fire;
5906 	// If there is a fof_cooldown value, increase the spread linearly
5907 	if (fof_cooldown != 0.0f) {
5908 		combined_fof = wip->field_of_fire + (fof_cooldown * wip->max_fof_spread);
5909 	}
5910 
5911 	if(combined_fof > 0.0f){
5912 		vec3d f;
5913 		vm_vec_random_cone(&f, &orient->vec.fvec, combined_fof);
5914 		vm_vec_normalize(&f);
5915 		vm_vector_2_matrix( orient, &f, NULL, NULL);
5916 	}
5917 
5918 	Weapons_created++;
5919     flagset<Object::Object_Flags> default_flags;
5920     default_flags.set(Object::Object_Flags::Renders);
5921     default_flags.set(Object::Object_Flags::Physics);
5922 
5923 	if (!wip->wi_flags[Weapon::Info_Flags::No_collide])
5924 		default_flags.set(Object::Object_Flags::Collides);
5925 
5926 	if (wip->wi_flags[Weapon::Info_Flags::Can_damage_shooter])
5927 		default_flags.set(Object::Object_Flags::Collides_with_parent);
5928 
5929 	// mark this object creation as essential, if it is created by a player.
5930 	// You don't want players mysteriously wondering why they aren't firing.
5931 	objnum = obj_create( OBJ_WEAPON, parent_objnum, n, orient, pos, 2.0f, default_flags, (parent_objp != nullptr && parent_objp->flags[Object::Object_Flags::Player_ship]));
5932 
5933 	if (objnum < 0) {
5934 		mprintf(("A weapon failed to be created because FSO is running out of object slots!\n"));
5935 		return -1;
5936 	}
5937 
5938 	objp = &Objects[objnum];
5939 
5940 	// Create laser n!
5941 	wp = &Weapons[n];
5942 
5943 	// check if laser or dumbfire missile
5944 	// set physics flag to allow optimization
5945 	if (((wip->subtype == WP_LASER) || (wip->subtype == WP_MISSILE)) && !(wip->is_homing()) && wip->acceleration_time == 0.0f) {
5946 		// set physics flag
5947 		objp->phys_info.flags |= PF_CONST_VEL;
5948 	}
5949 
5950 	wp->start_pos = *pos;
5951 	wp->objnum = objnum;
5952 	wp->model_instance_num = -1;
5953 	wp->homing_object = &obj_used_list;		//	Assume not homing on anything.
5954 	wp->homing_subsys = NULL;
5955 	wp->creation_time = Missiontime;
5956 	wp->group_id = group_id;
5957 
5958 	// we don't necessarily need a parent
5959 	if(parent_objp != NULL){
5960 		Assert(parent_objp->type == OBJ_SHIP);	//	Get Mike, a non-ship has fired a weapon!
5961 		Assert((parent_objp->instance >= 0) && (parent_objp->instance < MAX_SHIPS));
5962 		wp->team = Ships[parent_objp->instance].team;
5963 		wp->species = Ship_info[Ships[parent_objp->instance].ship_info_index].species;
5964 	} else {
5965 		// ugh - we need to prevent bad array accesses
5966 		wp->team = Iff_traitor;
5967 		wp->species = 0;
5968 	}
5969 	wp->turret_subsys = NULL;
5970 	vm_vec_zero(&wp->homing_pos);
5971 	wp->weapon_flags.reset();
5972 	wp->target_sig = -1;
5973 	wp->cmeasure_ignore_list = nullptr;
5974 	wp->det_range = wip->det_range;
5975 
5976 	// Init the thruster info
5977 	wp->thruster_bitmap = -1;
5978 	wp->thruster_frame = 0.0f;
5979 	wp->thruster_glow_bitmap = -1;
5980 	wp->thruster_glow_noise = 1.0f;
5981 	wp->thruster_glow_frame = 0.0f;
5982 
5983 	// init the laser info
5984 	wp->laser_bitmap_frame = 0.0f;
5985 	wp->laser_glow_bitmap_frame = 0.0f;
5986 
5987 	// init the weapon state
5988 	wp->weapon_state = WeaponState::INVALID;
5989 
5990 	if ( wip->wi_flags[Weapon::Info_Flags::Swarm] ) {
5991 		wp->swarm_info_ptr.reset(new swarm_info);
5992 		swarm_create(objp, wp->swarm_info_ptr.get());
5993 	}
5994 
5995 	// if this is a particle spewing weapon, setup some stuff
5996 	if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
5997 		for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {		// allow for multiple time values
5998 			if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
5999 				wp->particle_spew_time[s] = -1;
6000 				wp->particle_spew_rand = frand_range(0, PI2);	// per weapon randomness
6001 			}
6002 		}
6003 	}
6004 
6005 	// assign the network signature.  The starting sig is sent to all clients, so this call should
6006 	// result in the same net signature numbers getting assigned to every player in the game
6007 	if ( Game_mode & GM_MULTIPLAYER ) {
6008 		if(wip->subtype == WP_MISSILE){
6009 			Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
6010 
6011 			// for weapons that respawn, add the number of respawnable weapons to the net signature pool
6012 			// to reserve N signatures for the spawned weapons
6013 			if ( wip->wi_flags[Weapon::Info_Flags::Spawn] ){
6014                 multi_set_network_signature( (ushort)(Objects[objnum].net_signature + wip->maximum_children_spawned), MULTI_SIG_NON_PERMANENT );
6015 			}
6016 		} else {
6017 			Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
6018 		}
6019 		// for multiplayer clients, when creating lasers, add some more life to the lasers.  This helps
6020 		// to overcome some problems associated with lasers dying on client machine before they get message
6021 		// from server saying it hit something.
6022 	}
6023 
6024 	//Check if we want to gen a random number
6025 	//This is used for lifetime min/max
6026 	float rand_val;
6027 	if ( Game_mode & GM_NORMAL ){
6028 		rand_val = frand();
6029 	} else {
6030 		rand_val = static_randf(Objects[objnum].net_signature);
6031 	}
6032 
6033 	wp->weapon_info_index = weapon_type;
6034 	if(wip->life_min < 0.0f && wip->life_max < 0.0f) {
6035 		wp->lifeleft = wip->lifetime;
6036 	} else {
6037 		wp->lifeleft = ((rand_val) * (wip->life_max - wip->life_min)) + wip->life_min;
6038 		if((wip->wi_flags[Weapon::Info_Flags::Cmeasure]) && (parent_objp != NULL) && (parent_objp->flags[Object::Object_Flags::Player_ship])) {
6039 			wp->lifeleft *= The_mission.ai_profile->cmeasure_life_scale[Game_skill_level];
6040 		}
6041 	}
6042 
6043 	if(wip->wi_flags[Weapon::Info_Flags::Cmeasure]) {
6044 		// For the next two frames, any non-timer-based countermeasures will pulse each frame.
6045 		Cmeasures_homing_check = 2;
6046 		if (wip->cmeasure_timer_interval > 0) {
6047 			// Timer-based countermeasures spawn pulsing as well.
6048 			wp->cmeasure_timer = timestamp();	// Could also use timestamp(0), but it doesn't really matter either way.
6049 		}
6050 	}
6051 
6052 	//	Make remote detonate missiles look like they're getting detonated by firer simply by giving them variable lifetimes.
6053 	if (parent_objp != NULL && !(parent_objp->flags[Object::Object_Flags::Player_ship]) && (wip->wi_flags[Weapon::Info_Flags::Remote])) {
6054 		wp->lifeleft = wp->lifeleft/2.0f + rand_val * wp->lifeleft/2.0f;
6055 	}
6056 
6057 	objp->phys_info.mass = wip->mass;
6058 	objp->phys_info.side_slip_time_const = 0.0f;
6059 	objp->phys_info.rotdamp = wip->turn_accel_time ? wip->turn_accel_time / 2.f : 0.0f;
6060 	vm_vec_zero(&objp->phys_info.max_vel);
6061 	objp->phys_info.max_vel.xyz.z = wip->max_speed;
6062 	vm_vec_zero(&objp->phys_info.max_rotvel);
6063 	objp->shield_quadrant[0] = wip->damage;
6064 	if (wip->weapon_hitpoints > 0){
6065 		objp->hull_strength = (float) wip->weapon_hitpoints;
6066 	} else {
6067 		objp->hull_strength = 0.0f;
6068 	}
6069 
6070 	if (wip->collision_radius_override > 0.0f)
6071 		objp->radius = wip->collision_radius_override;
6072 	else if ( wip->render_type == WRT_POF ) {
6073 		// this should have been checked above, but let's be extra sure
6074 		Assert(wip->model_num >= 0);
6075 
6076 		objp->radius = model_get_radius(wip->model_num);
6077 
6078 		// Always create an instance in case we need them
6079 		if (model_get(wip->model_num)->flags & PM_FLAG_HAS_INTRINSIC_ROTATE || !wip->on_create_program.isEmpty()) {
6080 			wp->model_instance_num = model_create_instance(false, wip->model_num);
6081 		}
6082 	} else if ( wip->render_type == WRT_LASER ) {
6083 		objp->radius = wip->laser_head_radius;
6084 	}
6085 
6086 	//	Set desired velocity and initial velocity.
6087 	//	For lasers, velocity is always the same.
6088 	//	For missiles, it is a small amount plus the firing ship's velocity.
6089 	//	For missiles, the velocity trends towards some goal.
6090 	//	Note: If you change how speed works here, such as adding in speed of parent ship, you'll need to change the AI code
6091 	//	that predicts collision points.  See Mike Kulas or Dave Andsager.  (Or see ai_get_weapon_speed().)
6092 	bool already_inherited_parent_speed = false;
6093 	if (wip->acceleration_time > 0.0f) {
6094 		vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, 0.01f ); // Tiny initial velocity to avoid possible null vec issues
6095 		objp->phys_info.vel = objp->phys_info.desired_vel;
6096 		objp->phys_info.speed = 0.0f;
6097 		wp->launch_speed = 0.0f;
6098 	} else if (!(wip->is_homing())) {
6099 		vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
6100 		objp->phys_info.vel = objp->phys_info.desired_vel;
6101 		objp->phys_info.speed = vm_vec_mag(&objp->phys_info.desired_vel);
6102 	} else {
6103 		//	For weapons that home, set velocity to sum of the component of parent's velocity in the fire direction and freeflight speed factor(default 1/4)
6104 		//	Note that it is important to extract the component of parent's velocity in the fire direction to factor out sliding, else
6105 		//	the missile will not be moving forward.
6106 		if(parent_objp != NULL){
6107 			if (wip->free_flight_time > 0.0) {
6108 				vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, vm_vec_dot(&parent_objp->phys_info.vel, &objp->orient.vec.fvec) + objp->phys_info.max_vel.xyz.z * wip->free_flight_speed_factor);
6109 				already_inherited_parent_speed = true;
6110 			}
6111 			else
6112 				vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z*wip->free_flight_speed_factor );
6113 		} else {
6114 			if (!is_locked && wip->free_flight_time > 0.0)
6115             {
6116 			    vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z* wip->free_flight_speed_factor);
6117             }
6118             else
6119             {
6120                 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
6121             }
6122 		}
6123 		objp->phys_info.vel = objp->phys_info.desired_vel;
6124 		objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
6125 	}
6126 
6127 	wp->weapon_max_vel = objp->phys_info.max_vel.xyz.z;
6128 
6129 	// Turey - maybe make the initial speed of the weapon take into account the velocity of the parent.
6130 	// Improves aiming during gliding.
6131 	if ((parent_objp != nullptr) && (The_mission.ai_profile->flags[AI::Profile_Flags::Use_additive_weapon_velocity]) && !(already_inherited_parent_speed)) {
6132 		float pspeed = vm_vec_mag( &parent_objp->phys_info.vel );
6133 		vm_vec_scale_add2( &objp->phys_info.vel, &parent_objp->phys_info.vel, wip->vel_inherit_amount );
6134 		wp->weapon_max_vel += pspeed * wip->vel_inherit_amount;
6135 		objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
6136 
6137 		if (wip->acceleration_time > 0.0f)
6138 			wp->launch_speed += pspeed;
6139 	}
6140 
6141 	// create the corkscrew
6142 	if ( wip->wi_flags[Weapon::Info_Flags::Corkscrew] ) {
6143 		wp->cscrew_index = (short)cscrew_create(objp);
6144 	} else {
6145 		wp->cscrew_index = -1;
6146 	}
6147 
6148 	if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
6149 	{
6150 
6151 		Assert(parent_objp);		//local ssms must have a parent
6152 
6153 		wp->lssm_warpout_time=timestamp(wip->lssm_warpout_delay);
6154 		wp->lssm_warpin_time=timestamp(wip->lssm_warpout_delay + wip->lssm_warpin_delay);
6155 		wp->lssm_stage=1;
6156 	}
6157 	else{
6158 		wp->lssm_stage=-1;
6159 	}
6160 
6161 
6162 	// if this is a flak weapon shell, make it so
6163 	// NOTE : this function will change some fundamental things about the weapon object
6164     if ( (wip->wi_flags[Weapon::Info_Flags::Flak]) && !(wip->wi_flags[Weapon::Info_Flags::Render_flak]) ) {
6165 		obj_set_flags(&Objects[wp->objnum], Objects[wp->objnum].flags - Object::Object_Flags::Renders);
6166 	}
6167 
6168 	wp->missile_list_index = -1;
6169 	// If this is a missile, then add it to the Missile_obj_list
6170 	if ( wip->subtype == WP_MISSILE ) {
6171 		wp->missile_list_index = missile_obj_list_add(objnum);
6172 	}
6173 
6174 	if (wip->wi_flags[Weapon::Info_Flags::Trail] /*&& !(wip->wi_flags[Weapon::Info_Flags::Corkscrew]) */) {
6175 		wp->trail_ptr = trail_create(&wip->tr_info);
6176 
6177 		if ( wp->trail_ptr != NULL )	{
6178 			// Add two segments.  One to stay at launch pos, one to move.
6179 			trail_add_segment( wp->trail_ptr, &objp->pos, &objp->orient );
6180 			trail_add_segment( wp->trail_ptr, &objp->pos, &objp->orient );
6181 		}
6182 	}
6183 	else
6184 	{
6185 		//If a weapon has no trails, make sure we don't try to do anything with them.
6186 		wp->trail_ptr = NULL;
6187 	}
6188 
6189 	// Ensure weapon flyby sound doesn't get played for player lasers
6190 	if ( parent_objp != NULL && parent_objp == Player_obj ) {
6191         wp->weapon_flags.set(Weapon::Weapon_Flags::Played_flyby_sound);
6192 	}
6193 
6194 	wp->pick_big_attack_point_timestamp = timestamp(1);
6195 
6196 	if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
6197 		for (int i = 0; i < wip->num_spawn_weapons_defined; i++) {
6198 			if (wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL) {
6199 				int delay;
6200 				if (wip->spawn_info[i].spawn_interval_delay < 0.f)
6201 					delay = (int)(wip->spawn_info[i].spawn_interval * 1000);
6202 				else
6203 					delay = (int)(wip->spawn_info[i].spawn_interval_delay * 1000);
6204 
6205 				if (delay < 10)
6206 					delay = 10; // cap at 10 ms
6207 				wp->next_spawn_time[i] = timestamp(delay);
6208 			}
6209 			else
6210 				wp->next_spawn_time[i] = INT_MAX; // basically never expire
6211 		}
6212 	}
6213 
6214 	//	Set detail levels for POF-type weapons.
6215 	if (Weapon_info[wp->weapon_info_index].model_num != -1) {
6216 		polymodel * pm;
6217 		int	i;
6218 		pm = model_get(Weapon_info[wp->weapon_info_index].model_num);
6219 
6220 		for (i=0; i<pm->n_detail_levels; i++){
6221 			// for weapons, detail levels are all preset to -1
6222 			if (wip->detail_distance[i] >= 0)
6223 				pm->detail_depth[i] = i2fl(wip->detail_distance[i]);
6224 			else
6225 				pm->detail_depth[i] = (objp->radius*20.0f + 20.0f) * i;
6226 		}
6227 
6228 #ifndef NDEBUG
6229 		// since debug builds always have cheats enabled, we don't necessarily get the chance
6230 		// to enable thrusters for previously non-loaded weapons (ie, weapons_page_in_cheats())
6231 		// when using cheat-keys, so we need to make sure and enable thrusters here if needed
6232 		if (pm->n_thrusters > 0) {
6233             wip->wi_flags.set(Weapon::Info_Flags::Thruster);
6234 		}
6235 #endif
6236 	}
6237 
6238 		// if the weapon was fired locked
6239 	if(is_locked){
6240         wp->weapon_flags.set(Weapon::Weapon_Flags::Locked_when_fired);
6241 	}
6242 
6243 	//if the weapon was spawned from a spawning type weapon
6244 	if(is_spawned){
6245         wp->weapon_flags.set(Weapon::Weapon_Flags::Spawned);
6246 	}
6247 
6248 	wp->alpha_current = -1.0f;
6249 	wp->alpha_backward = 0;
6250 
6251 	wp->collisionInfo = nullptr;
6252 	wp->hud_in_flight_snd_sig = sound_handle::invalid();
6253 
6254 	Num_weapons++;
6255 
6256 	if (Weapons_inherit_parent_collision_group) {
6257 		Objects[objnum].collision_group_id = Objects[parent_objnum].collision_group_id;
6258 	}
6259 
6260 	weapon_update_state(wp);
6261 
6262 	if (wip->render_type == WRT_LASER) {
6263 		// No model so we also have no submodel
6264 		wip->on_create_program.start(&Objects[objnum], &vmd_zero_vector, &vmd_identity_matrix, -1);
6265 	} else if (wip->render_type == WRT_POF) {
6266 		// We have a model so we can specify a subobject
6267 		wip->on_create_program.start(&Objects[objnum],
6268 			&vmd_zero_vector,
6269 			&vmd_identity_matrix,
6270 			model_get(wip->model_num)->detail[0]);
6271 	}
6272 
6273 	if (wip->ambient_snd.isValid()) {
6274 		obj_snd_assign(objnum, wip->ambient_snd, &vmd_zero_vector , OS_MAIN);
6275 	}
6276 
6277 	if (Script_system.IsActiveAction(CHA_ONWEAPONCREATED)) {
6278 		Script_system.SetHookObject("Weapon", &Objects[objnum]);
6279 		Script_system.RunCondition(CHA_ONWEAPONCREATED);
6280 		Script_system.RemHookVar("Weapon");
6281 	}
6282 
6283 	return objnum;
6284 }
6285 
6286 /**
6287  * Spawn child weapons from object *objp.
6288  * Spawns all non-continuous spawn weapons when the overrides are -1 (on detonation)
6289  * When they are provided it is for continuous spawn weapons
6290  */
spawn_child_weapons(object * objp,int spawn_index_override)6291 void spawn_child_weapons(object *objp, int spawn_index_override)
6292 {
6293 	int	child_id;
6294 	int	parent_num;
6295 	ushort starting_sig;
6296 	weapon	*wp = NULL;
6297 	beam	*bp = NULL;
6298 	weapon_info	*wip, *child_wip;
6299 	vec3d	*opos, *fvec;
6300 
6301 	Assertion(objp->type == OBJ_WEAPON || objp->type == OBJ_BEAM, "spawn_child_weapons() doesn't make sense for non-weapon non-beam objects; get a coder!\n");
6302 	Assertion(objp->instance >= 0, "spawn_child_weapons() called with an object with an instance of %d; get a coder!\n", objp->instance);
6303 	Assertion(!(objp->type == OBJ_WEAPON) || (objp->instance < MAX_WEAPONS), "spawn_child_weapons() called with a weapon with an instance of %d while MAX_WEAPONS is %d; get a coder!\n", objp->instance, MAX_WEAPONS);
6304 	Assertion(!(objp->type == OBJ_BEAM) || (objp->instance < MAX_BEAMS), "spawn_child_weapons() called with a beam with an instance of %d while MAX_BEAMS is %d; get a coder!\n", objp->instance, MAX_BEAMS);
6305 
6306 	if (objp->type == OBJ_WEAPON) {
6307 		wp = &Weapons[objp->instance];
6308 		Assertion((wp->weapon_info_index >= 0) && (wp->weapon_info_index < weapon_info_size()), "Invalid weapon_info_index of %d; get a coder!\n", wp->weapon_info_index);
6309 		wip = &Weapon_info[wp->weapon_info_index];
6310 	} else if (objp->type == OBJ_BEAM) {
6311 		bp = &Beams[objp->instance];
6312 		Assertion((bp->weapon_info_index >= 0) && (bp->weapon_info_index < weapon_info_size()), "Invalid weapon_info_index of %d; get a coder!\n", bp->weapon_info_index);
6313 		wip = &Weapon_info[bp->weapon_info_index];
6314 	} else {	// Let's make sure we don't do screwball things in a release build if this gets called with a non-weapon non-beam.
6315 		return;
6316 	}
6317 
6318 	parent_num = objp->parent;
6319 
6320 	if (parent_num >= 0) {
6321 		if ((Objects[parent_num].type != objp->parent_type) || (Objects[parent_num].signature != objp->parent_sig)) {
6322 			mprintf(("Warning: Parent of spawn weapon does not exist.  Not spawning.\n"));
6323 			return;
6324 		}
6325 	}
6326 
6327 	ship* parent_shipp;
6328 	parent_shipp = &Ships[Objects[objp->parent].instance];
6329 	starting_sig = 0;
6330 
6331 	if ( Game_mode & GM_MULTIPLAYER ) {
6332 		// get the next network signature and save it.  Set the next usable network signature to be
6333 		// the passed in objects signature + 1.  We "reserved" N of these slots when we created objp
6334 		// for it's spawned children.
6335 		starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
6336 		multi_set_network_signature( objp->net_signature, MULTI_SIG_NON_PERMANENT );
6337 	}
6338 
6339 	opos = &objp->pos;
6340 	fvec = &objp->orient.vec.fvec;
6341 
6342 	// as opposed to continuous spawn
6343 	bool detonate_spawn = spawn_index_override == -1;
6344 
6345 	int start_index = detonate_spawn ? 0 : spawn_index_override;
6346 
6347 	for (int i = start_index; i < wip->num_spawn_weapons_defined; i++)
6348 	{
6349 		// don't spawn continuous spawning weapons on detonation
6350 		if (detonate_spawn && wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL)
6351 			continue;
6352 
6353 		// maybe roll for spawning
6354 		int spawn_count;
6355 		if (wip->spawn_info[i].spawn_chance < 1.f) {
6356 			spawn_count = 0;
6357 			for (int j = 0; j < wip->spawn_info[i].spawn_count; j++) {
6358 				if (frand() < wip->spawn_info[i].spawn_chance)
6359 					spawn_count++;
6360 			}
6361 		}
6362 		else {
6363 			spawn_count = wip->spawn_info[i].spawn_count;
6364 		}
6365 
6366 		for (int j = 0; j < spawn_count; j++)
6367 		{
6368 			int		weapon_objnum;
6369 			vec3d	tvec, pos;
6370 			matrix	orient;
6371 
6372 			child_id = wip->spawn_info[i].spawn_type;
6373 			child_wip = &Weapon_info[child_id];
6374 
6375 			// for multiplayer, use the static randvec functions based on the network signatures to provide
6376 			// the randomness so that it is the same on all machines.
6377 			if ( Game_mode & GM_MULTIPLAYER ) {
6378 				if (wip->spawn_info[i].spawn_min_angle <= 0)
6379 					static_rand_cone(objp->net_signature + j, &tvec, fvec, wip->spawn_info[i].spawn_angle);
6380 				else
6381 					static_rand_cone(objp->net_signature + j, &tvec, fvec, wip->spawn_info[i].spawn_min_angle, wip->spawn_info[i].spawn_angle);
6382 			} else {
6383 				if(wip->spawn_info[i].spawn_min_angle <= 0)
6384 					vm_vec_random_cone(&tvec, fvec, wip->spawn_info[i].spawn_angle);
6385 				else
6386 					vm_vec_random_cone(&tvec, fvec, wip->spawn_info[i].spawn_min_angle, wip->spawn_info[i].spawn_angle);
6387 			}
6388 			vm_vec_scale_add(&pos, opos, &tvec, objp->radius);
6389 
6390 			// Let's allow beam-spawn! -MageKing17
6391 			if (child_wip->wi_flags[Weapon::Info_Flags::Beam]) {
6392 				beam_fire_info fire_info;
6393 				memset(&fire_info, 0, sizeof(beam_fire_info));
6394 
6395 				fire_info.accuracy = 0.000001f;		// this will guarantee a hit
6396 				fire_info.shooter = &Objects[parent_num];
6397 				fire_info.turret = NULL;
6398 				if (child_wip->wi_flags[Weapon::Info_Flags::Inherit_parent_target] && wp->homing_object != &obj_used_list)
6399 					fire_info.target = wp->homing_object;
6400 				else
6401 					fire_info.target =  nullptr;
6402 				fire_info.target_subsys = NULL;
6403 				fire_info.target_pos1 = fire_info.target_pos2 = pos;
6404 				fire_info.bfi_flags |= BFIF_FLOATING_BEAM | BFIF_TARGETING_COORDS;
6405 				fire_info.starting_pos = *opos;
6406 				fire_info.beam_info_index = child_id;
6407 				fire_info.team = static_cast<char>(obj_team(&Objects[parent_num]));
6408 				fire_info.fire_method = BFM_SPAWNED;
6409 				fire_info.burst_index = j;
6410 				// we would normally accumulate this per burst rotation, which we can't do
6411 				// but we still need to do this once for the beam because it could be a negative, randomizing rotation
6412 				fire_info.per_burst_rotation = child_wip->b_info.t5info.per_burst_rot;
6413 
6414 				// fire the beam
6415 				beam_fire(&fire_info);
6416 			} else {
6417 				vm_vector_2_matrix(&orient, &tvec, nullptr, nullptr);
6418 				weapon_objnum = weapon_create(&pos, &orient, child_id, parent_num, -1, wp->weapon_flags[Weapon::Weapon_Flags::Locked_when_fired], 1);
6419 
6420 				//if the child inherits parent target, do it only if the parent weapon was locked to begin with
6421 				if ((child_wip->wi_flags[Weapon::Info_Flags::Inherit_parent_target]) && (wp->homing_object != &obj_used_list))
6422 				{
6423 					//Deal with swarm weapons
6424 					if (wp->swarm_info_ptr != nullptr) {
6425 						weapon_set_tracking_info(weapon_objnum, parent_num, wp->swarm_info_ptr->homing_objnum, 1, wp->homing_subsys);
6426 					} else {
6427 						weapon_set_tracking_info(weapon_objnum, parent_num, wp->target_num, 1, wp->homing_subsys);
6428 					}
6429 				}
6430 
6431 				//	Assign a little randomness to lifeleft so they don't all disappear at the same time.
6432 				if (weapon_objnum != -1) {
6433 					float rand_val;
6434 
6435 					if ( Game_mode & GM_NORMAL ){
6436 						rand_val = frand();
6437 					} else {
6438 						rand_val = static_randf(objp->net_signature + j);
6439 					}
6440 
6441 					Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f;
6442 					if (child_wip->wi_flags[Weapon::Info_Flags::Remote]) {
6443 						parent_shipp->weapons.detonate_weapon_time = timestamp((int)(DEFAULT_REMOTE_DETONATE_TRIGGER_WAIT * 1000));
6444 						parent_shipp->weapons.remote_detonaters_active++;
6445 					}
6446 				}
6447 			}
6448 		}
6449 
6450 		// only spawn one type if continuous spawn
6451 		if (!detonate_spawn)
6452 			break;
6453 	}
6454 
6455 	// in multiplayer, reset the next network signature to the one that was saved.
6456 	if ( Game_mode & GM_MULTIPLAYER ){
6457 		multi_set_network_signature( starting_sig, MULTI_SIG_NON_PERMANENT );
6458 	}
6459 }
6460 
6461 /**
6462  * Figures out whether to play disarmed or armed hit sound, checks that the
6463  * chosen one exists, and plays it
6464  */
weapon_play_impact_sound(weapon_info * wip,vec3d * hitpos,bool is_armed)6465 void weapon_play_impact_sound(weapon_info *wip, vec3d *hitpos, bool is_armed)
6466 {
6467 	if(is_armed)
6468 	{
6469 		if(wip->impact_snd.isValid()) {
6470 			snd_play_3d( gamesnd_get_game_sound(wip->impact_snd), hitpos, &Eye_position );
6471 		}
6472 	}
6473 	else
6474 	{
6475 		if(wip->disarmed_impact_snd.isValid()) {
6476 			snd_play_3d(gamesnd_get_game_sound(wip->disarmed_impact_snd), hitpos, &Eye_position);
6477 		}
6478 	}
6479 }
6480 
6481 /**
6482  * Play a sound effect when a weapon hits a ship
6483  *
6484  * To elimate the "stereo" effect of two lasers hitting at nearly
6485  * the same time, and to reduce the number of sound channels used,
6486  * only play one impact sound if IMPACT_SOUND_DELTA has elapsed
6487  *
6488  * @note Uses Weapon_impact_timer global for timer variable
6489  */
weapon_hit_do_sound(object * hit_obj,weapon_info * wip,vec3d * hitpos,bool is_armed,int quadrant)6490 void weapon_hit_do_sound(object *hit_obj, weapon_info *wip, vec3d *hitpos, bool is_armed, int quadrant)
6491 {
6492 	float shield_str;
6493 
6494 	// If non-missiles (namely lasers) expire without hitting a ship, don't play impact sound
6495 	if	( wip->subtype != WP_MISSILE ) {
6496 		if ( !hit_obj ) {
6497 			// flak weapons make sounds
6498 			if(wip->wi_flags[Weapon::Info_Flags::Flak])
6499 			{
6500 				weapon_play_impact_sound(wip, hitpos, is_armed);
6501 			}
6502 			return;
6503 		}
6504 
6505 		switch(hit_obj->type) {
6506 		case OBJ_SHIP:
6507 			// do nothing
6508 			break;
6509 
6510 		case OBJ_ASTEROID:
6511 			if ( timestamp_elapsed(Weapon_impact_timer) ) {
6512 				weapon_play_impact_sound(wip, hitpos, is_armed);
6513 				Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
6514 			}
6515 			return;
6516 			break;
6517 
6518 		default:
6519 			return;
6520 		}
6521 	}
6522 
6523 	if ( hit_obj == NULL ) {
6524 		weapon_play_impact_sound(wip, hitpos, is_armed);
6525 		return;
6526 	}
6527 
6528 	if ( timestamp_elapsed(Weapon_impact_timer) ) {
6529 
6530 		if ( hit_obj->type == OBJ_SHIP && quadrant >= 0 ) {
6531 			shield_str = ship_quadrant_shield_strength(hit_obj, quadrant);
6532 		} else {
6533 			shield_str = 0.0f;
6534 		}
6535 
6536 		// play a shield hit if shields are above 10% max in this quadrant
6537 		if ( shield_str > 0.1f ) {
6538 			// Play a shield impact sound effect
6539 			if ( hit_obj == Player_obj ) {
6540 				snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIELD_HIT_YOU), hitpos, &Eye_position );
6541 				// AL 12-15-97: Add missile impact sound even when shield is hit
6542 				if ( wip->subtype == WP_MISSILE ) {
6543 					snd_play_3d( gamesnd_get_game_sound(GameSounds::PLAYER_HIT_MISSILE), hitpos, &Eye_position);
6544 				}
6545 			} else {
6546 				snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIELD_HIT), hitpos, &Eye_position );
6547 			}
6548 		} else {
6549 			// Play a hull impact sound effect
6550 			switch ( wip->subtype ) {
6551 				case WP_LASER:
6552 					if ( hit_obj == Player_obj )
6553 						snd_play_3d( gamesnd_get_game_sound(GameSounds::PLAYER_HIT_LASER), hitpos, &Eye_position );
6554 					else {
6555 						weapon_play_impact_sound(wip, hitpos, is_armed);
6556 					}
6557 					break;
6558 				case WP_MISSILE:
6559 					if ( hit_obj == Player_obj )
6560 						snd_play_3d( gamesnd_get_game_sound(GameSounds::PLAYER_HIT_MISSILE), hitpos, &Eye_position);
6561 					else {
6562 						weapon_play_impact_sound(wip, hitpos, is_armed);
6563 					}
6564 					break;
6565 				default:
6566 					nprintf(("Warning","WARNING ==> Cannot determine sound to play for weapon impact\n"));
6567 					break;
6568 			} // end switch
6569 		}
6570 
6571 		Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
6572 	}
6573 }
6574 
6575 extern bool turret_weapon_has_flags(ship_weapon *swp, Weapon::Info_Flags flags);
6576 
6577 /**
6578  * Distrupt any subsystems that fall into damage sphere of this Electronics missile
6579  *
6580  * @param ship_objp	Pointer to ship that holds subsystem
6581  * @param blast_pos	World pos of weapon blast
6582  * @param wi_index	Weapon info index of weapon causing blast
6583  */
weapon_do_electronics_effect(object * ship_objp,vec3d * blast_pos,int wi_index)6584 void weapon_do_electronics_effect(object *ship_objp, vec3d *blast_pos, int wi_index)
6585 {
6586 	weapon_info			*wip;
6587 	ship				*shipp;
6588 	ship_subsys			*ss;
6589 	model_subsystem		*psub;
6590 	vec3d				subsys_world_pos;
6591 	float				dist;
6592 
6593 	shipp = &Ships[ship_objp->instance];
6594 	wip = &Weapon_info[wi_index];
6595 
6596 	for ( ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss) )
6597 	{
6598 		psub = ss->system_info;
6599 
6600 		// convert subsys point to world coords
6601 		vm_vec_unrotate(&subsys_world_pos, &psub->pnt, &ship_objp->orient);
6602 		vm_vec_add2(&subsys_world_pos, &ship_objp->pos);
6603 
6604 		// see if subsys point is within damage sphere
6605 		dist = vm_vec_dist_quick(blast_pos, &subsys_world_pos);
6606 		if ( dist < wip->shockwave.outer_rad )
6607 		{
6608 			float disrupt_time = (float)wip->elec_time;
6609 
6610 			//use new style electronics disruption
6611 			if (wip->elec_use_new_style)
6612 			{
6613 				//if its an engine subsytem, take the multiplier into account
6614 				if (psub->type==SUBSYSTEM_ENGINE)
6615 				{
6616 					disrupt_time*=wip->elec_eng_mult;
6617 				}
6618 
6619 				//if its a turret or weapon subsytem, take the multiplier into account
6620 				if ((psub->type==SUBSYSTEM_TURRET) || (psub->type==SUBSYSTEM_WEAPONS))
6621 				{
6622 					//disrupt beams
6623 					//WMC - do this even if there are other types of weapons on the turret.
6624 					//I figure, the big fancy electronics on beams will be used for the other
6625 					//weapons as well. No reason having two targeting computers on a turret.
6626 					//Plus, it's easy and fast to code. :)
6627 					if ((psub->type==SUBSYSTEM_TURRET) && turret_weapon_has_flags(&ss->weapons, Weapon::Info_Flags::Beam))
6628 					{
6629 						disrupt_time*=wip->elec_beam_mult;
6630 					}
6631 					//disrupt other weapons
6632 					else
6633 					{
6634 						disrupt_time*=wip->elec_weap_mult;
6635 					}
6636 				}
6637 
6638 				//disrupt sensor and awacs systems.
6639 				if ((psub->type==SUBSYSTEM_SENSORS) || (psub->flags[Model::Subsystem_Flags::Awacs]))
6640 				{
6641 					disrupt_time*=wip->elec_sensors_mult;
6642 				}
6643 			}
6644 
6645 			//add a little randomness to the disruption time
6646 			disrupt_time += frand_range(-1.0f, 1.0f) * wip->elec_randomness;
6647 
6648 			//disrupt this subsystem for the calculated time
6649 			//if it turns out to be less than 0 seconds, don't bother
6650 			if (disrupt_time > 0)
6651 			{
6652 				ship_subsys_set_disrupted(ss, fl2i(disrupt_time));
6653 			}
6654 		}
6655 	}
6656 }
6657 
6658 /**
6659  * Calculate the damage for an object based on the location of an area-effect
6660  * explosion.
6661  *
6662  * @param objp			Object pointer ship receiving blast effect
6663  * @param pos			World pos of blast center
6664  * @param inner_rad		Smallest radius at which full damage is done
6665  * @param outer_rad		Radius at which no damage is done
6666  * @param max_blast		Maximum blast possible from explosion
6667  * @param max_damage	Maximum damage possible from explosion
6668  * @param blast			OUTPUT PARAMETER: receives blast value from explosion
6669  * @param damage		OUTPUT PARAMETER: receives damage value from explosion
6670  * @param limit			A limit on the area, needed for shockwave damage
6671  *
6672  * @return		No damage occurred, -1
6673  * @return		Damage occured, 0
6674  */
weapon_area_calc_damage(object * objp,vec3d * pos,float inner_rad,float outer_rad,float max_blast,float max_damage,float * blast,float * damage,float limit)6675 int weapon_area_calc_damage(object *objp, vec3d *pos, float inner_rad, float outer_rad, float max_blast, float max_damage, float *blast, float *damage, float limit)
6676 {
6677 	float dist;
6678 	vec3d box_pt;
6679 
6680 	// if object receiving the blast is a ship, use the bbox for distances
6681 	// otherwise use the objects radius
6682 	// could possibly exclude SIF_SMALL_SHIP (& other small objects) from using the bbox
6683 	if (objp->type == OBJ_SHIP) {
6684 		int inside = get_nearest_bbox_point(objp, pos, &box_pt);
6685 		if (inside) {
6686 			dist = 0.0001f;
6687 		} else {
6688 			dist = vm_vec_dist_quick(pos, &box_pt);
6689 		}
6690 	} else {
6691 		dist = vm_vec_dist_quick(&objp->pos, pos) - objp->radius;
6692 	}
6693 
6694 	if ( (dist > outer_rad) || (dist > limit) ) {
6695 		return -1;	// spheres don't intersect at all
6696 	}
6697 
6698 	if ( dist < inner_rad ) {
6699 		// damage is maximum within inner radius
6700 		*damage = max_damage;
6701 		*blast = max_blast;
6702 	} else {
6703 		float dist_to_outer_rad_squared = (outer_rad-dist)*(outer_rad-dist);
6704 		float total_dist_squared = (inner_rad-outer_rad)*(inner_rad-outer_rad);
6705 
6706 		// this means the inner and outer radii are basically equal... and since we aren't within the inner radius,
6707 		// we fudge the law of excluded middle to place ourselves outside the outer radius
6708 		if (total_dist_squared < 0.0001f) {
6709 			return -1;	// avoid divide-by-zero; we won't take damage anyway
6710 		}
6711 
6712 		// AL 2-24-98: drop off damage relative to square of distance
6713 		Assert(dist_to_outer_rad_squared <= total_dist_squared);
6714 		*damage = max_damage * dist_to_outer_rad_squared/total_dist_squared;
6715 		*blast =  (dist - outer_rad) * max_blast /(inner_rad - outer_rad);
6716 	}
6717 
6718 	return 0;
6719 }
6720 
6721 /**
6722  * Apply the blast effects of an explosion to a ship
6723  *
6724  * @param force_apply_pos	World pos of where force is applied to object
6725  * @param ship_objp			Object pointer of ship receiving the blast
6726  * @param blast_pos			World pos of blast center
6727  * @param blast				Force of blast
6728  * @param make_shockwave	Boolean, whether to create a shockwave or not
6729  */
weapon_area_apply_blast(vec3d *,object * ship_objp,vec3d * blast_pos,float blast,int make_shockwave)6730 void weapon_area_apply_blast(vec3d * /*force_apply_pos*/, object *ship_objp, vec3d *blast_pos, float blast, int make_shockwave)
6731 {
6732 	vec3d		force, vec_blast_to_ship, vec_ship_to_impact;
6733 	polymodel		*pm;
6734 
6735 	// don't waste time here if there is no blast force
6736 	if ( blast == 0.0f )
6737 		return;
6738 
6739 	// apply blast force based on distance from center of explosion
6740 	vm_vec_sub(&vec_blast_to_ship, &ship_objp->pos, blast_pos);
6741 	vm_vec_normalize_safe(&vec_blast_to_ship);
6742 	vm_vec_copy_scale(&force, &vec_blast_to_ship, blast );
6743 
6744 	vm_vec_sub(&vec_ship_to_impact, blast_pos, &ship_objp->pos);
6745 
6746 	pm = model_get(Ship_info[Ships[ship_objp->instance].ship_info_index].model_num);
6747 	Assert ( pm != NULL );
6748 
6749 	if (make_shockwave) {
6750 		if (object_is_docked(ship_objp)) {
6751 			// TODO: this sales down the effect properly but physics_apply_shock will apply
6752 			// forces based on this ship's bbox, rather than the whole assembly's bbox like it should
6753 			blast *= ship_objp->phys_info.mass / dock_calc_total_docked_mass(ship_objp);
6754 		}
6755 		physics_apply_shock (&force, blast, &ship_objp->phys_info, &ship_objp->orient, &pm->mins, &pm->maxs, pm->rad);
6756 		if (ship_objp == Player_obj) {
6757 			joy_ff_play_vector_effect(&vec_blast_to_ship, blast * 2.0f);
6758 		}
6759 	} else {
6760 		ship_apply_whack( &force, blast_pos, ship_objp);
6761 	}
6762 }
6763 
6764 /**
6765  * Do the area effect for a weapon
6766  *
6767  * @param wobjp		Object pointer to weapon causing explosion
6768  * @param sci		Shockwave info
6769  * @param pos		World pos of explosion center
6770  * @param other_obj	Object pointer to ship that weapon impacted on (can be NULL)
6771  */
weapon_do_area_effect(object * wobjp,shockwave_create_info * sci,vec3d * pos,object * other_obj)6772 void weapon_do_area_effect(object *wobjp, shockwave_create_info *sci, vec3d *pos, object *other_obj)
6773 {
6774 	weapon_info	*wip;
6775 	object		*objp;
6776 	float			damage, blast;
6777 
6778 	wip = &Weapon_info[Weapons[wobjp->instance].weapon_info_index];
6779 
6780 	// only blast ships and asteroids
6781 	// And (some) weapons
6782 	for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
6783 		if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) && (objp->type != OBJ_WEAPON) ) {
6784 			continue;
6785 		}
6786 
6787 		if (objp->type == OBJ_WEAPON) {
6788 			// only apply to missiles with hitpoints
6789 			weapon_info* wip2 = &Weapon_info[Weapons[objp->instance].weapon_info_index];
6790 			if (wip2->weapon_hitpoints <= 0)
6791 				continue;
6792 			if (!((wip2->wi_flags[Weapon::Info_Flags::Takes_blast_damage]) || (wip->wi_flags[Weapon::Info_Flags::Ciws])))
6793 				continue;
6794 		}
6795 
6796 		if ( objp->type == OBJ_SHIP ) {
6797 			// don't blast navbuoys
6798 			if ( ship_get_SIF(objp->instance)[Ship::Info_Flags::Navbuoy] ) {
6799 				continue;
6800 			}
6801 		}
6802 
6803 		if ( weapon_area_calc_damage(objp, pos, sci->inner_rad, sci->outer_rad, sci->blast, sci->damage, &blast, &damage, sci->outer_rad) == -1 ){
6804 			continue;
6805 		}
6806 
6807 		// scale damage
6808 		damage *= weapon_get_damage_scale(wip, wobjp, other_obj);
6809 
6810 		weapon_info* target_wip;
6811 
6812 		switch ( objp->type ) {
6813 		case OBJ_SHIP:
6814 			// If we're doing an AoE Electronics blast, do the electronics stuff (unless it also has the regular "electronics"
6815 			// flag and this is the ship the missile directly impacted; then leave it for the regular code below) -MageKing17
6816 			if ( (wip->wi_flags[Weapon::Info_Flags::Aoe_Electronics]) && !((objp->flags[Object::Object_Flags::Invulnerable]) || ((objp == other_obj) && (wip->wi_flags[Weapon::Info_Flags::Electronics]))) ) {
6817 				weapon_do_electronics_effect(objp, pos, Weapons[wobjp->instance].weapon_info_index);
6818 			}
6819 			ship_apply_global_damage(objp, wobjp, pos, damage, wip->shockwave.damage_type_idx);
6820 			weapon_area_apply_blast(NULL, objp, pos, blast, 0);
6821 			break;
6822 		case OBJ_ASTEROID:
6823 			asteroid_hit(objp, NULL, NULL, damage);
6824 			break;
6825 		case OBJ_WEAPON:
6826 			target_wip = &Weapon_info[Weapons[objp->instance].weapon_info_index];
6827 			if (target_wip->armor_type_idx >= 0)
6828 				damage = Armor_types[target_wip->armor_type_idx].GetDamage(damage, wip->shockwave.damage_type_idx, 1.0f);
6829 
6830 			objp->hull_strength -= damage;
6831 			if (objp->hull_strength < 0.0f) {
6832 				Weapons[objp->instance].lifeleft = 0.01f;
6833 				Weapons[objp->instance].weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon);
6834 			}
6835 			break;
6836 		default:
6837 			Int3();
6838 			break;
6839 		}
6840 
6841 	}	// end for
6842 
6843 
6844 }
6845 
6846 //	----------------------------------------------------------------------
6847 //	weapon_armed(weapon)
6848 //
6849 //	Call to figure out if a weapon is armed or not
6850 //
6851 //Weapon is armed when...
6852 //1: Weapon is shot down by weapon
6853 //OR
6854 //1: weapon is destroyed before arm time
6855 //2: weapon is destroyed before arm distance from ship
6856 //3: weapon is outside arm radius from target ship
weapon_armed(weapon * wp,bool hit_target)6857 bool weapon_armed(weapon *wp, bool hit_target)
6858 {
6859 	Assert(wp != NULL);
6860 
6861 	weapon_info *wip = &Weapon_info[wp->weapon_info_index];
6862 
6863 	if((wp->weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon])
6864 		&& !wip->arm_time
6865 		&& wip->arm_dist == 0.0f
6866 		&& wip->arm_radius == 0.0f)
6867 	{
6868 		return false;
6869 	}
6870 	else
6871 	{
6872 		object *wobj = &Objects[wp->objnum];
6873 		object *pobj;
6874 
6875 		if(wobj->parent > -1) {
6876 			pobj = &Objects[wobj->parent];
6877 		} else {
6878 			pobj = NULL;
6879 		}
6880 
6881 		if(		((wip->arm_time) && ((Missiontime - wp->creation_time) < wip->arm_time))
6882 			|| ((wip->arm_dist) && (pobj != NULL && pobj->type != OBJ_NONE && (vm_vec_dist(&wobj->pos, &pobj->pos) < wip->arm_dist))))
6883 		{
6884 			return false;
6885 		}
6886 		if(wip->arm_radius && (!hit_target)) {
6887 			if(wp->homing_object == &obj_used_list)
6888 				return false;
6889 			if(IS_VEC_NULL(&wp->homing_pos) || vm_vec_dist(&wobj->pos, &wp->homing_pos) > wip->arm_radius)
6890 				return false;
6891 		}
6892 	}
6893 
6894 	return true;
6895 }
6896 
6897 
6898 /**
6899  * Called when a weapon hits something (or, in the case of
6900  * missiles explodes for any particular reason)
6901  */
weapon_hit(object * weapon_obj,object * other_obj,vec3d * hitpos,int quadrant,vec3d * hitnormal)6902 void weapon_hit( object * weapon_obj, object * other_obj, vec3d * hitpos, int quadrant, vec3d* hitnormal )
6903 {
6904 	Assert(weapon_obj != NULL);
6905 	if(weapon_obj == NULL){
6906 		return;
6907 	}
6908 	Assert((weapon_obj->type == OBJ_WEAPON) && (weapon_obj->instance >= 0) && (weapon_obj->instance < MAX_WEAPONS));
6909 	if((weapon_obj->type != OBJ_WEAPON) || (weapon_obj->instance < 0) || (weapon_obj->instance >= MAX_WEAPONS)){
6910 		return;
6911 	}
6912 
6913 	int			num = weapon_obj->instance;
6914 	int			weapon_type = Weapons[num].weapon_info_index;
6915 	weapon_info	*wip;
6916 	weapon *wp;
6917 	bool		hit_target = false;
6918 
6919 	object      *other_objp;
6920 	ship_obj	*so;
6921 	ship		*shipp;
6922 	int         objnum;
6923 
6924 	Assert((weapon_type >= 0) && (weapon_type < weapon_info_size()));
6925 	if ((weapon_type < 0) || (weapon_type >= weapon_info_size())) {
6926 		return;
6927 	}
6928 	wp = &Weapons[weapon_obj->instance];
6929 	wip = &Weapon_info[weapon_type];
6930 	objnum = wp->objnum;
6931 
6932 	// check if the weapon actually hit the intended target
6933 	if (wp->homing_object != &obj_used_list)
6934 		if (wp->homing_object == other_obj)
6935 			hit_target = true;
6936 
6937 	//This is an expensive check
6938 	bool armed_weapon = weapon_armed(&Weapons[num], hit_target);
6939 
6940 	// if this is the player ship, and is a laser hit, skip it. wait for player "pain" to take care of it
6941 	if ((other_obj != Player_obj) || (wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT) {
6942 		weapon_hit_do_sound(other_obj, wip, hitpos, armed_weapon, quadrant);
6943 	}
6944 
6945 	if (wip->impact_weapon_expl_effect.isValid() && armed_weapon) {
6946 		auto particleSource = particle::ParticleManager::get()->createSource(wip->impact_weapon_expl_effect);
6947 		particleSource.moveTo(hitpos);
6948 		particleSource.setOrientationFromVec(&weapon_obj->phys_info.vel);
6949 
6950 		if (hitnormal)
6951 		{
6952 			particleSource.setOrientationNormal(hitnormal);
6953 		}
6954 
6955 		particleSource.finish();
6956 	} else if (wip->dinky_impact_weapon_expl_effect.isValid() && !armed_weapon) {
6957 		auto particleSource = particle::ParticleManager::get()->createSource(wip->dinky_impact_weapon_expl_effect);
6958 		particleSource.moveTo(hitpos);
6959 		particleSource.setOrientationFromVec(&weapon_obj->phys_info.vel);
6960 
6961 		if (hitnormal)
6962 		{
6963 			particleSource.setOrientationNormal(hitnormal);
6964 		}
6965 
6966 		particleSource.finish();
6967 	}
6968 
6969 	if ((other_obj != nullptr) && (quadrant == -1) && (wip->piercing_impact_effect.isValid() && armed_weapon)) {
6970 		if ((other_obj->type == OBJ_SHIP) || (other_obj->type == OBJ_DEBRIS)) {
6971 
6972 			int ok_to_draw = 1;
6973 
6974 			if (other_obj->type == OBJ_SHIP) {
6975 				float draw_limit, hull_pct;
6976 				int dmg_type_idx, piercing_type;
6977 
6978 				shipp = &Ships[other_obj->instance];
6979 
6980 				hull_pct = other_obj->hull_strength / shipp->ship_max_hull_strength;
6981 				dmg_type_idx = wip->damage_type_idx;
6982 				draw_limit = Ship_info[shipp->ship_info_index].piercing_damage_draw_limit;
6983 
6984 				if (shipp->armor_type_idx != -1) {
6985 					piercing_type = Armor_types[shipp->armor_type_idx].GetPiercingType(dmg_type_idx);
6986 					if (piercing_type == SADTF_PIERCING_DEFAULT) {
6987 						draw_limit = Armor_types[shipp->armor_type_idx].GetPiercingLimit(dmg_type_idx);
6988 					} else if ((piercing_type == SADTF_PIERCING_NONE) || (piercing_type == SADTF_PIERCING_RETAIL)) {
6989 						ok_to_draw = 0;
6990 					}
6991 				}
6992 
6993 				if (hull_pct > draw_limit)
6994 					ok_to_draw = 0;
6995 			}
6996 
6997 			if (ok_to_draw) {
6998 				using namespace particle;
6999 
7000 				auto primarySource = ParticleManager::get()->createSource(wip->piercing_impact_effect);
7001 				primarySource.moveTo(&weapon_obj->pos);
7002 				primarySource.setOrientationMatrix(&weapon_obj->last_orient);
7003 
7004 				if (hitnormal)
7005 				{
7006 					primarySource.setOrientationNormal(hitnormal);
7007 				}
7008 
7009 				primarySource.finish();
7010 
7011 				if (wip->piercing_impact_secondary_effect.isValid()) {
7012 					auto secondarySource = ParticleManager::get()->createSource(wip->piercing_impact_secondary_effect);
7013 					secondarySource.moveTo(&weapon_obj->pos);
7014 					secondarySource.setOrientationMatrix(&weapon_obj->last_orient);
7015 
7016 					if (hitnormal)
7017 					{
7018 						secondarySource.setOrientationNormal(hitnormal);
7019 					}
7020 
7021 					secondarySource.finish();
7022 				}
7023 			}
7024 		}
7025 	}
7026 
7027 	//Set shockwaves flag
7028 	int sw_flag = SW_WEAPON;
7029 
7030 	if ( ((other_obj) && (other_obj->type == OBJ_WEAPON)) || (Weapons[num].weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon])) {
7031 		sw_flag |= SW_WEAPON_KILL;
7032 	}
7033 
7034 	//Which shockwave?
7035 	shockwave_create_info *sci = &wip->shockwave;
7036 	if(!armed_weapon) {
7037 		sci = &wip->dinky_shockwave;
7038 	}
7039 
7040 	// check if this is an area effect weapon (i.e. has a blast radius)
7041 	if (sci->inner_rad != 0.0f || sci->outer_rad != 0.0f)
7042 	{
7043 		if(sci->speed > 0.0f) {
7044 			shockwave_create(OBJ_INDEX(weapon_obj), hitpos, sci, sw_flag, -1);
7045 		}
7046 		else {
7047 			weapon_do_area_effect(weapon_obj, sci, hitpos, other_obj);
7048 		}
7049 	}
7050 
7051 	// check if this is an EMP weapon
7052 	if(wip->wi_flags[Weapon::Info_Flags::Emp]){
7053 		emp_apply(&weapon_obj->pos, wip->shockwave.inner_rad, wip->shockwave.outer_rad, wip->emp_intensity, wip->emp_time, (wip->wi_flags[Weapon::Info_Flags::Use_emp_time_for_capship_turrets]) != 0);
7054 	}
7055 
7056 	// if this weapon has the "Electronics" flag set, then disrupt subsystems in sphere
7057 	if ((other_obj != NULL) && (wip->wi_flags[Weapon::Info_Flags::Electronics])) {
7058 		if (other_obj->type == OBJ_SHIP) {
7059 			weapon_do_electronics_effect(other_obj, &weapon_obj->pos, Weapons[weapon_obj->instance].weapon_info_index);
7060 		}
7061 	}
7062 
7063 	if (!wip->pierce_objects || wip->spawn_children_on_pierce || !other_obj) {
7064 		// spawn weapons - note the change from FS 1 multiplayer.
7065 		if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
7066 			if (!((wip->wi_flags[Weapon::Info_Flags::Dont_spawn_if_shot]) && (Weapons[num].weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon]))) {			// prevent spawning of children if shot down and the dont spawn if shot flag is set (DahBlount)
7067 				spawn_child_weapons(weapon_obj);
7068 			}
7069 		}
7070 	}
7071 
7072 	//No other_obj means this weapon detonates
7073 	if (wip->pierce_objects && other_obj)
7074 		return;
7075 
7076 	// For all objects that had this weapon as a target, wipe it out, forcing find of a new enemy
7077 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
7078 		other_objp = &Objects[so->objnum];
7079 		Assert(other_objp->instance != -1);
7080 
7081 		shipp = &Ships[other_objp->instance];
7082 		Assert(shipp->ai_index != -1);
7083 
7084 		ai_info	*aip = &Ai_info[shipp->ai_index];
7085 
7086 		if (aip->target_objnum == objnum) {
7087 			set_target_objnum(aip, -1);
7088 			//	If this ship had a dynamic goal of chasing this weapon, clear the dynamic goal.
7089 			if (aip->resume_goal_time != -1)
7090 				aip->active_goal = AI_GOAL_NONE;
7091 		}
7092 
7093 		if (aip->goal_objnum == objnum) {
7094 			aip->goal_objnum = -1;
7095 			aip->goal_signature = -1;
7096 		}
7097 
7098 		if (aip->guard_objnum == objnum) {
7099 			aip->guard_objnum = -1;
7100 			aip->guard_signature = -1;
7101 		}
7102 
7103 		if (aip->hitter_objnum == objnum) {
7104 			aip->hitter_objnum = -1;
7105         }
7106 	}
7107 
7108     weapon_obj->flags.set(Object::Object_Flags::Should_be_dead);
7109 
7110 	// decrement parent's number of active remote detonators if applicable
7111 	if (wip->wi_flags[Weapon::Info_Flags::Remote] && weapon_obj->parent >= 0 && (weapon_obj->parent < MAX_OBJECTS)) {
7112 		object* parent = &Objects[weapon_obj->parent];
7113 		if ( parent->type == OBJ_SHIP && parent->signature == weapon_obj->parent_sig)
7114 			Ships[Objects[weapon_obj->parent].instance].weapons.remote_detonaters_active--;
7115 	}
7116 }
7117 
weapon_detonate(object * objp)7118 void weapon_detonate(object *objp)
7119 {
7120 	Assert(objp != NULL);
7121 	if(objp == NULL){
7122 		return;
7123 	}
7124 	Assert((objp->type == OBJ_WEAPON) && (objp->instance >= 0));
7125 	if((objp->type != OBJ_WEAPON) || (objp->instance < 0)){
7126 		return;
7127 	}
7128 
7129 	// send a detonate packet in multiplayer
7130 	if(MULTIPLAYER_MASTER){
7131 		send_weapon_detonate_packet(objp);
7132 	}
7133 
7134 	// call weapon hit
7135 	// Wanderer - use last frame pos for the corkscrew missiles
7136 	if ( (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Corkscrew]) ) {
7137 		weapon_hit(objp, NULL, &objp->last_pos);
7138 	} else {
7139 		weapon_hit(objp, NULL, &objp->pos);
7140 	}
7141 }
7142 
7143 
7144 // Group_id:  If you should quad lasers, they should all have the same group id.
7145 // This will be used to optimize lighting, since each group only needs to cast one light.
7146 // Call this to get a new group id, then pass it to each weapon_create call for all the
7147 // weapons in the group.   Number will be between 0 and WEAPON_MAX_GROUP_IDS and will
7148 // get reused.
weapon_create_group_id()7149 int weapon_create_group_id()
7150 {
7151 	static int current_id = 0;
7152 
7153 	int n = current_id;
7154 
7155 	current_id++;
7156 	if ( current_id >= WEAPON_MAX_GROUP_IDS )	{
7157 		current_id = 0;
7158 	}
7159 	return n;
7160 }
7161 
7162 /**
7163  * Call before weapons_page_in to mark a weapon as used
7164  */
weapon_mark_as_used(int weapon_type)7165 void weapon_mark_as_used(int weapon_type)
7166 {
7167 	if (weapon_type < 0)
7168 		return;
7169 
7170 	if ( used_weapons == NULL )
7171 		return;
7172 
7173 	Assert( weapon_type < weapon_info_size() );
7174 
7175 	if (weapon_type < weapon_info_size()) {
7176 		used_weapons[weapon_type]++;
7177 		if (Weapon_info[weapon_type].num_substitution_patterns > 0) {
7178 			for (size_t i = 0; i < Weapon_info[weapon_type].num_substitution_patterns; i++) {
7179 				used_weapons[Weapon_info[weapon_type].weapon_substitution_pattern[i]]++;
7180 			}
7181 		}
7182 	}
7183 }
7184 
weapons_page_in()7185 void weapons_page_in()
7186 {
7187 	TRACE_SCOPE(tracing::WeaponPageIn);
7188 
7189 	int i, j, idx;
7190 
7191 	Assert( used_weapons != NULL );
7192 
7193 	// for weapons in weaponry pool
7194 	for (i = 0; i < Num_teams; i++) {
7195 		for (j = 0; j < Team_data[i].num_weapon_choices; j++) {
7196 			used_weapons[Team_data[i].weaponry_pool[j]] += Team_data[i].weaponry_count[j];
7197 		}
7198 	}
7199 
7200 	// this grabs all spawn weapon types (Cluster Baby, etc.) which can't be
7201 	// assigned directly to a ship
7202 	for (i = 0; i < weapon_info_size(); i++) {
7203 		// we only want entries that already exist
7204 		if ( !used_weapons[i] )
7205 			continue;
7206 
7207 		// if it's got a spawn type then grab it
7208         for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
7209         {
7210             used_weapons[(int)Weapon_info[i].spawn_info[j].spawn_type]++;
7211         }
7212 	}
7213 
7214 	// release anything loaded that we don't have marked as used for this mission
7215 	if ( !Cmdline_load_all_weapons )
7216 		weapon_release_bitmaps();
7217 
7218 	// Page in bitmaps for all used weapons
7219 	for (i = 0; i < weapon_info_size(); i++) {
7220 		if ( !Cmdline_load_all_weapons ) {
7221 			if ( !used_weapons[i] ) {
7222 				nprintf(("Weapons", "Not loading weapon id %d (%s)\n", i, Weapon_info[i].name));
7223 				continue;
7224 			}
7225 		}
7226 
7227 		weapon_load_bitmaps(i);
7228 
7229 		weapon_info *wip = &Weapon_info[i];
7230 
7231         wip->wi_flags.remove(Weapon::Info_Flags::Thruster);		// Assume no thrusters
7232 
7233 		switch (wip->render_type)
7234 		{
7235 			case WRT_POF:
7236 			{
7237 				wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
7238 
7239 				polymodel *pm = model_get( wip->model_num );
7240 
7241 				// If it has a model, and the model pof has thrusters, then set
7242 				// the flags
7243 				if (pm->n_thrusters > 0) {
7244                     wip->wi_flags.set(Weapon::Info_Flags::Thruster);
7245 				}
7246 
7247 				for (j = 0; j < pm->n_textures; j++)
7248 					pm->maps[j].PageIn();
7249 
7250 				break;
7251 			}
7252 
7253 			case WRT_LASER:
7254 			{
7255 				bm_page_in_texture( wip->laser_bitmap.first_frame );
7256 				bm_page_in_texture( wip->laser_glow_bitmap.first_frame );
7257 
7258 				break;
7259 			}
7260 
7261 			default:
7262 				Assertion(wip->render_type != WRT_POF && wip->render_type != WRT_LASER, "Weapon %s does not have a valid rendering type. Type passed: %d\n", wip->name, wip->render_type);	// Invalid weapon rendering type.
7263 		}
7264 
7265 		wip->external_model_num = -1;
7266 
7267 		if ( strlen(wip->external_model_name) )
7268 			wip->external_model_num = model_load( wip->external_model_name, 0, NULL );
7269 
7270 		if (wip->external_model_num == -1)
7271 			wip->external_model_num = wip->model_num;
7272 
7273 
7274 		//Load shockwaves
7275 		shockwave_create_info_load(&wip->shockwave);
7276 		shockwave_create_info_load(&wip->dinky_shockwave);
7277 
7278 		// trail bitmaps
7279 		if ( (wip->wi_flags[Weapon::Info_Flags::Trail]) && (wip->tr_info.texture.bitmap_id > -1) )
7280 			bm_page_in_texture( wip->tr_info.texture.bitmap_id );
7281 
7282 		// if this is a beam weapon, page in its stuff
7283 		if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
7284 			// all beam sections
7285 			for (idx = 0; idx < wip->b_info.beam_num_sections; idx++)
7286 				bm_page_in_texture(wip->b_info.sections[idx].texture.first_frame);
7287 
7288 			// muzzle glow
7289 			bm_page_in_texture(wip->b_info.beam_glow.first_frame);
7290 
7291 			// particle ani
7292 			bm_page_in_texture(wip->b_info.beam_particle_ani.first_frame);
7293 		}
7294 
7295 		if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
7296 			for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {	// looped, multi particle spew -nuke
7297 				if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
7298 					bm_page_in_texture(wip->particle_spewers[s].particle_spew_anim.first_frame);
7299 				}
7300 			}
7301 		}
7302 
7303 		// muzzle flashes
7304 		if (wip->muzzle_flash >= 0)
7305 			mflash_mark_as_used(wip->muzzle_flash);
7306 
7307 		bm_page_in_texture(wip->thruster_flame.first_frame);
7308 		bm_page_in_texture(wip->thruster_glow.first_frame);
7309 
7310 		decals::pageInDecal(wip->impact_decal);
7311 	}
7312 }
7313 
7314 /**
7315  * Page_in function for cheaters, grabs all weapons that weren't already in a mission
7316  * and loads the models for them.
7317  *
7318  * Non-model graphics elements will get loaded when they are rendered for the first time.
7319  * Maybe not the best way to do this but faster and a lot less error prone.
7320  */
weapons_page_in_cheats()7321 void weapons_page_in_cheats()
7322 {
7323 	int i;
7324 
7325 	// don't bother if they are all loaded already
7326 	if ( Cmdline_load_all_weapons )
7327 		return;
7328 
7329 	Assert( used_weapons != NULL );
7330 
7331 	// force a page in of all muzzle flashes
7332 	mflash_page_in(true);
7333 
7334 	// page in models for all weapon types that aren't already loaded
7335 	for (i = 0; i < weapon_info_size(); i++) {
7336 		// skip over anything that's already loaded
7337 		if (used_weapons[i])
7338 			continue;
7339 
7340 		weapon_load_bitmaps(i);
7341 
7342 		weapon_info *wip = &Weapon_info[i];
7343 
7344         wip->wi_flags.remove(Weapon::Info_Flags::Thruster);		// Assume no thrusters
7345 
7346 		if ( wip->render_type == WRT_POF ) {
7347 			wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
7348 
7349 			polymodel *pm = model_get( wip->model_num );
7350 
7351 			// If it has a model, and the model pof has thrusters, then set
7352 			// the flags
7353 			if ( pm->n_thrusters > 0 )	{
7354                 wip->wi_flags.set(Weapon::Info_Flags::Thruster);
7355 			}
7356 		}
7357 
7358 		wip->external_model_num = -1;
7359 
7360 		if ( strlen(wip->external_model_name) )
7361 			wip->external_model_num = model_load( wip->external_model_name, 0, NULL );
7362 
7363 		if (wip->external_model_num == -1)
7364 			wip->external_model_num = wip->model_num;
7365 
7366 
7367 		//Load shockwaves
7368 		shockwave_create_info_load(&wip->shockwave);
7369 		shockwave_create_info_load(&wip->dinky_shockwave);
7370 
7371 		used_weapons[i]++;
7372 	}
7373 }
7374 
7375 /* Helper function for l_Weaponclass.isWeaponLoaded()
7376  * Pages in a single weapon and its substitutes and chilren given the weapon_info index for it
7377  */
weapon_page_in(int weapon_type)7378 bool weapon_page_in(int weapon_type)
7379 {
7380 	Assert(used_weapons != NULL);
7381 
7382 	if (weapon_type < 0 || weapon_type >= weapon_info_size()) {
7383 		return false;
7384 	}
7385 
7386 	SCP_vector<int> page_in_weapons;
7387 	page_in_weapons.push_back(weapon_type);
7388 
7389 	// Make sure substitution weapons are paged in as well
7390 	if (Weapon_info[weapon_type].num_substitution_patterns > 0) {
7391 		for (size_t i = 0; i < Weapon_info[weapon_type].num_substitution_patterns; i++) {
7392 			page_in_weapons.push_back(Weapon_info[weapon_type].weapon_substitution_pattern[i]);
7393 		}
7394 	}
7395 
7396 	// this grabs all spawn weapon types (Cluster Baby, etc.) which can't be
7397 	// assigned directly to a ship
7398 	// if it's got a spawn type then grab it
7399 	size_t size = page_in_weapons.size();
7400 	for (size_t x = 0; x < size; x++) {
7401 		if (Weapon_info[page_in_weapons.at(x)].num_spawn_weapons_defined > 0) {
7402 			for (int j = 0; j < Weapon_info[page_in_weapons.at(x)].num_spawn_weapons_defined; j++) {
7403 				page_in_weapons.push_back((int)Weapon_info[page_in_weapons.at(x)].spawn_info[j].spawn_type);
7404 			}
7405 		}
7406 	}
7407 
7408 	for (size_t k = 0; k < page_in_weapons.size(); k++) {
7409 		if (used_weapons[page_in_weapons.at(k)]) {
7410 			continue;		// If weapon is already paged_in, we don't need to page it in again
7411 		}
7412 
7413 		// Page in bitmaps for the weapon
7414 		weapon_load_bitmaps(page_in_weapons.at(k));
7415 
7416 		weapon_info *wip = &Weapon_info[page_in_weapons.at(k)];
7417 
7418 		wip->wi_flags.remove(Weapon::Info_Flags::Thruster);		// Assume no thrusters
7419 
7420 		switch (wip->render_type)
7421 		{
7422 		case WRT_POF:
7423 		{
7424 			wip->model_num = model_load(wip->pofbitmap_name, 0, NULL);
7425 
7426 			polymodel *pm = model_get(wip->model_num);
7427 
7428 			// If it has a model, and the model pof has thrusters, then set
7429 			// the flags
7430 			if (pm->n_thrusters > 0) {
7431 				wip->wi_flags.set(Weapon::Info_Flags::Thruster);
7432 			}
7433 
7434 			for (int j = 0; j < pm->n_textures; j++)
7435 				pm->maps[j].PageIn();
7436 
7437 			break;
7438 		}
7439 
7440 		case WRT_LASER:
7441 		{
7442 			bm_page_in_texture(wip->laser_bitmap.first_frame);
7443 			bm_page_in_texture(wip->laser_glow_bitmap.first_frame);
7444 
7445 			break;
7446 		}
7447 
7448 		default:
7449 			Assertion(wip->render_type != WRT_POF && wip->render_type != WRT_LASER, "Weapon %s does not have a valid rendering type. Type passed: %d\n", wip->name, wip->render_type);	// Invalid weapon rendering type.
7450 		}
7451 
7452 		wip->external_model_num = -1;
7453 
7454 		if (strlen(wip->external_model_name))
7455 			wip->external_model_num = model_load(wip->external_model_name, 0, NULL);
7456 
7457 		if (wip->external_model_num == -1)
7458 			wip->external_model_num = wip->model_num;
7459 
7460 
7461 		//Load shockwaves
7462 		shockwave_create_info_load(&wip->shockwave);
7463 		shockwave_create_info_load(&wip->dinky_shockwave);
7464 
7465 		// trail bitmaps
7466 		if ((wip->wi_flags[Weapon::Info_Flags::Trail]) && (wip->tr_info.texture.bitmap_id > -1))
7467 			bm_page_in_texture(wip->tr_info.texture.bitmap_id);
7468 
7469 		// if this is a beam weapon, page in its stuff
7470 		if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
7471 			// all beam sections
7472 			for (int idx = 0; idx < wip->b_info.beam_num_sections; idx++)
7473 				bm_page_in_texture(wip->b_info.sections[idx].texture.first_frame);
7474 
7475 			// muzzle glow
7476 			bm_page_in_texture(wip->b_info.beam_glow.first_frame);
7477 
7478 			// particle ani
7479 			bm_page_in_texture(wip->b_info.beam_particle_ani.first_frame);
7480 		}
7481 
7482 		if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
7483 			for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {	// looped, multi particle spew -nuke
7484 				if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
7485 					bm_page_in_texture(wip->particle_spewers[s].particle_spew_anim.first_frame);
7486 				}
7487 			}
7488 		}
7489 
7490 		// muzzle flashes
7491 		if (wip->muzzle_flash >= 0)
7492 			mflash_mark_as_used(wip->muzzle_flash);
7493 
7494 		bm_page_in_texture(wip->thruster_flame.first_frame);
7495 		bm_page_in_texture(wip->thruster_glow.first_frame);
7496 
7497 		// Page in decal bitmaps
7498 		decals::pageInDecal(wip->impact_decal);
7499 
7500 		used_weapons[page_in_weapons.at(k)]++;	// Ensures weapon can be counted as used
7501 	}
7502 
7503 	return true;
7504 }
7505 
weapon_used(int weapon_type)7506 bool weapon_used(int weapon_type) {
7507 	return used_weapons[weapon_type] > 0;
7508 }
7509 
7510 /**
7511  * Get the "color" of the laser at the given moment (since glowing lasers can cycle colors)
7512  */
weapon_get_laser_color(color * c,object * objp)7513 void weapon_get_laser_color(color *c, object *objp)
7514 {
7515 	weapon *wep;
7516 	weapon_info *winfo;
7517 	float pct;
7518 
7519 	// sanity
7520 	if (c == NULL)
7521 		return;
7522 
7523 	// sanity
7524 	Assert(objp != NULL);
7525 	Assert(objp->type == OBJ_WEAPON);
7526 	Assert(objp->instance >= 0);
7527 	Assert(Weapons[objp->instance].weapon_info_index >= 0);
7528 
7529 	if ( (objp == NULL) || (objp->type != OBJ_WEAPON) || (objp->instance < 0) || (Weapons[objp->instance].weapon_info_index < 0) )
7530 		return;
7531 
7532 	wep = &Weapons[objp->instance];
7533 	winfo = &Weapon_info[wep->weapon_info_index];
7534 
7535 	// if we're a one-color laser
7536 	if ( (winfo->laser_color_2.red == winfo->laser_color_1.red) && (winfo->laser_color_2.green == winfo->laser_color_1.green) && (winfo->laser_color_2.blue == winfo->laser_color_1.blue) ) {
7537 		*c = winfo->laser_color_1;
7538 		return;
7539 	}
7540 
7541 	int r = winfo->laser_color_1.red;
7542 	int g = winfo->laser_color_1.green;
7543 	int b = winfo->laser_color_1.blue;
7544 
7545 	// lifetime pct
7546 	pct = 1.0f - (wep->lifeleft / winfo->lifetime);
7547 	CLAMP(pct, 0.0f, 0.5f);
7548 
7549 	if (pct > 0.0f) {
7550 		pct *= 2.0f;
7551 
7552 		r += fl2i((winfo->laser_color_2.red - winfo->laser_color_1.red) * pct);
7553 		g += fl2i((winfo->laser_color_2.green - winfo->laser_color_1.green) * pct);
7554 		b += fl2i((winfo->laser_color_2.blue - winfo->laser_color_1.blue) * pct);
7555 	}
7556 
7557 	// otherwise interpolate between the colors
7558 	gr_init_color( c, r, g, b );
7559 }
7560 
7561 // default weapon particle spew data
7562 
7563 int Weapon_particle_spew_count = 1;
7564 int Weapon_particle_spew_time = 25;
7565 float Weapon_particle_spew_vel = 0.4f;
7566 float Weapon_particle_spew_radius = 2.0f;
7567 float Weapon_particle_spew_lifetime = 0.15f;
7568 float Weapon_particle_spew_scale = 0.8f;
7569 
7570 /**
7571  * For weapons flagged as particle spewers, spew particles. wheee
7572  */
weapon_maybe_spew_particle(object * obj)7573 void weapon_maybe_spew_particle(object *obj)
7574 {
7575 	weapon *wp;
7576 	weapon_info *wip;
7577 	int idx;
7578 
7579 	// check some stuff
7580 	Assert(obj->type == OBJ_WEAPON);
7581 	Assert(obj->instance >= 0);
7582 	Assert(Weapons[obj->instance].weapon_info_index >= 0);
7583 	Assert(Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Particle_spew]);
7584 
7585 	wp = &Weapons[obj->instance];
7586 	wip = &Weapon_info[wp->weapon_info_index];
7587 	vec3d spawn_pos, spawn_vel, output_pos, output_vel, input_pos, input_vel;
7588 
7589 	for (int psi = 0; psi < MAX_PARTICLE_SPEWERS; psi++) {	// iterate through spewers	-nuke
7590 		if (wip->particle_spewers[psi].particle_spew_type != PSPEW_NONE) {
7591 			// if the weapon's particle timestamp has elapsed
7592 			if ((wp->particle_spew_time[psi] == -1) || timestamp_elapsed(wp->particle_spew_time[psi])) {
7593 				// reset the timestamp
7594 				wp->particle_spew_time[psi] = timestamp(wip->particle_spewers[0].particle_spew_time);
7595 
7596 				// turn normals and origins to world space if we need to
7597 				if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_offset, &vmd_zero_vector)) {	// don't xform unused vectors
7598 					vm_vec_unrotate(&spawn_pos, &wip->particle_spewers[psi].particle_spew_offset, &obj->orient);
7599 				} else {
7600 					spawn_pos = vmd_zero_vector;
7601 				}
7602 
7603 				if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_velocity, &vmd_zero_vector)) {
7604 					vm_vec_unrotate(&spawn_vel, &wip->particle_spewers[psi].particle_spew_velocity, &obj->orient);
7605 				} else {
7606 					spawn_vel = vmd_zero_vector;
7607 				}
7608 
7609 				// spew some particles
7610 				if (wip->particle_spewers[psi].particle_spew_type == PSPEW_DEFAULT)	// default pspew type
7611 				{		// do the default pspew
7612 						vec3d direct, direct_temp, particle_pos;
7613 						vec3d null_vec = ZERO_VECTOR;
7614 						vec3d vel;
7615 						float ang;
7616 
7617 					for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
7618 						// get the backward vector of the weapon
7619 						direct = obj->orient.vec.fvec;
7620 						vm_vec_negate(&direct);
7621 
7622 						// randomly perturb x, y and z
7623 
7624 						// uvec
7625 						ang = frand_range(-PI_2,PI_2);	// fl_radian(frand_range(-90.0f, 90.0f));	-optimized by nuke
7626 						vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.fvec);
7627 						direct = direct_temp;
7628 						vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
7629 
7630 						// rvec
7631 						ang = frand_range(-PI_2,PI_2);	// fl_radian(frand_range(-90.0f, 90.0f));	-optimized by nuke
7632 						vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.rvec);
7633 						direct = direct_temp;
7634 						vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
7635 
7636 						// fvec
7637 						ang = frand_range(-PI_2,PI_2);	// fl_radian(frand_range(-90.0f, 90.0f));	-optimized by nuke
7638 						vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.uvec);
7639 						direct = direct_temp;
7640 						vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
7641 
7642 						// get a velocity vector of some percentage of the weapon's velocity
7643 						vel = obj->phys_info.vel;
7644 						vm_vec_scale(&vel, wip->particle_spewers[psi].particle_spew_vel);
7645 
7646 						// maybe add in offset and initial velocity
7647 						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add in particle velocity if its available
7648 							vm_vec_add2(&vel, &spawn_vel);
7649 						}
7650 						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if available
7651 							vm_vec_add2(&direct, &spawn_pos);
7652 						}
7653 
7654 						if (wip->wi_flags[Weapon::Info_Flags::Corkscrew]) {
7655 							vm_vec_add(&particle_pos, &obj->last_pos, &direct);
7656 						} else {
7657 							vm_vec_add(&particle_pos, &obj->pos, &direct);
7658 						}
7659 
7660 						// emit the particle
7661 						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7662 							particle::create(&particle_pos,
7663 											 &vel,
7664 											 wip->particle_spewers[psi].particle_spew_lifetime,
7665 											 wip->particle_spewers[psi].particle_spew_radius,
7666 											 particle::PARTICLE_SMOKE);
7667 						} else {
7668 							particle::create(&particle_pos,
7669 											 &vel,
7670 											 wip->particle_spewers[psi].particle_spew_lifetime,
7671 											 wip->particle_spewers[psi].particle_spew_radius,
7672 											 particle::PARTICLE_BITMAP,
7673 											 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7674 						}
7675 					}
7676 				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_HELIX) { // helix
7677 					float segment_length = wip->max_speed * flFrametime; // determine how long the segment is
7678 					float segment_angular_length = PI2 * wip->particle_spewers[psi].particle_spew_rotation_rate * flFrametime; 	// determine how much the segment rotates
7679 					float rotation_value = (wp->lifeleft * PI2 * wip->particle_spewers[psi].particle_spew_rotation_rate) + wp->particle_spew_rand; // calculate a rotational start point based on remaining life
7680 					float inc = 1.0f / wip->particle_spewers[psi].particle_spew_count;	// determine our incriment
7681 					float particle_rot;
7682 					vec3d input_pos_l = ZERO_VECTOR;
7683 
7684 					for (float is = 0; is < 1; is += inc ) { // use iterator as a scaler
7685 						particle_rot = rotation_value + (segment_angular_length * is); // find what point of the rotation were at
7686 						input_vel.xyz.x = sinf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale; // determine x/y velocity based on scale and rotation
7687 						input_vel.xyz.y = cosf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale;
7688 						input_vel.xyz.z = wip->max_speed * wip->particle_spewers[psi].particle_spew_vel; // velocity inheritance
7689 						vm_vec_unrotate(&output_vel, &input_vel, &obj->orient);				// orient velocity to weapon
7690 						input_pos_l.xyz.x = input_vel.xyz.x * flFrametime * (1.0f - is);	// interpolate particle motion
7691 						input_pos_l.xyz.y = input_vel.xyz.y * flFrametime * (1.0f - is);
7692 						input_pos_l.xyz.z = segment_length * is;							// position particle correctly on the z axis
7693 						vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient);			// orient to weapon
7694 						vm_vec_sub(&output_pos, &obj->pos, &input_pos);						// translate to world space
7695 
7696 						//maybe add in offset and initial velocity
7697 						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7698 							vm_vec_add2(&output_vel, &spawn_vel);
7699 						}
7700 						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7701 							vm_vec_add2(&output_pos, &spawn_pos);
7702 						}
7703 
7704 						//emit particles
7705 						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7706 							particle::create(&output_pos,
7707 											 &output_vel,
7708 											 wip->particle_spewers[psi].particle_spew_lifetime,
7709 											 wip->particle_spewers[psi].particle_spew_radius,
7710 											 particle::PARTICLE_SMOKE);
7711 						} else {
7712 							particle::create(&output_pos,
7713 											 &output_vel,
7714 											 wip->particle_spewers[psi].particle_spew_lifetime,
7715 											 wip->particle_spewers[psi].particle_spew_radius,
7716 											 particle::PARTICLE_BITMAP,
7717 											 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7718 						}
7719 					}
7720 				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_SPARKLER) { // sparkler
7721 					vec3d temp_vel;
7722 					output_vel = obj->phys_info.vel;
7723 					vm_vec_scale(&output_vel, wip->particle_spewers[psi].particle_spew_vel);
7724 
7725 					for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
7726 						// create a random unit vector and scale it
7727 						vm_vec_rand_vec_quick(&input_vel);
7728 						vm_vec_scale(&input_vel, wip->particle_spewers[psi].particle_spew_scale);
7729 
7730 						if (wip->particle_spewers[psi].particle_spew_z_scale != 1.0f) {	// don't do the extra math for spherical effect
7731 							temp_vel = input_vel;
7732 							temp_vel.xyz.z *= wip->particle_spewers[psi].particle_spew_z_scale;	// for an ovoid particle effect to better combine with laser effects
7733 							vm_vec_unrotate(&input_vel, &temp_vel, &obj->orient);				// so it has to be rotated
7734 						}
7735 
7736 						vm_vec_add2(&output_vel, &input_vel); // add to weapon velocity
7737 						output_pos = obj->pos;
7738 
7739 						// maybe add in offset and initial velocity
7740 						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7741 							vm_vec_add2(&output_vel, &spawn_vel);
7742 						}
7743 						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7744 							vm_vec_add2(&output_pos, &spawn_pos);
7745 						}
7746 
7747 						// emit particles
7748 						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7749 							particle::create(&output_pos,
7750 											 &output_vel,
7751 											 wip->particle_spewers[psi].particle_spew_lifetime,
7752 											 wip->particle_spewers[psi].particle_spew_radius,
7753 											 particle::PARTICLE_SMOKE);
7754 						} else {
7755 							particle::create(&output_pos,
7756 											 &output_vel,
7757 											 wip->particle_spewers[psi].particle_spew_lifetime,
7758 											 wip->particle_spewers[psi].particle_spew_radius,
7759 											 particle::PARTICLE_BITMAP,
7760 											 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7761 						}
7762 					}
7763 				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_RING) {
7764 					float inc = PI2 / wip->particle_spewers[psi].particle_spew_count;
7765 
7766 					for (float ir = 0; ir < PI2; ir += inc) { // use iterator for rotation
7767 						input_vel.xyz.x = sinf(ir) * wip->particle_spewers[psi].particle_spew_scale; // generate velocity from rotation data
7768 						input_vel.xyz.y = cosf(ir) * wip->particle_spewers[psi].particle_spew_scale;
7769 						input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
7770 						vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model
7771 
7772 						output_pos = obj->pos;
7773 
7774 						// maybe add in offset amd iitial velocity
7775 						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7776 							vm_vec_add2(&output_vel, &spawn_vel);
7777 						}
7778 						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7779 							vm_vec_add2(&output_pos, &spawn_pos);
7780 						}
7781 
7782 						// emit particles
7783 						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7784 							particle::create(&output_pos,
7785 											 &output_vel,
7786 											 wip->particle_spewers[psi].particle_spew_lifetime,
7787 											 wip->particle_spewers[psi].particle_spew_radius,
7788 											 particle::PARTICLE_SMOKE);
7789 						} else {
7790 							particle::create(&output_pos,
7791 											 &output_vel,
7792 											 wip->particle_spewers[psi].particle_spew_lifetime,
7793 											 wip->particle_spewers[psi].particle_spew_radius,
7794 											 particle::PARTICLE_BITMAP,
7795 											 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7796 						}
7797 					}
7798 				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_PLUME) {
7799 					float ang_rand, len_rand, sin_ang, cos_ang;
7800 					vec3d input_pos_l = ZERO_VECTOR;
7801 
7802 					for (int i = 0; i < wip->particle_spewers[psi].particle_spew_count; i++) {
7803 						// use polar coordinates to ensure a disk shaped spew plane
7804 						ang_rand = frand_range(-PI,PI);
7805 						len_rand = frand() * wip->particle_spewers[psi].particle_spew_scale;
7806 						sin_ang = sinf(ang_rand);
7807 						cos_ang = cosf(ang_rand);
7808 						// compute velocity
7809 						input_vel.xyz.x = wip->particle_spewers[psi].particle_spew_z_scale * -sin_ang;
7810 						input_vel.xyz.y = wip->particle_spewers[psi].particle_spew_z_scale * -cos_ang;
7811 						input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
7812 						vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model
7813 						// place particle on a disk prependicular to the weapon normal and rotate to model space
7814 						input_pos_l.xyz.x = sin_ang * len_rand;
7815 						input_pos_l.xyz.y = cos_ang * len_rand;
7816 						vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient); // rotate to world
7817 						vm_vec_sub(&output_pos, &obj->pos, &input_pos); // translate to world
7818 
7819 						// maybe add in offset amd iitial velocity
7820 						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7821 							vm_vec_add2(&output_vel, &spawn_vel);
7822 						}
7823 						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7824 							vm_vec_add2(&output_pos, &spawn_pos);
7825 						}
7826 
7827 						//emit particles
7828 						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7829 							particle::create(&output_pos,
7830 											 &output_vel,
7831 											 wip->particle_spewers[psi].particle_spew_lifetime,
7832 											 wip->particle_spewers[psi].particle_spew_radius,
7833 											 particle::PARTICLE_SMOKE);
7834 						} else {
7835 							particle::create(&output_pos,
7836 											 &output_vel,
7837 											 wip->particle_spewers[psi].particle_spew_lifetime,
7838 											 wip->particle_spewers[psi].particle_spew_radius,
7839 											 particle::PARTICLE_BITMAP,
7840 											 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7841 						}
7842 					}
7843 				}
7844 			}
7845 		}
7846 	}
7847 }
7848 
7849 /**
7850  * Debug console functionality
7851  */
7852 void dcf_pspew();
7853 DCF(pspew_count, "Number of particles spewed at a time")
7854 {
7855 	if (dc_optional_string_either("help", "--help")) {
7856 		dcf_pspew();
7857 		return;
7858 	}
7859 
7860 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7861 			dc_printf("Partical count is %i\n", Weapon_particle_spew_count);
7862 			return;
7863 	}
7864 
7865 	dc_stuff_int(&Weapon_particle_spew_count);
7866 
7867 	dc_printf("Partical count set to %i\n", Weapon_particle_spew_count);
7868 }
7869 
7870 DCF(pspew_time, "Time between particle spews")
7871 {
7872 	if (dc_optional_string_either("help", "--help")) {
7873 		dcf_pspew();
7874 		return;
7875 	}
7876 
7877 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7878 		dc_printf("Particle spawn period is %i\n", Weapon_particle_spew_time);
7879 		return;
7880 	}
7881 
7882 	dc_stuff_int(&Weapon_particle_spew_time);
7883 
7884 	dc_printf("Particle spawn period set to %i\n", Weapon_particle_spew_time);
7885 }
7886 
7887 DCF(pspew_vel, "Relative velocity of particles (0.0 - 1.0)")
7888 {
7889 	if (dc_optional_string_either("help", "--help")) {
7890 		dcf_pspew();
7891 		return;
7892 	}
7893 
7894 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7895 		dc_printf("Particle relative velocity is %f\n", Weapon_particle_spew_vel);
7896 		return;
7897 	}
7898 
7899 	dc_stuff_float(&Weapon_particle_spew_vel);
7900 
7901 	dc_printf("Particle relative velocity set to %f\n", Weapon_particle_spew_vel);
7902 }
7903 
7904 DCF(pspew_size, "Size of spewed particles")
7905 {
7906 	if (dc_optional_string_either("help", "--help")) {
7907 		dcf_pspew();
7908 		return;
7909 	}
7910 
7911 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7912 		dc_printf("Particle size is %f\n", Weapon_particle_spew_radius);
7913 		return;
7914 	}
7915 
7916 	dc_stuff_float(&Weapon_particle_spew_radius);
7917 
7918 	dc_printf("Particle size set to %f\n", Weapon_particle_spew_radius);
7919 }
7920 
7921 DCF(pspew_life, "Lifetime of spewed particles")
7922 {
7923 	if (dc_optional_string_either("help", "--help")) {
7924 		dcf_pspew();
7925 		return;
7926 	}
7927 
7928 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7929 		dc_printf("Particle lifetime is %f\n", Weapon_particle_spew_lifetime);
7930 		return;
7931 	}
7932 
7933 	dc_stuff_float(&Weapon_particle_spew_lifetime);
7934 
7935 	dc_printf("Particle lifetime set to %f\n", Weapon_particle_spew_lifetime);
7936 }
7937 
7938 DCF(pspew_scale, "How far away particles are from the weapon path")
7939 {
7940 	if (dc_optional_string_either("help", "--help")) {
7941 		dcf_pspew();
7942 		return;
7943 	}
7944 
7945 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7946 		dc_printf("Particle scale is %f\n", Weapon_particle_spew_scale);
7947 	}
7948 
7949 	dc_stuff_float(&Weapon_particle_spew_scale);
7950 
7951 	dc_printf("Particle scale set to %f\n", Weapon_particle_spew_scale);
7952 }
7953 
7954 // Help and Status provider
7955 DCF(pspew, "Particle spew help and status provider")
7956 {
7957 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7958 		dc_printf("Particle spew settings\n\n");
7959 
7960 		dc_printf(" Count   (pspew_count) : %d\n", Weapon_particle_spew_count);
7961 		dc_printf(" Time     (pspew_time) : %d\n", Weapon_particle_spew_time);
7962 		dc_printf(" Velocity  (pspew_vel) : %f\n", Weapon_particle_spew_vel);
7963 		dc_printf(" Size     (pspew_size) : %f\n", Weapon_particle_spew_radius);
7964 		dc_printf(" Lifetime (pspew_life) : %f\n", Weapon_particle_spew_lifetime);
7965 		dc_printf(" Scale   (psnew_scale) : %f\n", Weapon_particle_spew_scale);
7966 		return;
7967 	}
7968 
7969 	dc_printf("Available particlar spew commands:\n");
7970 	dc_printf("pspew_count : %s\n", dcmd_pspew_count.help);
7971 	dc_printf("pspew_time  : %s\n", dcmd_pspew_time.help);
7972 	dc_printf("pspew_vel   : %s\n", dcmd_pspew_vel.help);
7973 	dc_printf("pspew_size  : %s\n", dcmd_pspew_size.help);
7974 	dc_printf("pspew_life  : %s\n", dcmd_pspew_life.help);
7975 	dc_printf("pspew_scale : %s\n\n", dcmd_pspew_scale.help);
7976 
7977 	dc_printf("To view status of all pspew settings, type in 'pspew --status'.\n");
7978 	dc_printf("Passing '--status' as an argument to any of the individual spew commands will show the status of that variable only.\n\n");
7979 
7980 	dc_printf("These commands adjust the various properties of the particle spew system, which is used by weapons when they are fired, are in-flight, and die (either by impact or by end of life time.\n");
7981 	dc_printf("Generally, a large particle count with small size and scale will result in a nice dense particle spew.\n");
7982 	dc_printf("Be advised, this effect is applied to _ALL_ weapons, and as such may drastically reduce framerates on lower powered platforms.\n");
7983 }
7984 
7985 /**
7986  * Return a scale factor for damage which should be applied for 2 collisions
7987  */
weapon_get_damage_scale(weapon_info * wip,object * wep,object * target)7988 float weapon_get_damage_scale(weapon_info *wip, object *wep, object *target)
7989 {
7990 	weapon *wp;
7991 	int from_player = 0;
7992 	float total_scale = 1.0f;
7993 	float hull_pct;
7994 	int is_big_damage_ship = 0;
7995 
7996 	// Goober5000 - additional sanity (target can be NULL)
7997 	Assert(wip);
7998 	Assert(wep);
7999 
8000 	// sanity
8001 	if((wip == NULL) || (wep == NULL) || (target == NULL)){
8002 		return 1.0f;
8003 	}
8004 
8005 	// don't scale any damage if its not a weapon
8006 	if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){
8007 		return 1.0f;
8008 	}
8009 	wp = &Weapons[wep->instance];
8010 
8011 	// was the weapon fired by the player
8012 	from_player = 0;
8013 	if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags[Object::Object_Flags::Player_ship])){
8014 		from_player = 1;
8015 	}
8016 
8017 	// if this is a lockarm weapon, and it was fired unlocked
8018 	if((wip->wi_flags[Weapon::Info_Flags::Lockarm]) && !(wp->weapon_flags[Weapon::Weapon_Flags::Locked_when_fired])){
8019 		total_scale *= 0.1f;
8020 	}
8021 
8022 	// if the hit object was a ship and we're doing damage scaling
8023 	if ( (target->type == OBJ_SHIP) &&
8024 		!(The_mission.ai_profile->flags[AI::Profile_Flags::Disable_weapon_damage_scaling]) &&
8025 		!(Ship_info[Ships[target->instance].ship_info_index].flags[Ship::Info_Flags::Disable_weapon_damage_scaling])
8026 	) {
8027 		ship_info *sip;
8028 
8029 		// get some info on the ship
8030 		Assert((target->instance >= 0) && (target->instance < MAX_SHIPS));
8031 		if((target->instance < 0) || (target->instance >= MAX_SHIPS)){
8032 			return total_scale;
8033 		}
8034 		sip = &Ship_info[Ships[target->instance].ship_info_index];
8035 
8036 		// get hull pct of the ship currently
8037 		hull_pct = get_hull_pct(target);
8038 
8039 		// if it has hit a supercap ship and is not a supercap class weapon
8040 		if((sip->flags[Ship::Info_Flags::Supercap]) && !(wip->wi_flags[Weapon::Info_Flags::Supercap])){
8041 			// if the supercap is around 3/4 damage, apply nothing
8042 			if(hull_pct <= 0.75f){
8043 				return 0.0f;
8044 			} else {
8045 				total_scale *= SUPERCAP_DAMAGE_SCALE;
8046 			}
8047 		}
8048 
8049 		// determine if this is a big damage ship
8050 		is_big_damage_ship = (sip->flags[Ship::Info_Flags::Big_damage]);
8051 
8052 		// if this is a large ship, and is being hit by flak
8053 		if(is_big_damage_ship && (wip->wi_flags[Weapon::Info_Flags::Flak])){
8054 			total_scale *= FLAK_DAMAGE_SCALE;
8055 		}
8056 
8057 		// if the weapon is a small weapon being fired at a big ship
8058 		if( is_big_damage_ship && !(wip->hurts_big_ships()) ){
8059 
8060 			// if the player is firing it
8061 			if ( from_player && !(The_mission.ai_profile->flags[AI::Profile_Flags::Player_weapon_scale_fix])) {
8062 				// if it's a laser weapon
8063 				if(wip->subtype == WP_LASER){
8064 					total_scale *= 0.01f;
8065 				} else {
8066 					total_scale *= 0.05f;
8067 				}
8068 			}
8069 
8070 			// scale based on hull
8071 			if(hull_pct > 0.1f){
8072 				total_scale *= hull_pct;
8073 			} else {
8074 				return 0.0f;
8075 			}
8076 		}
8077 	}
8078 
8079 	return total_scale;
8080 }
8081 
pause_in_flight_sounds()8082 void pause_in_flight_sounds()
8083 {
8084 	for (int i = 0; i < MAX_WEAPONS; i++)
8085 	{
8086 		if (Weapons[i].objnum != -1)
8087 		{
8088 			weapon* wp = &Weapons[i];
8089 
8090 			if (wp->hud_in_flight_snd_sig.isValid() && snd_is_playing(wp->hud_in_flight_snd_sig)) {
8091 				// Stop sound, it will be restarted in the first frame after the game is unpaused
8092 				snd_stop(wp->hud_in_flight_snd_sig);
8093 			}
8094 		}
8095 	}
8096 }
8097 
weapon_pause_sounds()8098 void weapon_pause_sounds()
8099 {
8100 	// Pause all beam sounds
8101 	beam_pause_sounds();
8102 
8103 	// Pause in-flight sounds
8104 	pause_in_flight_sounds();
8105 }
8106 
weapon_unpause_sounds()8107 void weapon_unpause_sounds()
8108 {
8109 	// Pause all beam sounds
8110 	beam_unpause_sounds();
8111 }
8112 
shield_impact_explosion(vec3d * hitpos,object * objp,float radius,int idx)8113 void shield_impact_explosion(vec3d *hitpos, object *objp, float radius, int idx) {
8114 	int expl_ani_handle = Weapon_explosions.GetAnim(idx, hitpos, radius);
8115 	particle::create(hitpos,
8116 					 &vmd_zero_vector,
8117 					 0.0f,
8118 					 radius,
8119 					 particle::PARTICLE_BITMAP_PERSISTENT,
8120 					 expl_ani_handle,
8121 					 objp);
8122 }
8123 
weapon_render(object * obj,model_draw_list * scene)8124 void weapon_render(object* obj, model_draw_list *scene)
8125 {
8126 	int num;
8127 	weapon_info *wip;
8128 	weapon *wp;
8129 	color c;
8130 
8131 	MONITOR_INC(NumWeaponsRend, 1);
8132 
8133 	Assert(obj->type == OBJ_WEAPON);
8134 
8135 	num = obj->instance;
8136 	wp = &Weapons[num];
8137 	wip = &Weapon_info[Weapons[num].weapon_info_index];
8138 
8139 	if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
8140 		if (wp->alpha_current == -1.0f) {
8141 			wp->alpha_current = wip->alpha_max;
8142 		} else if (wip->alpha_cycle > 0.0f) {
8143 			if (wp->alpha_backward) {
8144 				wp->alpha_current += wip->alpha_cycle;
8145 
8146 				if (wp->alpha_current > wip->alpha_max) {
8147 					wp->alpha_current = wip->alpha_max;
8148 					wp->alpha_backward = 0;
8149 				}
8150 			} else {
8151 				wp->alpha_current -= wip->alpha_cycle;
8152 
8153 				if (wp->alpha_current < wip->alpha_min) {
8154 					wp->alpha_current = wip->alpha_min;
8155 					wp->alpha_backward = 1;
8156 				}
8157 			}
8158 		}
8159 	}
8160 
8161 	switch (wip->render_type)
8162 	{
8163 	case WRT_LASER:
8164 		{
8165 			if(wip->laser_length < 0.0001f)
8166 				return;
8167 
8168 			int alpha = 255;
8169 			int framenum = 0;
8170 
8171 			if (wip->laser_bitmap.first_frame >= 0) {
8172 				gr_set_color_fast(&wip->laser_color_1);
8173 
8174 				if (wip->laser_bitmap.num_frames > 1) {
8175 					wp->laser_bitmap_frame += flFrametime;
8176 
8177 					framenum = bm_get_anim_frame(wip->laser_bitmap.first_frame, wp->laser_bitmap_frame, wip->laser_bitmap.total_time, true);
8178 				}
8179 
8180 				if (wip->wi_flags[Weapon::Info_Flags::Transparent])
8181 					alpha = fl2i(wp->alpha_current * 255.0f);
8182 
8183 				if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_weapons)
8184 					alpha = (int)(alpha * neb2_get_fog_visibility(&obj->pos, NEB_FOG_VISIBILITY_MULT_WEAPON));
8185 
8186 				vec3d headp;
8187 
8188 				vm_vec_scale_add(&headp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length);
8189 
8190 				batching_add_laser(wip->laser_bitmap.first_frame + framenum, &headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius, alpha, alpha, alpha);
8191 			}
8192 
8193 			// maybe draw laser glow bitmap
8194 			if (wip->laser_glow_bitmap.first_frame >= 0) {
8195 				// get the laser color
8196 				weapon_get_laser_color(&c, obj);
8197 
8198 				// *Tail point "getting bigger" as well as headpoint isn't being taken into consideration, so
8199 				//  it caused uneven glow between the head and tail, which really shows in big lasers. So...fixed!    -Et1
8200 				vec3d headp2, tailp;
8201 
8202 				vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * weapon_glow_scale_l);
8203 				vm_vec_scale_add(&tailp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * (1 -  weapon_glow_scale_l) );
8204 
8205 				framenum = 0;
8206 
8207 				if (wip->laser_glow_bitmap.num_frames > 1) {
8208 					wp->laser_glow_bitmap_frame += flFrametime;
8209 
8210 					// Sanity checks
8211 					if (wp->laser_glow_bitmap_frame < 0.0f)
8212 						wp->laser_glow_bitmap_frame = 0.0f;
8213 					if (wp->laser_glow_bitmap_frame > 100.0f)
8214 						wp->laser_glow_bitmap_frame = 0.0f;
8215 
8216 					while (wp->laser_glow_bitmap_frame > wip->laser_glow_bitmap.total_time)
8217 						wp->laser_glow_bitmap_frame -= wip->laser_glow_bitmap.total_time;
8218 
8219 					framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );
8220 
8221 					CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
8222 				}
8223 
8224 				if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
8225 					alpha = fl2i(wp->alpha_current * 255.0f);
8226 					alpha -= 38; // take 1.5f into account for the normal glow alpha
8227 
8228 					if (alpha < 0)
8229 						alpha = 0;
8230 				} else {
8231 					alpha = weapon_glow_alpha;
8232 				}
8233 
8234 				if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_weapons)
8235 					alpha = (int)(alpha * neb2_get_fog_visibility(&obj->pos, NEB_FOG_VISIBILITY_MULT_WEAPON));
8236 
8237 				batching_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);
8238 			}
8239 
8240 			break;
8241 		}
8242 
8243 	case WRT_POF:
8244 		{
8245 			model_render_params render_info;
8246 
8247 			uint render_flags = MR_NORMAL|MR_IS_MISSILE|MR_NO_BATCH;
8248 
8249 			if (wip->wi_flags[Weapon::Info_Flags::Mr_no_lighting])
8250 				render_flags |= MR_NO_LIGHTING;
8251 
8252 			if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
8253 				render_info.set_alpha(wp->alpha_current);
8254 				render_flags |= MR_ALL_XPARENT;
8255 			}
8256 
8257 			model_clear_instance(wip->model_num);
8258 
8259 			if ( (wip->wi_flags[Weapon::Info_Flags::Thruster]) && ((wp->thruster_bitmap > -1) || (wp->thruster_glow_bitmap > -1)) ) {
8260 				float ft;
8261 				mst_info mst;
8262 
8263 				//	Add noise to thruster geometry.
8264 				ft = 1.0f;		// Always use 1.0f for missiles
8265 				ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
8266 				if (ft > 1.0f)
8267 					ft = 1.0f;
8268 
8269 				mst.length.xyz.x = ft;
8270 				mst.length.xyz.y = ft;
8271 				mst.length.xyz.z = ft;
8272 
8273 				mst.primary_bitmap = wp->thruster_bitmap;
8274 				mst.primary_glow_bitmap = wp->thruster_glow_bitmap;
8275 				mst.glow_rad_factor = wip->thruster_glow_factor;
8276 				mst.glow_noise = wp->thruster_glow_noise;
8277 
8278 				render_info.set_thruster_info(mst);
8279 
8280 				render_flags |= MR_SHOW_THRUSTERS;
8281 			}
8282 
8283 
8284 			//don't render local ssm's when they are still in subspace
8285 			if (wp->lssm_stage==3)
8286 				break;
8287 
8288 			// start a clip plane
8289 			if ( wp->lssm_stage == 2 ) {
8290 				object *wobj=&Objects[wp->lssm_warp_idx];		//warphole object
8291 
8292 				render_info.set_clip_plane(wobj->pos, wobj->orient.vec.fvec);
8293 			}
8294 
8295 			render_info.set_flags(render_flags);
8296 
8297 			model_render_queue(&render_info, scene, wip->model_num, &obj->orient, &obj->pos);
8298 
8299 			break;
8300 		}
8301 
8302 	default:
8303 		Warning(LOCATION, "Unknown weapon rendering type = %i for weapon %s\n", wip->render_type, wip->name);
8304 	}
8305 }
8306 
8307 // Called by hudartillery.cpp after SSMs have been parsed to make sure that $SSM: entries defined in weapons are valid.
validate_SSM_entries()8308 void validate_SSM_entries()
8309 {
8310 	int wi;
8311 	SCP_vector<SCP_string>::const_iterator it;
8312 	weapon_info *wip;
8313 
8314 	for (it = Delayed_SSM_names.begin(); it != Delayed_SSM_names.end(); ++it) {
8315 		delayed_ssm_data *dat = &Delayed_SSM_data[*it];
8316 		wi = weapon_info_lookup(it->c_str());
8317 		Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
8318 		wip = &Weapon_info[wi];
8319 		nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
8320 		wip->SSM_index = ssm_info_lookup(dat->ssm_entry.c_str());
8321 		if (wip->SSM_index < 0) {
8322 			if (Ssm_info.empty()) {
8323 				Warning(LOCATION, "SSM entry '%s' in specification for %s (%s:line %d), despite no SSM strikes being defined.\n", dat->ssm_entry.c_str(), it->c_str(), dat->filename.c_str(), dat->linenum);
8324 			} else {
8325 				Warning(LOCATION, "Unknown SSM entry '%s' in specification for %s (%s:line %d).\n", dat->ssm_entry.c_str(), it->c_str(), dat->filename.c_str(), dat->linenum);
8326 			}
8327 		}
8328 		nprintf(("parse", "Validation complete, SSM_index is %d.\n", wip->SSM_index));
8329 	}
8330 
8331 	// This information is no longer relevant, so might as well clear it out.
8332 	Delayed_SSM_data.clear();
8333 	Delayed_SSM_names.clear();
8334 
8335 	for (it = Delayed_SSM_indices.begin(); it != Delayed_SSM_indices.end(); ++it) {
8336 		delayed_ssm_index_data *dat = &Delayed_SSM_indices_data[*it];
8337 		wi = weapon_info_lookup(it->c_str());
8338 		Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
8339 		wip = &Weapon_info[wi];
8340 		nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
8341 		if (wip->SSM_index < -1 || wip->SSM_index >= static_cast<int>(Ssm_info.size())) {
8342 			if (Ssm_info.empty()) {
8343 				Warning(LOCATION, "SSM index '%d' in specification for %s (%s:line %d), despite no SSM strikes being defined.\n", wip->SSM_index, it->c_str(), dat->filename.c_str(), dat->linenum);
8344 			} else {
8345 				Warning(LOCATION, "Invalid SSM index '%d' (should be 0-" SIZE_T_ARG ") in specification for %s (%s:line %d).\n", wip->SSM_index, Ssm_info.size() - 1, it->c_str(), dat->filename.c_str(), dat->linenum);
8346 			}
8347 			wip->SSM_index = -1;
8348 		}
8349 		nprintf(("parse", "Validation complete, SSM-index is %d.\n", wip->SSM_index));
8350 	}
8351 }
8352 
weapon_get_random_player_usable_weapon()8353 int weapon_get_random_player_usable_weapon()
8354 {
8355 	SCP_vector<int> weapon_list;
8356 
8357 	for (int i = 0; i < weapon_info_size(); ++i)
8358 	{
8359 		// skip if we aren't supposed to use it
8360 		if (!Weapon_info[i].wi_flags[Weapon::Info_Flags::Player_allowed])
8361 			continue;
8362 
8363 		weapon_list.push_back(i);
8364 	}
8365 
8366 	if (weapon_list.empty())
8367 		return -1;
8368 
8369 	auto rand_wep = Random::next((int)weapon_list.size());
8370 
8371 	return weapon_list[rand_wep];
8372 }
8373 
weapon_info()8374 weapon_info::weapon_info()
8375 {
8376 	this->reset();
8377 }
8378 
reset()8379 void weapon_info::reset()
8380 {
8381 	// INITIALIZE NEW FIELDS HERE
8382 	// The order should match the order in the struct!
8383 	int i, j;
8384 
8385 	memset(this->name, 0, sizeof(this->name));
8386 	memset(this->display_name, 0, sizeof(this->display_name));
8387 	memset(this->title, 0, sizeof(this->title));
8388 	this->desc = nullptr;
8389 
8390 	memset(this->pofbitmap_name, 0, sizeof(this->pofbitmap_name));
8391 	this->model_num = -1;
8392 	memset(this->external_model_name, 0, sizeof(this->external_model_name));
8393 	this->external_model_num = -1;
8394 
8395 	this->tech_desc = nullptr;
8396 	memset(this->tech_anim_filename, 0, sizeof(this->tech_anim_filename));
8397 	memset(this->tech_title, 0, sizeof(this->tech_title));
8398 	memset(this->tech_model, 0, sizeof(this->tech_model));
8399 
8400 	this->hud_target_lod = -1;
8401 	this->num_detail_levels = -1;
8402 	for (i = 0; i < MAX_MODEL_DETAIL_LEVELS; i++)
8403 	{
8404 		this->detail_distance[i] = -1;
8405 	}
8406 	this->subtype = WP_UNUSED;
8407 	this->render_type = WRT_NONE;
8408 
8409 	vm_vec_zero(&this->closeup_pos);
8410 	this->closeup_zoom = 1.0f;
8411 
8412 	memset(this->hud_filename, 0, sizeof(this->hud_filename));
8413 	this->hud_image_index = -1;
8414 
8415 	generic_anim_init(&this->laser_bitmap);
8416 	generic_anim_init(&this->laser_glow_bitmap);
8417 
8418 	this->laser_length = 10.0f;
8419 	gr_init_color(&this->laser_color_1, 255, 255, 255);
8420 	gr_init_color(&this->laser_color_2, 255, 255, 255);
8421 	this->laser_head_radius = 1.0f;
8422 	this->laser_tail_radius = 1.0f;
8423 
8424 	this->collision_radius_override = -1.0f;
8425 	this->max_speed = 10.0f;
8426 	this->acceleration_time = 0.0f;
8427 	this->vel_inherit_amount = 1.0f;
8428 	this->free_flight_time = 0.0f;
8429 	this->free_flight_speed_factor = 0.25f;
8430 	this->mass = 1.0f;
8431 	this->fire_wait = 1.0f;
8432 	this->max_delay = 0.0f;
8433 	this->min_delay = 0.0f;
8434 
8435 	this->damage = 0.0f;
8436 	this->damage_time = -1.0f;
8437 	this->atten_damage = -1.0f;
8438 	this->damage_incidence_max = 1.0f;
8439 	this->damage_incidence_min = 1.0f;
8440 
8441 	shockwave_create_info_init(&this->shockwave);
8442 	shockwave_create_info_init(&this->dinky_shockwave);
8443 
8444 	this->arm_time = 0;
8445 	this->arm_dist = 0.0f;
8446 	this->arm_radius = 0.0f;
8447 	this->det_range = 0.0f;
8448 	this->det_radius = 0.0f;
8449 
8450 	this->flak_detonation_accuracy = 65.0f;
8451 	this->flak_targeting_accuracy = 60.0f; // Standard value as defined in flak.cpp
8452 	this->untargeted_flak_range_penalty = 20.0f;
8453 
8454 	this->armor_factor = 1.0f;
8455 	this->shield_factor = 1.0f;
8456 	this->subsystem_factor = 1.0f;
8457 
8458 	this->life_min = -1.0f;
8459 	this->life_max = -1.0f;
8460 	this->max_lifetime = 1.0f;
8461 	this->lifetime = 1.0f;
8462 
8463 	this->energy_consumed = 0.0f;
8464 
8465 	this->wi_flags.reset();
8466 
8467 	this->turn_time = 1.0f;
8468 	this->turn_accel_time = 0.f;
8469 	this->cargo_size = 1.0f;
8470 	this->rearm_rate = 1.0f;
8471 	this->reloaded_per_batch = -1;
8472 	this->weapon_range = WEAPON_DEFAULT_TABLED_MAX_RANGE;
8473 	// *Minimum weapon range, default is 0 -Et1
8474 	this->weapon_min_range = 0.0f;
8475 	this->optimum_range = 0.0f;
8476 
8477 	this->pierce_objects = false;
8478 	this->spawn_children_on_pierce = false;
8479 
8480 	this->num_spawn_weapons_defined = 0;
8481 	this->maximum_children_spawned = 0;
8482 	for (i = 0; i < MAX_SPAWN_TYPES_PER_WEAPON; i++)
8483 	{
8484 		this->spawn_info[i].spawn_type = -1;
8485 		this->spawn_info[i].spawn_angle = 180;
8486 		this->spawn_info[i].spawn_min_angle = 0;
8487 		this->spawn_info[i].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
8488 		this->spawn_info[i].spawn_interval = -1.f;
8489 		this->spawn_info[i].spawn_interval_delay = -1.f;
8490 		this->spawn_info[i].spawn_chance = 1.f;
8491 	}
8492 
8493 	this->swarm_count = -1;
8494 	// *Default is 150  -Et1
8495 	this->SwarmWait = SWARM_MISSILE_DELAY;
8496 
8497 	this->target_restrict = LR_CURRENT_TARGET;
8498 	this->multi_lock = false;
8499 	this->trigger_lock = false;
8500 	this->launch_reset_locks = false;
8501 
8502 	this->max_seeking = 1;
8503 	this->max_seekers_per_target = 1;
8504 	this->ship_restrict.clear();
8505 	this->ship_restrict_strings.clear();
8506 
8507 	this->acquire_method = WLOCK_PIXEL;
8508 	this->auto_target_method = HomingAcquisitionType::CLOSEST;
8509 
8510 	this->min_lock_time = 0.0f;
8511 	this->lock_pixels_per_sec = 50;
8512 	this->catchup_pixels_per_sec = 50;
8513 	this->catchup_pixel_penalty = 50;
8514 	this->fov = 0;				//should be cos(pi), not pi
8515 	this->seeker_strength = 1.0f;
8516 	this->lock_fov = 0.85f;
8517 
8518 	this->pre_launch_snd = gamesnd_id();
8519 	this->pre_launch_snd_min_interval = 0;
8520 
8521 	this->launch_snd = gamesnd_id();
8522 	this->impact_snd = gamesnd_id();
8523 	this->disarmed_impact_snd = gamesnd_id();
8524 	this->flyby_snd = gamesnd_id();
8525 
8526 	this->hud_tracking_snd = gamesnd_id();
8527 	this->hud_locked_snd = gamesnd_id();
8528 	this->hud_in_flight_snd = gamesnd_id();
8529 	this->in_flight_play_type = ALWAYS;
8530 
8531 	// Trails
8532 	this->tr_info.pt = vmd_zero_vector;
8533 	this->tr_info.w_start = 1.0f;
8534 	this->tr_info.w_end = 1.0f;
8535 	this->tr_info.a_start = 1.0f;
8536 	this->tr_info.a_end = 1.0f;
8537 	this->tr_info.max_life = 1.0f;
8538 	this->tr_info.spread = 0.0f;
8539 	this->tr_info.a_decay_exponent = 1.0f;
8540 	this->tr_info.stamp = 0;
8541 	generic_bitmap_init(&this->tr_info.texture, NULL);
8542 	this->tr_info.n_fade_out_sections = 0;
8543 	this->tr_info.texture_stretch = 1.0f;
8544 
8545 	memset(this->icon_filename, 0, sizeof(this->icon_filename));
8546 	memset(this->anim_filename, 0, sizeof(this->anim_filename));
8547 	this->selection_effect = Default_weapon_select_effect;
8548 
8549 	this->shield_impact_explosion_radius = 1.0f;
8550 
8551 	this->impact_weapon_expl_effect = particle::ParticleEffectHandle::invalid();
8552 	this->dinky_impact_weapon_expl_effect = particle::ParticleEffectHandle::invalid();
8553 	this->flash_impact_weapon_expl_effect = particle::ParticleEffectHandle::invalid();
8554 
8555 	this->piercing_impact_effect = particle::ParticleEffectHandle::invalid();
8556 	this->piercing_impact_secondary_effect = particle::ParticleEffectHandle::invalid();
8557 
8558 	this->state_effects.clear();
8559 
8560 	this->emp_intensity = EMP_DEFAULT_INTENSITY;
8561 	this->emp_time = EMP_DEFAULT_TIME;	// Goober5000: <-- Look!  I fixed a Volition bug!  Gimme $5, Dave!
8562 
8563 	this->recoil_modifier = 1.0f;
8564 
8565 	this->weapon_reduce = ESUCK_DEFAULT_WEAPON_REDUCE;
8566 	this->afterburner_reduce = ESUCK_DEFAULT_AFTERBURNER_REDUCE;
8567 
8568 	this->tag_time = -1.0f;
8569 	this->tag_level = -1;
8570 
8571 	this->muzzle_flash = -1;
8572 
8573 	this->field_of_fire = 0.0f;
8574 	this->fof_spread_rate = 0.0f;
8575 	this->fof_reset_rate = 0.0f;
8576 	this->max_fof_spread = 0.0f;
8577 	this->shots = 1;
8578 
8579 	//customizeable corkscrew stuff
8580 	this->cs_num_fired = 4;
8581 	this->cs_radius = 1.25f;
8582 	this->cs_delay = 30;
8583 	this->cs_crotate = 1;
8584 	this->cs_twist = 5.0f;
8585 
8586 	this->elec_time = 8000;
8587 	this->elec_eng_mult = 1.0f;
8588 	this->elec_weap_mult = 1.0f;
8589 	this->elec_beam_mult = 1.0f;
8590 	this->elec_sensors_mult = 1.0f;
8591 	this->elec_randomness = 2000;
8592 	this->elec_use_new_style = 0;
8593 
8594 	this->SSM_index = -1;				// tag C SSM index, wich entry in the SSM table this weapon calls -Bobboau
8595 
8596 	this->lssm_warpout_delay = 0;			//delay between launch and warpout (ms)
8597 	this->lssm_warpin_delay = 0;			//delay between warpout and warpin (ms)
8598 	this->lssm_stage5_vel = 0;		//velocity during final stage
8599 	this->lssm_warpin_radius = 0;
8600 	this->lssm_lock_range = 1000000.0f;	//local ssm lock range (optional)
8601 	this->lssm_warpeffect = FIREBALL_WARP;		//Which fireballtype is used for the warp effect
8602 
8603 	this->b_info.beam_type = -1;
8604 	this->b_info.beam_life = -1.0f;
8605 	this->b_info.beam_warmup = -1;
8606 	this->b_info.beam_warmdown = -1;
8607 	this->b_info.beam_muzzle_radius = 0.0f;
8608 	this->b_info.beam_particle_count = -1;
8609 	this->b_info.beam_particle_radius = 0.0f;
8610 	this->b_info.beam_particle_angle = 0.0f;
8611 	this->b_info.beam_loop_sound = gamesnd_id();
8612 	this->b_info.beam_warmup_sound = gamesnd_id();
8613 	this->b_info.beam_warmdown_sound = gamesnd_id();
8614 	this->b_info.beam_num_sections = 0;
8615 	this->b_info.glow_length = 0;
8616 	this->b_info.directional_glow = false;
8617 	this->b_info.beam_shots = 1;
8618 	this->b_info.beam_shrink_factor = 0.0f;
8619 	this->b_info.beam_shrink_pct = 0.0f;
8620 	this->b_info.beam_grow_factor = 0.0f;
8621 	this->b_info.beam_grow_pct = 0.0f;
8622 	this->b_info.beam_initial_width = 1.0f;
8623 	this->b_info.range = BEAM_FAR_LENGTH;
8624 	this->b_info.damage_threshold = 1.0f;
8625 	this->b_info.beam_width = -1.0f;
8626 	this->b_info.flags.reset();
8627 
8628 	// type 5 beam stuff
8629 	this->b_info.t5info.no_translate = true;
8630 	this->b_info.t5info.start_pos = Type5BeamPos::CENTER;
8631 	this->b_info.t5info.end_pos = Type5BeamPos::CENTER;
8632 	vm_vec_zero(&this->b_info.t5info.start_pos_offset);
8633 	vm_vec_zero(&this->b_info.t5info.end_pos_offset);
8634 	vm_vec_zero(&this->b_info.t5info.start_pos_rand);
8635 	vm_vec_zero(&this->b_info.t5info.end_pos_rand);
8636 	this->b_info.t5info.target_orient_positions = false;
8637 	this->b_info.t5info.target_scale_positions = false;
8638 	this->b_info.t5info.continuous_rot = 0.f;
8639 	this->b_info.t5info.continuous_rot_axis = Type5BeamRotAxis::UNSPECIFIED;
8640 	this->b_info.t5info.per_burst_rot = 0.f;
8641 	this->b_info.t5info.per_burst_rot_axis = Type5BeamRotAxis::UNSPECIFIED;
8642 	this->b_info.t5info.burst_rot_pattern.clear();
8643 	this->b_info.t5info.burst_rot_axis = Type5BeamRotAxis::UNSPECIFIED;
8644 
8645 	generic_anim_init(&this->b_info.beam_glow, NULL);
8646 	generic_anim_init(&this->b_info.beam_particle_ani, NULL);
8647 
8648 	for (i = 0; i < MAX_IFFS; i++)
8649 		for (j = 0; j < NUM_SKILL_LEVELS; j++)
8650 			this->b_info.beam_iff_miss_factor[i][j] = 0.00001f;
8651 
8652 	//WMC - Okay, so this is needed now
8653 	beam_weapon_section_info *bsip;
8654 	for (i = 0; i < MAX_BEAM_SECTIONS; i++) {
8655 		bsip = &this->b_info.sections[i];
8656 
8657 		generic_anim_init(&bsip->texture, NULL);
8658 
8659 		bsip->width = 1.0f;
8660 		bsip->flicker = 0.1f;
8661 		bsip->z_add = i2fl(MAX_BEAM_SECTIONS - i - 1);
8662 		bsip->tile_type = 0;
8663 		bsip->tile_factor = 1.0f;
8664 		bsip->translation = 0.0f;
8665 	}
8666 
8667 	for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {						// default values for everything -nuke
8668 		this->particle_spewers[s].particle_spew_type = PSPEW_NONE;				// added by nuke
8669 		this->particle_spewers[s].particle_spew_count = 1;
8670 		this->particle_spewers[s].particle_spew_time = 25;
8671 		this->particle_spewers[s].particle_spew_vel = 0.4f;
8672 		this->particle_spewers[s].particle_spew_radius = 2.0f;
8673 		this->particle_spewers[s].particle_spew_lifetime = 0.15f;
8674 		this->particle_spewers[s].particle_spew_scale = 0.8f;
8675 		this->particle_spewers[s].particle_spew_z_scale = 1.0f;			// added by nuke
8676 		this->particle_spewers[s].particle_spew_rotation_rate = 10.0f;
8677 		this->particle_spewers[s].particle_spew_offset = vmd_zero_vector;
8678 		this->particle_spewers[s].particle_spew_velocity = vmd_zero_vector;
8679 		generic_anim_init(&this->particle_spewers[s].particle_spew_anim, NULL);
8680 	}
8681 
8682 	this->cm_aspect_effectiveness = 1.0f;
8683 	this->cm_heat_effectiveness = 1.0f;
8684 	this->cm_effective_rad = MAX_CMEASURE_TRACK_DIST;
8685 	this->cm_detonation_rad = CMEASURE_DETONATE_DISTANCE;
8686 	this->cm_kill_single = false;
8687 	this->cmeasure_timer_interval = 0;
8688 	this->cmeasure_firewait = CMEASURE_WAIT;
8689 	this->cmeasure_use_firewait = false;
8690 	this->cmeasure_failure_delay_multiplier_ai = -1;
8691 	this->cmeasure_sucess_delay_multiplier_ai = 2;
8692 
8693 	this->weapon_submodel_rotate_accell = 10.0f;
8694 	this->weapon_submodel_rotate_vel = 0.0f;
8695 
8696 	this->damage_type_idx = -1;
8697 	this->damage_type_idx_sav = -1;
8698 
8699 	this->armor_type_idx = -1;
8700 
8701 	this->alpha_max = 1.0f;
8702 	this->alpha_min = 0.0f;
8703 	this->alpha_cycle = 0.0f;
8704 
8705 	this->weapon_hitpoints = 0;
8706 
8707 	this->burst_shots = 0;
8708 	this->burst_delay = 1.0f; // 1 second, just incase its not defined
8709     this->burst_flags.reset();
8710 
8711 	generic_anim_init(&this->thruster_flame);
8712 	generic_anim_init(&this->thruster_glow);
8713 	this->thruster_glow_factor = 1.0f;
8714 
8715 	this->target_lead_scaler = 0.0f;
8716 	this->num_targeting_priorities = 0;
8717 	for (i = 0; i < 32; ++i)
8718 		this->targeting_priorities[i] = -1;
8719 
8720 	this->failure_rate = 0.0f;
8721 	this->failure_sub_name.clear();
8722 	this->failure_sub = -1;
8723 
8724 	this->num_substitution_patterns = 0;
8725 	for (i = 0; i < MAX_SUBSTITUTION_PATTERNS; ++i)
8726 	{
8727 		this->weapon_substitution_pattern[i] = -1;
8728 		this->weapon_substitution_pattern_names[i][0] = 0;
8729 	}
8730 
8731 	this->score = 0;
8732 
8733 	// Reset using default constructor
8734 	this->impact_decal = decals::creation_info();
8735 
8736 	this->on_create_program = actions::ProgramSet();
8737 }
8738 
get_display_name() const8739 const char* weapon_info::get_display_name() const
8740 {
8741 	if (has_display_name())
8742 		return display_name;
8743 	else
8744 		return name;
8745 }
8746 
has_display_name() const8747 bool weapon_info::has_display_name() const
8748 {
8749 	return wi_flags[Weapon::Info_Flags::Has_display_name];
8750 }
8751 
weapon_spew_stats(WeaponSpewType type)8752 void weapon_spew_stats(WeaponSpewType type)
8753 {
8754 #ifndef NDEBUG
8755 	if (type == WeaponSpewType::NONE)
8756 		return;	// then why did we even call the function?
8757 	bool all_weapons = (type == WeaponSpewType::ALL);
8758 
8759 	// csv weapon stats for comparisons
8760 	mprintf(("Name,Type,Velocity,Range,Damage Hull,DPS Hull,Damage Shield,DPS Shield,Damage Subsystem,DPS Subsystem,Power Use,Fire Wait,ROF,Reload,1/Reload,Area Effect,Shockwave%s\n", all_weapons ? ",Player Allowed" : ""));
8761 	for (auto &wi : Weapon_info)
8762 	{
8763 		if (wi.subtype != WP_LASER && wi.subtype != WP_BEAM)
8764 			continue;
8765 
8766 		if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed])
8767 		{
8768 			mprintf(("%s,%s,", wi.name, "Primary"));
8769 			//Beam range is set in the b_info and velocity isn't very relevant to them.
8770 			if (wi.wi_flags[Weapon::Info_Flags::Beam])
8771 				mprintf((",%.2f,",wi.b_info.range));
8772 			else
8773 				mprintf(("%.2f,%.2f,", wi.max_speed, wi.max_speed * wi.lifetime));
8774 
8775 			float damage;
8776 			if (wi.wi_flags[Weapon::Info_Flags::Beam])
8777 				damage = wi.damage * wi.b_info.beam_life * (1000.0f / i2fl(BEAM_DAMAGE_TIME));
8778 			else
8779 				damage = wi.damage;
8780 
8781 			float fire_rate;
8782 			//To get overall fire rate, divide the number of shots in a firing cycle by the length of that cycle
8783 			//In random length bursts, average between the longest and shortest firing cycle to get average rof
8784 			if (wi.burst_shots > 1 && (wi.burst_flags[Weapon::Burst_Flags::Random_length]))
8785 				fire_rate = (wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1)) + (1 / wi.fire_wait)) / 2;
8786 			else if (wi.burst_shots > 1)
8787 				fire_rate = wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1));
8788 			else
8789 				fire_rate = 1 / wi.fire_wait;
8790 
8791 			// doubled damage is handled strangely...
8792 			float hull_multiplier = 1.0f;
8793 			float shield_multiplier = 1.0f;
8794 			float subsys_multiplier = 1.0f;
8795 
8796 			// area effect?
8797 			if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8798 			{
8799 				hull_multiplier = 2.0f;
8800 				shield_multiplier = 2.0f;
8801 
8802 				// shockwave?
8803 				if (wi.shockwave.speed > 0.0f)
8804 				{
8805 					subsys_multiplier = 2.0f;
8806 				}
8807 			}
8808 
8809 			// puncture?
8810 			if (wi.wi_flags[Weapon::Info_Flags::Puncture])
8811 				hull_multiplier /= 4;
8812 
8813 			// beams ignore factors unless specified
8814 			float armor_factor = wi.armor_factor;
8815 			float shield_factor = wi.shield_factor;
8816 			float subsys_factor = wi.subsystem_factor;
8817 			if (wi.wi_flags[Weapon::Info_Flags::Beam] && !Beams_use_damage_factors)
8818 				armor_factor = shield_factor = subsys_factor = 1.0f;
8819 
8820 			mprintf(("%.2f,%.2f,", hull_multiplier * damage * armor_factor, hull_multiplier * damage * armor_factor * fire_rate));
8821 			mprintf(("%.2f,%.2f,", shield_multiplier * damage * shield_factor, shield_multiplier * damage * shield_factor * fire_rate));
8822 			mprintf(("%.2f,%.2f,", subsys_multiplier * damage * subsys_factor, subsys_multiplier * damage * subsys_factor * fire_rate));
8823 
8824 			mprintf(("%.2f,", wi.energy_consumed / wi.fire_wait));
8825 			mprintf(("%.2f,%.2f,", wi.fire_wait, fire_rate));
8826 			mprintf((",,"));	// no reload for primaries
8827 
8828 			if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8829 				mprintf(("Yes,"));
8830 			else
8831 				mprintf((","));
8832 
8833 			if (wi.shockwave.speed > 0.0f)
8834 				mprintf(("Yes"));
8835 
8836 			if (all_weapons)
8837 			{
8838 				mprintf((","));
8839 				mprintf((wi.wi_flags[Weapon::Info_Flags::Player_allowed] ? "Yes" : ""));
8840 			}
8841 
8842 			mprintf(("\n"));
8843 		}
8844 	}
8845 	for (auto &wi : Weapon_info)
8846 	{
8847 		if (wi.subtype != WP_MISSILE)
8848 			continue;
8849 
8850 		if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed] || wi.wi_flags[Weapon::Info_Flags::Child])
8851 		{
8852 			mprintf(("%s,%s,", wi.name, "Secondary"));
8853 			mprintf(("%.2f,%.2f,", wi.max_speed, wi.max_speed * wi.lifetime));
8854 
8855 			// doubled damage is handled strangely...
8856 			float hull_multiplier = 1.0f;
8857 			float shield_multiplier = 1.0f;
8858 			float subsys_multiplier = 1.0f;
8859 
8860 			// area effect?
8861 			if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8862 			{
8863 				hull_multiplier = 2.0f;
8864 				shield_multiplier = 2.0f;
8865 
8866 				// shockwave?
8867 				if (wi.shockwave.speed > 0.0f)
8868 				{
8869 					subsys_multiplier = 2.0f;
8870 				}
8871 			}
8872 
8873 			// puncture?
8874 			if (wi.wi_flags[Weapon::Info_Flags::Puncture])
8875 				hull_multiplier /= 4;
8876 
8877 			mprintf(("%.2f,%.2f,", hull_multiplier * wi.damage * wi.armor_factor, hull_multiplier * wi.damage * wi.armor_factor / wi.fire_wait));
8878 			mprintf(("%.2f,%.2f,", shield_multiplier * wi.damage * wi.shield_factor, shield_multiplier * wi.damage * wi.shield_factor / wi.fire_wait));
8879 			mprintf(("%.2f,%.2f,", subsys_multiplier * wi.damage * wi.subsystem_factor, subsys_multiplier * wi.damage * wi.subsystem_factor / wi.fire_wait));
8880 
8881 			mprintf((","));	// no power use for secondaries
8882 			mprintf(("%.2f,%.2f,", wi.fire_wait, 1.0f / wi.fire_wait));
8883 			mprintf(("%.2f,%.2f,", wi.reloaded_per_batch / wi.rearm_rate, wi.rearm_rate / wi.reloaded_per_batch));	// rearm_rate is actually the reciprocal of what is in weapons.tbl
8884 
8885 			if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8886 				mprintf(("Yes,"));
8887 			else
8888 				mprintf((","));
8889 
8890 			if (wi.shockwave.speed > 0.0f)
8891 				mprintf(("Yes"));
8892 
8893 			if (all_weapons)
8894 			{
8895 				mprintf((","));
8896 				mprintf((wi.wi_flags[Weapon::Info_Flags::Player_allowed] ? "Yes" : ""));
8897 			}
8898 
8899 			mprintf(("\n"));
8900 		}
8901 	}
8902 
8903 	// mvp-style stats
8904 	mprintf(("\n"));
8905 	for (auto &wi : Weapon_info)
8906 	{
8907 		if (wi.subtype != WP_LASER && wi.subtype != WP_BEAM)
8908 			continue;
8909 
8910 		if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed])
8911 		{
8912 			mprintf(("%s\n", wi.name));
8913 			//Beam range is set in the b_info and velocity isn't very relevant to them.
8914 			if (wi.wi_flags[Weapon::Info_Flags::Beam])
8915 				mprintf(("\tVelocity: N/A        Range: %.0f\n", wi.b_info.range));
8916 			else
8917 				mprintf(("\tVelocity: %-11.0fRange: %.0f\n", wi.max_speed, wi.max_speed* wi.lifetime));
8918 
8919 			float damage;
8920 			if (wi.wi_flags[Weapon::Info_Flags::Beam])
8921 				damage = wi.damage * wi.b_info.beam_life * (1000.0f / i2fl(BEAM_DAMAGE_TIME));
8922 			else
8923 				damage = wi.damage;
8924 
8925 			float fire_rate;
8926 			//We need to count the length of a firing cycle and then divide by the number of shots in that cycle
8927 			//In random length bursts, average between the longest and shortest firing cycle to get average rof
8928 			if (wi.burst_shots > 1 && (wi.burst_flags[Weapon::Burst_Flags::Random_length]))
8929 				fire_rate = (wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1)) + (1 / wi.fire_wait)) / 2;
8930 			else if (wi.burst_shots > 1)
8931 				fire_rate = wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1));
8932 			else
8933 				fire_rate = 1 / wi.fire_wait;
8934 
8935 			// doubled damage is handled strangely...
8936 			float hull_multiplier = 1.0f;
8937 			float shield_multiplier = 1.0f;
8938 			float subsys_multiplier = 1.0f;
8939 
8940 			// area effect?
8941 			if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8942 			{
8943 				hull_multiplier = 2.0f;
8944 				shield_multiplier = 2.0f;
8945 
8946 				// shockwave?
8947 				if (wi.shockwave.speed > 0.0f)
8948 				{
8949 					subsys_multiplier = 2.0f;
8950 				}
8951 			}
8952 
8953 			// puncture?
8954 			if (wi.wi_flags[Weapon::Info_Flags::Puncture])
8955 				hull_multiplier /= 4;
8956 
8957 			// beams ignore factors unless specified
8958 			float armor_factor = wi.armor_factor;
8959 			float shield_factor = wi.shield_factor;
8960 			float subsys_factor = wi.subsystem_factor;
8961 			if (wi.wi_flags[Weapon::Info_Flags::Beam] && !Beams_use_damage_factors)
8962 				armor_factor = shield_factor = subsys_factor = 1.0f;
8963 
8964 			mprintf(("\tDPS: "));
8965 			mprintf(("%.0f Hull, ", hull_multiplier * damage * armor_factor * fire_rate));
8966 			mprintf(("%.0f Shield, ", shield_multiplier * damage * shield_factor * fire_rate));
8967 			mprintf(("%.0f Subsystem\n", subsys_multiplier * damage * subsys_factor * fire_rate));
8968 
8969 			char watts[NAME_LENGTH];
8970 			sprintf(watts, "%.1f", wi.energy_consumed * fire_rate);
8971 			char *p = strstr(watts, ".0");
8972 			if (p)
8973 				*p = 0;
8974 			strcat(watts, "W");
8975 
8976 			char rof[NAME_LENGTH];
8977 			sprintf(rof, "%.1f", fire_rate);
8978 			p = strstr(rof, ".0");
8979 			if (p)
8980 				*p = 0;
8981 			strcat(rof, "/s");
8982 
8983 			mprintf(("\tPower Use: %-10sROF: %s\n\n", watts, rof));
8984 		}
8985 	}
8986 	for (auto &wi : Weapon_info)
8987 	{
8988 		if (wi.subtype != WP_MISSILE)
8989 			continue;
8990 
8991 		if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed] || wi.wi_flags[Weapon::Info_Flags::Child])
8992 		{
8993 			mprintf(("%s\n", wi.name));
8994 			mprintf(("\tVelocity: %-11.0fRange: %.0f\n", wi.max_speed, wi.max_speed * wi.lifetime));
8995 
8996 			// doubled damage is handled strangely...
8997 			float hull_multiplier = 1.0f;
8998 			float shield_multiplier = 1.0f;
8999 			float subsys_multiplier = 1.0f;
9000 
9001 			// area effect?
9002 			if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
9003 			{
9004 				hull_multiplier = 2.0f;
9005 				shield_multiplier = 2.0f;
9006 
9007 				// shockwave?
9008 				if (wi.shockwave.speed > 0.0f)
9009 				{
9010 					subsys_multiplier = 2.0f;
9011 				}
9012 			}
9013 
9014 			// puncture?
9015 			if (wi.wi_flags[Weapon::Info_Flags::Puncture])
9016 				hull_multiplier /= 4;
9017 
9018 			mprintf(("\tDamage: "));
9019 			mprintf(("%.0f Hull, ", hull_multiplier * wi.damage * wi.armor_factor));
9020 			mprintf(("%.0f Shield, ", shield_multiplier * wi.damage * wi.shield_factor));
9021 			mprintf(("%.0f Subsystem\n", subsys_multiplier * wi.damage * wi.subsystem_factor));
9022 
9023 			char wait[NAME_LENGTH];
9024 			sprintf(wait, "%.1f", wi.fire_wait);
9025 			char *p = strstr(wait, ".0");
9026 			if (p)
9027 				*p = 0;
9028 			strcat(wait, "s");
9029 
9030 			bool flip = wi.rearm_rate <= 1.0f;
9031 			char flip_str[NAME_LENGTH];
9032 			sprintf(flip_str, "%d/", wi.reloaded_per_batch);
9033 
9034 			char reload[NAME_LENGTH];
9035 			sprintf(reload, "%.1f", flip ? wi.reloaded_per_batch / wi.rearm_rate : wi.rearm_rate);
9036 			p = strstr(reload, ".0");
9037 			if (p)
9038 				*p = 0;
9039 
9040 			mprintf(("\tFire Wait: %-10sReload: %s%s%ss\n\n", wait, !flip ? flip_str : "", reload, flip ? "/" : ""));
9041 		}
9042 	}
9043 #endif
9044 }
9045 
9046 // Given a weapon, figure out how many independent locks we can have with it.
weapon_get_max_missile_seekers(weapon_info * wip)9047 int weapon_get_max_missile_seekers(weapon_info *wip)
9048 {
9049 	int max_target_locks;
9050 
9051 	if ( wip->multi_lock ) {
9052 		if ( wip->wi_flags[Weapon::Info_Flags::Swarm] ) {
9053 			max_target_locks = wip->swarm_count;
9054 		} else if ( wip->wi_flags[Weapon::Info_Flags::Corkscrew] ) {
9055 			max_target_locks = wip->cs_num_fired;
9056 		} else {
9057 			max_target_locks = 1;
9058 		}
9059 	} else {
9060 		max_target_locks = 1;
9061 	}
9062 
9063 	return max_target_locks;
9064 }
9065 
9066 // returns whether a homing weapon can home on a particular ship
weapon_target_satisfies_lock_restrictions(weapon_info * wip,object * target)9067 bool weapon_target_satisfies_lock_restrictions(weapon_info* wip, object* target)
9068 {
9069 	if (target->type != OBJ_SHIP)
9070 		return true;
9071 
9072 	auto& restrictions = wip->ship_restrict;
9073 	// if you didn't specify any restrictions, you can always lock
9074 	if (restrictions.empty()) return true;
9075 
9076 	int type_num = Ship_info[Ships[target->instance].ship_info_index].class_type;
9077 	int class_num = Ships[target->instance].ship_info_index;
9078 	int species_num = Ship_info[Ships[target->instance].ship_info_index].species;
9079 	int iff_num = Ships[target->instance].team;
9080 
9081 	// otherwise, you're good as long as it matches one of the allowances in the restriction list
9082 	return std::any_of(restrictions.begin(), restrictions.end(),
9083 		[=](std::pair<LockRestrictionType, int>& restriction) {
9084 			switch (restriction.first) {
9085 				case LockRestrictionType::TYPE: return restriction.second == type_num;
9086 				case LockRestrictionType::CLASS: return restriction.second == class_num;
9087 				case LockRestrictionType::SPECIES: return restriction.second == species_num;
9088 				case LockRestrictionType::IFF: return restriction.second == iff_num;
9089 				default: return true;
9090 			}
9091 		});
9092 }
9093 
9094 // what it says on the tin
9095 // if true, the the IFF restrictions (later to be checked by the above) will replace the normal logic (can lock on enemies, not on friendlies)
weapon_has_iff_restrictions(weapon_info * wip)9096 bool weapon_has_iff_restrictions(weapon_info* wip)
9097 {
9098 	auto& restrictions = wip->ship_restrict;
9099 
9100 	return std::any_of(restrictions.begin(), restrictions.end(),
9101 		[=](std::pair<LockRestrictionType, int>& restriction) {
9102 			return restriction.first == LockRestrictionType::IFF;
9103 		});
9104 }
9105 
weapon_secondary_world_pos_in_range(object * shooter,weapon_info * wip,vec3d * target_world_pos)9106 bool weapon_secondary_world_pos_in_range(object* shooter, weapon_info* wip, vec3d* target_world_pos)
9107 {
9108 	vec3d vec_to_target;
9109 	vm_vec_sub(&vec_to_target, target_world_pos, &shooter->pos);
9110 	float dist_to_target = vm_vec_mag(&vec_to_target);
9111 
9112 	float weapon_range;
9113 	//local ssms are always in range :)
9114 	if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
9115 		weapon_range = wip->lssm_lock_range;
9116 	else
9117 		// if the weapon can actually hit the target
9118 		weapon_range = MIN((wip->max_speed * wip->lifetime), wip->weapon_range);
9119 
9120 
9121 	extern int Nebula_sec_range;
9122 	// reduce firing range in nebula
9123 	if ((The_mission.flags[Mission::Mission_Flags::Fullneb]) && Nebula_sec_range) {
9124 		weapon_range *= 0.8f;
9125 	}
9126 
9127 	return dist_to_target <= weapon_range;
9128 }
9129 
weapon_multilock_can_lock_on_subsys(object * shooter,object * target,ship_subsys * target_subsys,weapon_info * wip,float * out_dot)9130 bool weapon_multilock_can_lock_on_subsys(object* shooter, object* target, ship_subsys* target_subsys, weapon_info* wip, float* out_dot) {
9131 	Assertion(shooter->type == OBJ_SHIP, "weapon_multilock_can_lock_on_subsys called with a non-ship shooter");
9132 	if (shooter->type != OBJ_SHIP)
9133 		return false;
9134 
9135 	if (target_subsys->flags[Ship::Subsystem_Flags::Untargetable])
9136 		return false;
9137 
9138 	//by not checking for max_hits > 0 here, subsys' with disabled hitpoints are also excluded.
9139 	if (!wip->wi_flags[Weapon::Info_Flags::Multilock_target_dead_subsys] && target_subsys->current_hits <= 0.0f)
9140 		return false;
9141 
9142 	vec3d ss_pos;
9143 	get_subsystem_world_pos(target, target_subsys, &ss_pos);
9144 
9145 	if (!weapon_secondary_world_pos_in_range(shooter, wip, &ss_pos))
9146 		return false;
9147 
9148 	vec3d vec_to_target;
9149 	vm_vec_normalized_dir(&vec_to_target, &ss_pos, &shooter->pos);
9150 	float dot = vm_vec_dot(&shooter->orient.vec.fvec, &vec_to_target);
9151 
9152 	if (out_dot != nullptr)
9153 		*out_dot = dot;
9154 
9155 	if (dot < wip->lock_fov)
9156 		return false;
9157 
9158 	vec3d gsubpos;
9159 	vm_vec_unrotate(&gsubpos, &target_subsys->system_info->pnt, &target->orient);
9160 	vm_vec_add2(&gsubpos, &target->pos);
9161 
9162 	polymodel* pm = model_get(Ship_info[Ships[shooter->instance].ship_info_index].model_num);
9163 	vec3d eye_pos = pm->view_positions[0].pnt + shooter->pos;
9164 
9165 	return ship_subsystem_in_sight(target, target_subsys, &eye_pos, &gsubpos) == 1;
9166 }
9167 
weapon_multilock_can_lock_on_target(object * shooter,object * target_objp,weapon_info * wip,float * out_dot)9168 bool weapon_multilock_can_lock_on_target(object* shooter, object* target_objp, weapon_info* wip, float* out_dot) {
9169 	Assertion(shooter->type == OBJ_SHIP, "weapon_multilock_can_lock_on_target called with a non-ship shooter");
9170 	if (target_objp->type != OBJ_SHIP)
9171 		return false;
9172 
9173 	if (hud_target_invalid_awacs(target_objp))
9174 		return false;
9175 
9176 	ship* target_ship = &Ships[target_objp->instance];
9177 
9178 	if (target_objp->flags[Object::Object_Flags::Should_be_dead])
9179 		return false;
9180 
9181 	if (target_ship->flags[Ship::Ship_Flags::Dying])
9182 		return false;
9183 
9184 	if (should_be_ignored(target_ship))
9185 		return false;
9186 
9187 	// if this is part of the same team and doesn't have any iff restrictions, reject lock
9188 	if (!weapon_has_iff_restrictions(wip) && Ships[shooter->instance].team == obj_team(target_objp))
9189 		return false;
9190 
9191 	vec3d vec_to_target;
9192 	vm_vec_normalized_dir(&vec_to_target, &target_objp->pos, &shooter->pos);
9193 	float dot = vm_vec_dot(&shooter->orient.vec.fvec, &vec_to_target);
9194 
9195 	if (out_dot != nullptr)
9196 		*out_dot = dot;
9197 
9198 	return weapon_target_satisfies_lock_restrictions(wip, target_objp);
9199 }
9200