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, ¢er, &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, ¢er, &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(¢er, &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