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 
13 #include <algorithm>
14 
15 #include "asteroid/asteroid.h"
16 #include "cmdline/cmdline.h"
17 #include "debris/debris.h"
18 #include "debugconsole/console.h"
19 #include "freespace.h"
20 #include "gamesnd/gamesnd.h"
21 #include "globalincs/linklist.h"
22 #include "hud/hudets.h"
23 #include "hud/hudmessage.h"
24 #include "hud/hudshield.h"
25 #include "iff_defs/iff_defs.h"
26 #include "io/timer.h"
27 #include "lighting/lighting.h"
28 #include "math/fvi.h"
29 #include "math/staticrand.h"
30 #include "nebula/neb.h"
31 #include "mod_table/mod_table.h"
32 #include "network/multi.h"
33 #include "network/multimsgs.h"
34 #include "object/objcollide.h"
35 #include "object/object.h"
36 #include "object/objectshield.h"
37 #include "parse/parselo.h"
38 #include "scripting/scripting.h"
39 #include "scripting/api/objs/vecmath.h"
40 #include "particle/particle.h"
41 #include "playerman/player.h"
42 #include "render/3d.h"
43 #include "ship/ship.h"
44 #include "ship/shipfx.h"
45 #include "ship/shiphit.h"
46 #include "weapon/beam.h"
47 #include "weapon/weapon.h"
48 #include "globalincs/globals.h"
49 #include "tracing/tracing.h"
50 
51 // ------------------------------------------------------------------------------------------------
52 // BEAM WEAPON DEFINES/VARS
53 //
54 
55 // this is the constant which defines when a beam is an "area" beam. meaning, when we switch on sphereline checking and when
56 // a beam gets "stopped" by an object. It is a percentage of the object radius which the beam must be wider than
57 #define BEAM_AREA_PERCENT			0.4f
58 
59 // randomness factor - all beam weapon aiming is adjusted by +/- some factor within this range
60 #define BEAM_RANDOM_FACTOR			0.4f
61 
62 #define MAX_SHOT_POINTS				30
63 #define SHOT_POINT_TIME				200			// 5 arcs a second
64 
65 #define TOOLTIME						1500.0f
66 
67 std::array<beam, MAX_BEAMS> Beams;				// all beams
68 beam Beam_free_list;					// free beams
69 beam Beam_used_list;					// used beams
70 int Beam_count = 0;					// how many beams are in use
71 
72 // octant indices. These are "good" pairs of octants to use for beam target
73 #define BEAM_NUM_GOOD_OCTANTS			8
74 int Beam_good_slash_octants[BEAM_NUM_GOOD_OCTANTS][4] = {
75 	{ 2, 5, 1, 0 },					// octant, octant, min/max pt, min/max pt
76 	{ 7, 0, 1, 0 },
77 	{ 1, 6, 1, 0 },
78 	{ 6, 1, 0, 1 },
79 	{ 5, 2, 0, 1 },
80 	{ 0, 7, 0, 1 },
81 	{ 7, 1, 1, 0 },
82 	{ 6, 0, 1, 0 },
83 };
84 int Beam_good_shot_octants[BEAM_NUM_GOOD_OCTANTS][4] = {
85 	{ 5, 0, 1, 0 },					// octant, octant, min/max pt, min/max pt
86 	{ 7, 2, 1, 0 },
87 	{ 7, 1, 1, 0 },
88 	{ 6, 0, 1, 0 },
89 	{ 7, 3, 1, 0 },
90 	{ 6, 2, 1, 0 },
91 	{ 5, 1, 1, 0 },
92 	{ 4, 0, 1, 0 },
93 };
94 
95 // debug stuff - keep track of how many collision tests we perform a second and how many we toss a second
96 #define BEAM_TEST_STAMP_TIME		4000	// every 4 seconds
97 int Beam_test_stamp = -1;
98 int Beam_test_ints = 0;
99 int Beam_test_ship = 0;
100 int Beam_test_ast = 0;
101 int Beam_test_framecount = 0;
102 
103 // beam warmup completion %
104 #define BEAM_WARMUP_PCT(b)			( ((float)Weapon_info[b->weapon_info_index].b_info.beam_warmup - (float)timestamp_until(b->warmup_stamp)) / (float)Weapon_info[b->weapon_info_index].b_info.beam_warmup )
105 
106 // beam warmdown completion %
107 #define BEAM_WARMDOWN_PCT(b)		( ((float)Weapon_info[b->weapon_info_index].b_info.beam_warmdown - (float)timestamp_until(b->warmdown_stamp)) / (float)Weapon_info[b->weapon_info_index].b_info.beam_warmdown )
108 
109 // link into the physics paused system
110 extern int physics_paused;
111 
112 // beam lighting info
113 #define MAX_BEAM_LIGHT_INFO		100
114 typedef struct beam_light_info {
115 	beam *bm;					// beam casting the light
116 	int objnum;					// object getting light cast on it
117 	ubyte source;				// 0 to light the shooter, 1 for lighting any ship the beam passes, 2 to light the collision ship
118 	vec3d c_point;			// collision point for type 2 lights
119 } beam_light_info;
120 
121 beam_light_info Beam_lights[MAX_BEAM_LIGHT_INFO];
122 int Beam_light_count = 0;
123 
124 float b_whack_small = 2000.0f;	// used to be 500.0f with the retail whack bug
125 float b_whack_big = 10000.0f;	// used to be 1500.0f with the retail whack bug
126 float b_whack_damage = 150.0f;
127 
128 DCF(b_whack_small, "Sets the whack factor for small whacks (Default is 2000f)")
129 {
130 	dc_stuff_float(&b_whack_small);
131 }
132 DCF(b_whack_big, "Sets the whack factor for big whacks (Default is 10000f)")
133 {
134 	dc_stuff_float(&b_whack_big);
135 }
136 DCF(b_whack_damage, "Sets the whack damage threshold (Default is 150f)")
137 {
138 	if (dc_optional_string_either("help", "--help")) {
139 		dc_printf("Sets the threshold to determine whether a big whack or a small whack should be applied. Values equal or greater than this threshold will trigger a big whack, while smaller values will trigger a small whack\n");
140 		return;
141 	}
142 
143 	dc_stuff_float(&b_whack_damage);
144 }
145 
146 
147 // ------------------------------------------------------------------------------------------------
148 // BEAM WEAPON FORWARD DECLARATIONS
149 //
150 
151 // delete a beam
152 void beam_delete(beam *b);
153 
154 // handle a hit on a specific object
155 void beam_handle_collisions(beam *b);
156 
157 // fills in binfo
158 void beam_get_binfo(beam* b, float accuracy, int num_shots, int burst_seed, float burst_shot_rotation, float per_burst_shot_rotation);
159 
160 // aim the beam (setup last_start and last_shot - the endpoints). also recalculates object collision info
161 void beam_aim(beam *b);
162 
163 // type A functions
164 void beam_type_a_move(beam *b);
165 
166 // type B functions
167 void beam_type_b_move(beam *b);
168 
169 // type C functions
170 void beam_type_c_move(beam *b);
171 
172 // type D functions
173 void beam_type_d_move(beam *b);
174 // stuffs the index of the current pulse in shot_index
175 // stuffs 0 in fire_wait if the beam is active, 1 if it is between pulses
176 void beam_type_d_get_status(beam *b, int *shot_index, int *fire_wait);
177 
178 // type e functions
179 void beam_type_e_move(beam *b);
180 
181 // given a model #, and an object, stuff 2 good world coord points
182 void beam_get_octant_points(int modelnum, object *objp, int oct_index, int oct_array[BEAM_NUM_GOOD_OCTANTS][4], vec3d *v1, vec3d *v2);
183 
184 // given an object, return its model num
185 int beam_get_model(object *objp);
186 
187 // for rendering the beam effect
188 // output top and bottom vectors
189 // fvec == forward vector (eye viewpoint basically. in world coords)
190 // pos == world coordinate of the point we're calculating "around"
191 // w == width of the diff between top and bottom around pos
192 void beam_calc_facing_pts(vec3d *top, vec3d *bot, vec3d *fvec, vec3d *pos, float w, float z_add);
193 
194 // render the muzzle glow for a beam weapon
195 void beam_render_muzzle_glow(beam *b);
196 
197 // generate particles for the muzzle glow
198 void beam_generate_muzzle_particles(beam *b);
199 
200 // throw some jitter into the aim - based upon shot_aim
201 void beam_jitter_aim(beam *b, float aim);
202 
203 // if it is legal for the beam to continue firing
204 // returns -1 if the beam should stop firing immediately
205 // returns 0 if the beam should go to warmdown
206 // returns 1 if the beam can continue along its way
207 int beam_ok_to_fire(beam *b);
208 
209 // start the warmup phase for the beam
210 void beam_start_warmup(beam *b);
211 
212 // start the firing phase for the beam, return 0 if the beam failed to start, and should be deleted altogether
213 int beam_start_firing(beam *b);
214 
215 // start the warmdown phase for the beam
216 void beam_start_warmdown(beam *b);
217 
218 // add a collision to the beam for this frame (to be evaluated later)
219 void beam_add_collision(beam *b, object *hit_object, mc_info *cinfo, int quad = -1, bool exit_flag = false);
220 
221 // mark an object as being lit
222 void beam_add_light(beam *b, int objnum, int source, vec3d *c_point);
223 
224 // apply lighting from any beams
225 void beam_apply_lighting();
226 
227 // recalculate beam sounds (looping sounds relative to the player)
228 void beam_recalc_sounds(beam *b);
229 
230 // apply a whack to a ship
231 void beam_apply_whack(beam *b, object *objp, vec3d *hit_point);
232 
233 // if the beam is likely to tool a given target before its lifetime expires
234 int beam_will_tool_target(beam *b, object *objp);
235 
236 // ------------------------------------------------------------------------------------------------
237 // BEAM WEAPON FUNCTIONS
238 //
239 
240 // init at game startup
beam_init()241 void beam_init()
242 {
243 	beam_level_close();
244 }
245 
246 // initialize beam weapons for this level
beam_level_init()247 void beam_level_init()
248 {
249 	// intialize beams
250 	int idx;
251 
252 	Beam_count = 0;
253 	list_init( &Beam_free_list );
254 	list_init( &Beam_used_list );
255 	Beams.fill({});
256 
257 	// Link all object slots into the free list
258 	for (idx=0; idx<MAX_BEAMS; idx++)	{
259 		Beams[idx].objnum = -1;
260 		list_append(&Beam_free_list, &Beams[idx] );
261 	}
262 
263 	// reset muzzle particle spew timestamp
264 }
265 
266 // shutdown beam weapons for this level
beam_level_close()267 void beam_level_close()
268 {
269 	// clear the beams
270 	list_init( &Beam_free_list );
271 	list_init( &Beam_used_list );
272 }
273 
274 // get the width of the widest section of the beam
beam_get_widest(beam * b)275 float beam_get_widest(beam* b)
276 {
277 	int idx;
278 	float widest = -1.0f;
279 
280 	// sanity
281 	Assert(b->weapon_info_index >= 0);
282 	if (b->weapon_info_index < 0) {
283 		return -1.0f;
284 	}
285 
286 	// lookup
287 	for (idx = 0; idx < Weapon_info[b->weapon_info_index].b_info.beam_num_sections; idx++) {
288 		if (Weapon_info[b->weapon_info_index].b_info.sections[idx].width > widest) {
289 			widest = Weapon_info[b->weapon_info_index].b_info.sections[idx].width;
290 		}
291 	}
292 
293 	// return
294 	return widest;
295 }
296 
297 // return false if the particular beam fire method doesn't have all the required, and specific to its fire method, info
beam_has_valid_params(beam_fire_info * fire_info)298 bool beam_has_valid_params(beam_fire_info* fire_info) {
299 	switch (fire_info->fire_method) {
300 		case BFM_TURRET_FIRED:
301 			if (fire_info->shooter == nullptr || fire_info->turret == nullptr || fire_info->target == nullptr)
302 				return false;
303 			break;
304 		case BFM_TURRET_FORCE_FIRED:
305 			if (fire_info->shooter == nullptr || fire_info->turret == nullptr || (fire_info->target == nullptr && !(fire_info->bfi_flags & BFIF_TARGETING_COORDS)))
306 				return false;
307 			break;
308 		case BFM_FIGHTER_FIRED:
309 			if (fire_info->shooter == nullptr || fire_info->turret == nullptr)
310 				return false;
311 			break;
312 		case BFM_SPAWNED:
313 		case BFM_SEXP_FLOATING_FIRED:
314 			if (!(fire_info->bfi_flags & BFIF_FLOATING_BEAM) || (fire_info->target == nullptr && !(fire_info->bfi_flags & BFIF_TARGETING_COORDS)))
315 				return false;
316 			break;
317 		case BFM_SUBSPACE_STRIKE:
318 			if (!(fire_info->bfi_flags & BFIF_FLOATING_BEAM) || (fire_info->target == nullptr))
319 				return false;
320 			break;
321 		default:
322 			Assertion(false, "Unrecognized beam fire method in beam_has_valid_params");
323 			return false;
324 	}
325 
326 	// let's also validate the type of target, if applicable
327 	if (fire_info->target != nullptr) {
328 		if ((fire_info->target->type != OBJ_SHIP) && (fire_info->target->type != OBJ_ASTEROID) && (fire_info->target->type != OBJ_DEBRIS) && (fire_info->target->type != OBJ_WEAPON))
329 			return false;
330 	}
331 
332 	return true;
333 }
334 
335 // fire a beam, returns nonzero on success. the innards of the code handle all the rest, foo
beam_fire(beam_fire_info * fire_info)336 int beam_fire(beam_fire_info *fire_info)
337 {
338 	beam *new_item;
339 	weapon_info *wip;
340 	ship *firing_ship = NULL;
341 	int objnum;
342 
343 	// sanity check
344 	if(fire_info == NULL){
345 		Int3();
346 		return -1;
347 	}
348 
349 	// if we're out of beams, bail
350 	if(Beam_count >= MAX_BEAMS){
351 		return -1;
352 	}
353 
354 	// make sure the beam_info_index is valid
355 	if ((fire_info->beam_info_index < 0) || (fire_info->beam_info_index >= weapon_info_size()) || !(Weapon_info[fire_info->beam_info_index].wi_flags[Weapon::Info_Flags::Beam])) {
356 		UNREACHABLE("beam_info_index (%d) invalid (either <0, >= %d, or not actually a beam)!\n", fire_info->beam_info_index, weapon_info_size());
357 		return -1;
358 	}
359 
360 	wip = &Weapon_info[fire_info->beam_info_index];
361 
362 	// copied from weapon_create()
363 	if ((wip->num_substitution_patterns > 0) && (fire_info->shooter != nullptr)) {
364 		// using substitution
365 
366 		// get to the instance of the gun
367 		Assertion(fire_info->shooter->type == OBJ_SHIP, "Expected type OBJ_SHIP, got %d", fire_info->shooter->type);
368 		Assertion((fire_info->shooter->instance < MAX_SHIPS) && (fire_info->shooter->instance >= 0),
369 			"Ship index is %d, which is out of range [%d,%d)", fire_info->shooter->instance, 0, MAX_SHIPS);
370 		ship* parent_shipp = &(Ships[fire_info->shooter->instance]);
371 		Assert(parent_shipp != nullptr);
372 
373 		size_t* position = get_pointer_to_weapon_fire_pattern_index(fire_info->beam_info_index, fire_info->shooter->instance, fire_info->turret);
374 		Assertion(position != nullptr, "'%s' is trying to fire a weapon that is not selected", Ships[fire_info->shooter->instance].ship_name);
375 
376 		size_t curr_pos = *position;
377 		if ((parent_shipp->flags[Ship::Ship_Flags::Primary_linked]) && curr_pos > 0) {
378 			curr_pos--;
379 		}
380 		++(*position);
381 		*position = (*position) % wip->num_substitution_patterns;
382 
383 		if (wip->weapon_substitution_pattern[curr_pos] == -1) {
384 			// weapon doesn't want any sub
385 			return -1;
386 		}
387 		else if (wip->weapon_substitution_pattern[curr_pos] != fire_info->beam_info_index) {
388 			fire_info->beam_info_index = wip->weapon_substitution_pattern[curr_pos];
389 			// weapon wants to sub with weapon other than me
390 			return beam_fire(fire_info);
391 		}
392 	}
393 
394 	if (!beam_has_valid_params(fire_info))
395 		return -1;
396 
397 	if (fire_info->shooter != NULL) {
398 		firing_ship = &Ships[fire_info->shooter->instance];
399 	}
400 
401 	// get a free beam
402 	new_item = GET_FIRST(&Beam_free_list);
403 	Assert( new_item != &Beam_free_list );		// shouldn't have the dummy element
404 	if(new_item == &Beam_free_list){
405 		return -1;
406 	}
407 
408 	// make sure that our textures are loaded as well
409 	extern bool weapon_is_used(int weapon_index);
410 	extern void weapon_load_bitmaps(int weapon_index);
411 	if ( !weapon_is_used(fire_info->beam_info_index) ) {
412 		weapon_load_bitmaps(fire_info->beam_info_index);
413 	}
414 
415 	// remove from the free list
416 	list_remove( &Beam_free_list, new_item );
417 
418 	// insert onto the end of used list
419 	list_append( &Beam_used_list, new_item );
420 
421 	// increment counter
422 	Beam_count++;
423 
424 	// fill in some values
425 	new_item->warmup_stamp = -1;
426 	new_item->warmdown_stamp = -1;
427 	new_item->weapon_info_index = fire_info->beam_info_index;
428 	new_item->objp = fire_info->shooter;
429 	new_item->sig = (fire_info->shooter != NULL) ? fire_info->shooter->signature : 0;
430 	new_item->subsys = fire_info->turret;
431 	new_item->life_left = wip->b_info.beam_life;
432 	new_item->life_total = wip->b_info.beam_life;
433 	new_item->r_collision_count = 0;
434 	new_item->f_collision_count = 0;
435 	new_item->target = fire_info->target;
436 	new_item->target_subsys = fire_info->target_subsys;
437 	new_item->target_sig = (fire_info->target != NULL) ? fire_info->target->signature : 0;
438 	new_item->beam_sound_loop        = sound_handle::invalid();
439 	new_item->type = wip->b_info.beam_type;
440 	new_item->local_fire_postion = fire_info->local_fire_postion;
441 	new_item->framecount = 0;
442 	new_item->flags = 0;
443 	new_item->shot_index = 0;
444 	new_item->current_width_factor = wip->b_info.beam_initial_width < 0.1f ? 0.1f : wip->b_info.beam_initial_width;
445 	new_item->team = (firing_ship == NULL) ? fire_info->team : static_cast<char>(firing_ship->team);
446 	new_item->range = wip->b_info.range;
447 	new_item->damage_threshold = wip->b_info.damage_threshold;
448 	new_item->bank = fire_info->bank;
449 	new_item->Beam_muzzle_stamp = -1;
450 	new_item->beam_glow_frame = 0.0f;
451 	new_item->firingpoint = (fire_info->bfi_flags & BFIF_FLOATING_BEAM) ? -1 : fire_info->turret->turret_next_fire_pos;
452 	new_item->last_start = fire_info->starting_pos;
453 	new_item->type5_rot_speed = wip->b_info.t5info.continuous_rot;
454 	new_item->rotates = wip->b_info.beam_type == BEAM_TYPE_F && wip->b_info.t5info.continuous_rot_axis != Type5BeamRotAxis::UNSPECIFIED;
455 
456 	if (fire_info->bfi_flags & BFIF_FORCE_FIRING)
457 		new_item->flags |= BF_FORCE_FIRING;
458 	if (fire_info->bfi_flags & BFIF_IS_FIGHTER_BEAM)
459 		new_item->flags |= BF_IS_FIGHTER_BEAM;
460 	if (fire_info->bfi_flags & BFIF_FLOATING_BEAM)
461 		new_item->flags |= BF_FLOATING_BEAM;
462 
463 	if (fire_info->bfi_flags & BFIF_TARGETING_COORDS) {
464 		new_item->flags |= BF_TARGETING_COORDS;
465 		new_item->target_pos1 = fire_info->target_pos1;
466 		new_item->target_pos2 = fire_info->target_pos2;
467 	} else {
468 		vm_vec_zero(&new_item->target_pos1);
469 		vm_vec_zero(&new_item->target_pos2);
470 	}
471 
472 	for (float &frame : new_item->beam_section_frame)
473 		frame = 0.0f;
474 
475 	// beam collision and light width
476 	if (wip->b_info.beam_width > 0.0f) {
477 		new_item->beam_collide_width = wip->b_info.beam_width;
478 		new_item->beam_light_width = wip->b_info.beam_width;
479 	} else {
480 		float widest = beam_get_widest(new_item);
481 		new_item->beam_collide_width = wip->collision_radius_override > 0.0f ? wip->collision_radius_override : widest;
482 		new_item->beam_light_width = widest;
483 	}
484 
485 	if (fire_info->bfi_flags & BFIF_IS_FIGHTER_BEAM && new_item->type != BEAM_TYPE_F) {
486 		new_item->type = BEAM_TYPE_C;
487 	}
488 
489 	// if the targeted subsystem is not NULL, force it to be a type A beam
490 	if(new_item->target_subsys != nullptr && new_item->type != BEAM_TYPE_C && new_item->type != BEAM_TYPE_F){
491 		new_item->type = BEAM_TYPE_A;
492 	}
493 
494 	// type D weapons can only fire at small ships and missiles
495 	if(new_item->type == BEAM_TYPE_D){
496 		// if its a targeted ship, get the target ship
497 		if((fire_info->target != NULL) && (fire_info->target->type == OBJ_SHIP) && (fire_info->target->instance >= 0)){
498 			ship *target_ship = &Ships[fire_info->target->instance];
499 
500 			// maybe force to be a type A
501 			if(Ship_info[target_ship->ship_info_index].class_type > -1 && (Ship_types[Ship_info[target_ship->ship_info_index].class_type].flags[Ship::Type_Info_Flags::Beams_easily_hit])){
502 				new_item->type = BEAM_TYPE_A;
503 			}
504 		}
505 	}
506 
507 	// ----------------------------------------------------------------------
508 	// THIS IS THE CRITICAL POINT FOR MULTIPLAYER
509 	// beam_get_binfo(...) determines exactly how the beam will behave over the course of its life
510 	// it fills in binfo, which we can pass to clients in multiplayer
511 	if(fire_info->beam_info_override != NULL){
512 		new_item->binfo = *fire_info->beam_info_override;
513 	} else {
514 		float burst_rot = 0.0f;
515 		if (new_item->type == BEAM_TYPE_F && !wip->b_info.t5info.burst_rot_pattern.empty()) {
516 			burst_rot = wip->b_info.t5info.burst_rot_pattern[fire_info->burst_index];
517 		}
518 		beam_get_binfo(new_item, fire_info->accuracy, wip->b_info.beam_shots,fire_info->burst_seed, burst_rot, fire_info->per_burst_rotation);			// to fill in b_info	- the set of directional aim vectors
519 	}
520 
521     flagset<Object::Object_Flags> default_flags;
522 	if (!wip->wi_flags[Weapon::Info_Flags::No_collide])
523 		default_flags.set(Object::Object_Flags::Collides);
524 
525 	// create the associated object
526 	objnum = obj_create(OBJ_BEAM, ((fire_info->shooter != NULL) ? OBJ_INDEX(fire_info->shooter) : -1), BEAM_INDEX(new_item), &vmd_identity_matrix, &vmd_zero_vector, 1.0f, default_flags);
527 	if(objnum < 0){
528 		beam_delete(new_item);
529 		mprintf(("obj_create() failed for a beam weapon because you are running out of object slots!\n"));
530 		return -1;
531 	}
532 	new_item->objnum = objnum;
533 
534 	if (new_item->objp != nullptr && Weapons_inherit_parent_collision_group) {
535 		Objects[objnum].collision_group_id = new_item->objp->collision_group_id;
536 	}
537 
538 	// this sets up all info for the first frame the beam fires
539 	beam_aim(new_item);						// to fill in shot_point, etc.
540 
541 	// check to see if its legal to fire at this guy
542 	if (beam_ok_to_fire(new_item) != 1) {
543 		beam_delete(new_item);
544 		mprintf(("Killing beam at initial fire because of illegal targeting!!!\n"));
545 		return -1;
546 	}
547 
548 	// if we're a multiplayer master - send a packet
549 	if (MULTIPLAYER_MASTER) {
550 		send_beam_fired_packet(fire_info, &new_item->binfo);
551 	}
552 
553 	// start the warmup phase
554 	beam_start_warmup(new_item);
555 
556 	return objnum;
557 }
558 
559 // fire a targeting beam, returns objnum on success. a much much simplified version of a beam weapon
560 // targeting lasers last _one_ frame. For a continuous stream - they must be created every frame.
561 // this allows it to work smoothly in multiplayer (detect "trigger down". every frame just create a targeting laser firing straight out of the
562 // object. this way you get all the advantages of nice rendering and collisions).
563 // NOTE : only references beam_info_index and shooter
beam_fire_targeting(fighter_beam_fire_info * fire_info)564 int beam_fire_targeting(fighter_beam_fire_info *fire_info)
565 {
566 	beam *new_item;
567 	weapon_info *wip;
568 	int objnum;
569 	ship *firing_ship;
570 
571 	// sanity check
572 	if(fire_info == NULL){
573 		Int3();
574 		return -1;
575 	}
576 
577 	// if we're out of beams, bail
578 	if(Beam_count >= MAX_BEAMS){
579 		return -1;
580 	}
581 
582 	// make sure the beam_info_index is valid
583 	Assert((fire_info->beam_info_index >= 0) && (fire_info->beam_info_index < weapon_info_size()) && (Weapon_info[fire_info->beam_info_index].wi_flags[Weapon::Info_Flags::Beam]));
584 	if((fire_info->beam_info_index < 0) || (fire_info->beam_info_index >= weapon_info_size()) || !(Weapon_info[fire_info->beam_info_index].wi_flags[Weapon::Info_Flags::Beam])){
585 		return -1;
586 	}
587 	wip = &Weapon_info[fire_info->beam_info_index];
588 
589 	// make sure a ship is firing this
590 	Assert((fire_info->shooter->type == OBJ_SHIP) && (fire_info->shooter->instance >= 0) && (fire_info->shooter->instance < MAX_SHIPS));
591 	if ( (fire_info->shooter->type != OBJ_SHIP) || (fire_info->shooter->instance < 0) || (fire_info->shooter->instance >= MAX_SHIPS) ) {
592 		return -1;
593 	}
594 	firing_ship = &Ships[fire_info->shooter->instance];
595 
596 
597 	// get a free beam
598 	new_item = GET_FIRST(&Beam_free_list);
599 	Assert( new_item != &Beam_free_list );		// shouldn't have the dummy element
600 
601 	// remove from the free list
602 	list_remove( &Beam_free_list, new_item );
603 
604 	// insert onto the end of used list
605 	list_append( &Beam_used_list, new_item );
606 
607 	// increment counter
608 	Beam_count++;
609 
610 	// maybe allocate some extra data based on the beam type
611 	Assert(wip->b_info.beam_type == BEAM_TYPE_C);
612 	if(wip->b_info.beam_type != BEAM_TYPE_C){
613 		return -1;
614 	}
615 
616 	// fill in some values
617 	new_item->warmup_stamp = fire_info->warmup_stamp;
618 	new_item->warmdown_stamp = fire_info->warmdown_stamp;
619 	new_item->weapon_info_index = fire_info->beam_info_index;
620 	new_item->objp = fire_info->shooter;
621 	new_item->sig = fire_info->shooter->signature;
622 	new_item->subsys = NULL;
623 	new_item->life_left = fire_info->life_left;
624 	new_item->life_total = fire_info->life_total;
625 	new_item->r_collision_count = 0;
626 	new_item->f_collision_count = 0;
627 	new_item->target = NULL;
628 	new_item->target_subsys = NULL;
629 	new_item->target_sig = 0;
630 	new_item->beam_sound_loop        = sound_handle::invalid();
631 	new_item->type = BEAM_TYPE_C;
632 	new_item->local_fire_postion = fire_info->local_fire_postion;
633 	new_item->framecount = 0;
634 	new_item->flags = 0;
635 	new_item->shot_index = 0;
636 	new_item->current_width_factor = wip->b_info.beam_initial_width < 0.1f ? 0.1f : wip->b_info.beam_initial_width;
637 	new_item->team = (char)firing_ship->team;
638 	new_item->range = wip->b_info.range;
639 	new_item->damage_threshold = wip->b_info.damage_threshold;
640 
641 	// beam collision and light width
642 	if (wip->b_info.beam_width > 0.0f) {
643 		new_item->beam_collide_width = wip->b_info.beam_width;
644 		new_item->beam_light_width = wip->b_info.beam_width;
645 	}
646 	else {
647 		float widest = beam_get_widest(new_item);
648 		new_item->beam_collide_width = wip->collision_radius_override > 0.0f ? wip->collision_radius_override : widest;
649 		new_item->beam_light_width = widest;
650 	}
651 
652 	// type c is a very special weapon type - binfo has no meaning
653 
654 	flagset<Object::Object_Flags> initial_flags;
655 	if (!wip->wi_flags[Weapon::Info_Flags::No_collide])
656 		initial_flags.set(Object::Object_Flags::Collides);
657 
658 	// create the associated object
659 	objnum = obj_create(OBJ_BEAM, OBJ_INDEX(fire_info->shooter), BEAM_INDEX(new_item), &vmd_identity_matrix, &vmd_zero_vector, 1.0f, initial_flags);
660 
661 	if(objnum < 0){
662 		beam_delete(new_item);
663 		nprintf(("General", "obj_create() failed for beam weapon! bah!\n"));
664 		Int3();
665 		return -1;
666 	}
667 	new_item->objnum = objnum;
668 
669 	// this sets up all info for the first frame the beam fires
670 	beam_aim(new_item);						// to fill in shot_point, etc.
671 
672 	if(Beams[Objects[objnum].instance].objnum != objnum){
673 		Int3();
674 		return -1;
675 	}
676 
677 	return objnum;
678 }
679 
680 // return an object index of the guy who's firing this beam
beam_get_parent(object * bm)681 int beam_get_parent(object *bm)
682 {
683 	beam *b;
684 
685 	// get a handle to the beam
686 	Assert(bm->type == OBJ_BEAM);
687 	Assert(bm->instance >= 0);
688 	if(bm->type != OBJ_BEAM){
689 		return -1;
690 	}
691 	if(bm->instance < 0){
692 		return -1;
693 	}
694 	b = &Beams[bm->instance];
695 
696 	if(b->objp == NULL){
697 		return -1;
698 	}
699 
700 	// if the object handle is invalid
701 	if(b->objp->signature != b->sig){
702 		return -1;
703 	}
704 
705 	// return the handle
706 	return OBJ_INDEX(b->objp);
707 }
708 
709 // return weapon_info_index of beam
beam_get_weapon_info_index(object * bm)710 int beam_get_weapon_info_index(object *bm)
711 {
712 	Assert(bm->type == OBJ_BEAM);
713 	if (bm->type != OBJ_BEAM) {
714 		return -1;
715 	}
716 
717 	Assert(bm->instance >= 0 && bm->instance < MAX_BEAMS);
718 	if (bm->instance < 0) {
719 		return -1;
720 	}
721     //make sure it's returning a valid info index
722 	Assert((Beams[bm->instance].weapon_info_index > -1) && (Beams[bm->instance].weapon_info_index < weapon_info_size()));
723 
724 	// return weapon_info_index
725 	return Beams[bm->instance].weapon_info_index;
726 }
727 
728 
729 
730 // given a beam object, get the # of collisions which happened during the last collision check (typically, last frame)
beam_get_num_collisions(int objnum)731 int beam_get_num_collisions(int objnum)
732 {
733 	// sanity checks
734 	if((objnum < 0) || (objnum >= MAX_OBJECTS)){
735 		Int3();
736 		return -1;
737 	}
738 	if((Objects[objnum].instance < 0) || (Objects[objnum].instance >= MAX_BEAMS)){
739 		Int3();
740 		return -1;
741 	}
742 	if(Beams[Objects[objnum].instance].objnum != objnum){
743 		Int3();
744 		return -1;
745 	}
746 
747 	if(Beams[Objects[objnum].instance].objnum < 0){
748 		Int3();
749 		return -1;
750 	}
751 
752 	// return the # of recent collisions
753 	return Beams[Objects[objnum].instance].r_collision_count;
754 }
755 
756 // stuff collision info, returns 1 on success
beam_get_collision(int objnum,int num,int * collision_objnum,mc_info ** cinfo)757 int beam_get_collision(int objnum, int num, int *collision_objnum, mc_info **cinfo)
758 {
759 	// sanity checks
760 	if((objnum < 0) || (objnum >= MAX_OBJECTS)){
761 		Int3();
762 		return 0;
763 	}
764 	if((Objects[objnum].instance < 0) || (Objects[objnum].instance >= MAX_BEAMS)){
765 		Int3();
766 		return 0;
767 	}
768 	if((Beams[Objects[objnum].instance].objnum != objnum) || (Beams[Objects[objnum].instance].objnum < 0)){
769 		Int3();
770 		return 0;
771 	}
772 	if(num >= Beams[Objects[objnum].instance].r_collision_count){
773 		Int3();
774 		return 0;
775 	}
776 
777 	// return - success
778 	*cinfo = &Beams[Objects[objnum].instance].r_collisions[num].cinfo;
779 	*collision_objnum = Beams[Objects[objnum].instance].r_collisions[num].c_objnum;
780 	return 1;
781 }
782 
783 // pause all looping beam sounds
beam_pause_sounds()784 void beam_pause_sounds()
785 {
786 	beam *moveup = NULL;
787 
788 	// set all beam volumes to 0
789 	moveup = GET_FIRST(&Beam_used_list);
790 	if(moveup == NULL){
791 		return;
792 	}
793 	while(moveup != END_OF_LIST(&Beam_used_list)){
794 		// set the volume to 0, if he has a looping beam sound
795 		if (moveup->beam_sound_loop.isValid()) {
796 			snd_set_volume(moveup->beam_sound_loop, 0.0f);
797 		}
798 
799 		// next beam
800 		moveup = GET_NEXT(moveup);
801 	}
802 }
803 
804 // unpause looping beam sounds
beam_unpause_sounds()805 void beam_unpause_sounds()
806 {
807 	beam *moveup = NULL;
808 
809 	// recalc all beam sounds
810 	moveup = GET_FIRST(&Beam_used_list);
811 	if(moveup == NULL){
812 		return;
813 	}
814 	while(moveup != END_OF_LIST(&Beam_used_list)){
815 		if (Cmdline_no_3d_sound) {
816 			beam_recalc_sounds(moveup);
817 		} else {
818 			if (moveup->beam_sound_loop.isValid()) {
819 				snd_set_volume(moveup->beam_sound_loop, 1.0f);
820 			}
821 		}
822 
823 		// next beam
824 		moveup = GET_NEXT(moveup);
825 	}
826 }
827 
beam_get_global_turret_gun_info(object * objp,ship_subsys * ssp,vec3d * gpos,vec3d * gvec,int use_angles,vec3d * targetp,bool fighter_beam)828 void beam_get_global_turret_gun_info(object *objp, ship_subsys *ssp, vec3d *gpos, vec3d *gvec, int use_angles, vec3d *targetp, bool fighter_beam)
829 {
830 	ship_get_global_turret_gun_info(objp, ssp, gpos, gvec, use_angles, targetp);
831 
832 	if (fighter_beam)
833 		*gvec = objp->orient.vec.fvec;
834 }
835 
836 // -----------------------------===========================------------------------------
837 // BEAM MOVEMENT FUNCTIONS
838 // -----------------------------===========================------------------------------
839 
840 // move a type A beam weapon
beam_type_a_move(beam * b)841 void beam_type_a_move(beam *b)
842 {
843 	vec3d dir;
844 	vec3d temp, temp2;
845 
846 	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
847 	// get the "originating point" of the beam for this frame. essentially bashes last_start
848 	if (b->subsys != NULL)
849 		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
850 
851 	// if the "warming up" timestamp has not expired
852 	if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
853 		return;
854 	}
855 
856 	// put the "last_shot" point arbitrarily far away
857 	vm_vec_sub(&dir, &b->last_shot, &b->last_start);
858 	vm_vec_normalize_quick(&dir);
859 	vm_vec_scale_add(&b->last_shot, &b->last_start, &dir, b->range);
860 	Assert(is_valid_vec(&b->last_shot));
861 }
862 
863 // move a type B beam weapon
864 #define BEAM_T(b)						((b->life_total - b->life_left) / b->life_total)
beam_type_b_move(beam * b)865 void beam_type_b_move(beam *b)
866 {
867 	vec3d actual_dir;
868 	vec3d temp, temp2;
869 	float dot_save;
870 
871 	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
872 	// get the "originating point" of the beam for this frame. essentially bashes last_start
873 	if (b->subsys != NULL)
874 		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
875 
876 	// if the "warming up" timestamp has not expired
877 	if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
878 		return;
879 	}
880 
881 	// if the two direction vectors are _really_ close together, just use the original direction
882 	dot_save = vm_vec_dot(&b->binfo.dir_a, &b->binfo.dir_b);
883 	if((double)dot_save >= 0.999999999){
884 		actual_dir = b->binfo.dir_a;
885 	}
886 	// otherwise move towards the dir	we calculated when firing this beam
887 	else {
888 		vm_vec_interp_constant(&actual_dir, &b->binfo.dir_a, &b->binfo.dir_b, BEAM_T(b));
889 	}
890 
891 	// now recalculate shot_point to be shooting through our new point
892 	vm_vec_scale_add(&b->last_shot, &b->last_start, &actual_dir, b->range);
893 	bool is_valid = is_valid_vec(&b->last_shot);
894 	Assert(is_valid);
895 	if(!is_valid){
896 		actual_dir = b->binfo.dir_a;
897 		vm_vec_scale_add(&b->last_shot, &b->last_start, &actual_dir, b->range);
898 	}
899 }
900 
901 // type C functions
beam_type_c_move(beam * b)902 void beam_type_c_move(beam *b)
903 {
904 	vec3d temp;
905 
906 	// ugh
907 	if ( (b->objp == NULL) || (b->objp->instance < 0) ) {
908 		Int3();
909 		return;
910 	}
911 
912 	// type c beams only last one frame so we never have to "move" them.
913 	temp = b->local_fire_postion;
914 	vm_vec_unrotate(&b->last_start, &temp, &b->objp->orient);
915 	vm_vec_add2(&b->last_start, &b->objp->pos);
916 	vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.vec.fvec, b->range);
917 }
918 
919 // type D functions
beam_type_d_move(beam * b)920 void beam_type_d_move(beam *b)
921 {
922 	int shot_index, fire_wait;
923 	vec3d temp, temp2, dir;
924 
925 	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
926 	// get the "originating point" of the beam for this frame. essentially bashes last_start
927 	if (b->subsys != NULL)
928 		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
929 
930 	// if the "warming up" timestamp has not expired
931 	if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
932 		return;
933 	}
934 
935 	// determine what stage of the beam we're in
936 	beam_type_d_get_status(b, &shot_index, &fire_wait);
937 
938 	// if we've changed shot index
939 	if(shot_index != b->shot_index){
940 		// set the new index
941 		b->shot_index = shot_index;
942 
943 		// re-aim
944 		beam_aim(b);
945 	}
946 
947 	// if we're in the fire wait stage
948 	b->flags &= ~BF_SAFETY;
949 	if(fire_wait){
950 		b->flags |= BF_SAFETY;
951 	}
952 
953 	// put the "last_shot" point arbitrarily far away
954 	vm_vec_sub(&dir, &b->last_shot, &b->last_start);
955 	vm_vec_normalize_quick(&dir);
956 	vm_vec_scale_add(&b->last_shot, &b->last_start, &dir, b->range);
957 	Assert(is_valid_vec(&b->last_shot));
958 }
beam_type_d_get_status(beam * b,int * shot_index,int * fire_wait)959 void beam_type_d_get_status(beam *b, int *shot_index, int *fire_wait)
960 {
961 	float shot_time = b->life_total / (float)b->binfo.shot_count;
962 	float beam_time = b->life_total - b->life_left;
963 
964 	// determine what "shot" we're on
965 	*shot_index = (int)(beam_time / shot_time);
966 
967 	if(*shot_index >= b->binfo.shot_count){
968 		nprintf(("Beam","Shot of type D beam had bad shot_index value\n"));
969 		*shot_index = b->binfo.shot_count - 1;
970 	}
971 
972 	// determine if its the firing or waiting section of the shot (fire happens first, THEN wait)
973 	*fire_wait = 0;
974 	if(beam_time > ((shot_time * (*shot_index)) + (shot_time * 0.5f))){
975 		*fire_wait = 1;
976 	}
977 }
978 
979 // type e functions
beam_type_e_move(beam * b)980 void beam_type_e_move(beam *b)
981 {
982 	vec3d temp, turret_norm;
983 
984 	if (b->subsys == NULL) {	// If we're a free-floating beam, there's nothing to calculate here.
985 		return;
986 	}
987 
988 	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
989 	// get the "originating point" of the beam for this frame. essentially bashes last_start
990 	beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &turret_norm, 1, &temp, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
991 
992 	// if the "warming up" timestamp has not expired
993 	if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
994 		return;
995 	}
996 
997 	// put the "last_shot" point arbitrarily far away
998 	vm_vec_scale_add(&b->last_shot, &b->last_start, &turret_norm, b->range);
999 	Assert(is_valid_vec(&b->last_shot));
1000 }
1001 
beam_type_f_move(beam * b)1002 void beam_type_f_move(beam* b)
1003 {
1004 
1005 	// keep this updated even if still warming up
1006 	if (b->flags & BF_IS_FIGHTER_BEAM) {
1007 		vm_vec_unrotate(&b->last_start, &b->local_fire_postion, &b->objp->orient);
1008 		vm_vec_add2(&b->last_start, &b->objp->pos);
1009 
1010 		// compute the change in orientation the fighter went through
1011 		matrix inv_new_orient, transform_matrix;
1012 		vm_copy_transpose(&inv_new_orient, &b->objp->orient);
1013 		vm_matrix_x_matrix(&transform_matrix, &b->objp->last_orient, &inv_new_orient);
1014 		// and put the beam vectors through the same change
1015 		vec3d old_dirA = b->binfo.dir_a;
1016 		vec3d old_dirB = b->binfo.dir_b;
1017 		vec3d old_rot_axis = b->binfo.rot_axis;
1018 		vm_vec_rotate(&b->binfo.dir_a, &old_dirA, &transform_matrix);
1019 		vm_vec_rotate(&b->binfo.dir_b, &old_dirB, &transform_matrix);
1020 		vm_vec_rotate(&b->binfo.rot_axis, &old_rot_axis, &transform_matrix);
1021 	}
1022 	else if (b->subsys != nullptr) {
1023 		vec3d temp, temp2;
1024 		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, false);
1025 	}
1026 
1027 	// if the "warming up" timestamp has not expired
1028 	if ((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)) {
1029 		return;
1030 	}
1031 
1032 	vec3d newdir_a = b->binfo.dir_a;
1033 	vec3d newdir_b = b->binfo.dir_b;
1034 	vec3d zero_vec = vmd_zero_vector;
1035 	vec3d actual_dir;
1036 	bool no_sweep = vm_vec_dot(&b->binfo.dir_a, &b->binfo.dir_b) > 0.9999f;
1037 
1038 	if (b->rotates) {
1039 		vm_rot_point_around_line(&newdir_a, &b->binfo.dir_a, (b->life_total - b->life_left) * b->type5_rot_speed, &zero_vec, &b->binfo.rot_axis);
1040 		if (no_sweep)
1041 			actual_dir = newdir_a;
1042 		else
1043 			vm_rot_point_around_line(&newdir_b, &b->binfo.dir_b, (b->life_total - b->life_left) * b->type5_rot_speed, &zero_vec, &b->binfo.rot_axis);
1044 	}
1045 
1046 	if (no_sweep)
1047 		actual_dir = newdir_a;
1048 	else
1049 		vm_vec_interp_constant(&actual_dir, &newdir_a, &newdir_b, BEAM_T(b));
1050 
1051 	// now recalculate shot_point to be shooting through our new point
1052 	vm_vec_scale_add(&b->last_shot, &b->last_start, &actual_dir, b->range);
1053 }
1054 
1055 // pre-move (before collision checking - but AFTER ALL OTHER OBJECTS HAVE BEEN MOVED)
beam_move_all_pre()1056 void beam_move_all_pre()
1057 {
1058 	beam *b;
1059 	beam *moveup;
1060 
1061 	// zero lights for this frame yet
1062 	Beam_light_count = 0;
1063 
1064 	// traverse through all active beams
1065 	moveup = GET_FIRST(&Beam_used_list);
1066 	while (moveup != END_OF_LIST(&Beam_used_list)) {
1067 		// get the beam
1068 		b = moveup;
1069 
1070 		// check if parent object has died, if so then delete beam
1071 		if (b->objp != NULL && b->objp->type == OBJ_NONE) {
1072 			// set next beam
1073 			moveup = GET_NEXT(moveup);
1074 			// delete current beam
1075 			beam_delete(b);
1076 
1077 			continue;
1078 		}
1079 
1080 		// unset collision info
1081 		b->f_collision_count = 0;
1082 
1083 		if ( !physics_paused ) {
1084 			// make sure to check that firingpoint is still properly set
1085 			int temp = -1;
1086 			if (b->subsys != NULL) {
1087 				temp = b->subsys->turret_next_fire_pos;
1088 
1089 				if (!(b->flags & BF_IS_FIGHTER_BEAM))
1090 					b->subsys->turret_next_fire_pos = b->firingpoint;
1091 			}
1092 
1093 			// move the beam
1094 			switch (b->type)
1095 			{
1096 				// type A beam weapons don't move
1097 				case BEAM_TYPE_A :
1098 					beam_type_a_move(b);
1099 					break;
1100 
1101 				// type B beam weapons move across the target somewhat randomly
1102 				case BEAM_TYPE_B :
1103 					beam_type_b_move(b);
1104 					break;
1105 
1106 				// type C beam weapons are attached to a fighter - pointing forward
1107 				case BEAM_TYPE_C:
1108 					beam_type_c_move(b);
1109 					break;
1110 
1111 				// type D
1112 				case BEAM_TYPE_D:
1113 					beam_type_d_move(b);
1114 					break;
1115 
1116 				// type E
1117 				case BEAM_TYPE_E:
1118 					beam_type_e_move(b);
1119 					break;
1120 
1121 				case BEAM_TYPE_F:
1122 					beam_type_f_move(b);
1123 					break;
1124 
1125 				// illegal beam type
1126 				default :
1127 					Int3();
1128 			}
1129 			if (b->subsys != NULL) {
1130 				b->subsys->turret_next_fire_pos = temp;
1131 			}
1132 		}
1133 
1134 		// next
1135 		moveup = GET_NEXT(moveup);
1136 	}
1137 }
1138 
1139 // post-collision time processing for beams
beam_move_all_post()1140 void beam_move_all_post()
1141 {
1142 	beam *moveup;
1143 	beam *next_one;
1144 	int bf_status;
1145 	beam_weapon_info *bwi;
1146 
1147 	// traverse through all active beams
1148 	moveup = GET_FIRST(&Beam_used_list);
1149 	while(moveup != END_OF_LIST(&Beam_used_list)){
1150         bwi = &Weapon_info[moveup->weapon_info_index].b_info;
1151 
1152         // check the status of the beam
1153 		bf_status = beam_ok_to_fire(moveup);
1154 
1155 		// if we're warming up
1156 		if(moveup->warmup_stamp != -1){
1157 			next_one = GET_NEXT(moveup);
1158 
1159 			// should we be stopping?
1160 			if(bf_status < 0){
1161 				beam_delete(moveup);
1162 			} else {
1163 				if (moveup->objp != NULL) {
1164 					// add a muzzle light for the shooter
1165 					beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
1166 				}
1167 
1168 				// if the warming up timestamp has expired, start firing
1169 				if(timestamp_elapsed(moveup->warmup_stamp)){
1170 					// start firing
1171 					if(!beam_start_firing(moveup)){
1172 						beam_delete(moveup);
1173 					}
1174 				}
1175 			}
1176 
1177 			// next
1178 			moveup = next_one;
1179 			continue;
1180 		}
1181 		// if we're warming down
1182 		else if(moveup->warmdown_stamp != -1){
1183 			next_one = GET_NEXT(moveup);
1184 
1185 			// should we be stopping?
1186 			if(bf_status < 0){
1187 				beam_delete(moveup);
1188 			} else {
1189 				if (moveup->objp != NULL) {
1190 					// add a muzzle light for the shooter
1191 					beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
1192 				}
1193 
1194 				// if we're done warming down, the beam is finished
1195 				if(timestamp_elapsed(moveup->warmdown_stamp)){
1196 					beam_delete(moveup);
1197 				}
1198 			}
1199 
1200 			// next
1201 			moveup = next_one;
1202 			continue;
1203 		}
1204 		// otherwise, we're firing away.........
1205 
1206 		if (moveup->objp != NULL) {
1207 			// add a muzzle light for the shooter
1208 			beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
1209 		}
1210 
1211 		// subtract out the life left for the beam
1212 		if(!physics_paused){
1213 			moveup->life_left -= flFrametime;
1214 		}
1215 
1216 		// if we're past the shrink point, start shrinking the beam
1217 		if(moveup->life_left <= (moveup->life_total * bwi->beam_shrink_factor)){
1218 			moveup->flags |= BF_SHRINK;
1219 		}
1220 
1221 		// if we're shrinking the beam
1222 		if(moveup->flags & BF_SHRINK){
1223 			moveup->current_width_factor -= bwi->beam_shrink_pct * flFrametime;
1224 			if(moveup->current_width_factor < 0.1f){
1225 				moveup->current_width_factor = 0.1f;
1226 			}
1227 		}
1228 
1229 		// if we're past the grow point and haven't already finished growing, start growing the beam
1230 		if (moveup->life_left <= (moveup->life_total * bwi->beam_grow_factor) && !(moveup->flags & BF_FINISHED_GROWING)) {
1231 			moveup->flags |= BF_GROW;
1232 		}
1233 
1234 		// if we're growing the beam (but not shrinking yet)
1235 		if ((moveup->flags & BF_GROW) && !(moveup->flags & BF_SHRINK)) {
1236 			moveup->current_width_factor += bwi->beam_grow_pct * flFrametime;
1237 			if (moveup->current_width_factor > 1.0f) { // We've finished growing!
1238 				moveup->current_width_factor = 1.0f;
1239 				moveup->flags &= ~BF_GROW;
1240 				moveup->flags |= BF_FINISHED_GROWING;
1241 			}
1242 		}
1243 
1244 		// add tube light for the beam
1245 		if (moveup->objp != nullptr) {
1246 			if (moveup->type == BEAM_TYPE_D) {
1247 
1248 				//we only use the second variable but we need two pointers to pass.
1249 				int type_d_index, type_d_waiting = 0;
1250 
1251 				beam_type_d_get_status(moveup, &type_d_index, &type_d_waiting);
1252 
1253 				//create a tube light only if we are not waiting between shots
1254 				if (type_d_waiting == 0) {
1255 					beam_add_light(moveup, OBJ_INDEX(moveup->objp), 1, nullptr);
1256 				}
1257 			}
1258 			else
1259 			{
1260 				beam_add_light(moveup, OBJ_INDEX(moveup->objp), 1, nullptr);
1261 			}
1262 		}
1263 
1264 		// deal with ammo/energy for fighter beams
1265 		bool multi_ai = MULTIPLAYER_CLIENT && (moveup->objp != Player_obj);
1266 		bool cheating_player = Weapon_energy_cheat && (moveup->objp == Player_obj);
1267 
1268 		if (moveup->flags & BF_IS_FIGHTER_BEAM && !multi_ai && !cheating_player) {
1269 			ship* shipp = &Ships[moveup->objp->instance];
1270 			weapon_info* wip = &Weapon_info[moveup->weapon_info_index];
1271 
1272 			shipp->weapon_energy -= wip->energy_consumed * flFrametime;
1273 
1274 			if (shipp->weapon_energy < 0.0f)
1275 				shipp->weapon_energy = 0.0f;
1276 		}
1277 
1278 		// stop shooting?
1279 		if(bf_status <= 0){
1280 			next_one = GET_NEXT(moveup);
1281 
1282 			// if beam should abruptly stop
1283 			if(bf_status == -1){
1284 				beam_delete(moveup);
1285 			}
1286 			// if the beam should just power down
1287 			else {
1288 				beam_start_warmdown(moveup);
1289 			}
1290 
1291 			// next beam
1292 			moveup = next_one;
1293 			continue;
1294 		}
1295 
1296 		// increment framecount
1297 		moveup->framecount++;
1298 		// type c weapons live for one frame only
1299 		// done firing, so go into the warmdown phase
1300 		{
1301 			if((moveup->life_left <= 0.0f) &&
1302                (moveup->warmdown_stamp == -1) &&
1303                (moveup->framecount > 1))
1304             {
1305 				beam_start_warmdown(moveup);
1306 
1307 				moveup = GET_NEXT(moveup);
1308 				continue;
1309 			}
1310 		}
1311 
1312 		// handle any collisions which occured collision (will take care of applying damage to all objects which got hit)
1313 		beam_handle_collisions(moveup);
1314 
1315 		// recalculate beam sounds
1316 		beam_recalc_sounds(moveup);
1317 
1318 		// next item
1319 		moveup = GET_NEXT(moveup);
1320 	}
1321 
1322 	// apply all beam lighting
1323 	beam_apply_lighting();
1324 }
1325 
1326 // -----------------------------===========================------------------------------
1327 // BEAM RENDERING FUNCTIONS
1328 // -----------------------------===========================------------------------------
1329 
1330 // render a beam weapon
1331 #define STUFF_VERTICES()	do {\
1332 	verts[0]->texture_position.u = 0.0f;\
1333 	verts[0]->texture_position.v = 0.0f;\
1334 	verts[1]->texture_position.u = 1.0f;\
1335 	verts[1]->texture_position.v = 0.0f;\
1336 	verts[2]->texture_position.u = 1.0f;\
1337 	verts[2]->texture_position.v = 1.0f;\
1338 	verts[3]->texture_position.u = 0.0f;\
1339 	verts[3]->texture_position.v = 1.0f;\
1340 } while(false);
1341 
1342 #define P_VERTICES()		do {\
1343 	for(idx=0; idx<4; idx++){\
1344 		g3_project_vertex(verts[idx]);\
1345 	}\
1346 } while(false);
1347 
beam_render(beam * b,float u_offset)1348 void beam_render(beam *b, float u_offset)
1349 {
1350 	int idx, s_idx;
1351 	vertex h1[4];				// halves of a beam section
1352 	vertex *verts[4] = { &h1[0], &h1[1], &h1[2], &h1[3] };
1353 	vec3d fvec, top1, bottom1, top2, bottom2;
1354 	float scale;
1355 	float u_scale;	// beam tileing -Bobboau
1356 	float length;	// beam tileing -Bobboau
1357 	beam_weapon_section_info *bwsi;
1358 	beam_weapon_info *bwi;
1359 
1360 	memset( h1, 0, sizeof(vertex) * 4 );
1361 
1362 	// bogus weapon info index
1363 	if ( (b == NULL) || (b->weapon_info_index < 0) )
1364 		return;
1365 
1366 	// if the beam start and endpoints are the same
1367 	if ( vm_vec_same(&b->last_start, &b->last_shot) )
1368 		return;
1369 
1370 	// get beam direction
1371 	vm_vec_sub(&fvec, &b->last_shot, &b->last_start);
1372 	vm_vec_normalize_quick(&fvec);
1373 
1374 	// turn off backface culling
1375 	//int cull = gr_set_cull(0);
1376 
1377 	length = vm_vec_dist(&b->last_start, &b->last_shot);					// beam tileing -Bobboau
1378 
1379 	bwi = &Weapon_info[b->weapon_info_index].b_info;
1380 
1381 	// if this beam tracks its own u_offset, use that instead
1382 	if (bwi->flags[Weapon::Beam_Info_Flags::Track_own_texture_tiling]) {
1383 		u_offset = b->u_offset_local;			// the parameter is passed by value so this won't interfere with the u_offset in the calling function
1384 		b->u_offset_local += flFrametime;		// increment *after* we grab the offset so that the first frame will always be at offset=0
1385 	}
1386 
1387 	// draw all sections
1388 	for (s_idx = 0; s_idx < bwi->beam_num_sections; s_idx++) {
1389 		bwsi = &bwi->sections[s_idx];
1390 
1391 		if ( (bwsi->texture.first_frame < 0) || (bwsi->width <= 0.0f) )
1392 			continue;
1393 
1394 		// calculate the beam points
1395 		scale = frand_range(1.0f - bwsi->flicker, 1.0f + bwsi->flicker);
1396 		beam_calc_facing_pts(&top1, &bottom1, &fvec, &b->last_start, bwsi->width * scale * b->current_width_factor, bwsi->z_add);
1397 		beam_calc_facing_pts(&top2, &bottom2, &fvec, &b->last_shot, bwsi->width * scale * scale * b->current_width_factor, bwsi->z_add);
1398 
1399 		g3_transfer_vertex(verts[0], &bottom1);
1400 		g3_transfer_vertex(verts[1], &bottom2);
1401 		g3_transfer_vertex(verts[2], &top2);
1402 		g3_transfer_vertex(verts[3], &top1);
1403 
1404 		P_VERTICES();
1405 		STUFF_VERTICES();		// stuff the beam with creamy goodness (texture coords)
1406 
1407 		if (bwsi->tile_type == 1)
1408 			u_scale = length / (bwsi->width * 0.5f) / bwsi->tile_factor;	// beam tileing, might make a tileing factor in beam index later -Bobboau
1409 		else
1410 			u_scale = bwsi->tile_factor;
1411 
1412 		verts[1]->texture_position.u = (u_scale + (u_offset * bwsi->translation));	// beam tileing -Bobboau
1413 		verts[2]->texture_position.u = (u_scale + (u_offset * bwsi->translation));	// beam tileing -Bobboau
1414 		verts[3]->texture_position.u = (0 + (u_offset * bwsi->translation));
1415 		verts[0]->texture_position.u = (0 + (u_offset * bwsi->translation));
1416 
1417 		float per = 1.0f;
1418 		if (bwi->range)
1419 			per -= length / bwi->range;
1420 
1421 		//this should never happen but, just to be safe
1422 		CLAMP(per, 0.0f, 1.0f);
1423 
1424 		ubyte alpha = (ubyte)(255.0f * per);
1425 
1426 		verts[1]->r = alpha;
1427 		verts[2]->r = alpha;
1428 		verts[1]->g = alpha;
1429 		verts[2]->g = alpha;
1430 		verts[1]->b = alpha;
1431 		verts[2]->b = alpha;
1432 		verts[1]->a = alpha;
1433 		verts[2]->a = alpha;
1434 
1435 		verts[0]->r = 255;
1436 		verts[3]->r = 255;
1437 		verts[0]->g = 255;
1438 		verts[3]->g = 255;
1439 		verts[0]->b = 255;
1440 		verts[3]->b = 255;
1441 		verts[0]->a = 255;
1442 		verts[3]->a = 255;
1443 
1444 		// set the right texture with additive alpha, and draw the poly
1445 		int framenum = 0;
1446 
1447 		if (bwsi->texture.num_frames > 1) {
1448 			b->beam_section_frame[s_idx] += flFrametime;
1449 
1450 			framenum = bm_get_anim_frame(bwsi->texture.first_frame, b->beam_section_frame[s_idx], bwsi->texture.total_time, true);
1451 		}
1452 
1453 		float fade = 0.9999f;
1454 
1455 		if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_beams) {
1456 			vec3d nearest;
1457 			int result = vm_vec_dist_to_line(&Eye_position, &b->last_start, &b->last_shot, &nearest, nullptr);
1458 			if (result == 1)
1459 				nearest = b->last_shot;
1460 			if (result == -1)
1461 				nearest = b->last_start;
1462 
1463 			fade *= neb2_get_fog_visibility(&nearest, NEB_FOG_VISIBILITY_MULT_BEAM(b->beam_light_width));
1464 		}
1465 
1466 		material material_params;
1467 		material_set_unlit_emissive(&material_params, bwsi->texture.first_frame + framenum, fade, 2.0f);
1468 		g3_render_primitives_colored_textured(&material_params, h1, 4, PRIM_TYPE_TRIFAN, false);
1469 	}
1470 
1471 	// turn backface culling back on
1472 	//gr_set_cull(cull);
1473 }
1474 
1475 // generate particles for the muzzle glow
1476 int hack_time = 100;
1477 DCF(h_time, "Sets the hack time for beam muzzle glow (Default is 100)")
1478 {
1479 	dc_stuff_int(&hack_time);
1480 }
1481 
beam_generate_muzzle_particles(beam * b)1482 void beam_generate_muzzle_particles(beam *b)
1483 {
1484 	int particle_count;
1485 	int idx;
1486 	weapon_info *wip;
1487 	vec3d turret_norm, turret_pos, particle_pos, particle_dir;
1488 	matrix m;
1489 
1490 	// if our hack stamp has expired
1491 	if(!((b->Beam_muzzle_stamp == -1) || timestamp_elapsed(b->Beam_muzzle_stamp))){
1492 		return;
1493 	}
1494 
1495 	// never generate anything past about 1/5 of the beam fire time
1496 	if(b->warmup_stamp == -1){
1497 		return;
1498 	}
1499 
1500 	// get weapon info
1501 	wip = &Weapon_info[b->weapon_info_index];
1502 
1503 	// no specified particle for this beam weapon
1504 	if (wip->b_info.beam_particle_ani.first_frame < 0)
1505 		return;
1506 
1507 
1508 	// reset the hack stamp
1509 	b->Beam_muzzle_stamp = timestamp(hack_time);
1510 
1511 	// randomly generate 10 to 20 particles
1512 	particle_count = (int)frand_range(0.0f, (float)wip->b_info.beam_particle_count);
1513 
1514 	// get turret info - position and normal
1515 	turret_pos = b->last_start;
1516 	if (b->subsys != NULL) {
1517 		turret_norm = b->subsys->system_info->turret_norm;
1518 	} else {
1519 		vm_vec_normalized_dir(&turret_norm, &b->last_shot, &b->last_start);
1520 	}
1521 
1522 	// randomly perturb a vector within a cone around the normal
1523 	vm_vector_2_matrix(&m, &turret_norm, NULL, NULL);
1524 	for(idx=0; idx<particle_count; idx++){
1525 		// get a random point in the cone
1526 		vm_vec_random_cone(&particle_dir, &turret_norm, wip->b_info.beam_particle_angle, &m);
1527 		vm_vec_scale_add(&particle_pos, &turret_pos, &particle_dir, wip->b_info.beam_muzzle_radius * frand_range(0.75f, 0.9f));
1528 
1529 		// now generate some interesting values for the particle
1530 		float p_time_ref = wip->b_info.beam_life + ((float)wip->b_info.beam_warmup / 1000.0f);
1531 		float p_life = frand_range(p_time_ref * 0.5f, p_time_ref * 0.7f);
1532 		float p_vel = (wip->b_info.beam_muzzle_radius / p_life) * frand_range(0.85f, 1.2f);
1533 		vm_vec_scale(&particle_dir, -p_vel);
1534 		if (b->objp != NULL) {
1535 			vm_vec_add2(&particle_dir, &b->objp->phys_info.vel);	//move along with our parent
1536 		}
1537 
1538 		particle::particle_info pinfo;
1539 		pinfo.pos = particle_pos;
1540 		pinfo.vel = particle_dir;
1541 		pinfo.lifetime = p_life;
1542 		pinfo.attached_objnum = -1;
1543 		pinfo.attached_sig = 0;
1544 		pinfo.rad = wip->b_info.beam_particle_radius;
1545 		pinfo.reverse = 1;
1546 		pinfo.type = particle::PARTICLE_BITMAP;
1547 		pinfo.optional_data = wip->b_info.beam_particle_ani.first_frame;
1548 		particle::create(&pinfo);
1549 	}
1550 }
1551 
get_muzzle_glow_alpha(beam * b)1552 static float get_muzzle_glow_alpha(beam* b)
1553 {
1554 	float dist;
1555 	float alpha = 0.8f;
1556 
1557 	const float inner_radius = 15.0f;
1558 	const float magic_num = 2.75f;
1559 
1560 	// determine what alpha to draw this bitmap with
1561 	// higher alpha the closer the bitmap gets to the eye
1562 	dist = vm_vec_dist_quick(&Eye_position, &b->last_start);
1563 
1564 	// if the point is inside the inner radius, alpha is based on distance to the player's eye,
1565 	// becoming more transparent as it gets close
1566 	if (dist <= inner_radius) {
1567 		// alpha per meter between the magic # and the inner radius
1568 		alpha /= (inner_radius - magic_num);
1569 
1570 		// above value times the # of meters away we are
1571 		alpha *= (dist - magic_num);
1572 		if (alpha < 0.005f)
1573 			return 0.0f;
1574 	}
1575 
1576 	if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_beams) {
1577 		alpha *= neb2_get_fog_visibility(&b->last_start, NEB_FOG_VISIBILITY_MULT_B_MUZZLE(b->beam_light_width));
1578 	}
1579 
1580 	return alpha;
1581 }
1582 
1583 // render the muzzle glow for a beam weapon
beam_render_muzzle_glow(beam * b)1584 void beam_render_muzzle_glow(beam *b)
1585 {
1586 	vertex pt;
1587 	weapon_info *wip = &Weapon_info[b->weapon_info_index];
1588 	beam_weapon_info *bwi = &wip->b_info;
1589 	float rad, pct, rand_val;
1590 	pt.flags = 0;    // avoid potential read of uninit var
1591 
1592 	// if we don't have a glow bitmap
1593 	if (bwi->beam_glow.first_frame < 0)
1594 		return;
1595 
1596 	// don't show the muzzle glow for players in the cockpit unless show_ship_model is on, provided Render_player_mflash isn't on
1597 	bool in_cockpit_view = (Viewer_mode & (VM_EXTERNAL | VM_CHASE | VM_OTHER_SHIP | VM_WARP_CHASE)) == 0;
1598 	bool player_show_ship_model = b->objp == Player_obj && Ship_info[Ships[b->objp->instance].ship_info_index].flags[Ship::Info_Flags::Show_ship_model];
1599 	if ((b->flags & BF_IS_FIGHTER_BEAM) && (b->objp == Player_obj && !Render_player_mflash && in_cockpit_view && !player_show_ship_model)) {
1600 		return;
1601 	}
1602 
1603 	// if the beam is warming up, scale the glow
1604 	if (b->warmup_stamp != -1) {
1605 		// get warmup pct
1606 		pct = BEAM_WARMUP_PCT(b);
1607 		rand_val = 1.0f;
1608 	} else
1609 	// if the beam is warming down
1610 	if (b->warmdown_stamp != -1) {
1611 		// get warmup pct
1612 		pct = 1.0f - BEAM_WARMDOWN_PCT(b);
1613 		rand_val = 1.0f;
1614 	}
1615 	// otherwise the beam is really firing
1616 	else {
1617 		pct = 1.0f;
1618 		rand_val = frand_range(0.90f, 1.0f);
1619 	}
1620 
1621 	rad = wip->b_info.beam_muzzle_radius * pct * rand_val;
1622 
1623 	// don't bother trying to draw if there is no radius
1624 	if (rad <= 0.0f)
1625 		return;
1626 
1627 	float alpha = get_muzzle_glow_alpha(b);
1628 
1629 	if (alpha <= 0.0f)
1630 		return;
1631 
1632 	if (bwi->directional_glow == true){
1633 		vertex h1[4];
1634 		vertex *verts[4] = { &h1[0], &h1[1], &h1[2], &h1[3] };
1635 		vec3d fvec, top1, top2, bottom1, bottom2, sub1, sub2, start, end;
1636 		int idx;
1637 		float g_length = bwi->glow_length * pct * rand_val;
1638 
1639 		vm_vec_sub(&fvec, &b->last_shot, &b->last_start);
1640 		vm_vec_normalize_quick(&fvec);
1641 
1642 		/* (DahBlount)
1643 		If the glow_length is less than the diameter of the muzzle glow
1644 		we need to account for that by placing the start and end distances
1645 		such that the glow is centered on the firing point of the turret.
1646 		There was actually some oversight here when developing the directional glow feature
1647 		and any refactoring of it will require some more complex parameters for glow placement.
1648 		*/
1649 		if (bwi->glow_length >= 2.0f*rad) {
1650 			vm_vec_copy_scale(&sub1, &fvec, rad);
1651 			vm_vec_sub(&start, &b->last_start, &sub1);
1652 			vm_vec_copy_scale(&sub2, &fvec, g_length);
1653 			vm_vec_add(&end, &start, &sub2);
1654 		} else {
1655 			vm_vec_copy_scale(&sub1, &fvec, 0.5f*g_length);
1656 			vm_vec_sub(&start, &b->last_start, &sub1);
1657 			vm_vec_add(&end, &b->last_start, &sub1);
1658 		}
1659 
1660 		beam_calc_facing_pts(&top1, &bottom1, &fvec, &start, rad, 1.0f);
1661 		beam_calc_facing_pts(&top2, &bottom2, &fvec, &end, rad, 1.0f);
1662 
1663 		g3_transfer_vertex(verts[0], &bottom1);
1664 		g3_transfer_vertex(verts[1], &bottom2);
1665 		g3_transfer_vertex(verts[2], &top2);
1666 		g3_transfer_vertex(verts[3], &top1);
1667 
1668 		P_VERTICES();
1669 		STUFF_VERTICES();
1670 
1671 		verts[0]->r = 255;
1672 		verts[1]->r = 255;
1673 		verts[2]->r = 255;
1674 		verts[3]->r = 255;
1675 		verts[0]->g = 255;
1676 		verts[1]->g = 255;
1677 		verts[2]->g = 255;
1678 		verts[3]->g = 255;
1679 		verts[0]->b = 255;
1680 		verts[1]->b = 255;
1681 		verts[2]->b = 255;
1682 		verts[3]->b = 255;
1683 		verts[0]->a = 255;
1684 		verts[1]->a = 255;
1685 		verts[2]->a = 255;
1686 		verts[3]->a = 255;
1687 
1688 		int framenum = 0;
1689 
1690 		if ( bwi->beam_glow.num_frames > 1 ) {
1691 			b->beam_glow_frame += flFrametime;
1692 
1693 			framenum = bm_get_anim_frame(bwi->beam_glow.first_frame, b->beam_glow_frame, bwi->beam_glow.total_time, true);
1694 		}
1695 
1696 		//gr_set_bitmap(bwi->beam_glow.first_frame + framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha * pct);
1697 
1698 		// draw a poly
1699 		//g3_draw_poly(4, verts, TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT | TMAP_HTL_3D_UNLIT);
1700 
1701 		material material_info;
1702 		material_set_unlit_emissive(&material_info, bwi->beam_glow.first_frame + framenum, alpha * pct, 2.0f);
1703 		g3_render_primitives_textured(&material_info, h1, 4, PRIM_TYPE_TRIFAN, false);
1704 
1705 	} else {
1706 
1707 		// draw the bitmap
1708 		g3_transfer_vertex(&pt, &b->last_start);
1709 
1710 		int framenum = 0;
1711 
1712 		if ( bwi->beam_glow.num_frames > 1 ) {
1713 			b->beam_glow_frame += flFrametime;
1714 
1715 			framenum = bm_get_anim_frame(bwi->beam_glow.first_frame, b->beam_glow_frame, bwi->beam_glow.total_time, true);
1716 		}
1717 
1718 		//gr_set_bitmap(bwi->beam_glow.first_frame + framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha * pct);
1719 
1720 		// draw 1 bitmap
1721 		//g3_draw_bitmap(&pt, 0, rad, tmap_flags);
1722 		material mat_params;
1723 		material_set_unlit_emissive(&mat_params, bwi->beam_glow.first_frame + framenum, alpha * pct, 2.0f);
1724 		g3_render_rect_screen_aligned(&mat_params, &pt, 0, rad, 0.0f);
1725 
1726 		// maybe draw more
1727 		if ( pct > 0.3f ) {
1728 			//g3_draw_bitmap(&pt, 0, rad * 0.75f, tmap_flags, rad * 0.25f);
1729 			g3_render_rect_screen_aligned(&mat_params, &pt, 0, rad * 0.75f, rad * 0.25f);
1730 		}
1731 
1732 		if ( pct > 0.5f ) {
1733 			//g3_draw_bitmap(&pt, 0, rad * 0.45f, tmap_flags, rad * 0.55f);
1734 			g3_render_rect_screen_aligned(&mat_params, &pt, 0, rad * 0.45f, rad * 0.55f);
1735 		}
1736 
1737 		if ( pct > 0.7f ) {
1738 			//g3_draw_bitmap(&pt, 0, rad * 0.25f, tmap_flags, rad * 0.75f);
1739 			g3_render_rect_screen_aligned(&mat_params, &pt, 0, rad * 0.25f, rad * 0.75f);
1740 		}
1741 	}
1742 }
1743 
1744 // render all beam weapons
beam_render_all()1745 void beam_render_all()
1746 {
1747 	GR_DEBUG_SCOPE("Render Beams");
1748 	TRACE_SCOPE(tracing::DrawBeams);
1749 
1750 	beam *moveup;
1751 
1752 	// moves the U value of texture coods in beams if desired-Bobboau
1753 	static float u_offset = 0.0f;
1754 	u_offset += flFrametime;
1755 
1756 	// traverse through all active beams
1757 	moveup = GET_FIRST(&Beam_used_list);
1758 	while ( moveup != END_OF_LIST(&Beam_used_list) ) {
1759 		// each beam type renders a little bit differently
1760 		if ( (moveup->warmup_stamp == -1) && (moveup->warmdown_stamp == -1) && !(moveup->flags & BF_SAFETY) ) {
1761 			// HACK -  if this is the first frame the beam is firing, don't render it
1762             if (moveup->framecount <= 0) {
1763 				moveup->u_offset_local = 0;
1764 				moveup = GET_NEXT(moveup);
1765 				continue;
1766 			}
1767 
1768 			// render the beam itself
1769 			Assert(moveup->weapon_info_index >= 0);
1770 
1771 			if (moveup->weapon_info_index < 0) {
1772 				moveup = GET_NEXT(moveup);
1773 				continue;
1774 			}
1775 
1776 			beam_render(moveup, u_offset);
1777 		}
1778 
1779 		// render the muzzle glow
1780 		beam_render_muzzle_glow(moveup);
1781 
1782 		// maybe generate some muzzle particles
1783 		beam_generate_muzzle_particles(moveup);
1784 
1785 		// next item
1786 		moveup = GET_NEXT(moveup);
1787 	}
1788 }
1789 
1790 // output top and bottom vectors
1791 // fvec == forward vector (eye viewpoint basically. in world coords)
1792 // pos == world coordinate of the point we're calculating "around"
1793 // w == width of the diff between top and bottom around pos
beam_calc_facing_pts(vec3d * top,vec3d * bot,vec3d * fvec,vec3d * pos,float w,float)1794 void beam_calc_facing_pts( vec3d *top, vec3d *bot, vec3d *fvec, vec3d *pos, float w, float  /*z_add*/ )
1795 {
1796 	vec3d uvec, rvec;
1797 	vec3d temp;
1798 
1799 	temp = *pos;
1800 
1801 	vm_vec_sub( &rvec, &Eye_position, &temp );
1802 	vm_vec_normalize( &rvec );
1803 
1804 	vm_vec_cross(&uvec,fvec,&rvec);
1805 	// VECMAT-ERROR: NULL VEC3D (value of, fvec == rvec)
1806 	vm_vec_normalize_safe(&uvec);
1807 
1808 	vm_vec_scale_add( top, &temp, &uvec, w * 0.5f );
1809 	vm_vec_scale_add( bot, &temp, &uvec, -w * 0.5f );
1810 }
1811 
1812 // light scale factor
1813 float blight = 25.5f;
1814 DCF(blight, "Sets the beam light scale factor (Default is 25.5f)")
1815 {
1816 	dc_stuff_float(&blight);
1817 }
1818 
1819 // call to add a light source to a small object
beam_add_light_small(beam * bm,object * objp,vec3d * pt_override=NULL)1820 void beam_add_light_small(beam *bm, object *objp, vec3d *pt_override = NULL)
1821 {
1822 	weapon_info *wip;
1823 	beam_weapon_info *bwi;
1824 	float noise;
1825 
1826 	// no lighting
1827 	if(Detail.lighting < 2){
1828 		return;
1829 	}
1830 
1831 	// sanity
1832 	Assert(bm != nullptr);
1833 	if(bm == nullptr){
1834 		return;
1835 	}
1836 	Assert(objp != nullptr);
1837 	if(objp == nullptr){
1838 		return;
1839 	}
1840 	Assert(bm->weapon_info_index >= 0);
1841 	wip = &Weapon_info[bm->weapon_info_index];
1842 	bwi = &wip->b_info;
1843 
1844 	// some noise
1845 	if ( (bm->warmup_stamp < 0) && (bm->warmdown_stamp < 0) ) // disable noise when warming up or down
1846 		noise = frand_range(1.0f - bwi->sections[0].flicker, 1.0f + bwi->sections[0].flicker);
1847 	else
1848 		noise = 1.0f;
1849 
1850 	// get the width of the beam
1851 	float light_rad = bm->beam_light_width * bm->current_width_factor * blight * noise;
1852 
1853 	// nearest point on the beam, and its distance to the ship
1854 	vec3d near_pt;
1855 	if(pt_override == NULL){
1856 		float dist;
1857 		vm_vec_dist_to_line(&objp->pos, &bm->last_start, &bm->last_shot, &near_pt, &dist);
1858 		if(dist > light_rad){
1859 			return;
1860 		}
1861 	} else {
1862 		near_pt = *pt_override;
1863 	}
1864 
1865 	// average rgb of the beam
1866 	float fr = (float)wip->laser_color_1.red / 255.0f;
1867 	float fg = (float)wip->laser_color_1.green / 255.0f;
1868 	float fb = (float)wip->laser_color_1.blue / 255.0f;
1869 
1870 	float pct = 0.0f;
1871 
1872 	if (bm->warmup_stamp != -1) {	// calculate muzzle light intensity
1873 		// get warmup pct
1874 		pct = BEAM_WARMUP_PCT(bm)*0.5f;
1875 	} else
1876 	// if the beam is warming down
1877 	if (bm->warmdown_stamp != -1) {
1878 		// get warmup pct
1879 		pct = MAX(1.0f - BEAM_WARMDOWN_PCT(bm)*1.3f,0.0f)*0.5f;
1880 	}
1881 	// otherwise the beam is really firing
1882 	else {
1883 		pct = 1.0f;
1884 	}
1885 	// add a unique light
1886 	light_add_point_unique(&near_pt, light_rad * 0.0001f, light_rad, pct, fr, fg, fb, OBJ_INDEX(objp));
1887 }
1888 
1889 // call to add a light source to a large object
beam_add_light_large(beam * bm,object * objp,vec3d * pt0,vec3d * pt1)1890 void beam_add_light_large(beam *bm, object *objp, vec3d *pt0, vec3d *pt1)
1891 {
1892 	weapon_info *wip;
1893 	beam_weapon_info *bwi;
1894 	float noise;
1895 
1896 	// no lighting
1897 	if(Detail.lighting < 2){
1898 		return;
1899 	}
1900 
1901 	// sanity
1902 	Assert(bm != NULL);
1903 	if(bm == NULL){
1904 		return;
1905 	}
1906 	Assert(objp != NULL);
1907 	if(objp == NULL){
1908 		return;
1909 	}
1910 	Assert(bm->weapon_info_index >= 0);
1911 	wip = &Weapon_info[bm->weapon_info_index];
1912 	bwi = &wip->b_info;
1913 
1914 	// some noise
1915 	noise = frand_range(1.0f - bwi->sections[0].flicker, 1.0f + bwi->sections[0].flicker);
1916 
1917 	// width of the beam
1918 	float light_rad = bm->beam_light_width * bm->current_width_factor * blight * noise;
1919 
1920 	// average rgb of the beam
1921 	float fr = (float)wip->laser_color_1.red / 255.0f;
1922 	float fg = (float)wip->laser_color_1.green / 255.0f;
1923 	float fb = (float)wip->laser_color_1.blue / 255.0f;
1924 
1925 	light_add_tube(pt0, pt1, 1.0f, light_rad, 1.0f * noise, fr, fg, fb, OBJ_INDEX(objp));
1926 }
1927 
1928 // mark an object as being lit
beam_add_light(beam * b,int objnum,int source,vec3d * c_point)1929 void beam_add_light(beam *b, int objnum, int source, vec3d *c_point)
1930 {
1931 	beam_light_info *l;
1932 
1933 	// if we're out of light slots!
1934 	if(Beam_light_count >= MAX_BEAM_LIGHT_INFO){
1935 		return;
1936 	}
1937 
1938 	// otherwise add it
1939 	l = &Beam_lights[Beam_light_count++];
1940 	l->bm = b;
1941 	l->objnum = objnum;
1942 	l->source = (ubyte)source;
1943 
1944 	// only type 2 lights (from collisions) need a collision point
1945 	if(c_point != NULL){
1946 		l->c_point = *c_point;
1947 	} else {
1948 		Assert(source != 2);
1949 		if(source == 2){
1950 			Beam_light_count--;
1951 		}
1952 	}
1953 }
1954 
1955 // apply lighting from any beams
beam_apply_lighting()1956 void beam_apply_lighting()
1957 {
1958 	int idx;
1959 	beam_light_info *l;
1960 	vec3d pt, dir;
1961 	beam_weapon_info *bwi;
1962 
1963 	// convert all beam lights into real lights
1964 	for(idx=0; idx<Beam_light_count; idx++){
1965 		// get the light
1966 		l = &Beam_lights[idx];
1967 
1968 		// bad object
1969 		if((l->objnum < 0) || (l->objnum >= MAX_OBJECTS) || (l->bm == NULL)){
1970 			continue;
1971 		}
1972 
1973 		bwi = &Weapon_info[l->bm->weapon_info_index].b_info;
1974 
1975 		// different light types
1976 		switch(l->source){
1977 		// from the muzzle of the gun
1978 		case 0:
1979 			// a few meters in from the of muzzle
1980 			vm_vec_sub(&dir, &l->bm->last_start, &l->bm->last_shot);
1981 			vm_vec_normalize_quick(&dir);
1982 			vm_vec_scale(&dir, -0.8f);  // TODO: This probably needs to *not* be stupid. -taylor
1983 			vm_vec_scale_add(&pt, &l->bm->last_start, &dir, bwi->beam_muzzle_radius * 5.0f);
1984 
1985 			beam_add_light_small(l->bm, &Objects[l->objnum], &pt);
1986 			break;
1987 
1988 		// from the beam passing by
1989 		case 1:
1990 			Assert( Objects[l->objnum].instance >= 0 );
1991 			// Valathil: Everyone gets tube lights now
1992 			beam_add_light_large(l->bm, &Objects[l->objnum], &l->bm->last_start, &l->bm->last_shot);
1993 			break;
1994 
1995 		// from a collision
1996 		case 2:
1997 			// Valathil: Dont render impact lights for shaders, handled by tube lighting
1998 			break;
1999 		}
2000 	}
2001 }
2002 
2003 // -----------------------------===========================------------------------------
2004 // BEAM BOOKKEEPING FUNCTIONS
2005 // -----------------------------===========================------------------------------
2006 
2007 // delete a beam
beam_delete(beam * b)2008 void beam_delete(beam *b)
2009 {
2010 	// remove from active list and put on free list
2011 	list_remove(&Beam_used_list, b);
2012 	list_append(&Beam_free_list, b);
2013 
2014 	// delete our associated object
2015 	if(b->objnum >= 0){
2016 		obj_delete(b->objnum);
2017 	}
2018 	b->objnum = -1;
2019 
2020 	// kill the beam looping sound
2021 	if (b->beam_sound_loop.isValid()) {
2022 		snd_stop(b->beam_sound_loop);
2023 		b->beam_sound_loop = sound_handle::invalid();
2024 	}
2025 
2026 	// handle model animation reversal (closing)
2027 	// (beam animations should end pretty much immediately - taylor)
2028     if ((b->subsys) &&
2029         (b->subsys->turret_animation_position == MA_POS_READY))
2030     {
2031         b->subsys->turret_animation_done_time = timestamp(50);
2032     }
2033 
2034 	// subtract one
2035 	Beam_count--;
2036 	Assert(Beam_count >= 0);
2037 	nprintf(("Beam", "Recycled beam (%d beams remaining)\n", Beam_count));
2038 }
2039 
2040 // given an object, return its model num
beam_get_model(object * objp)2041 int beam_get_model(object *objp)
2042 {
2043 	int pof;
2044 
2045 	if (objp == NULL) {
2046 		return -1;
2047 	}
2048 
2049 	Assert(objp->instance >= 0);
2050 	if(objp->instance < 0){
2051 		return -1;
2052 	}
2053 
2054 	switch(objp->type){
2055 	case OBJ_SHIP:
2056 		return Ship_info[Ships[objp->instance].ship_info_index].model_num;
2057 
2058 	case OBJ_WEAPON:
2059 		Assert(Weapons[objp->instance].weapon_info_index >= 0);
2060 		if(Weapons[objp->instance].weapon_info_index < 0){
2061 			return -1;
2062 		}
2063 		return Weapon_info[Weapons[objp->instance].weapon_info_index].model_num;
2064 
2065 	case OBJ_DEBRIS:
2066 		Assert(Debris[objp->instance].is_hull);
2067 		if(!Debris[objp->instance].is_hull){
2068 			return -1;
2069 		}
2070 		return Debris[objp->instance].model_num;
2071 
2072 	case OBJ_ASTEROID:
2073 		pof = Asteroids[objp->instance].asteroid_subtype;
2074 		Assert(Asteroids[objp->instance].asteroid_type >= 0);
2075 		if(Asteroids[objp->instance].asteroid_type < 0){
2076 			return -1;
2077 		}
2078 		return Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[pof];
2079 
2080 	default:
2081 		// this shouldn't happen too often
2082 		mprintf(("Beam couldn't find a good object model/type!! (%d)\n", objp->type));
2083 		return -1;
2084 	}
2085 }
2086 
2087 // start the warmup phase for the beam
beam_start_warmup(beam * b)2088 void beam_start_warmup(beam *b)
2089 {
2090 	// set the warmup stamp
2091 	b->warmup_stamp = timestamp(Weapon_info[b->weapon_info_index].b_info.beam_warmup);
2092 
2093 	// start playing warmup sound
2094 	if(!(Game_mode & GM_STANDALONE_SERVER) && (Weapon_info[b->weapon_info_index].b_info.beam_warmup_sound.isValid())){
2095 		snd_play_3d(gamesnd_get_game_sound(Weapon_info[b->weapon_info_index].b_info.beam_warmup_sound), &b->last_start, &View_position);
2096 	}
2097 }
2098 
2099 // start the firing phase for the beam, return 0 if the beam failed to start, and should be deleted altogether
beam_start_firing(beam * b)2100 int beam_start_firing(beam *b)
2101 {
2102 	// kill the warmup stamp so the rest of the code knows its firing
2103 	b->warmup_stamp = -1;
2104 
2105 	// any special stuff for each weapon type
2106 	switch(b->type){
2107 	// re-aim type A and D beam weapons here, otherwise they tend to miss
2108 	case BEAM_TYPE_A:
2109 	case BEAM_TYPE_D:
2110 		beam_aim(b);
2111 		break;
2112 
2113 	case BEAM_TYPE_B:
2114 		break;
2115 
2116 	case BEAM_TYPE_C:
2117 		break;
2118 
2119 	case BEAM_TYPE_E:
2120 		break;
2121 
2122 	case BEAM_TYPE_F:
2123 		break;
2124 
2125 	default:
2126 		Int3();
2127 	}
2128 
2129 	// determine if we can legitimately start firing, or if we need to take other action
2130 	switch(beam_ok_to_fire(b)){
2131 	case -1 :
2132 		return 0;
2133 
2134 	case 0 :
2135 		beam_start_warmdown(b);
2136 		return 1;
2137 	}
2138 
2139 	weapon_info* wip = &Weapon_info[b->weapon_info_index];
2140 
2141 	// start the beam firing sound now, if we haven't already
2142 	if ((!b->beam_sound_loop.isValid()) && (Weapon_info[b->weapon_info_index].b_info.beam_loop_sound.isValid())) {
2143 		b->beam_sound_loop = snd_play_3d(gamesnd_get_game_sound(Weapon_info[b->weapon_info_index].b_info.beam_loop_sound), &b->last_start, &View_position, 0.0f, NULL, 1, 1.0, SND_PRIORITY_SINGLE_INSTANCE, NULL, 1.0f, 1);
2144 	}
2145 
2146 	// "shot" sound
2147 	if (Weapon_info[b->weapon_info_index].launch_snd.isValid())
2148 		snd_play_3d(gamesnd_get_game_sound(Weapon_info[b->weapon_info_index].launch_snd), &b->last_start, &View_position);
2149 
2150 	// if this is a fighter ballistic beam, always take at least one ammo to start with
2151 	if (b->flags & BF_IS_FIGHTER_BEAM && wip->wi_flags[Weapon::Info_Flags::Ballistic])
2152 		Ships[b->objp->instance].weapons.primary_bank_ammo[b->bank]--;
2153 
2154 	if (Script_system.IsActiveAction(CHA_BEAMFIRE)) {
2155 		Script_system.SetHookObjects(3, "Beam", &Objects[b->objnum], "User", b->objp, "Target", b->target);
2156 		Script_system.RunCondition(CHA_BEAMFIRE, &Objects[b->objnum], b->weapon_info_index);
2157 		Script_system.RemHookVars({"Beam", "User", "Target"});
2158 	}
2159 
2160 	// success
2161 	return 1;
2162 }
2163 
2164 // start the warmdown phase for the beam
beam_start_warmdown(beam * b)2165 void beam_start_warmdown(beam *b)
2166 {
2167 	// timestamp
2168 	b->warmdown_stamp = timestamp(Weapon_info[b->weapon_info_index].b_info.beam_warmdown);
2169 
2170 	// start the warmdown sound
2171 	if(Weapon_info[b->weapon_info_index].b_info.beam_warmdown_sound.isValid()){
2172 		snd_play_3d(gamesnd_get_game_sound(Weapon_info[b->weapon_info_index].b_info.beam_warmdown_sound), &b->last_start, &View_position);
2173 	}
2174 
2175 	// kill the beam looping sound
2176 	if (b->beam_sound_loop.isValid()) {
2177 		snd_stop(b->beam_sound_loop);
2178 		b->beam_sound_loop = sound_handle::invalid();
2179 	}
2180 
2181 	if (b->subsys != nullptr) {
2182 		// Starts the warmdown program if it exists
2183 		b->subsys->system_info->beam_warmdown_program.start(b->objp,
2184 			&vmd_zero_vector,
2185 			&vmd_identity_matrix,
2186 			b->subsys->system_info->subobj_num);
2187 	}
2188 }
2189 
2190 // recalculate beam sounds (looping sounds relative to the player)
beam_recalc_sounds(beam * b)2191 void beam_recalc_sounds(beam *b)
2192 {
2193 	beam_weapon_info *bwi;
2194 	vec3d pos;
2195 
2196 	Assert(b->weapon_info_index >= 0);
2197 	if(b->weapon_info_index < 0){
2198 		return;
2199 	}
2200 	bwi = &Weapon_info[b->weapon_info_index].b_info;
2201 
2202 	// update the sound position relative to the player
2203 	if (b->beam_sound_loop.isValid()) {
2204 		// get the point closest to the player's viewing position
2205 		switch(vm_vec_dist_to_line(&View_position, &b->last_start, &b->last_shot, &pos, NULL)){
2206 		// behind the beam, so use the start pos
2207 		case -1:
2208 			pos = b->last_start;
2209 			break;
2210 
2211 		// use the closest point
2212 		case 0:
2213 			// already calculated in vm_vec_dist_to_line(...)
2214 			break;
2215 
2216 		// past the beam, so use the shot pos
2217 		case 1:
2218 			pos = b->last_shot;
2219 			break;
2220 		}
2221 
2222 		snd_update_3d_pos(b->beam_sound_loop, gamesnd_get_game_sound(bwi->beam_loop_sound), &pos);
2223 	}
2224 }
2225 
2226 
2227 // -----------------------------===========================------------------------------
2228 // BEAM AIMING FUNCTIONS
2229 // -----------------------------===========================------------------------------
2230 
2231 // fills in binfo
beam_get_binfo(beam * b,float accuracy,int num_shots,int burst_seed,float burst_shot_rotation,float per_burst_shot_rotation)2232 void beam_get_binfo(beam *b, float accuracy, int num_shots, int burst_seed, float burst_shot_rotation, float per_burst_shot_rotation)
2233 {
2234 	vec3d p2;
2235 	int model_num, idx;
2236 	vec3d pos1, pos2;
2237 	vec3d turret_point, turret_norm;
2238 	beam_weapon_info *bwi;
2239 	float miss_factor;
2240 
2241 	if (b->flags & BF_IS_FIGHTER_BEAM) {
2242 		vm_vec_unrotate(&turret_point, &b->local_fire_postion, &b->objp->orient);
2243 		turret_point += b->objp->pos;
2244 		turret_norm = b->objp->orient.vec.fvec;
2245 	} else if (b->subsys != nullptr) {
2246 		int temp = b->subsys->turret_next_fire_pos;
2247 
2248 		b->subsys->turret_next_fire_pos = b->firingpoint;
2249 
2250 		// where the shot is originating from (b->last_start gets filled in)
2251 		beam_get_global_turret_gun_info(b->objp, b->subsys, &turret_point, &turret_norm, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
2252 
2253 		b->subsys->turret_next_fire_pos = temp;
2254 	} else {
2255 		turret_point = b->last_start;
2256 		if (b->flags & BF_TARGETING_COORDS) {
2257 			p2 = b->target_pos1;
2258 		} else {
2259 			p2 = b->target->pos;
2260 		}
2261 		vm_vec_normalized_dir(&turret_norm, &p2, &turret_point);
2262 	}
2263 
2264 	// get a model # to work with
2265 	model_num = beam_get_model(b->target);
2266 	if ((model_num < 0) && !(b->flags & BF_TARGETING_COORDS)) {
2267 		return;
2268 	}
2269 
2270 	// get beam weapon info
2271 	Assert(b->weapon_info_index >= 0);
2272 	if(b->weapon_info_index < 0){
2273 		return;
2274 	}
2275 	bwi = &Weapon_info[b->weapon_info_index].b_info;
2276 
2277 	// stuff num shots even though its only used for type D weapons
2278 	b->binfo.shot_count = (ubyte)num_shots;
2279 	if(b->binfo.shot_count > MAX_BEAM_SHOTS){
2280 		b->binfo.shot_count = MAX_BEAM_SHOTS;
2281 	}
2282 
2283 	int seed = bwi->flags[Weapon::Beam_Info_Flags::Burst_share_random] ? burst_seed : Random::next();
2284 
2285 	// generate the proper amount of directional vectors
2286 	switch(b->type){
2287 	// pick an accuracy. beam will be properly aimed at actual fire time
2288 	case BEAM_TYPE_A:
2289 		// determine the miss factor
2290 		Assert(Game_skill_level >= 0 && Game_skill_level < NUM_SKILL_LEVELS);
2291 		Assert(b->team >= 0 && b->team < Num_iffs);
2292 		miss_factor = bwi->beam_iff_miss_factor[b->team][Game_skill_level];
2293 
2294 		// all we will do is decide whether or not we will hit - type A beam weapons are re-aimed immediately before firing
2295 		b->binfo.shot_aim[0] = frand_range(0.0f, 1.0f + miss_factor * accuracy);
2296 		b->binfo.shot_count = 1;
2297 
2298 		if (b->flags & BF_TARGETING_COORDS) {
2299 			// these aren't used for type A beams, so zero them out
2300 			vm_vec_zero(&b->binfo.dir_a);
2301 			vm_vec_zero(&b->binfo.dir_b);
2302 		} else {
2303 			// get random model points, this is useful for big ships, because we never miss when shooting at them
2304 			submodel_get_two_random_points_better(model_num, 0, &b->binfo.dir_a, &b->binfo.dir_b, seed);
2305 		}
2306 		break;
2307 
2308 	// just 2 points in the "slash"
2309 	case BEAM_TYPE_B:
2310 		if (b->flags & BF_TARGETING_COORDS) {
2311 			// slash between the two
2312 			pos1 = b->target_pos1;
2313 			pos2 = b->target_pos2;
2314 		} else {
2315 			beam_get_octant_points(model_num, b->target, seed % BEAM_NUM_GOOD_OCTANTS, Beam_good_slash_octants, &pos1, &pos2);
2316 		}
2317 
2318 		// point 1
2319 		vm_vec_sub(&b->binfo.dir_a, &pos1, &turret_point);
2320 		vm_vec_normalize(&b->binfo.dir_a);
2321 
2322 		// point 2
2323 		vm_vec_sub(&b->binfo.dir_b, &pos2, &turret_point);
2324 		vm_vec_normalize(&b->binfo.dir_b);
2325 
2326 		break;
2327 
2328 	// nothing for this beam - its very special case
2329 	case BEAM_TYPE_C:
2330 		break;
2331 
2332 	// type D beams fire at small ship multiple times
2333 	case BEAM_TYPE_D:
2334 		// determine the miss factor
2335 		Assert(Game_skill_level >= 0 && Game_skill_level < NUM_SKILL_LEVELS);
2336 		Assert(b->team >= 0 && b->team < Num_iffs);
2337 		miss_factor = bwi->beam_iff_miss_factor[b->team][Game_skill_level];
2338 
2339 		// get a bunch of shot aims
2340 		for(idx=0; idx<b->binfo.shot_count; idx++){
2341 			//	MK, 9/3/99: Added pow() function to make increasingly likely to miss with subsequent shots.  30% more likely with each shot.
2342 			float r = ((float) pow(1.3f, (float) idx)) * miss_factor * accuracy;
2343 			b->binfo.shot_aim[idx] = frand_range(0.0f, 1.0f + r);
2344 		}
2345 		break;
2346 
2347 	// type e beams just fire straight
2348 	case BEAM_TYPE_E:
2349 		b->binfo.shot_aim[0] = 0.0000001f;
2350 		b->binfo.shot_count = 1;
2351 		b->binfo.dir_a = turret_norm;
2352 		b->binfo.dir_b = turret_norm;
2353 		break;
2354 
2355 	case BEAM_TYPE_F:
2356 	{
2357 		vm_vec_zero(&pos1);
2358 		vm_vec_zero(&pos2);
2359 		vec3d rot_axis, burst_rot_axis, per_burst_rot_axis;
2360 
2361 		object* usable_target = nullptr;
2362 		// don't use the target if this is a fighter beam
2363 		if (!(b->flags & BF_IS_FIGHTER_BEAM) && b->target)
2364 			usable_target = b->target;
2365 
2366 		// set up shooter orient now
2367 		matrix orient = vmd_identity_matrix;
2368 		if (b->flags & BF_IS_FIGHTER_BEAM) {
2369 			orient = b->objp->orient;
2370 		} else if (b->subsys) {
2371 			vec3d fvec, uvec, target_pos;
2372 			if (b->target)
2373 				target_pos = b->target->pos;
2374 			else if (b->flags & BF_TARGETING_COORDS)
2375 				target_pos = b->target_pos1;
2376 			else
2377 				UNREACHABLE("Turret beam fired without a target or target coordinates?");
2378 			vm_vec_sub(&fvec, &target_pos, &turret_point);
2379 			vm_vec_unrotate(&uvec, &b->subsys->system_info->turret_norm, &b->objp->orient);
2380 			vm_vector_2_matrix(&orient, &fvec, &uvec);
2381 		} else if (b->flags & BF_TARGETING_COORDS) {
2382 			// targeting coords already set up turret_norm with target_pos above
2383 			vm_vector_2_matrix(&orient, &turret_norm);
2384 		}
2385 
2386 		vec3d rand1_on = vm_vec_new(0.f, 0.f, 0.f);
2387 		vec3d rand2_on = vm_vec_new(0.f, 0.f, 0.f);
2388 		vec3d rand1_off = vm_vec_new(0.f, 0.f, 0.f);
2389 		vec3d rand2_off = vm_vec_new(0.f, 0.f, 0.f);
2390 
2391 		// Get our two starting points
2392 		if (usable_target) {
2393 			// set up our two kinds of random points if needed
2394 			if (bwi->t5info.start_pos == Type5BeamPos::RANDOM_INSIDE || bwi->t5info.end_pos == Type5BeamPos::RANDOM_INSIDE) {
2395 				vec3d temp1, temp2;
2396 				submodel_get_two_random_points_better(model_num, 0, &temp1, &temp2, seed);
2397 				vm_vec_rotate(&rand1_on, &temp1, &b->target->orient);
2398 				vm_vec_rotate(&rand2_on, &temp2, &b->target->orient);
2399 				rand1_on += b->target->pos;
2400 				rand2_on += b->target->pos;
2401 			}
2402 			if (bwi->t5info.start_pos == Type5BeamPos::RANDOM_OUTSIDE || bwi->t5info.end_pos == Type5BeamPos::RANDOM_OUTSIDE)
2403 				beam_get_octant_points(model_num, usable_target, seed % BEAM_NUM_GOOD_OCTANTS, Beam_good_slash_octants, &rand1_off, &rand2_off);
2404 
2405 			// get start and end points
2406 			switch (bwi->t5info.start_pos) {
2407 				case Type5BeamPos::CENTER:
2408 					pos1 = b->target->pos;
2409 					break;
2410 				case Type5BeamPos::RANDOM_INSIDE:
2411 					pos1 = rand1_on;
2412 					break;
2413 				case Type5BeamPos::RANDOM_OUTSIDE:
2414 					pos1 = rand1_off;
2415 					break;
2416 				default:;
2417 					// the other cases dont matter
2418 			}
2419 
2420 
2421 			if (bwi->t5info.no_translate || bwi->t5info.end_pos == Type5BeamPos::SAME_RANDOM)
2422 				pos2 = pos1;
2423 			else {
2424 				switch (bwi->t5info.end_pos) {
2425 					case Type5BeamPos::CENTER:
2426 						pos2 = b->target->pos;
2427 						break;
2428 					case Type5BeamPos::RANDOM_INSIDE:
2429 						pos2 = rand2_on;
2430 						break;
2431 					case Type5BeamPos::RANDOM_OUTSIDE:
2432 						pos2 = rand2_off;
2433 						break;
2434 					default:;
2435 						// the other cases dont matter
2436 				}
2437 			}
2438 
2439 			// set rot_axis if its center
2440 			if (bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::CENTER)
2441 				rot_axis = b->target->pos;
2442 			if (bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::CENTER)
2443 				per_burst_rot_axis = b->target->pos;
2444 			if (bwi->t5info.burst_rot_axis == Type5BeamRotAxis::CENTER)
2445 				burst_rot_axis = b->target->pos;
2446 
2447 		} else { // No usable target
2448 			vec3d center = vm_vec_new(0.f, 0.f, 0.f);
2449 			// if we have no target let's act as though we're shooting at something with a 300m radius 300m away
2450 
2451 			// randomize the start and end points if not center aiming
2452 			// aim on the edge for random outside
2453 			if (bwi->t5info.start_pos != Type5BeamPos::CENTER)
2454 				vm_vec_random_in_circle(&pos1, &center, &orient, 1.f, bwi->t5info.start_pos == Type5BeamPos::RANDOM_OUTSIDE);
2455 
2456 			if (bwi->t5info.end_pos != Type5BeamPos::CENTER)
2457 				vm_vec_random_in_circle(&pos2, &center, &orient, 1.f, bwi->t5info.start_pos == Type5BeamPos::RANDOM_OUTSIDE);
2458 
2459 			if (bwi->t5info.no_translate || bwi->t5info.end_pos == Type5BeamPos::SAME_RANDOM)
2460 				pos2 = pos1;
2461 
2462 			pos1 *= 300.f;
2463 			pos2 *= 300.f;
2464 			vec3d move_forward = vm_vec_new(0.f, 0.f, 300.f);
2465 			center += move_forward;
2466 			pos1 += move_forward;
2467 			pos2 += move_forward;
2468 
2469 			// unrotate the points to get world positions
2470 			vec3d temp = pos1; vm_vec_unrotate(&pos1, &temp, &orient);
2471 			temp = pos2;       vm_vec_unrotate(&pos2, &temp, &orient);
2472 			temp = center;     vm_vec_unrotate(&center, &temp, &orient);
2473 			pos1 += turret_point;
2474 			pos2 += turret_point;
2475 			center += turret_point;
2476 
2477 			// set rot_axis if its center
2478 			if (bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::CENTER)
2479 				rot_axis = center;
2480 			if (bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::CENTER)
2481 				per_burst_rot_axis = center;
2482 			if (bwi->t5info.burst_rot_axis == Type5BeamRotAxis::CENTER)
2483 				burst_rot_axis = center;
2484 
2485 		}
2486 		// OKAY DONE WITH THE INITIAL SET UP
2487 
2488 		// set rot_axis if its one of the before offset points
2489 		if (bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::STARTPOS_NO_OFFSET || bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::ENDPOS_NO_OFFSET)
2490 			rot_axis = bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::STARTPOS_NO_OFFSET ? pos1 : pos2;
2491 		if (bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::STARTPOS_NO_OFFSET || bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::ENDPOS_NO_OFFSET)
2492 			per_burst_rot_axis = bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::STARTPOS_NO_OFFSET ? pos1 : pos2;
2493 		if (bwi->t5info.burst_rot_axis == Type5BeamRotAxis::STARTPOS_NO_OFFSET || bwi->t5info.burst_rot_axis == Type5BeamRotAxis::ENDPOS_NO_OFFSET)
2494 			burst_rot_axis = bwi->t5info.burst_rot_axis == Type5BeamRotAxis::STARTPOS_NO_OFFSET ? pos1 : pos2;
2495 
2496 		// now the offsets
2497 		float scale_factor;
2498 		if (b->target != nullptr) {
2499 			if (bwi->t5info.target_scale_positions)
2500 				scale_factor = b->target->radius;
2501 			else
2502 				scale_factor = vm_vec_dist(&b->target->pos, &turret_point); // using dist here means we have a constant angular width
2503 		} else
2504 			scale_factor = 300.f; // no target, just use 300m like the notarget scenario above
2505 
2506 		vec3d offset = bwi->t5info.start_pos_offset;
2507 		offset *= scale_factor;
2508 
2509 		// switch to the target's orient if applicable
2510 		if (bwi->t5info.target_orient_positions && b->target != nullptr)
2511 			orient = b->target->orient;
2512 
2513 		// maybe add some random
2514 		vec3d random_offset;
2515 		vm_vec_random_in_sphere(&random_offset, &vmd_zero_vector, 1.f, false, true);
2516 		random_offset *= scale_factor;
2517 		random_offset.xyz.x *= bwi->t5info.start_pos_rand.xyz.x;
2518 		random_offset.xyz.y *= bwi->t5info.start_pos_rand.xyz.y;
2519 		random_offset.xyz.z *= bwi->t5info.start_pos_rand.xyz.z;
2520 		offset += random_offset;
2521 
2522 		// then unrotate by it to get the world orientation
2523 		vec3d rotated_offset;
2524 		vm_vec_unrotate(&rotated_offset, &offset, &orient);
2525 		pos1 += rotated_offset;
2526 
2527 		// end pos offset
2528 		if (bwi->t5info.no_translate)
2529 			pos2 = pos1;
2530 		else {
2531 			offset = bwi->t5info.end_pos_offset;
2532 			offset *= scale_factor;
2533 
2534 			// randomness
2535 			vm_vec_random_in_sphere(&random_offset, &vmd_zero_vector, 1.f, false, true);
2536 			random_offset *= scale_factor;
2537 			random_offset.xyz.x *= bwi->t5info.start_pos_rand.xyz.x;
2538 			random_offset.xyz.y *= bwi->t5info.start_pos_rand.xyz.y;
2539 			random_offset.xyz.z *= bwi->t5info.start_pos_rand.xyz.z;
2540 			offset += random_offset;
2541 
2542 			// rotate
2543 			vm_vec_unrotate(&rotated_offset, &offset, &orient);
2544 			pos2 += rotated_offset;
2545 		}
2546 
2547 		// finally grab the last cases for rot_axis
2548 		if (bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::STARTPOS_OFFSET || bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::ENDPOS_OFFSET)
2549 			rot_axis = bwi->t5info.continuous_rot_axis == Type5BeamRotAxis::STARTPOS_OFFSET ? pos1 : pos2;
2550 		if (bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::STARTPOS_OFFSET || bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::ENDPOS_OFFSET)
2551 			per_burst_rot_axis = bwi->t5info.per_burst_rot_axis == Type5BeamRotAxis::STARTPOS_OFFSET ? pos1 : pos2;
2552 		if (bwi->t5info.burst_rot_axis == Type5BeamRotAxis::STARTPOS_OFFSET || bwi->t5info.burst_rot_axis == Type5BeamRotAxis::ENDPOS_OFFSET)
2553 			burst_rot_axis = bwi->t5info.burst_rot_axis == Type5BeamRotAxis::STARTPOS_OFFSET ? pos1 : pos2;
2554 
2555 		// normalize the vectors
2556 		vec3d per_burst_rot_axis_direction, burst_rot_axis_direction;
2557 
2558 		vm_vec_sub(&per_burst_rot_axis_direction, &per_burst_rot_axis, &turret_point);
2559 		vm_vec_normalize(&per_burst_rot_axis_direction);
2560 
2561 		vm_vec_sub(&burst_rot_axis_direction, &burst_rot_axis, &turret_point);
2562 		vm_vec_normalize(&burst_rot_axis_direction);
2563 
2564 		if (bwi->t5info.continuous_rot_axis != Type5BeamRotAxis::UNSPECIFIED) {
2565 			vm_vec_sub(&b->binfo.rot_axis, &rot_axis, &turret_point);
2566 			vm_vec_normalize(&b->binfo.rot_axis);
2567 		}
2568 
2569 		vm_vec_sub(&b->binfo.dir_a, &pos1, &turret_point);
2570 		vm_vec_normalize(&b->binfo.dir_a);
2571 
2572 		vm_vec_sub(&b->binfo.dir_b, &pos2, &turret_point);
2573 		vm_vec_normalize(&b->binfo.dir_b);
2574 
2575 		vec3d zero_vec = vmd_zero_vector;
2576 		// and finally rotate around the per_burst and burst rot_axes
2577 		if (bwi->t5info.per_burst_rot_axis != Type5BeamRotAxis::UNSPECIFIED) {
2578 			// negative means random
2579 			float per_burst_rot = per_burst_shot_rotation;
2580 			if (per_burst_rot < 0.0f)
2581 				per_burst_rot = static_randf_range(seed, 0.f, PI2);
2582 
2583 			vm_rot_point_around_line(&b->binfo.dir_a,    &b->binfo.dir_a,    per_burst_rot, &zero_vec, &per_burst_rot_axis_direction);
2584 			vm_rot_point_around_line(&b->binfo.dir_b,    &b->binfo.dir_b,    per_burst_rot, &zero_vec, &per_burst_rot_axis_direction);
2585 			vm_rot_point_around_line(&b->binfo.rot_axis, &b->binfo.rot_axis, per_burst_rot, &zero_vec, &per_burst_rot_axis_direction);
2586 		}
2587 
2588 		if (bwi->t5info.burst_rot_axis != Type5BeamRotAxis::UNSPECIFIED) {
2589 			// negative means random
2590 			float burst_rot = burst_shot_rotation;
2591 			if (burst_rot < 0.0f)
2592 				burst_rot = frand_range(0.f, PI2);
2593 
2594 			vm_rot_point_around_line(&b->binfo.dir_a,    &b->binfo.dir_a,    burst_rot, &zero_vec, &burst_rot_axis_direction);
2595 			vm_rot_point_around_line(&b->binfo.dir_b,    &b->binfo.dir_b,    burst_rot, &zero_vec, &burst_rot_axis_direction);
2596 			vm_rot_point_around_line(&b->binfo.rot_axis, &b->binfo.rot_axis, burst_rot, &zero_vec, &burst_rot_axis_direction);
2597 		}
2598 
2599 		break;
2600 	}
2601 	default:
2602 		break;
2603 	}
2604 }
2605 
2606 // aim the beam (setup last_start and last_shot - the endpoints). also recalculates collision pairs
beam_aim(beam * b)2607 void beam_aim(beam *b)
2608 {
2609 	vec3d temp, p2;
2610 
2611 	if (!(b->flags & BF_TARGETING_COORDS)) {
2612 		// type C beam weapons have no target
2613 		if (b->target == NULL) {
2614 			Assert(b->type == BEAM_TYPE_C);
2615 			if(b->type != BEAM_TYPE_C){
2616 				return;
2617 			}
2618 		}
2619 		// get a model # to work with
2620 		else {
2621 			// this can happen if we fire at a target that was just destroyed
2622 			if (beam_get_model(b->target) < 0) {
2623 				return;
2624 			}
2625 		}
2626 	}
2627 
2628 	if (b->subsys != nullptr && b->type != BEAM_TYPE_C) {	// Type C beams don't use this information.
2629 		int temp_int = b->subsys->turret_next_fire_pos;
2630 
2631 		if (!(b->flags & BF_IS_FIGHTER_BEAM))
2632 			b->subsys->turret_next_fire_pos = b->firingpoint;
2633 
2634 		if (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction]) {
2635 			beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 0, nullptr, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2636 		} else {
2637 			// where the shot is originating from (b->last_start gets filled in)
2638 			beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2639 		}
2640 
2641 		b->subsys->turret_next_fire_pos = temp_int;
2642 	}
2643 
2644 	// setup our initial shot point and aim direction
2645 	switch(b->type){
2646 	case BEAM_TYPE_A:
2647 		// if we're targeting a subsystem - shoot directly at it
2648 		if(b->target_subsys != nullptr){
2649 			vm_vec_unrotate(&b->last_shot, &b->target_subsys->system_info->pnt, &b->target->orient);
2650 			vm_vec_add2(&b->last_shot, &b->target->pos);
2651 
2652 			if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2653 				float dist = vm_vec_dist(&b->last_shot,&b->last_start);
2654 				vm_vec_scale(&temp, dist);
2655 			} else {
2656 				vm_vec_sub(&temp, &b->last_shot, &b->last_start);
2657 			}
2658 
2659 			vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, 2.0f);
2660 			break;
2661 		}
2662 
2663 		// if we're shooting at a big ship - shoot directly at the model
2664 		if((b->target != nullptr) && (b->target->type == OBJ_SHIP) && (Ship_info[Ships[b->target->instance].ship_info_index].is_big_or_huge())){
2665 			if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2666 				vec3d pnt;
2667 				vm_vec_unrotate(&pnt, &b->binfo.dir_a, &b->target->orient);
2668 				vm_vec_add2(&pnt, &b->target->pos);
2669 
2670 				float dist = vm_vec_dist(&pnt, &b->last_start);
2671 				vm_vec_scale(&temp, dist);
2672 				p2 = temp;
2673 			} else {
2674 				// rotate into world coords
2675 				vm_vec_unrotate(&temp, &b->binfo.dir_a, &b->target->orient);
2676 				vm_vec_add2(&temp, &b->target->pos);
2677 
2678 				// get the shot point
2679 				vm_vec_sub(&p2, &temp, &b->last_start);
2680 			}
2681 			vm_vec_scale_add(&b->last_shot, &b->last_start, &p2, 2.0f);
2682 			break;
2683 		}
2684 
2685 		// point at the center of the target...
2686 		if (b->flags & BF_TARGETING_COORDS) {
2687 			if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2688 				beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 0, &b->target_pos1, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2689 				float dist = vm_vec_dist(&b->target_pos1, &b->last_start);
2690 				vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, dist);
2691 			} else {
2692 				b->last_shot = b->target_pos1;
2693 			}
2694 		} else {
2695 			if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2696 				beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 0, &b->target->pos, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2697 				float dist = vm_vec_dist(&b->target->pos, &b->last_start);
2698 				vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, dist);
2699 			} else {
2700 				b->last_shot = b->target->pos;
2701 			}
2702 			// ...then jitter based on shot_aim (requires target)
2703 			beam_jitter_aim(b, b->binfo.shot_aim[0]);
2704 		}
2705 		break;
2706 
2707 	case BEAM_TYPE_B:
2708 		if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2709 			vm_vec_scale(&b->binfo.dir_a, b->range);
2710 			beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 0, &b->binfo.dir_a, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2711 			vm_vec_add(&b->last_shot, &b->last_start, &temp);
2712 		} else {
2713 			// set the shot point
2714 			vm_vec_scale_add(&b->last_shot, &b->last_start, &b->binfo.dir_a, b->range);
2715 		}
2716 		Assert(is_valid_vec(&b->last_shot));
2717 		break;
2718 
2719 	case BEAM_TYPE_C:
2720 		// start point
2721 		temp = b->local_fire_postion;
2722 		vm_vec_unrotate(&b->last_start, &temp, &b->objp->orient);
2723 		vm_vec_add2(&b->last_start, &b->objp->pos);
2724 		vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.vec.fvec, b->range);
2725 		break;
2726 
2727 	case BEAM_TYPE_D:
2728 		// point at the center of the target...
2729 		if (b->flags & BF_TARGETING_COORDS) {
2730 			if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2731 				beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 0, &b->target_pos1, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2732 				float dist = vm_vec_dist(&b->target_pos1, &b->last_start);
2733 				vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, dist);
2734 			} else {
2735 				b->last_shot = b->target_pos1;
2736 			}
2737 		} else {
2738 			if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2739 				beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 0, &b->target->pos, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2740 				float dist = vm_vec_dist(&b->target->pos, &b->last_start);
2741 				vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, dist);
2742 			} else {
2743 				b->last_shot = b->target->pos;
2744 			}
2745 			// ...then jitter based on shot_aim (requires target)
2746 			beam_jitter_aim(b, b->binfo.shot_aim[b->shot_index]);
2747 		}
2748 		nprintf(("AI", "Frame %i: FIRING\n", Framecount));
2749 		break;
2750 
2751 	case BEAM_TYPE_E:
2752 		// point directly in the direction of the turret
2753 		vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, b->range);
2754 		break;
2755 
2756 	case BEAM_TYPE_F:
2757 		if ((b->subsys != nullptr) && (b->subsys->system_info->flags[Model::Subsystem_Flags::Share_fire_direction])) {
2758 			vm_vec_scale(&b->binfo.dir_a, b->range);
2759 			beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 0, &b->binfo.dir_a, (b->flags & BF_IS_FIGHTER_BEAM) != 0);
2760 			vm_vec_add(&b->last_shot, &b->last_start, &temp);
2761 		}
2762 		else {
2763 			// set the shot point
2764 			vm_vec_scale_add(&b->last_shot, &b->last_start, &b->binfo.dir_a, b->range);
2765 		}
2766 		Assert(is_valid_vec(&b->last_shot));
2767 		break;
2768 
2769 	default:
2770 		UNREACHABLE("Impossible beam type (%d); get a coder!\n", b->type);
2771 	}
2772 
2773 	if (!Weapon_info[b->weapon_info_index].wi_flags[Weapon::Info_Flags::No_collide])
2774 		// recalculate object pairs
2775 		OBJ_RECALC_PAIRS((&Objects[b->objnum]));
2776 }
2777 
2778 // given a model #, and an object, stuff 2 good world coord points
beam_get_octant_points(int modelnum,object * objp,int oct_index,int oct_array[BEAM_NUM_GOOD_OCTANTS][4],vec3d * v1,vec3d * v2)2779 void beam_get_octant_points(int modelnum, object *objp, int oct_index, int oct_array[BEAM_NUM_GOOD_OCTANTS][4], vec3d *v1, vec3d *v2)
2780 {
2781 	vec3d t1, t2, temp;
2782 	polymodel *m = model_get(modelnum);
2783 
2784 	// bad bad bad bad bad bad
2785 	if(m == NULL){
2786 		Int3();
2787 		return;
2788 	}
2789 
2790 	Assert((oct_index >= 0) && (oct_index < BEAM_NUM_GOOD_OCTANTS));
2791 
2792 	// randomly pick octants
2793 	t1 = oct_array[oct_index][2] ? m->octants[oct_array[oct_index][0]].max : m->octants[oct_array[oct_index][0]].min;
2794 	t2 = oct_array[oct_index][3] ? m->octants[oct_array[oct_index][1]].max : m->octants[oct_array[oct_index][1]].min;
2795 	Assert(!vm_vec_same(&t1, &t2));
2796 
2797 	// get them in world coords
2798 	vm_vec_unrotate(&temp, &t1, &objp->orient);
2799 	vm_vec_add(v1, &temp, &objp->pos);
2800 	vm_vec_unrotate(&temp, &t2, &objp->orient);
2801 	vm_vec_add(v2, &temp, &objp->pos);
2802 }
2803 
2804 // throw some jitter into the aim - based upon shot_aim
beam_jitter_aim(beam * b,float aim)2805 void beam_jitter_aim(beam *b, float aim)
2806 {
2807 	Assert(b->target != NULL);
2808 	vec3d forward, circle;
2809 	matrix m;
2810 	float subsys_strength;
2811 
2812 	// if the weapons subsystem is damaged or destroyed
2813 	if((b->objp != NULL) && (b->objp->signature == b->sig) && (b->objp->type == OBJ_SHIP) && (b->objp->instance >= 0) && (b->objp->instance < MAX_SHIPS)){
2814 		// get subsytem strength
2815 		subsys_strength = ship_get_subsystem_strength(&Ships[b->objp->instance], SUBSYSTEM_WEAPONS);
2816 
2817 		// when subsytem strength is 0, double the aim error factor
2818 		aim += aim * (1.0f - subsys_strength);
2819 	}
2820 
2821 	// shot aim is a direct linear factor of the target model's radius.
2822 	// so, pick a random point on the circle
2823 	vm_vec_sub(&forward, &b->last_shot, &b->last_start);
2824 	vm_vec_normalize_quick(&forward);
2825 
2826 	// vector
2827 	vm_vector_2_matrix(&m, &forward, NULL, NULL);
2828 
2829 	// get a random vector on the circle, but somewhat biased towards the center
2830 	vm_vec_random_in_circle(&circle, &b->last_shot, &m, aim * b->target->radius, false, true);
2831 
2832 	// get the vector pointing to the circle point
2833 	vm_vec_sub(&forward, &circle, &b->last_start);
2834 	vm_vec_scale_add(&b->last_shot, &b->last_start, &forward, 2.0f);
2835 }
2836 
2837 
2838 // -----------------------------===========================------------------------------
2839 // BEAM COLLISION FUNCTIONS
2840 // -----------------------------===========================------------------------------
2841 
2842 // collide a beam with a ship, returns 1 if we can ignore all future collisions between the 2 objects
beam_collide_ship(obj_pair * pair)2843 int beam_collide_ship(obj_pair *pair)
2844 {
2845 	beam * a_beam;
2846 	object *weapon_objp;
2847 	object *ship_objp;
2848 	ship *shipp;
2849 	ship_info *sip;
2850 	weapon_info *bwi;
2851 	mc_info mc, mc_shield, mc_hull_enter, mc_hull_exit;
2852 	int model_num;
2853 	float width;
2854 
2855 	// bogus
2856 	if (pair == NULL) {
2857 		return 0;
2858 	}
2859 
2860 	if (reject_due_collision_groups(pair->a, pair->b))
2861 		return 0;
2862 
2863 	// get the beam
2864 	Assert(pair->a->instance >= 0);
2865 	Assert(pair->a->type == OBJ_BEAM);
2866 	Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2867 	weapon_objp = pair->a;
2868 	a_beam = &Beams[pair->a->instance];
2869 
2870 	// Don't check collisions for warping out player if past stage 1.
2871 	if (Player->control_mode >= PCM_WARPOUT_STAGE1) {
2872 		if ( pair->a == Player_obj ) return 0;
2873 		if ( pair->b == Player_obj ) return 0;
2874 	}
2875 
2876 	// if the "warming up" timestamp has not expired
2877 	if ((a_beam->warmup_stamp != -1) || (a_beam->warmdown_stamp != -1)) {
2878 		return 0;
2879 	}
2880 
2881 	// if the beam is on "safety", don't collide with anything
2882 	if (a_beam->flags & BF_SAFETY) {
2883 		return 0;
2884 	}
2885 
2886 	// if the colliding object is the shooting object, return 1 so this is culled
2887 	if (pair->b == a_beam->objp) {
2888 		return 1;
2889 	}
2890 
2891 	// try and get a model
2892 	model_num = beam_get_model(pair->b);
2893 	if (model_num < 0) {
2894 		return 1;
2895 	}
2896 
2897 #ifndef NDEBUG
2898 	Beam_test_ints++;
2899 	Beam_test_ship++;
2900 #endif
2901 
2902 	// get the ship
2903 	Assert(pair->b->instance >= 0);
2904 	Assert(pair->b->type == OBJ_SHIP);
2905 	Assert(Ships[pair->b->instance].objnum == OBJ_INDEX(pair->b));
2906 	if ((pair->b->type != OBJ_SHIP) || (pair->b->instance < 0))
2907 		return 1;
2908 	ship_objp = pair->b;
2909 	shipp = &Ships[ship_objp->instance];
2910 
2911 	if (shipp->flags[Ship::Ship_Flags::Arriving_stage_1])
2912 		return 0;
2913 
2914 	int quadrant_num = -1;
2915 	bool valid_hit_occurred = false;
2916 	sip = &Ship_info[shipp->ship_info_index];
2917 	bwi = &Weapon_info[a_beam->weapon_info_index];
2918 
2919 	polymodel *pm = model_get(model_num);
2920 
2921 	// get the width of the beam
2922 	width = a_beam->beam_collide_width * a_beam->current_width_factor;
2923 
2924 
2925 	// Goober5000 - I tried to make collision code much saner... here begin the (major) changes
2926 	mc_info_init(&mc);
2927 
2928 	// set up collision structs, part 1
2929 	mc.model_instance_num = shipp->model_instance_num;
2930 	mc.model_num = model_num;
2931 	mc.submodel_num = -1;
2932 	mc.orient = &ship_objp->orient;
2933 	mc.pos = &ship_objp->pos;
2934 	mc.p0 = &a_beam->last_start;
2935 	mc.p1 = &a_beam->last_shot;
2936 
2937 	// maybe do a sphereline
2938 	if (width > ship_objp->radius * BEAM_AREA_PERCENT) {
2939 		mc.radius = width * 0.5f;
2940 		mc.flags = MC_CHECK_SPHERELINE;
2941 	} else {
2942 		mc.flags = MC_CHECK_RAY;
2943 	}
2944 
2945 	// set up collision structs, part 2
2946 	memcpy(&mc_shield, &mc, sizeof(mc_info));
2947 	memcpy(&mc_hull_enter, &mc, sizeof(mc_info));
2948 	memcpy(&mc_hull_exit, &mc, sizeof(mc_info));
2949 
2950 	// reverse this vector so that we check for exit holes as opposed to entrance holes
2951 	mc_hull_exit.p1 = &a_beam->last_start;
2952 	mc_hull_exit.p0 = &a_beam->last_shot;
2953 
2954 	// set flags
2955 	mc_shield.flags |= MC_CHECK_SHIELD;
2956 	mc_hull_enter.flags |= MC_CHECK_MODEL;
2957 	mc_hull_exit.flags |= MC_CHECK_MODEL;
2958 
2959 	// check all three kinds of collisions
2960 	int shield_collision = (pm->shield.ntris > 0) ? model_collide(&mc_shield) : 0;
2961 	int hull_enter_collision = model_collide(&mc_hull_enter);
2962 	int hull_exit_collision = (beam_will_tool_target(a_beam, ship_objp)) ? model_collide(&mc_hull_exit) : 0;
2963 
2964     // If we have a range less than the "far" range, check if the ray actually hit within the range
2965     if (a_beam->range < BEAM_FAR_LENGTH
2966         && (shield_collision || hull_enter_collision || hull_exit_collision))
2967     {
2968         // We can't use hit_dist as "1" is the distance between p0 and p1
2969         float rangeSq = a_beam->range * a_beam->range;
2970 
2971         // actually make sure that the collision points are within range of our beam
2972         if (shield_collision && vm_vec_dist_squared(&a_beam->last_start, &mc_shield.hit_point_world) > rangeSq)
2973         {
2974             shield_collision = 0;
2975         }
2976 
2977         if (hull_enter_collision && vm_vec_dist_squared(&a_beam->last_start, &mc_hull_enter.hit_point_world) > rangeSq)
2978         {
2979             hull_enter_collision = 0;
2980         }
2981 
2982         if (hull_exit_collision && vm_vec_dist_squared(&mc_hull_exit.hit_point_world, &a_beam->last_start) > rangeSq)
2983         {
2984             hull_exit_collision = 0;
2985         }
2986     }
2987 
2988 
2989 	if (hull_enter_collision || hull_exit_collision || shield_collision) {
2990 		WarpEffect* warp_effect = nullptr;
2991 
2992 		if (shipp->flags[Ship::Ship_Flags::Depart_warp] && shipp->warpout_effect != nullptr)
2993 			warp_effect = shipp->warpout_effect;
2994 		else if (shipp->flags[Ship::Ship_Flags::Arriving_stage_2] && shipp->warpin_effect != nullptr)
2995 			warp_effect = shipp->warpin_effect;
2996 
2997 
2998 		bool hull_no_collide, shield_no_collide;
2999 		hull_no_collide = shield_no_collide = false;
3000 		if (warp_effect != nullptr) {
3001 			hull_no_collide = point_is_clipped_by_warp(&mc_hull_enter.hit_point_world, warp_effect);
3002 			shield_no_collide = point_is_clipped_by_warp(&mc_shield.hit_point_world, warp_effect);
3003 		}
3004 
3005 		if (hull_no_collide)
3006 			hull_enter_collision = hull_exit_collision = 0;
3007 		if (shield_no_collide)
3008 			shield_collision = 0;
3009 	}
3010 
3011 	// check shields for impact
3012 	// (tooled ships are probably not going to be maintaining a shield over their exit hole,
3013 	// therefore we need only check the entrance, just as with conventional weapons)
3014 	if (!(ship_objp->flags[Object::Object_Flags::No_shields]))
3015 	{
3016 		// pick out the shield quadrant
3017 		if (shield_collision)
3018 			quadrant_num = get_quadrant(&mc_shield.hit_point, ship_objp);
3019 		else if (hull_enter_collision && (sip->flags[Ship::Info_Flags::Surface_shields]))
3020 			quadrant_num = get_quadrant(&mc_hull_enter.hit_point, ship_objp);
3021 
3022 		// make sure that the shield is active in that quadrant
3023 		if ((quadrant_num >= 0) && ((shipp->flags[Ship::Ship_Flags::Dying]) || !ship_is_shield_up(ship_objp, quadrant_num)))
3024 			quadrant_num = -1;
3025 
3026 		// see if we hit the shield
3027 		if (quadrant_num >= 0)
3028 		{
3029 			// do the hit effect
3030 			if (shield_collision) {
3031 				if (mc_shield.shield_hit_tri != -1) {
3032 					add_shield_point(OBJ_INDEX(ship_objp), mc_shield.shield_hit_tri, &mc_shield.hit_point);
3033 				}
3034 			} else {
3035 				/* TODO */;
3036 			}
3037 
3038 			// if this weapon pierces the shield, then do the hit effect, but act like a shield collision never occurred;
3039 			// otherwise, we have a valid hit on this shield
3040 			if (bwi->wi_flags[Weapon::Info_Flags::Pierce_shields])
3041 				quadrant_num = -1;
3042 			else
3043 				valid_hit_occurred = 1;
3044 		}
3045 	}
3046 
3047 	// see which impact we use
3048 	if (shield_collision && valid_hit_occurred)
3049 	{
3050 		memcpy(&mc, &mc_shield, sizeof(mc_info));
3051 		Assert(quadrant_num >= 0);
3052 	}
3053 	else if (hull_enter_collision)
3054 	{
3055 		memcpy(&mc, &mc_hull_enter, sizeof(mc_info));
3056 		valid_hit_occurred = 1;
3057 	}
3058 
3059 	// if we got a hit
3060 	if (valid_hit_occurred)
3061 	{
3062 		// since we might have two collisions handled the same way, let's loop over both of them
3063 		mc_info *mc_array[2];
3064 		int mc_size = 1;
3065 		mc_array[0] = &mc;
3066 		if (hull_exit_collision)
3067 		{
3068 			mc_array[1] = &mc_hull_exit;
3069 			++mc_size;
3070 		}
3071 
3072 		for (int i = 0; i < mc_size; ++i)
3073 		{
3074 			bool ship_override = false, weapon_override = false;
3075 
3076 			if (Script_system.IsActiveAction(CHA_COLLIDEBEAM)) {
3077 				Script_system.SetHookObjects(4, "Self", ship_objp, "Object", weapon_objp, "Ship", ship_objp, "Beam", weapon_objp);
3078 				Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(mc_array[i]->hit_point_world));
3079 				ship_override = Script_system.IsConditionOverride(CHA_COLLIDEBEAM, ship_objp);
3080 				Script_system.RemHookVars({ "Self", "Object", "Ship", "Beam", "Hitpos" });
3081 			}
3082 
3083 			if (Script_system.IsActiveAction(CHA_COLLIDESHIP)) {
3084 				Script_system.SetHookObjects(4, "Self", weapon_objp, "Object", ship_objp, "Ship", ship_objp, "Beam", weapon_objp);
3085 				Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(mc_array[i]->hit_point_world));
3086 				weapon_override = Script_system.IsConditionOverride(CHA_COLLIDESHIP, weapon_objp);
3087 				Script_system.RemHookVars({ "Self", "Object", "Ship", "Beam", "Hitpos" });
3088 			}
3089 
3090 			if (!ship_override && !weapon_override)
3091 			{
3092 				// add to the collision_list
3093 				// if we got "tooled", add an exit hole too
3094 				beam_add_collision(a_beam, ship_objp, mc_array[i], quadrant_num, i != 0);
3095 			}
3096 
3097 			if (Script_system.IsActiveAction(CHA_COLLIDEBEAM) && !(weapon_override && !ship_override))
3098 			{
3099 				Script_system.SetHookObjects(4, "Self", ship_objp, "Object", weapon_objp, "Ship", ship_objp, "Beam", weapon_objp);
3100 				Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(mc_array[i]->hit_point_world));
3101 				Script_system.RunCondition(CHA_COLLIDEBEAM, ship_objp);
3102 				Script_system.RemHookVars({ "Self", "Object", "Ship", "Beam", "Hitpos" });
3103 			}
3104 
3105 			if (Script_system.IsActiveAction(CHA_COLLIDESHIP) && ((weapon_override && !ship_override) || (!weapon_override && !ship_override)))
3106 			{
3107 				Script_system.SetHookObjects(4, "Self", weapon_objp, "Object", ship_objp, "Ship", ship_objp, "Beam", weapon_objp);
3108 				Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(mc_array[i]->hit_point_world));
3109 				Script_system.RunCondition(CHA_COLLIDESHIP, weapon_objp);
3110 				Script_system.RemHookVars({ "Self", "Object", "Ship", "Beam", "Hitpos" });
3111 			}
3112 		}
3113 	}
3114 
3115 	// reset timestamp to timeout immediately
3116 	pair->next_check_time = timestamp(0);
3117 
3118 	return 0;
3119 }
3120 
3121 
3122 // collide a beam with an asteroid, returns 1 if we can ignore all future collisions between the 2 objects
beam_collide_asteroid(obj_pair * pair)3123 int beam_collide_asteroid(obj_pair *pair)
3124 {
3125 	beam * a_beam;
3126 	mc_info test_collide;
3127 	int model_num;
3128 
3129 	// bogus
3130 	if(pair == NULL){
3131 		return 0;
3132 	}
3133 
3134 	// get the beam
3135 	Assert(pair->a->instance >= 0);
3136 	Assert(pair->a->type == OBJ_BEAM);
3137 	Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
3138 	a_beam = &Beams[pair->a->instance];
3139 
3140 	// if the "warming up" timestamp has not expired
3141 	if((a_beam->warmup_stamp != -1) || (a_beam->warmdown_stamp != -1)){
3142 		return 0;
3143 	}
3144 
3145 	// if the beam is on "safety", don't collide with anything
3146 	if(a_beam->flags & BF_SAFETY){
3147 		return 0;
3148 	}
3149 
3150 	// if the colliding object is the shooting object, return 1 so this is culled
3151 	if(pair->b == a_beam->objp){
3152 		return 1;
3153 	}
3154 
3155 	// try and get a model
3156 	model_num = beam_get_model(pair->b);
3157 	if(model_num < 0){
3158 		Int3();
3159 		return 1;
3160 	}
3161 
3162 #ifndef NDEBUG
3163 	Beam_test_ints++;
3164 	Beam_test_ast++;
3165 #endif
3166 
3167 	// do the collision
3168 	mc_info_init(&test_collide);
3169 	test_collide.model_instance_num = -1;
3170 	test_collide.model_num = model_num;
3171 	test_collide.submodel_num = -1;
3172 	test_collide.orient = &pair->b->orient;
3173 	test_collide.pos = &pair->b->pos;
3174 	test_collide.p0 = &a_beam->last_start;
3175 	test_collide.p1 = &a_beam->last_shot;
3176 	test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
3177 	model_collide(&test_collide);
3178 
3179 	// if we got a hit
3180 	if (test_collide.num_hits)
3181 	{
3182 		// add to the collision list
3183 		bool weapon_override = false, asteroid_override = false;
3184 
3185 		if (Script_system.IsActiveAction(CHA_COLLIDEASTEROID)) {
3186 			Script_system.SetHookObjects(4, "Self", pair->a, "Object", pair->b, "Beam", pair->a, "Asteroid", pair->b);
3187 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3188 			weapon_override = Script_system.IsConditionOverride(CHA_COLLIDEASTEROID, pair->a);
3189 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Asteroid", "Hitpos" });
3190 		}
3191 
3192 		if (Script_system.IsActiveAction(CHA_COLLIDEBEAM)) {
3193 			Script_system.SetHookObjects(4, "Self", pair->b, "Object", pair->a, "Beam", pair->a, "Asteroid", pair->b);
3194 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3195 			asteroid_override = Script_system.IsConditionOverride(CHA_COLLIDEBEAM, pair->b);
3196 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Asteroid", "Hitpos" });
3197 		}
3198 
3199 		if (!weapon_override && !asteroid_override)
3200 		{
3201 			beam_add_collision(a_beam, pair->b, &test_collide);
3202 		}
3203 
3204 		if (Script_system.IsActiveAction(CHA_COLLIDEASTEROID) && !(asteroid_override && !weapon_override))
3205 		{
3206 			Script_system.SetHookObjects(4, "Self", pair->a, "Object", pair->b, "Beam", pair->a, "Asteroid", pair->b);
3207 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3208 			Script_system.RunCondition(CHA_COLLIDEASTEROID, pair->a);
3209 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Asteroid", "Hitpos" });
3210 		}
3211 
3212 		if (Script_system.IsActiveAction(CHA_COLLIDEBEAM) && ((asteroid_override && !weapon_override) || (!asteroid_override && !weapon_override)))
3213 		{
3214 			Script_system.SetHookObjects(4, "Self", pair->b, "Object", pair->a, "Beam", pair->a, "Asteroid", pair->b);
3215 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3216 			Script_system.RunCondition(CHA_COLLIDEBEAM, pair->b);
3217 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Asteroid", "Hitpos" });
3218 		}
3219 
3220 		return 0;
3221 	}
3222 
3223 	// reset timestamp to timeout immediately
3224 	pair->next_check_time = timestamp(0);
3225 
3226 	return 0;
3227 }
3228 
3229 // collide a beam with a missile, returns 1 if we can ignore all future collisions between the 2 objects
beam_collide_missile(obj_pair * pair)3230 int beam_collide_missile(obj_pair *pair)
3231 {
3232 	beam *a_beam;
3233 	mc_info test_collide;
3234 	int model_num;
3235 
3236 	// bogus
3237 	if(pair == NULL){
3238 		return 0;
3239 	}
3240 
3241 	// get the beam
3242 	Assert(pair->a->instance >= 0);
3243 	Assert(pair->a->type == OBJ_BEAM);
3244 	Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
3245 	a_beam = &Beams[pair->a->instance];
3246 
3247 	// if the "warming up" timestamp has not expired
3248 	if((a_beam->warmup_stamp != -1) || (a_beam->warmdown_stamp != -1)){
3249 		return 0;
3250 	}
3251 
3252 	// if the beam is on "safety", don't collide with anything
3253 	if(a_beam->flags & BF_SAFETY){
3254 		return 0;
3255 	}
3256 
3257 	// don't collide if the beam and missile share their parent
3258 	if (pair->b->parent_sig >= 0 && a_beam->objp && pair->b->parent_sig == a_beam->objp->signature) {
3259 		return 1;
3260 	}
3261 
3262 	// try and get a model
3263 	model_num = beam_get_model(pair->b);
3264 	if(model_num < 0){
3265 		return 1;
3266 	}
3267 
3268 #ifndef NDEBUG
3269 	Beam_test_ints++;
3270 #endif
3271 
3272 	// do the collision
3273 	mc_info_init(&test_collide);
3274 	test_collide.model_instance_num = -1;
3275 	test_collide.model_num = model_num;
3276 	test_collide.submodel_num = -1;
3277 	test_collide.orient = &pair->b->orient;
3278 	test_collide.pos = &pair->b->pos;
3279 	test_collide.p0 = &a_beam->last_start;
3280 	test_collide.p1 = &a_beam->last_shot;
3281 	test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
3282 	model_collide(&test_collide);
3283 
3284 	// if we got a hit
3285 	if(test_collide.num_hits)
3286 	{
3287 		// add to the collision list
3288 		bool a_override = false, b_override = false;
3289 
3290 		if (Script_system.IsActiveAction(CHA_COLLIDEWEAPON)) {
3291 			Script_system.SetHookObjects(4, "Self", pair->a, "Object", pair->b, "Beam", pair->a, "Weapon", pair->b);
3292 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3293 			a_override = Script_system.IsConditionOverride(CHA_COLLIDEWEAPON, pair->a);
3294 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Weapon", "Hitpos" });
3295 		}
3296 
3297 		//Should be reversed
3298 		if (Script_system.IsActiveAction(CHA_COLLIDEBEAM)) {
3299 			Script_system.SetHookObjects(4, "Self", pair->b, "Object", pair->a, "Beam", pair->a, "Weapon", pair->b);
3300 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3301 			b_override = Script_system.IsConditionOverride(CHA_COLLIDEBEAM, pair->b);
3302 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Weapon", "Hitpos" });
3303 		}
3304 
3305 		if(!a_override && !b_override)
3306 		{
3307 			beam_add_collision(a_beam, pair->b, &test_collide);
3308 		}
3309 
3310 		if(Script_system.IsActiveAction(CHA_COLLIDEWEAPON) && !(b_override && !a_override))
3311 		{
3312 			Script_system.SetHookObjects(4, "Self", pair->a, "Object", pair->b, "Beam", pair->a, "Weapon", pair->b);
3313 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3314 			Script_system.RunCondition(CHA_COLLIDEWEAPON, pair->a);
3315 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Weapon", "Hitpos" });
3316 		}
3317 
3318 		if(Script_system.IsActiveAction(CHA_COLLIDEBEAM) && ((b_override && !a_override) || (!b_override && !a_override)))
3319 		{
3320 			//Should be reversed
3321 			Script_system.SetHookObjects(4, "Self", pair->b, "Object", pair->a, "Beam", pair->a, "Weapon", pair->b);
3322 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3323 			Script_system.RunCondition(CHA_COLLIDEBEAM, pair->b);
3324 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Weapon", "Hitpos" });
3325 		}
3326 	}
3327 
3328 	// reset timestamp to timeout immediately
3329 	pair->next_check_time = timestamp(0);
3330 
3331 	return 0;
3332 }
3333 
3334 // collide a beam with debris, returns 1 if we can ignore all future collisions between the 2 objects
beam_collide_debris(obj_pair * pair)3335 int beam_collide_debris(obj_pair *pair)
3336 {
3337 	beam * a_beam;
3338 	mc_info test_collide;
3339 	int model_num;
3340 
3341 	// bogus
3342 	if(pair == NULL){
3343 		return 0;
3344 	}
3345 
3346 	if (reject_due_collision_groups(pair->a, pair->b))
3347 		return 0;
3348 
3349 	// get the beam
3350 	Assert(pair->a->instance >= 0);
3351 	Assert(pair->a->type == OBJ_BEAM);
3352 	Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
3353 	a_beam = &Beams[pair->a->instance];
3354 
3355 	// if the "warming up" timestamp has not expired
3356 	if((a_beam->warmup_stamp != -1) || (a_beam->warmdown_stamp != -1)){
3357 		return 0;
3358 	}
3359 
3360 	// if the beam is on "safety", don't collide with anything
3361 	if(a_beam->flags & BF_SAFETY){
3362 		return 0;
3363 	}
3364 
3365 	// if the colliding object is the shooting object, return 1 so this is culled
3366 	if(pair->b == a_beam->objp){
3367 		return 1;
3368 	}
3369 
3370 	// try and get a model
3371 	model_num = beam_get_model(pair->b);
3372 	if(model_num < 0){
3373 		return 1;
3374 	}
3375 
3376 #ifndef NDEBUG
3377 	Beam_test_ints++;
3378 #endif
3379 
3380 	// do the collision
3381 	mc_info_init(&test_collide);
3382 	test_collide.model_instance_num = -1;
3383 	test_collide.model_num = model_num;
3384 	test_collide.submodel_num = -1;
3385 	test_collide.orient = &pair->b->orient;
3386 	test_collide.pos = &pair->b->pos;
3387 	test_collide.p0 = &a_beam->last_start;
3388 	test_collide.p1 = &a_beam->last_shot;
3389 	test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
3390 	model_collide(&test_collide);
3391 
3392 	// if we got a hit
3393 	if(test_collide.num_hits)
3394 	{
3395 		bool weapon_override = false, debris_override = false;
3396 
3397 		if (Script_system.IsActiveAction(CHA_COLLIDEDEBRIS)) {
3398 			Script_system.SetHookObjects(4, "Self", pair->a, "Object", pair->b, "Beam", pair->a, "Debris", pair->b);
3399 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3400 			weapon_override = Script_system.IsConditionOverride(CHA_COLLIDEDEBRIS, pair->a);
3401 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Debris", "Hitpos" });
3402 		}
3403 
3404 		if (Script_system.IsActiveAction(CHA_COLLIDEBEAM)) {
3405 			Script_system.SetHookObjects(4, "Self", pair->b, "Object",  pair->a, "Beam", pair->a, "Debris", pair->b);
3406 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3407 			debris_override = Script_system.IsConditionOverride(CHA_COLLIDEBEAM, pair->b);
3408 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Debris", "Hitpos" });
3409 		}
3410 
3411 		if(!weapon_override && !debris_override)
3412 		{
3413 			// add to the collision list
3414 			beam_add_collision(a_beam, pair->b, &test_collide);
3415 		}
3416 
3417 		if (Script_system.IsActiveAction(CHA_COLLIDEDEBRIS) && !(debris_override && !weapon_override))
3418 		{
3419 			Script_system.SetHookObjects(4, "Self", pair->a, "Object", pair->b, "Beam", pair->a, "Debris", pair->b);
3420 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3421 			Script_system.RunCondition(CHA_COLLIDEDEBRIS, pair->a);
3422 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Debris", "Hitpos" });
3423 		}
3424 
3425 		if (Script_system.IsActiveAction(CHA_COLLIDEBEAM) && ((debris_override && !weapon_override) || (!debris_override && !weapon_override)))
3426 		{
3427 			Script_system.SetHookObjects(4, "Self", pair->b, "Object", pair->a, "Beam", pair->a, "Debris", pair->b);
3428 			Script_system.SetHookVar("Hitpos", 'o', scripting::api::l_Vector.Set(test_collide.hit_point_world));
3429 			Script_system.RunCondition(CHA_COLLIDEBEAM, pair->b);
3430 			Script_system.RemHookVars({ "Self", "Object", "Beam", "Debris", "Hitpos" });
3431 		}
3432 	}
3433 
3434 	// reset timestamp to timeout immediately
3435 	pair->next_check_time = timestamp(0);
3436 
3437 	return 0;
3438 }
3439 
3440 // early-out function for when adding object collision pairs, return 1 if the pair should be ignored
beam_collide_early_out(object * a,object * b)3441 int beam_collide_early_out(object *a, object *b)
3442 {
3443 	beam *bm;
3444 	weapon_info *bwi;
3445 
3446 	// get the beam
3447 	Assert(a->instance >= 0);
3448 	if(a->instance < 0){
3449 		return 1;
3450 	}
3451 	Assert(a->type == OBJ_BEAM);
3452 	if(a->type != OBJ_BEAM){
3453 		return 1;
3454 	}
3455 	Assert(Beams[a->instance].objnum == OBJ_INDEX(a));
3456 	if(Beams[a->instance].objnum != OBJ_INDEX(a)){
3457 		return 1;
3458 	}
3459 	bm = &Beams[a->instance];
3460 	Assert(bm->weapon_info_index >= 0);
3461 	if(bm->weapon_info_index < 0){
3462 		return 1;
3463 	}
3464 	bwi = &Weapon_info[bm->weapon_info_index];
3465 
3466 	// if the second object has an invalid instance, bail
3467 	if(b->instance < 0){
3468 		return 1;
3469 	}
3470 
3471 	if((vm_vec_dist(&bm->last_start, &b->pos)-b->radius) > bwi->b_info.range){
3472 		return 1;
3473 	}//if the object is too far away, don't bother trying to colide with it-Bobboau
3474 
3475 	// baseline bails
3476 	switch(b->type){
3477 	case OBJ_SHIP:
3478 		break;
3479 	case OBJ_ASTEROID:
3480 		// targeting lasers only hit ships
3481 /*		if(bwi->b_info.beam_type == BEAM_TYPE_C){
3482 			return 1;
3483 		}*/
3484 		break;
3485 	case OBJ_DEBRIS:
3486 		// targeting lasers only hit ships
3487 /*		if(bwi->b_info.beam_type == BEAM_TYPE_C){
3488 			return 1;
3489 		}*/
3490 		// don't ever collide with non hull pieces
3491 		if(!Debris[b->instance].is_hull){
3492 			return 1;
3493 		}
3494 		break;
3495 	case OBJ_WEAPON:
3496 		// targeting lasers only hit ships
3497 /*		if(bwi->b_info.beam_type == BEAM_TYPE_C){
3498 			return 1;
3499 		}*/
3500 		if(The_mission.ai_profile->flags[AI::Profile_Flags::Beams_damage_weapons]) {
3501 			if((Weapon_info[Weapons[b->instance].weapon_info_index].weapon_hitpoints <= 0) && (Weapon_info[Weapons[b->instance].weapon_info_index].subtype == WP_LASER)) {
3502 				return 1;
3503 			}
3504 		} else {
3505 			// don't ever collide against laser weapons - duh
3506 			if(Weapon_info[Weapons[b->instance].weapon_info_index].subtype == WP_LASER){
3507 				return 1;
3508 			}
3509 		}
3510 		break;
3511 	}
3512 
3513 	float beam_radius = bm->beam_collide_width * bm->current_width_factor * 0.5f;
3514 	// do a cylinder-sphere collision test
3515 	if (!fvi_cylinder_sphere_may_collide(&bm->last_start, &bm->last_shot,
3516 		beam_radius, &b->pos, b->radius * 1.2f)) {
3517 		return 1;
3518 	}
3519 
3520 	// don't cull
3521 	return 0;
3522 }
3523 
3524 // add a collision to the beam for this frame (to be evaluated later)
3525 // Goober5000 - erg.  Rearranged for clarity, and also to fix a bug that caused is_exit_collision to hardly ever be assigned,
3526 // resulting in "tooled" ships taking twice as much damage (in a later function) as they should.
beam_add_collision(beam * b,object * hit_object,mc_info * cinfo,int quadrant_num,bool exit_flag)3527 void beam_add_collision(beam *b, object *hit_object, mc_info *cinfo, int quadrant_num, bool exit_flag)
3528 {
3529 	beam_collision *bc = nullptr;
3530 	int idx;
3531 
3532 	// if we haven't reached the limit for beam collisions, just add it
3533 	if (b->f_collision_count < MAX_FRAME_COLLISIONS) {
3534 		bc = &b->f_collisions[b->f_collision_count++];
3535 	}
3536 	// otherwise, we've got to do some checking, ick.
3537 	// I guess we can always just remove the farthest item
3538 	else {
3539 		for (idx = 0; idx < MAX_FRAME_COLLISIONS; idx++) {
3540 			if ((bc == nullptr) || (b->f_collisions[idx].cinfo.hit_dist > bc->cinfo.hit_dist))
3541 				bc = &b->f_collisions[idx];
3542 		}
3543 	}
3544 
3545 	if (bc == nullptr) {
3546 		Int3();
3547 		return;
3548 	}
3549 
3550 	// copy in
3551 	bc->c_objnum = OBJ_INDEX(hit_object);
3552 	bc->cinfo = *cinfo;
3553 	bc->quadrant = quadrant_num;
3554 	bc->is_exit_collision = exit_flag;
3555 
3556 	// let the hud shield gauge know when Player or Player target is hit
3557 	if (quadrant_num >= 0)
3558 		hud_shield_quadrant_hit(hit_object, quadrant_num);
3559 }
3560 
3561 // sort collisions for the frame
beam_sort_collisions_func(const beam_collision & b1,const beam_collision & b2)3562 bool beam_sort_collisions_func(const beam_collision &b1, const beam_collision &b2)
3563 {
3564 	return (b1.cinfo.hit_dist < b2.cinfo.hit_dist);
3565 }
3566 
3567 // handle a hit on a specific object
beam_handle_collisions(beam * b)3568 void beam_handle_collisions(beam *b)
3569 {
3570 	int idx, s_idx;
3571 	beam_collision r_coll[MAX_FRAME_COLLISIONS];
3572 	int r_coll_count = 0;
3573 	weapon_info *wi;
3574 	float width;
3575 
3576 	// early out if we had no collisions
3577 	if(b->f_collision_count <= 0){
3578 		return;
3579 	}
3580 
3581 	// get beam weapon info
3582 	if((b->weapon_info_index < 0) || (b->weapon_info_index >= weapon_info_size())){
3583 		Int3();
3584 		return;
3585 	}
3586 	wi = &Weapon_info[b->weapon_info_index];
3587 
3588 	// get the width of the beam
3589 	width = b->beam_collide_width * b->current_width_factor;
3590 
3591 	// the first thing we need to do is sort the collisions, from closest to farthest
3592 	std::sort(b->f_collisions, b->f_collisions + b->f_collision_count, beam_sort_collisions_func);
3593 
3594 	float damage_time_mod = (flFrametime * 1000.0f) / i2fl(BEAM_DAMAGE_TIME);
3595 	float real_damage = wi->damage * damage_time_mod;
3596 
3597 	// now apply all collisions until we reach a ship which "stops" the beam or we reach the end of the list
3598 	for(idx=0; idx<b->f_collision_count; idx++){
3599 		int model_num = -1;
3600 		int apply_beam_physics = 0;
3601 		int draw_effects = 1;
3602 		int first_hit = 1;
3603 		int target = b->f_collisions[idx].c_objnum;
3604 
3605 		// if we have an invalid object
3606 		if((target < 0) || (target >= MAX_OBJECTS)){
3607 			continue;
3608 		}
3609 
3610 		// try and get a model to deal with
3611 		model_num = beam_get_model(&Objects[target]);
3612 		if(model_num < 0){
3613 			continue;
3614 		}
3615 
3616 		if (wi->wi_flags[Weapon::Info_Flags::Huge]) {
3617 			if (Objects[target].type == OBJ_SHIP) {
3618 				ship_type_info *sti;
3619 				sti = ship_get_type_info(&Objects[target]);
3620 				if (sti->flags[Ship::Type_Info_Flags::No_huge_impact_eff])
3621 					draw_effects = 0;
3622 			}
3623 		}
3624 
3625 		//Don't draw effects if we're in the cockpit of the hit ship
3626 		if (Viewer_obj == &Objects[target])
3627 			draw_effects = 0;
3628 
3629 		// add to the recent collision list
3630 		r_coll[r_coll_count].c_objnum = target;
3631 		r_coll[r_coll_count].c_sig = Objects[target].signature;
3632 		r_coll[r_coll_count].c_stamp = -1;
3633 		r_coll[r_coll_count].cinfo = b->f_collisions[idx].cinfo;
3634 		r_coll[r_coll_count].quadrant = -1;
3635 		r_coll[r_coll_count].is_exit_collision = false;
3636 
3637 		// if he was already on the recent collision list, copy his timestamp
3638 		// also, be sure not to play the impact sound again.
3639 		for(s_idx=0; s_idx<b->r_collision_count; s_idx++){
3640 			if((r_coll[r_coll_count].c_objnum == b->r_collisions[s_idx].c_objnum) && (r_coll[r_coll_count].c_sig == b->r_collisions[s_idx].c_sig)){
3641 				// timestamp
3642 				r_coll[r_coll_count].c_stamp = b->r_collisions[s_idx].c_stamp;
3643 
3644 				// don't play the impact sound again
3645 				first_hit = 0;
3646 			}
3647 		}
3648 
3649 		// if the physics timestamp has expired or is not set yet, apply physics
3650 		if((r_coll[r_coll_count].c_stamp == -1) || timestamp_elapsed(r_coll[r_coll_count].c_stamp))
3651         {
3652             float time_compression = f2fl(Game_time_compression);
3653             float delay_time = i2fl(BEAM_DAMAGE_TIME) / time_compression;
3654             apply_beam_physics = 1;
3655             r_coll[r_coll_count].c_stamp = timestamp(fl2i(delay_time));
3656 		}
3657 
3658 		// increment collision count
3659 		r_coll_count++;
3660 
3661 		// play the impact sound
3662 		if ( first_hit && (wi->impact_snd.isValid()) ) {
3663 			snd_play_3d( gamesnd_get_game_sound(wi->impact_snd), &b->f_collisions[idx].cinfo.hit_point_world, &Eye_position );
3664 		}
3665 
3666 		// KOMET_EXT -->
3667 
3668 		// draw flash, explosion
3669 		if (draw_effects &&
3670 		    ((wi->piercing_impact_effect.isValid()) || (wi->flash_impact_weapon_expl_effect.isValid()))) {
3671 			float rnd = frand();
3672 			int do_expl = 0;
3673 			if ((rnd < 0.2f || apply_beam_physics) && wi->impact_weapon_expl_effect.isValid()) {
3674 				do_expl = 1;
3675 			}
3676 			vec3d temp_pos, temp_local_pos;
3677 
3678 			vm_vec_sub(&temp_pos, &b->f_collisions[idx].cinfo.hit_point_world, &Objects[target].pos);
3679 			vm_vec_rotate(&temp_local_pos, &temp_pos, &Objects[target].orient);
3680 
3681 			vec3d worldNormal;
3682 			if (Objects[target].type == OBJ_SHIP) {
3683 				auto shipp = &Ships[Objects[target].instance];
3684 				model_instance_find_world_dir(&worldNormal,
3685 											  &b->f_collisions[idx].cinfo.hit_normal,
3686 											  shipp->model_instance_num,
3687 											  b->f_collisions[idx].cinfo.submodel_num,
3688 											  &Objects[target].orient);
3689 			} else {
3690 				// Just assume that we don't need to handle model subobjects here
3691 				vm_vec_unrotate(&worldNormal, &b->f_collisions[idx].cinfo.hit_normal, &Objects[target].orient);
3692 			}
3693 
3694 			if (wi->flash_impact_weapon_expl_effect.isValid()) {
3695 				auto particleSource = particle::ParticleManager::get()->createSource(wi->flash_impact_weapon_expl_effect);
3696 				particleSource.moveToObject(&Objects[target], &temp_local_pos);
3697 				particleSource.setOrientationNormal(&worldNormal);
3698 
3699 				vec3d fvec;
3700 				vm_vec_sub(&fvec, &b->last_shot, &b->last_start);
3701 
3702 				if (!IS_VEC_NULL(&fvec)) {
3703 					particleSource.setOrientationFromVec(&fvec);
3704 				}
3705 
3706 				particleSource.finish();
3707 			}
3708 
3709 			if(do_expl){
3710 				auto particleSource = particle::ParticleManager::get()->createSource(wi->impact_weapon_expl_effect);
3711 				particleSource.moveToObject(&Objects[target], &temp_local_pos);
3712 				particleSource.setOrientationNormal(&worldNormal);
3713 
3714 				vec3d fvec;
3715 				vm_vec_sub(&fvec, &b->last_shot, &b->last_start);
3716 
3717 				if (!IS_VEC_NULL(&fvec)) {
3718 					particleSource.setOrientationFromVec(&fvec);
3719 				}
3720 
3721 				particleSource.finish();
3722 			}
3723 
3724 			if (wi->piercing_impact_effect.isValid()) {
3725 				vec3d fvec;
3726 				vm_vec_sub(&fvec, &b->last_shot, &b->last_start);
3727 
3728 				if(!IS_VEC_NULL(&fvec)){
3729 					// get beam direction
3730 
3731 					int ok_to_draw = 0;
3732 
3733 					if (beam_will_tool_target(b, &Objects[target])) {
3734 						ok_to_draw = 1;
3735 
3736 						if (Objects[target].type == OBJ_SHIP) {
3737 							ship *shipp = &Ships[Objects[target].instance];
3738 
3739 							if (shipp->armor_type_idx != -1) {
3740 								if (Armor_types[shipp->armor_type_idx].GetPiercingType(wi->damage_type_idx) == SADTF_PIERCING_RETAIL) {
3741 									ok_to_draw = 0;
3742 								}
3743 							}
3744 						}
3745 					} else {
3746 						ok_to_draw = 0;
3747 
3748 						if (Objects[target].type == OBJ_SHIP) {
3749 							float draw_limit, hull_pct;
3750 							int dmg_type_idx, piercing_type;
3751 
3752 							ship *shipp = &Ships[Objects[target].instance];
3753 
3754 							hull_pct = Objects[target].hull_strength / shipp->ship_max_hull_strength;
3755 							dmg_type_idx = wi->damage_type_idx;
3756 							draw_limit = Ship_info[shipp->ship_info_index].piercing_damage_draw_limit;
3757 
3758 							if (shipp->armor_type_idx != -1) {
3759 								piercing_type = Armor_types[shipp->armor_type_idx].GetPiercingType(dmg_type_idx);
3760 								if (piercing_type == SADTF_PIERCING_DEFAULT) {
3761 									draw_limit = Armor_types[shipp->armor_type_idx].GetPiercingLimit(dmg_type_idx);
3762 								} else if ((piercing_type == SADTF_PIERCING_NONE) || (piercing_type == SADTF_PIERCING_RETAIL)) {
3763 									draw_limit = -1.0f;
3764 								}
3765 							}
3766 
3767 							if ((draw_limit != -1.0f) && (hull_pct <= draw_limit))
3768 								ok_to_draw = 1;
3769 						}
3770 					}
3771 
3772 					if (ok_to_draw){
3773 						vm_vec_normalize_quick(&fvec);
3774 
3775 						// stream of fire for big ships
3776 						if (width <= Objects[target].radius * BEAM_AREA_PERCENT) {
3777 							auto particleSource = particle::ParticleManager::get()->createSource(wi->piercing_impact_effect);
3778 							particleSource.moveTo(&b->f_collisions[idx].cinfo.hit_point_world);
3779 							particleSource.setOrientationFromNormalizedVec(&fvec);
3780 							particleSource.setOrientationNormal(&worldNormal);
3781 
3782 							particleSource.finish();
3783 						}
3784 					}
3785 				}
3786 			}
3787 			// <-- KOMET_EXT
3788 		} else {
3789 			if(draw_effects && apply_beam_physics && !physics_paused){
3790 				// maybe draw an explosion, if we aren't hitting shields
3791 				if ((wi->impact_weapon_expl_effect.isValid()) && (b->f_collisions[idx].quadrant < 0)) {
3792 					vec3d worldNormal;
3793 					if (Objects[target].type == OBJ_SHIP) {
3794 						auto shipp = &Ships[Objects[target].instance];
3795 						model_instance_find_world_dir(&worldNormal,
3796 													  &b->f_collisions[idx].cinfo.hit_normal,
3797 													  shipp->model_instance_num,
3798 													  b->f_collisions[idx].cinfo.submodel_num,
3799 													  &Objects[target].orient);
3800 					} else {
3801 						// Just assume that we don't need to handle model subobjects here
3802 						vm_vec_unrotate(&worldNormal, &b->f_collisions[idx].cinfo.hit_normal, &Objects[target].orient);
3803 					}
3804 
3805 					auto particleSource = particle::ParticleManager::get()->createSource(wi->impact_weapon_expl_effect);
3806 					particleSource.moveTo(&b->f_collisions[idx].cinfo.hit_point_world);
3807 					particleSource.setOrientationNormal(&worldNormal);
3808 
3809 					vec3d fvec;
3810 					vm_vec_sub(&fvec, &b->last_shot, &b->last_start);
3811 
3812 					if (!IS_VEC_NULL(&fvec)) {
3813 						particleSource.setOrientationFromVec(&fvec);
3814 					}
3815 
3816 					particleSource.finish();
3817 				}
3818 			}
3819 		}
3820 
3821 		if(!physics_paused){
3822 
3823 			switch(Objects[target].type){
3824 			case OBJ_DEBRIS:
3825 				// hit the debris - the debris hit code takes care of checking for MULTIPLAYER_CLIENT, etc
3826 				debris_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, wi->damage);
3827 				break;
3828 
3829 			case OBJ_WEAPON:
3830 				if (The_mission.ai_profile->flags[AI::Profile_Flags::Beams_damage_weapons]) {
3831 					if (!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER) {
3832 						object *trgt = &Objects[target];
3833 
3834 						if (trgt->hull_strength > 0) {
3835 							float attenuation = 1.0f;
3836 							if ((b->damage_threshold >= 0.0f) && (b->damage_threshold < 1.0f)) {
3837 								float dist = vm_vec_dist(&b->f_collisions[idx].cinfo.hit_point_world, &b->last_start);
3838 								float range = b->range;
3839 								float atten_dist = range * b->damage_threshold;
3840 								if ((range > dist) && (atten_dist < dist)) {
3841 									attenuation = 1 - ((dist - atten_dist) / (range - atten_dist));
3842 								}
3843 							}
3844 
3845 							float damage = real_damage * attenuation;
3846 
3847 							int dmg_type_idx = wi->damage_type_idx;
3848 
3849 							weapon_info* trgt_wip = &Weapon_info[Weapons[trgt->instance].weapon_info_index];
3850 							if (trgt_wip->armor_type_idx != -1)
3851 								damage = Armor_types[trgt_wip->armor_type_idx].GetDamage(damage, dmg_type_idx, 1.0f, true);
3852 
3853 							trgt->hull_strength -= damage;
3854 
3855 							if (trgt->hull_strength < 0) {
3856 								Weapons[trgt->instance].weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon);
3857 								weapon_hit(trgt, NULL, &trgt->pos);
3858 							}
3859 						} else {
3860 							if (!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER) {
3861 								Weapons[trgt->instance].weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon);
3862 								weapon_hit(&Objects[target], NULL, &Objects[target].pos);
3863 							}
3864 						}
3865 
3866 
3867 					}
3868 				} else {
3869 					// detonate the missile
3870 					Assert(Weapon_info[Weapons[Objects[target].instance].weapon_info_index].subtype == WP_MISSILE);
3871 
3872 					if (!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER) {
3873 						Weapons[Objects[target].instance].weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon);
3874 						weapon_hit(&Objects[target], NULL, &Objects[target].pos);
3875 					}
3876 				}
3877 				break;
3878 
3879 			case OBJ_ASTEROID:
3880 				// hit the asteroid
3881 				if (!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER) {
3882 					asteroid_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, wi->damage);
3883 				}
3884 				break;
3885 			case OBJ_SHIP:
3886 				// hit the ship - again, the innards of this code handle multiplayer cases
3887 				// maybe vaporize ship.
3888 				//only apply damage if the collision is not an exit collision.  this prevents twice the damage from being done, although it probably be more realistic since two holes are being punched in the ship instead of one.
3889 				if (!b->f_collisions[idx].is_exit_collision) {
3890 					real_damage = beam_get_ship_damage(b, &Objects[target], &b->f_collisions[idx].cinfo.hit_point_world) * damage_time_mod;
3891 					ship_apply_local_damage(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, real_damage, wi->damage_type_idx, b->f_collisions[idx].quadrant);
3892 				}
3893 				// if this is the first hit on the player ship. whack him
3894 				if(apply_beam_physics)
3895 				{
3896 					beam_apply_whack(b, &Objects[target], &b->f_collisions[idx].cinfo.hit_point_world);
3897 				}
3898 				break;
3899 			}
3900 		}
3901 
3902 		// if the radius of the target is somewhat close to the radius of the beam, "stop" the beam here
3903 		// for now : if its smaller than about 1/3 the radius of the ship
3904 		if(width <= (Objects[target].radius * BEAM_AREA_PERCENT) && !beam_will_tool_target(b, &Objects[target])){
3905 			// set last_shot so we know where to properly draw the beam
3906 			b->last_shot = b->f_collisions[idx].cinfo.hit_point_world;
3907 			Assert(is_valid_vec(&b->last_shot));
3908 
3909 			// done wif the beam
3910 			break;
3911 		}
3912 	}
3913 
3914 	// store the new recent collisions
3915 	for(idx=0; idx<r_coll_count; idx++){
3916 		b->r_collisions[idx] = r_coll[idx];
3917 	}
3918 	b->r_collision_count = r_coll_count;
3919 }
3920 
3921 // if it is legal for the beam to fire, or continue firing
beam_ok_to_fire(beam * b)3922 int beam_ok_to_fire(beam *b)
3923 {
3924 	if (b->objp == NULL) {	// If we don't have a firing object, none of these checks make sense.
3925 		return 1;
3926 	}
3927 	// if my own object is invalid, stop firing
3928 	if (b->objp->signature != b->sig) {
3929 		mprintf(("BEAM : killing beam because of invalid parent object SIGNATURE!\n"));
3930 		return -1;
3931 	}
3932 
3933 	// if my own object is a ghost
3934 	if (b->objp->type != OBJ_SHIP) {
3935 		mprintf(("BEAM : killing beam because of invalid parent object TYPE!\n"));
3936 		return -1;
3937 	}
3938 
3939 	// type C beams are ok to fire all the time
3940 	if (b->type == BEAM_TYPE_C) {
3941 		ship *shipp = &Ships[b->objp->instance];
3942 
3943 		if (shipp->weapon_energy <= 0.0f ) {
3944 
3945 			if ( OBJ_INDEX(Player_obj) == shipp->objnum && !(b->life_left>0.0f)) {
3946 				extern void ship_maybe_play_primary_fail_sound();
3947 				ship_maybe_play_primary_fail_sound();
3948 			}
3949 
3950 			return 0;
3951 		} else {
3952 			return 1;
3953 		}
3954 	}
3955 
3956 	if (b->subsys == NULL) {	// IF we don't have a firing turret, none of these checks make sense.
3957 		return 1;
3958 	}
3959 
3960 	if (!(b->flags & BF_FORCE_FIRING)) {
3961 		// if the shooting turret is destroyed
3962 		if (b->subsys->current_hits <= 0.0f) {
3963 			mprintf(("BEAM : killing beam because turret has been destroyed!\n"));
3964 			return -1;
3965 		}
3966 
3967 		// kill it if its disrupted
3968 		if (ship_subsys_disrupted(b->subsys)) {
3969 			return -1;
3970 		}
3971 
3972 		// if the beam will be firing out of its FOV, power it down
3973 		vec3d aim_dir;
3974 		vm_vec_sub(&aim_dir, &b->last_shot, &b->last_start);
3975 		vm_vec_normalize(&aim_dir);
3976 
3977 		if (The_mission.ai_profile->flags[AI::Profile_Flags::Force_beam_turret_fov]) {
3978 			vec3d turret_normal;
3979 
3980 			if (b->flags & BF_IS_FIGHTER_BEAM) {
3981 				turret_normal = b->objp->orient.vec.fvec;
3982                 b->subsys->system_info->flags.remove(Model::Subsystem_Flags::Turret_restricted_fov);
3983 			} else {
3984 				model_instance_find_world_dir(&turret_normal, &b->subsys->system_info->turret_norm, Ships[b->objp->instance].model_instance_num, b->subsys->system_info->subobj_num, &b->objp->orient, true);
3985 			}
3986 
3987 			if (!(turret_fov_test(b->subsys, &turret_normal, &aim_dir))) {
3988 				nprintf(("BEAM", "BEAM : powering beam down because of FOV condition!\n"));
3989 				return 0;
3990 			}
3991 		} else {
3992 			vec3d turret_dir, turret_pos, temp;
3993 			beam_get_global_turret_gun_info(b->objp, b->subsys, &turret_pos, &turret_dir, 1, &temp, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
3994 			if (vm_vec_dot(&aim_dir, &turret_dir) < b->subsys->system_info->turret_fov) {
3995 				nprintf(("BEAM", "BEAM : powering beam down because of FOV condition!\n"));
3996 				return 0;
3997 			}
3998 		}
3999 	}
4000 
4001 	// ok to fire/continue firing
4002 	return 1;
4003 }
4004 
4005 // apply a whack to a ship
beam_apply_whack(beam * b,object * objp,vec3d * hit_point)4006 void beam_apply_whack(beam *b, object *objp, vec3d *hit_point)
4007 {
4008 	weapon_info *wip;
4009 	ship *shipp;
4010 
4011 	// sanity
4012 	Assert((b != NULL) && (objp != NULL) && (hit_point != NULL));
4013 	if((b == NULL) || (objp == NULL) || (hit_point == NULL)){
4014 		return;
4015 	}
4016 	Assert(b->weapon_info_index >= 0);
4017 	wip = &Weapon_info[b->weapon_info_index];
4018 	Assert((objp != NULL) && (objp->type == OBJ_SHIP) && (objp->instance >= 0) && (objp->instance < MAX_SHIPS));
4019 	if((objp == NULL) || (objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
4020 		return;
4021 	}
4022 	shipp = &Ships[objp->instance];
4023 	if((shipp->ai_index < 0) || (shipp->ai_index >= MAX_AI_INFO)){
4024 		return;
4025 	}
4026 
4027 	// don't whack docked ships
4028 	// Goober5000 - whacking docked ships should work now, so whack them
4029 	// Goober5000 - weapons with no mass don't whack (bypass the calculations)
4030 	if(wip->mass == 0.0f) {
4031 		return;
4032 	}
4033 
4034 	// determine how big of a whack to apply
4035 	float whack;
4036 
4037 	// this if block was added by Bobboau to make beams whack properly while preserving reverse compatibility
4038 	if(wip->mass == 100.0f){
4039 		if(wip->damage < b_whack_damage){
4040 			whack = b_whack_small;
4041 		} else {
4042 			whack = b_whack_big;
4043 		}
4044 	}else{
4045 		whack = wip->mass;
4046 	}
4047 
4048 	// whack direction
4049 	vec3d whack_dir;
4050 	vm_vec_sub(&whack_dir, &b->last_shot, &b->last_start); // Valathil - use the beam direction as the force direction (like a high pressure water jet)
4051 	vm_vec_normalize(&whack_dir);
4052 	vm_vec_scale(&whack_dir, whack);
4053 
4054 	// apply the whack
4055 	ship_apply_whack(&whack_dir, hit_point, objp);
4056 }
4057 
4058 // return the amount of damage which should be applied to a ship. basically, filters friendly fire damage
beam_get_ship_damage(beam * b,object * objp,vec3d * hitpos)4059 float beam_get_ship_damage(beam *b, object *objp, vec3d* hitpos)
4060 {
4061 	// if the beam is on the same team as the object
4062 	if ( (objp == NULL) || (b == NULL) ) {
4063 		Int3();
4064 		return 0.0f;
4065 	}
4066 
4067 	if ( (objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS) ) {
4068 		Int3();
4069 		return 0.0f;
4070 	}
4071 
4072 	weapon_info *wip = &Weapon_info[b->weapon_info_index];
4073 
4074 	if (wip->damage <= 0)
4075 		return 0.0f;	// Not much point in calculating the attenuation if the beam doesn't hurt in the first place.
4076 
4077 	float attenuation = 1.0f;
4078 
4079 	if ((b->damage_threshold >= 0.0f) && (b->damage_threshold < 1.0f)) {
4080 		float dist = hitpos ? vm_vec_dist(hitpos, &b->last_start) : 0.0f;
4081 		float range = b->range;
4082 		float atten_dist = range * b->damage_threshold;
4083 		if ((range > dist) && (atten_dist < dist)) {
4084 			attenuation =  1 - ((dist - atten_dist) / (range - atten_dist));
4085 		}
4086 	}
4087 
4088 	float damage = 0.0f;
4089 
4090 	// same team. yikes
4091 	if ( (b->team == Ships[objp->instance].team) && (wip->damage > The_mission.ai_profile->beam_friendly_damage_cap[Game_skill_level]) ) {
4092 		damage = The_mission.ai_profile->beam_friendly_damage_cap[Game_skill_level] * attenuation;
4093 	} else {
4094 		// normal damage
4095 		damage = wip->damage * attenuation;
4096 	}
4097 
4098 	return damage;
4099 }
4100 
4101 // if the beam is likely to tool a given target before its lifetime expires
beam_will_tool_target(beam * b,object * objp)4102 int beam_will_tool_target(beam *b, object *objp)
4103 {
4104 	weapon_info *wip = &Weapon_info[b->weapon_info_index];
4105 	float total_strength, damage_in_a_few_seconds, hp_limit, hp_pct;
4106 
4107 	// sanity
4108 	if(objp == NULL){
4109 		return 0;
4110 	}
4111 
4112 	// if the object is not a ship, bail
4113 	if(objp->type != OBJ_SHIP){
4114 		return 0;
4115 	}
4116 	if((objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
4117 		return 0;
4118 	}
4119 
4120 	ship *shipp = &Ships[objp->instance];
4121 	total_strength = objp->hull_strength;
4122 
4123 	if (shipp->armor_type_idx != -1) {
4124 		if (Armor_types[shipp->armor_type_idx].GetPiercingType(wip->damage_type_idx) == SADTF_PIERCING_NONE) {
4125 			return 0;
4126 		}
4127 		hp_limit = Armor_types[shipp->armor_type_idx].GetPiercingLimit(wip->damage_type_idx);
4128 		if (hp_limit > 0.0f) {
4129 			hp_pct = total_strength / shipp->ship_max_hull_strength;
4130 			if (hp_limit >= hp_pct)
4131 				return 1;
4132 		}
4133 	}
4134 
4135 	// calculate total strength, factoring in shield
4136 	if (!(wip->wi_flags[Weapon::Info_Flags::Pierce_shields]))
4137 		total_strength += shield_get_strength(objp);
4138 
4139 	// if the beam is going to apply more damage in about 1 and a half than the ship can take
4140 	damage_in_a_few_seconds = (TOOLTIME / (float)BEAM_DAMAGE_TIME) * wip->damage;
4141 	return (damage_in_a_few_seconds > total_strength);
4142 }
4143 
4144 float beam_accuracy = 1.0f;
4145 DCF(b_aim, "Adjusts the beam accuracy factor (Default is 1.0f)")
4146 {
4147 	dc_stuff_float(&beam_accuracy);
4148 }
4149 DCF(beam_list, "Lists all beams")
4150 {
4151 	int b_count = 0;
4152 
4153 	for (auto &wi : Weapon_info) {
4154 		if (wi.wi_flags[Weapon::Info_Flags::Beam]) {
4155 			++b_count;
4156 			dc_printf("Beam %d : %s\n", b_count, wi.name);
4157 		}
4158 	}
4159 }
4160