2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/Weapon/Beam.cpp $
15 * all sorts of cool stuff about ships
18 * Revision 1.5 2002/06/17 06:33:11 relnev
19 * ryan's struct patch for gcc 2.95
21 * Revision 1.4 2002/06/09 04:41:29 relnev
22 * added copyright header
24 * Revision 1.3 2002/06/02 00:31:36 relnev
25 * implemented osregistry
27 * Revision 1.2 2002/05/07 03:16:53 theoddone33
28 * The Great Newline Fix
30 * Revision 1.1.1.1 2002/05/03 03:28:11 root
34 * 68 9/09/99 11:40p Dave
35 * Handle an Assert() in beam code. Added supernova sounds. Play the right
36 * 2 end movies properly, based upon what the player did in the mission.
38 * 67 9/09/99 2:36p Mikek
39 * Put back in the "1.0f +" in BEAM_TYPE_A aiming. Not the best way to
42 * 65 9/08/99 10:29p Dave
43 * Make beam sound pausing and unpausing much safer.
45 * 64 9/06/99 12:46a Andsager
46 * Add weapon_explosion_ani LOD
48 * 63 9/03/99 5:12p Mikek
49 * Change miss_factor code, making it a lot more likely for beams to miss
50 * and also making them increasingly likely to miss with each subsequent
51 * shot in the multi-shot burst.
53 * 62 8/30/99 5:01p Dave
54 * Made d3d do less state changing in the nebula. Use new chat server for
57 * 61 8/28/99 7:29p Dave
58 * Fixed wingmen persona messaging. Make sure locked turrets don't count
59 * towards the # attacking a player.
61 * 60 8/27/99 9:07p Dave
62 * LOD explosions. Improved beam weapon accuracy.
64 * 59 8/26/99 10:15a Dave
65 * Don't apply beam whacks to docked ships.
67 * 58 7/31/99 1:16p Dave
68 * Use larger font for 1024 HUD flash text box. Make beam weapons aware of
69 * weapon subsystem damage on firing ship.
71 * 57 7/22/99 4:00p Dave
72 * Fixed beam weapon muzzle glow rendering. Externalized hud shield info.
74 * 56 7/19/99 7:20p Dave
75 * Beam tooling. Specialized player-killed-self messages. Fixed d3d nebula
78 * 55 7/15/99 9:20a Andsager
79 * FS2_DEMO initial checkin
81 * 54 7/09/99 5:54p Dave
82 * Seperated cruiser types into individual types. Added tons of new
83 * briefing icons. Campaign screen.
85 * 53 7/08/99 10:53a Dave
86 * New multiplayer interpolation scheme. Not 100% done yet, but still
87 * better than the old way.
89 * 52 7/02/99 10:51p Dave
90 * Limit friendly beam fire damage. :(
92 * 51 7/01/99 11:44a Dave
93 * Updated object sound system to allow multiple obj sounds per ship.
94 * Added hit-by-beam sound. Added killed by beam sound.
96 * 50 6/29/99 7:39p Dave
97 * Lots of small bug fixes.
99 * 49 6/29/99 2:53p Dave
100 * Re-enabled beam lighting.
102 * 48 6/25/99 3:04p Dave
105 * 47 6/24/99 3:00p Dave
108 * 46 6/23/99 4:49p Dave
109 * Temporarily removed beam lighting.
111 * 45 6/21/99 7:25p Dave
112 * netplayer pain packet. Added type E unmoving beams.
114 * 44 6/18/99 5:16p Dave
115 * Added real beam weapon lighting. Fixed beam weapon sounds. Added MOTD
116 * dialog to PXO screen.
118 * 43 6/14/99 10:45a Dave
119 * Made beam weapons specify accuracy by skill level in the weapons.tbl
121 * 42 6/09/99 2:55p Andsager
122 * Allow multiple asteroid subtypes (of large, medium, small) and follow
125 * 41 6/08/99 6:02p Jamesa
126 * Handle case where type B beam start and end directions are really
129 * 40 6/04/99 2:16p Dave
130 * Put in shrink effect for beam weapons.
132 * 39 5/14/99 11:47a Andsager
133 * Added beam_get_weapon_info_index(object *bm)
135 * 38 5/12/99 10:43a Andsager
136 * Increase max beam length
138 * 37 5/08/99 8:25p Dave
139 * Upped object pairs. First run of nebula lightning.
141 * 36 5/05/99 9:02p Dave
142 * Fixed D3D aabitmap rendering. Spiffed up nebula effect a bit (added
143 * rotations, tweaked values, made bitmap selection more random). Fixed
144 * D3D beam weapon clipping problem. Added D3d frame dumping.
146 * 35 5/03/99 9:07a Dave
147 * Pirate Bob. Changed beam test code a bit.
149 * 34 4/27/99 12:16a Dave
150 * Fixed beam weapon muzzle glow problem. Fixed premature timeout on the
151 * pxo server list screen. Fixed secondary firing for hosts on a
152 * standalone. Fixed wacky multiplayer weapon "shuddering" problem.
154 * 33 4/25/99 6:12p Johnson
155 * Fixed bug where multi-shot beams were getting the shot count from the
158 * 32 4/25/99 3:36p Dave
159 * Fixed nebula table code. Tweaked beam weapon explosion stuff.
161 * 31 4/23/99 2:33p Johnson
162 * Allow beams to shoot at missiles properly.
164 * 30 4/23/99 12:01p Johnson
165 * Added SIF_HUGE_SHIP
167 * 29 4/22/99 11:06p Dave
168 * Final pass at beam weapons. Solidified a lot of stuff. All that remains
169 * now is to tweak and fix bugs as they come up. No new beam weapon
172 * 28 4/21/99 6:15p Dave
173 * Did some serious housecleaning in the beam code. Made it ready to go
174 * for anti-fighter "pulse" weapons. Fixed collision pair creation. Added
175 * a handy macro for recalculating collision pairs for a given object.
177 * 27 4/20/99 6:39p Dave
178 * Almost done with artillery targeting. Added support for downloading
179 * images on the PXO screen.
181 * 26 4/19/99 11:01p Dave
182 * More sophisticated targeting laser support. Temporary checkin.
184 * 25 4/19/99 4:54p Johnson
185 * Removed infinite loop from beam_render_all() oops :)
187 * 24 4/16/99 5:54p Dave
188 * Support for on/off style "stream" weapons. Real early support for
189 * target-painting lasers.
191 * 23 4/04/99 2:13p Dave
192 * Put in area effect beam weapons. May be a bit too expensive though.
194 * 22 4/02/99 9:55a Dave
195 * Added a few more options in the weapons.tbl for beam weapons. Attempt
196 * at putting "pain" packets into multiplayer.
198 * 21 3/31/99 8:24p Dave
199 * Beefed up all kinds of stuff, incluging beam weapons, nebula effects
200 * and background nebulae. Added per-ship non-dimming pixel colors.
202 * 20 3/08/99 7:03p Dave
203 * First run of new object update system. Looks very promising.
205 * 19 3/04/99 6:09p Dave
206 * Added in sexpressions for firing beams and checking for if a ship is
209 * 18 3/02/99 9:25p Dave
210 * Added a bunch of model rendering debug code. Started work on fixing
211 * beam weapon wacky firing.
213 * 17 2/21/99 1:48p Dave
214 * Some code for monitoring datarate for multiplayer in detail.
216 * 16 2/11/99 3:08p Dave
217 * PXO refresh button. Very preliminary squad war support.
219 * 15 2/05/99 3:23p Mattf
220 * Made beams a little more forgiving when checking object types.
222 * 14 2/05/99 12:52p Dave
223 * Fixed Glide nondarkening textures.
225 * 13 2/04/99 6:29p Dave
226 * First full working rev of FS2 PXO support. Fixed Glide lighting
229 * 12 1/30/99 9:02p Dave
232 * 11 1/30/99 1:29a Dave
233 * Fixed nebula thumbnail problem. Full support for 1024x768 choose pilot
234 * screen. Fixed beam weapon death messages.
236 * 10 1/29/99 7:08p Johnson
237 * Put in fix for beam weapons which are on dying ships.
239 * 9 1/29/99 12:47a Dave
240 * Put in sounds for beam weapon. A bunch of interface screens (tech
243 * 8 1/27/99 9:56a Dave
244 * Temporary checkin of beam weapons for Dan to make cool sounds.
246 * 7 1/24/99 11:37p Dave
247 * First full rev of beam weapons. Very customizable. Removed some bogus
248 * Int3()'s in low level net code.
250 * 6 1/21/99 2:06p Dave
251 * Final checkin for multiplayer testing.
253 * 5 1/21/99 10:45a Dave
254 * More beam weapon stuff. Put in warmdown time.
256 * 4 1/14/99 12:48a Dave
257 * Todo list bug fixes. Made a pass at putting briefing icons back into
258 * FRED. Sort of works :(
260 * 3 1/12/99 12:53a Dave
261 * More work on beam weapons - made collision detection very efficient -
262 * collide against all object types properly - made 3 movement types
263 * smooth. Put in test code to check for possible non-darkening pixels on
266 * 2 1/08/99 2:08p Dave
267 * Fixed software rendering for pofview. Super early support for AWACS and
274 #include "linklist.h"
276 #include "objcollide.h"
278 #include "freespace.h"
281 #include "alphacolors.h"
283 #include "fireballs.h"
285 #include "asteroid.h"
287 #include "multimsgs.h"
289 #include "particle.h"
295 #include "hudmessage.h"
297 #include "lighting.h"
299 // ------------------------------------------------------------------------------------------------
300 // BEAM WEAPON DEFINES/VARS
303 // use this to extend a beam to "infinity"
304 #define BEAM_FAR_LENGTH 30000.0f
306 // this is the constant which defines when a beam is an "area" beam. meaning, when we switch on sphereline checking and when
307 // a beam gets "stopped" by an object. It is a percentage of the object radius which the beam must be wider than
308 #define BEAM_AREA_PERCENT 0.4f
310 // randomness factor - all beam weapon aiming is adjusted by +/- some factor within this range
311 #define BEAM_RANDOM_FACTOR 0.4f
314 #define BEAM_DAMAGE_TIME 170 // apply damage
315 #define MAX_SHOT_POINTS 30
316 #define SHOT_POINT_TIME 200 // 5 arcs a second
318 #define TOOLTIME 1500.0f
320 // max # of collisions we'll allow per frame
321 #define MAX_FRAME_COLLISIONS 5
324 #define BF_SAFETY (1<<0) // if this is set, don't collide or render for this frame. lifetime still increases though
325 #define BF_SHRINK (1<<1) // if this is set, the beam is in the warmdown phase
327 // beam struct (the actual weapon/object)
328 typedef struct beam {
330 int objnum; // our own objnum
331 int weapon_info_index;
332 int sig; // signature for the shooting object
333 object *objp; // the shooting object (who owns the turret that I am being fired from)
334 object *target; // target object
335 ship_subsys *target_subsys; // targeted subsys
336 int target_sig; // target sig
337 ship_subsys *subsys; // subsys its being fired from
338 beam *next, *prev; // link list stuff
339 vector targeting_laser_offset;
340 int framecount; // how many frames the beam has been active
341 int flags; // see BF_* defines
342 float shrink; // shrink factor
345 int warmup_stamp; // timestamp for "warming up"
346 int warmdown_stamp; // timestamp for "warming down"
347 int type; // see BEAM_TYPE_* defines in beam.h
348 float life_left; // in seconds
349 float life_total; // total life
350 // this vector has very special meaning. BEFORE performing collision checks, it basically implies a "direction". meaning
351 // the vector between it and last_start is where the beam will be aiming. AFTER performing collision checks, it is the
352 // literal world collision point on the object (or meaningless, if we hit nothing). The function beam_move_all_pre() is
353 // responsible for then setting it up pre-collision time
356 int shot_index; // for type D beam weapons
359 beam_collision r_collisions[MAX_FRAME_COLLISIONS]; // recent collisions
360 int r_collision_count; // # of recent collisions
362 // collision info for this frame
363 beam_collision f_collisions[MAX_FRAME_COLLISIONS]; // collisions for the current frame
364 int f_collision_count; // # of collisions we recorded this frame
366 // looping sound info, HANDLE
367 int beam_sound_loop; // -1 if none
372 // exactly how the beam will behave. by passing this is multiplayer from server to client, we can ensure that
373 // everything looks the same
377 beam Beams[MAX_BEAMS]; // all beams
378 beam Beam_free_list; // free beams
379 beam Beam_used_list; // used beams
380 int Beam_count = 0; // how many beams are in use
382 // octant indices. These are "good" pairs of octants to use for beam target
383 #define BEAM_NUM_GOOD_OCTANTS 8
384 int Beam_good_slash_octants[BEAM_NUM_GOOD_OCTANTS][4] = {
385 { 2, 5, 1, 0 }, // octant, octant, min/max pt, min/max pt
394 int Beam_good_shot_octants[BEAM_NUM_GOOD_OCTANTS][4] = {
395 { 5, 0, 1, 0 }, // octant, octant, min/max pt, min/max pt
405 // damage cap values for friendly beam fire
406 float Beam_friendly_cap[NUM_SKILL_LEVELS] = { 0.0f, 5.0f, 10.0f, 20.0f, 30.0f };
408 // beam lighting effects
409 int Beam_lighting = 1;
411 // debug stuff - keep track of how many collision tests we perform a second and how many we toss a second
412 #define BEAM_TEST_STAMP_TIME 4000 // every 4 seconds
413 int Beam_test_stamp = -1;
414 int Beam_test_ints = 0;
415 int Beam_test_ship = 0;
416 int Beam_test_ast = 0;
417 int Beam_test_framecount = 0;
419 // beam warmup completion %
420 #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 )
422 // beam warmdown completion %
423 #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 )
425 // timestamp for spewing muzzle particles
426 int Beam_muzzle_stamp = -1;
428 // link into the physics paused system
429 extern int physics_paused;
431 // beam lighting info
432 #define MAX_BEAM_LIGHT_INFO 100
433 typedef struct beam_light_info {
434 beam *bm; // beam casting the light
435 int objnum; // object getting light cast on it
436 ubyte source; // 0 to light the shooter, 1 for lighting any ship the beam passes, 2 to light the collision ship
437 vector c_point; // collision point for type 2 lights
440 beam_light_info Beam_lights[MAX_BEAM_LIGHT_INFO];
441 int Beam_light_count = 0;
443 float b_whack_small = 500.0f;
444 float b_whack_big = 1500.0f;
445 float b_whack_damage = 150.0f;
446 DCF(b_whack_small, "")
448 dc_get_arg(ARG_FLOAT);
449 b_whack_small = Dc_arg_float;
453 dc_get_arg(ARG_FLOAT);
454 b_whack_big = Dc_arg_float;
456 DCF(b_whack_damage, "")
458 dc_get_arg(ARG_FLOAT);
459 b_whack_damage = Dc_arg_float;
463 // ------------------------------------------------------------------------------------------------
464 // BEAM WEAPON FORWARD DECLARATIONS
468 void beam_delete(beam *b);
470 // handle a hit on a specific object
471 void beam_handle_collisions(beam *b);
474 void beam_get_binfo(beam *b, float accuracy, int num_shots);
476 // aim the beam (setup last_start and last_shot - the endpoints). also recalculates object collision info
477 void beam_aim(beam *b);
480 void beam_type_a_move(beam *b);
483 void beam_type_b_move(beam *b);
486 void beam_type_c_move(beam *b);
489 void beam_type_d_move(beam *b);
490 void beam_type_d_get_status(beam *b, int *shot_index, int *fire_wait);
493 void beam_type_e_move(beam *b);
495 // given a model #, and an object, stuff 2 good world coord points
496 void beam_get_octant_points(int modelnum, object *objp, int oct_index, int oct_array[BEAM_NUM_GOOD_OCTANTS][4], vector *v1, vector *v2);
498 // given an object, return its model num
499 int beam_get_model(object *objp);
501 // for a given object, and a firing beam, determine its critical dot product and range
502 void beam_get_cull_vals(object *objp, beam *b, float *cull_dot, float *cull_dist);
504 // get the total possible cone for a given beam in radians
505 float beam_get_cone_dot(beam *b);
507 // for rendering the beam effect
508 // output top and bottom vectors
509 // fvec == forward vector (eye viewpoint basically. in world coords)
510 // pos == world coordinate of the point we're calculating "around"
511 // w == width of the diff between top and bottom around pos
512 void beam_calc_facing_pts(vector *top, vector *bot, vector *fvec, vector *pos, float w, float z_add);
514 // render the muzzle glow for a beam weapon
515 void beam_render_muzzle_glow(beam *b);
517 // generate particles for the muzzle glow
518 void beam_generate_muzzle_particles(beam *b);
520 // throw some jitter into the aim - based upon shot_aim
521 void beam_jitter_aim(beam *b, float aim);
523 // if it is legal for the beam to continue firing
524 // returns -1 if the beam should stop firing immediately
525 // returns 0 if the beam should go to warmdown
526 // returns 1 if the beam can continue along its way
527 int beam_ok_to_fire(beam *b);
529 // start the warmup phase for the beam
530 void beam_start_warmup(beam *b);
532 // start the firing phase for the beam, return 0 if the beam failed to start, and should be deleted altogether
533 int beam_start_firing(beam *b);
535 // start the warmdown phase for the beam
536 void beam_start_warmdown(beam *b);
538 // add a collision to the beam for this frame (to be evaluated later)
539 void beam_add_collision(beam *b, object *hit_object, mc_info *cinfo);
541 // sort collisions for the frame
542 int beam_sort_collisions_func(const void *e1, const void *e2);
544 // get the width of the widest section of the beam
545 float beam_get_widest(beam *b);
547 // mark an object as being lit
548 void beam_add_light(beam *b, int objnum, int source, vector *c_point);
550 // apply lighting from any beams
551 void beam_apply_lighting();
553 // recalculate beam sounds (looping sounds relative to the player)
554 void beam_recalc_sounds(beam *b);
556 // apply a whack to a ship
557 void beam_apply_whack(beam *b, object *objp, vector *hit_point);
559 // return the amount of damage which should be applied to a ship. basically, filters friendly fire damage
560 float beam_get_ship_damage(beam *b, object *objp);
562 // if the beam is likely to tool a given target before its lifetime expires
563 int beam_will_tool_target(beam *b, object *objp);
566 // ------------------------------------------------------------------------------------------------
567 // BEAM WEAPON FUNCTIONS
570 // init at game startup
574 list_init( &Beam_free_list );
575 list_init( &Beam_used_list );
578 // initialize beam weapons for this level
579 void beam_level_init()
585 list_init( &Beam_free_list );
586 list_init( &Beam_used_list );
587 memset(Beams, 0, sizeof(beam) * MAX_BEAMS);
589 // Link all object slots into the free list
590 for (idx=0; idx<MAX_BEAMS; idx++) {
591 Beams[idx].objnum = -1;
592 list_append(&Beam_free_list, &Beams[idx] );
595 // reset muzzle particle spew timestamp
596 Beam_muzzle_stamp = -1;
599 // shutdown beam weapons for this level
600 void beam_level_close()
603 list_init( &Beam_free_list );
604 list_init( &Beam_used_list );
607 // fire a beam, returns nonzero on success. the innards of the code handle all the rest, foo
608 int beam_fire(beam_fire_info *fire_info)
616 if(fire_info == NULL){
621 // if we're out of beams, bail
622 if(Beam_count >= MAX_BEAMS){
626 // for now, only allow ship targets
627 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)){
631 // make sure the beam_info_index is valid
632 Assert((fire_info->beam_info_index >= 0) && (fire_info->beam_info_index < MAX_WEAPON_TYPES) && (Weapon_info[fire_info->beam_info_index].wi_flags & WIF_BEAM));
633 if((fire_info->beam_info_index < 0) || (fire_info->beam_info_index >= MAX_WEAPON_TYPES) || !(Weapon_info[fire_info->beam_info_index].wi_flags & WIF_BEAM)){
636 wip = &Weapon_info[fire_info->beam_info_index];
638 // make sure a ship is firing this
639 Assert((fire_info->shooter->type == OBJ_SHIP) && (fire_info->shooter->instance >= 0) && (fire_info->shooter->instance < MAX_SHIPS));
640 if((fire_info->shooter->type != OBJ_SHIP) || (fire_info->shooter->instance < 0) && (fire_info->shooter->instance >= MAX_SHIPS)){
643 firing_ship = &Ships[fire_info->shooter->instance];
646 new_item = GET_FIRST(&Beam_free_list);
647 Assert( new_item != &Beam_free_list ); // shouldn't have the dummy element
648 if(new_item == &Beam_free_list){
652 // remove from the free list
653 list_remove( &Beam_free_list, new_item );
655 // insert onto the end of used list
656 list_append( &Beam_used_list, new_item );
661 // fill in some values
662 new_item->warmup_stamp = -1;
663 new_item->warmdown_stamp = -1;
664 new_item->weapon_info_index = fire_info->beam_info_index;
665 new_item->objp = fire_info->shooter;
666 new_item->sig = fire_info->shooter->signature;
667 new_item->subsys = fire_info->turret;
668 new_item->life_left = wip->b_info.beam_life;
669 new_item->life_total = wip->b_info.beam_life;
670 new_item->r_collision_count = 0;
671 new_item->f_collision_count = 0;
672 new_item->target = fire_info->target;
673 new_item->target_subsys = fire_info->target_subsys;
674 new_item->target_sig = fire_info->target->signature;
675 new_item->beam_sound_loop = -1;
676 new_item->type = wip->b_info.beam_type;
677 new_item->targeting_laser_offset = fire_info->targeting_laser_offset;
678 new_item->framecount = 0;
680 new_item->shot_index = 0;
681 new_item->shrink = 1.0f;
682 new_item->team = (char)firing_ship->team;
684 // if the targeted subsystem is not NULL, force it to be a type A beam
685 if(new_item->target_subsys != NULL){
686 new_item->type = BEAM_TYPE_A;
689 // type D weapons can only fire at small ships and missiles
690 if(new_item->type == BEAM_TYPE_D){
691 // if its a targeted ship, get the target ship
692 if((fire_info->target != NULL) && (fire_info->target->type == OBJ_SHIP) && (fire_info->target->instance >= 0)){
693 ship *target_ship = &Ships[fire_info->target->instance];
695 // maybe force to be a type A
696 if(Ship_info[target_ship->ship_info_index].flags & (SIF_BIG_SHIP | SIF_CAPITAL | SIF_SUPERCAP | SIF_FREIGHTER | SIF_DRYDOCK | SIF_CARGO)){
697 new_item->type = BEAM_TYPE_A;
702 // ----------------------------------------------------------------------
703 // THIS IS THE CRITICAL POINT FOR MULTIPLAYER
704 // beam_get_binfo(...) determines exactly how the beam will behave over the course of its life
705 // it fills in binfo, which we can pass to clients in multiplayer
706 if(fire_info->beam_info_override != NULL){
707 new_item->binfo = *fire_info->beam_info_override;
709 beam_get_binfo(new_item, fire_info->accuracy, wip->b_info.beam_shots); // to fill in b_info - the set of directional aim vectors
712 // create the associated object
713 objnum = obj_create(OBJ_BEAM, -1, new_item - Beams, &vmd_identity_matrix, &vmd_zero_vector, 1.0f, OF_COLLIDES);
716 beam_delete(new_item);
717 nprintf(("General", "obj_create() failed for beam weapon! bah!\n"));
720 new_item->objnum = objnum;
722 // this sets up all info for the first frame the beam fires
723 beam_aim(new_item); // to fill in shot_point, etc.
725 // check to see if its legal to fire at this guy
726 if(beam_ok_to_fire(new_item) != 1){
727 beam_delete(new_item);
728 mprintf(("Killing beam at initial fire because of illegal targeting!!!\n"));
732 // if we're a multiplayer master - send a packet
733 if(MULTIPLAYER_MASTER){
734 send_beam_fired_packet(fire_info->shooter, fire_info->turret, fire_info->target, fire_info->beam_info_index, &new_item->binfo);
737 // start the warmup phase
738 beam_start_warmup(new_item);
743 // fire a targeting beam, returns objnum on success. a much much simplified version of a beam weapon
744 // targeting lasers last _one_ frame. For a continuous stream - they must be created every frame.
745 // this allows it to work smoothly in multiplayer (detect "trigger down". every frame just create a targeting laser firing straight out of the
746 // object. this way you get all the advantages of nice rendering and collisions).
747 // NOTE : only references beam_info_index and shooter
748 int beam_fire_targeting(beam_fire_info *fire_info)
756 if(fire_info == NULL){
761 // if we're out of beams, bail
762 if(Beam_count >= MAX_BEAMS){
766 // make sure the beam_info_index is valid
767 Assert((fire_info->beam_info_index >= 0) && (fire_info->beam_info_index < MAX_WEAPON_TYPES) && (Weapon_info[fire_info->beam_info_index].wi_flags & WIF_BEAM));
768 if((fire_info->beam_info_index < 0) || (fire_info->beam_info_index >= MAX_WEAPON_TYPES) || !(Weapon_info[fire_info->beam_info_index].wi_flags & WIF_BEAM)){
771 wip = &Weapon_info[fire_info->beam_info_index];
773 // make sure a ship is firing this
774 Assert((fire_info->shooter->type == OBJ_SHIP) && (fire_info->shooter->instance >= 0) && (fire_info->shooter->instance < MAX_SHIPS));
775 if((fire_info->shooter->type != OBJ_SHIP) || (fire_info->shooter->instance < 0) && (fire_info->shooter->instance >= MAX_SHIPS)){
778 firing_ship = &Ships[fire_info->shooter->instance];
782 new_item = GET_FIRST(&Beam_free_list);
783 Assert( new_item != &Beam_free_list ); // shouldn't have the dummy element
785 // remove from the free list
786 list_remove( &Beam_free_list, new_item );
788 // insert onto the end of used list
789 list_append( &Beam_used_list, new_item );
794 // maybe allocate some extra data based on the beam type
795 Assert(wip->b_info.beam_type == BEAM_TYPE_C);
796 if(wip->b_info.beam_type != BEAM_TYPE_C){
800 // fill in some values
801 new_item->warmup_stamp = -1;
802 new_item->warmdown_stamp = -1;
803 new_item->weapon_info_index = fire_info->beam_info_index;
804 new_item->objp = fire_info->shooter;
806 new_item->subsys = NULL;
807 new_item->life_left = 0;
808 new_item->life_total = 0;
809 new_item->r_collision_count = 0;
810 new_item->f_collision_count = 0;
811 new_item->target = NULL;
812 new_item->target_subsys = NULL;
813 new_item->target_sig = 0;
814 new_item->beam_sound_loop = -1;
815 new_item->type = BEAM_TYPE_C;
816 new_item->targeting_laser_offset = fire_info->targeting_laser_offset;
817 new_item->framecount = 0;
819 new_item->shot_index = 0;
820 new_item->team = (char)firing_ship->team;
822 // type c is a very special weapon type - binfo has no meaning
824 // create the associated object
825 objnum = obj_create(OBJ_BEAM, -1, new_item - Beams, &vmd_identity_matrix, &vmd_zero_vector, 1.0f, OF_COLLIDES);
828 beam_delete(new_item);
829 nprintf(("General", "obj_create() failed for beam weapon! bah!\n"));
832 new_item->objnum = objnum;
834 // this sets up all info for the first frame the beam fires
835 beam_aim(new_item); // to fill in shot_point, etc.
840 // return an object index of the guy who's firing this beam
841 int beam_get_parent(object *bm)
845 // get a handle to the beam
846 Assert(bm->type == OBJ_BEAM);
847 Assert(bm->instance >= 0);
848 if(bm->type != OBJ_BEAM){
851 if(bm->instance < 0){
854 b = &Beams[bm->instance];
856 Assert(b->objp != NULL);
861 // if the object handle is invalid
862 if(b->objp->signature != b->sig){
867 return OBJ_INDEX(b->objp);
870 // return weapon_info_index of beam
871 int beam_get_weapon_info_index(object *bm)
873 Assert(bm->type == OBJ_BEAM);
874 if (bm->type != OBJ_BEAM) {
878 Assert(bm->instance >= 0 && bm->instance < MAX_BEAMS);
879 if (bm->instance < 0) {
883 // return weapon_info_index
884 return Beams[bm->instance].weapon_info_index;
889 // given a beam object, get the # of collisions which happened during the last collision check (typically, last frame)
890 int beam_get_num_collisions(int objnum)
893 if((objnum < 0) || (objnum >= MAX_OBJECTS)){
897 if((Objects[objnum].instance < 0) || (Objects[objnum].instance >= MAX_BEAMS)){
901 if((Beams[Objects[objnum].instance].objnum != objnum) || (Beams[Objects[objnum].instance].objnum < 0)){
906 // return the # of recent collisions
907 return Beams[Objects[objnum].instance].r_collision_count;
910 // stuff collision info, returns 1 on success
911 int beam_get_collision(int objnum, int num, int *collision_objnum, mc_info **cinfo)
914 if((objnum < 0) || (objnum >= MAX_OBJECTS)){
918 if((Objects[objnum].instance < 0) || (Objects[objnum].instance >= MAX_BEAMS)){
922 if((Beams[Objects[objnum].instance].objnum != objnum) || (Beams[Objects[objnum].instance].objnum < 0)){
926 if(num >= Beams[Objects[objnum].instance].r_collision_count){
932 *cinfo = &Beams[Objects[objnum].instance].r_collisions[num].cinfo;
933 *collision_objnum = Beams[Objects[objnum].instance].r_collisions[num].c_objnum;
937 // pause all looping beam sounds
938 void beam_pause_sounds()
942 // set all beam volumes to 0
943 moveup = GET_FIRST(&Beam_used_list);
947 while(moveup != END_OF_LIST(&Beam_used_list)){
948 // set the volume to 0, if he has a looping beam sound
949 if(moveup->beam_sound_loop >= 0){
950 snd_set_volume(moveup->beam_sound_loop, 0.0f);
954 moveup = GET_NEXT(moveup);
958 // unpause looping beam sounds
959 void beam_unpause_sounds()
963 // recalc all beam sounds
964 moveup = GET_FIRST(&Beam_used_list);
968 while(moveup != END_OF_LIST(&Beam_used_list)){
969 beam_recalc_sounds(moveup);
972 moveup = GET_NEXT(moveup);
977 // -----------------------------===========================------------------------------
978 // BEAM MOVEMENT FUNCTIONS
979 // -----------------------------===========================------------------------------
981 // move a type A beam weapon
982 void beam_type_a_move(beam *b)
987 // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
988 // get the "originating point" of the beam for this frame. essentially bashes last_start
989 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2);
991 // if the "warming up" timestamp has not expired
992 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
996 // put the "last_shot" point arbitrarily far away
997 vm_vec_sub(&dir, &b->last_shot, &b->last_start);
998 vm_vec_normalize_quick(&dir);
999 vm_vec_scale_add(&b->last_shot, &b->last_start, &dir, BEAM_FAR_LENGTH);
1000 Assert(is_valid_vec(&b->last_shot));
1003 // move a type B beam weapon
1004 #define BEAM_T(b) ( ((b->binfo.delta_ang / b->life_total) * (b->life_total - b->life_left)) / b->binfo.delta_ang )
1005 void beam_type_b_move(beam *b)
1011 // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
1012 // get the "originating point" of the beam for this frame. essentially bashes last_start
1013 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2);
1015 // if the "warming up" timestamp has not expired
1016 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
1020 // if the two direction vectors are _really_ close together, just use the original direction
1021 dot_save = vm_vec_dot(&b->binfo.dir_a, &b->binfo.dir_b);
1022 if((double)dot_save >= 0.999999999){
1023 actual_dir = b->binfo.dir_a;
1025 // otherwise move towards the dir we calculated when firing this beam
1027 vm_vec_interp_constant(&actual_dir, &b->binfo.dir_a, &b->binfo.dir_b, BEAM_T(b));
1030 // now recalculate shot_point to be shooting through our new point
1031 vm_vec_scale_add(&b->last_shot, &b->last_start, &actual_dir, BEAM_FAR_LENGTH);
1032 int is_valid = is_valid_vec(&b->last_shot);
1035 actual_dir = b->binfo.dir_a;
1036 vm_vec_scale_add(&b->last_shot, &b->last_start, &actual_dir, BEAM_FAR_LENGTH);
1041 void beam_type_c_move(beam *b)
1046 if(b->objp == NULL){
1051 // type c beams only last one frame so we never have to "move" them.
1052 temp = b->targeting_laser_offset;
1053 vm_vec_unrotate(&b->last_start, &temp, &b->objp->orient);
1054 vm_vec_add2(&b->last_start, &b->objp->pos);
1055 vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.v.fvec, BEAM_FAR_LENGTH);
1059 void beam_type_d_move(beam *b)
1061 int shot_index, fire_wait;
1062 vector temp, temp2, dir;
1064 // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
1065 // get the "originating point" of the beam for this frame. essentially bashes last_start
1066 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2);
1068 // if the "warming up" timestamp has not expired
1069 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
1073 // determine what stage of the beam we're in
1074 beam_type_d_get_status(b, &shot_index, &fire_wait);
1076 // if we've changed shot index
1077 if(shot_index != b->shot_index){
1078 // set the new index
1079 b->shot_index = shot_index;
1085 // if we're in the fire wait stage
1086 b->flags &= ~BF_SAFETY;
1088 b->flags |= BF_SAFETY;
1091 // put the "last_shot" point arbitrarily far away
1092 vm_vec_sub(&dir, &b->last_shot, &b->last_start);
1093 vm_vec_normalize_quick(&dir);
1094 vm_vec_scale_add(&b->last_shot, &b->last_start, &dir, BEAM_FAR_LENGTH);
1095 Assert(is_valid_vec(&b->last_shot));
1097 void beam_type_d_get_status(beam *b, int *shot_index, int *fire_wait)
1099 float shot_time = b->life_total / (float)b->binfo.shot_count;
1100 float beam_time = b->life_total - b->life_left;
1102 // determine what "shot" we're on
1103 *shot_index = (int)(beam_time / shot_time);
1104 Assert(*shot_index < b->binfo.shot_count);
1105 if(*shot_index >= b->binfo.shot_count){
1106 *shot_index = b->binfo.shot_count - 1;
1109 // determine if its the firing or waiting section of the shot (fire happens first, THEN wait)
1111 if(beam_time > ((shot_time * (*shot_index)) + (shot_time * 0.5f))){
1117 void beam_type_e_move(beam *b)
1119 vector temp, turret_norm;
1121 // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
1122 // get the "originating point" of the beam for this frame. essentially bashes last_start
1123 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &turret_norm, 1, &temp);
1125 // if the "warming up" timestamp has not expired
1126 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
1130 // put the "last_shot" point arbitrarily far away
1131 vm_vec_scale_add(&b->last_shot, &b->last_start, &turret_norm, BEAM_FAR_LENGTH);
1132 Assert(is_valid_vec(&b->last_shot));
1135 // pre-move (before collision checking - but AFTER ALL OTHER OBJECTS HAVE BEEN MOVED)
1136 void beam_move_all_pre()
1141 // zero lights for this frame yet
1142 Beam_light_count = 0;
1144 // traverse through all active beams
1145 moveup = GET_FIRST(&Beam_used_list);
1146 while(moveup != END_OF_LIST(&Beam_used_list)){
1150 // unset collision info
1151 b->f_collision_count = 0;
1153 if(!physics_paused){
1156 // type A beam weapons don't move
1158 beam_type_a_move(b);
1161 // type B beam weapons move across the target somewhat randomly
1163 beam_type_b_move(b);
1166 // type C beam weapons are attached to a fighter - pointing forward
1168 beam_type_c_move(b);
1173 beam_type_d_move(b);
1178 beam_type_e_move(b);
1181 // illegal beam type
1188 moveup = GET_NEXT(moveup);
1192 // post-collision time processing for beams
1193 void beam_move_all_post()
1198 beam_weapon_info *bwi;
1200 // traverse through all active beams
1201 moveup = GET_FIRST(&Beam_used_list);
1202 while(moveup != END_OF_LIST(&Beam_used_list)){
1203 bwi = &Weapon_info[moveup->weapon_info_index].b_info;
1205 // check the status of the beam
1206 bf_status = beam_ok_to_fire(moveup);
1208 // if we're warming up
1209 if(moveup->warmup_stamp != -1){
1210 next_one = GET_NEXT(moveup);
1212 // should we be stopping?
1214 beam_delete(moveup);
1216 // if the warming up timestamp has expired, start firing
1217 if(timestamp_elapsed(moveup->warmup_stamp)){
1219 if(!beam_start_firing(moveup)){
1220 beam_delete(moveup);
1223 // add a muzzle light for the shooter
1224 beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
1232 // if we're warming down
1233 else if(moveup->warmdown_stamp != -1){
1234 next_one = GET_NEXT(moveup);
1236 // should we be stopping?
1238 beam_delete(moveup);
1240 // if we're done warming down, the beam is finished
1241 if(timestamp_elapsed(moveup->warmdown_stamp)){
1242 beam_delete(moveup);
1251 // otherwise, we're firing away.........
1253 // add a muzzle light for the shooter
1254 beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
1256 // subtract out the life left for the beam
1257 if(!physics_paused){
1258 moveup->life_left -= flFrametime;
1261 // if we're past the shrink point, start shrinking the beam
1262 if(moveup->life_left <= (moveup->life_total * bwi->beam_shrink_factor)){
1263 moveup->flags |= BF_SHRINK;
1266 // if we're shrinking the beam
1267 if(moveup->flags & BF_SHRINK){
1268 moveup->shrink -= bwi->beam_shrink_pct * flFrametime;
1269 if(moveup->shrink < 0.1f){
1270 moveup->shrink = 0.1f;
1276 next_one = GET_NEXT(moveup);
1278 // if beam should abruptly stop
1279 if(bf_status == -1){
1280 beam_delete(moveup);
1282 // if the beam should just power down
1284 beam_start_warmdown(moveup);
1292 // increment framecount
1293 moveup->framecount++;
1295 // type c weapons live for one frame only
1296 if(moveup->type == BEAM_TYPE_C){
1297 if(moveup->framecount > 1){
1298 next_one = GET_NEXT(moveup);
1300 beam_delete(moveup);
1305 // done firing, so go into the warmdown phase
1307 if((moveup->life_left <= 0.0f) && (moveup->warmdown_stamp == -1)){
1308 beam_start_warmdown(moveup);
1310 moveup = GET_NEXT(moveup);
1315 // handle any collisions which occured collision (will take care of applying damage to all objects which got hit)
1316 beam_handle_collisions(moveup);
1318 // recalculate beam sounds
1319 beam_recalc_sounds(moveup);
1322 moveup = GET_NEXT(moveup);
1325 // apply all beam lighting
1326 beam_apply_lighting();
1328 // process beam culling info
1331 if(Beam_test_stamp == -1){
1332 Beam_test_stamp = timestamp(BEAM_TEST_STAMP_TIME);
1334 Beam_test_framecount = 0;
1336 if(timestamp_elapsed(Beam_test_stamp)){
1337 // report the results
1338 nprintf(("General", "Performed %f beam ints/frame (%d, %d, %d, %d), over %f seconds\n", (float)Beam_test_ints/(float)Beam_test_framecount, Beam_test_ints, Beam_test_framecount, Beam_test_ship, Beam_test_ast, (float)BEAM_TEST_STAMP_TIME / 1000.0f));
1341 Beam_test_stamp = timestamp(BEAM_TEST_STAMP_TIME);
1345 Beam_test_framecount = 0;
1347 Beam_test_framecount++;
1354 // -----------------------------===========================------------------------------
1355 // BEAM RENDERING FUNCTIONS
1356 // -----------------------------===========================------------------------------
1358 // render a beam weapon
1359 #define STUFF_VERTICES() do { verts[0]->u = 0.0f; verts[0]->v = 0.0f; verts[1]->u = 1.0f; verts[1]->v = 0.0f; verts[2]->u = 1.0f; verts[2]->v = 1.0f; verts[3]->u = 0.0f; verts[3]->v = 1.0f; } while(0);
1360 #define R_VERTICES() do { g3_rotate_vertex(verts[0], &bottom1); g3_rotate_vertex(verts[1], &bottom2); g3_rotate_vertex(verts[2], &top2); g3_rotate_vertex(verts[3], &top1); } while(0);
1361 #define P_VERTICES() do { for(idx=0; idx<4; idx++){ g3_project_vertex(verts[idx]); } } while(0);
1363 void beam_render(beam_weapon_info *bwi, vector *start, vector *shot, float shrink)
1366 vertex h1[4]; // halves of a beam section
1367 vertex *verts[4] = { &h1[0], &h1[1], &h1[2], &h1[3] };
1368 vector fvec, top1, bottom1, top2, bottom2;
1371 // bogus weapon info index
1376 // if the beam start and endpoints are the same
1377 if(vm_vec_same(start, shot)){
1381 // get beam direction
1382 vm_vec_sub(&fvec, shot, start);
1383 vm_vec_normalize_quick(&fvec);
1385 // turn off backface culling
1388 // draw all sections
1389 for(s_idx=0; s_idx<bwi->beam_num_sections; s_idx++){
1390 // calculate the beam points
1391 scale = frand_range(1.0f - bwi->sections[s_idx].flicker, 1.0f + bwi->sections[s_idx].flicker);
1392 beam_calc_facing_pts(&top1, &bottom1, &fvec, start, bwi->sections[s_idx].width * scale * shrink, bwi->sections[s_idx].z_add);
1393 beam_calc_facing_pts(&top2, &bottom2, &fvec, shot, bwi->sections[s_idx].width * scale * scale * shrink, bwi->sections[s_idx].z_add);
1394 R_VERTICES(); // rotate and project the vertices
1396 STUFF_VERTICES(); // stuff the beam with creamy goodness (texture coords)
1398 // set the right texture with additive alpha, and draw the poly
1399 gr_set_bitmap(bwi->sections[s_idx].texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.9999f);
1400 g3_draw_poly( 4, verts, TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT);
1403 // turn backface culling back on
1407 // generate particles for the muzzle glow
1408 int hack_time = 100;
1411 dc_get_arg(ARG_INT);
1412 hack_time = Dc_arg_int;
1414 void beam_generate_muzzle_particles(beam *b)
1419 vector turret_norm, turret_pos, particle_pos, particle_dir, p_temp;
1421 particle_info pinfo;
1423 // if our hack stamp has expired
1424 if(!((Beam_muzzle_stamp == -1) || timestamp_elapsed(Beam_muzzle_stamp))){
1428 // never generate anything past about 1/5 of the beam fire time
1429 if(b->warmup_stamp == -1){
1434 wip = &Weapon_info[b->weapon_info_index];
1436 // no specified particle for this beam weapon
1437 if(wip->b_info.beam_particle_ani < 0){
1441 // reset the hack stamp
1442 Beam_muzzle_stamp = timestamp(hack_time);
1444 // randomly generate 10 to 20 particles
1445 particle_count = (int)frand_range(0.0f, (float)wip->b_info.beam_particle_count);
1447 // get turret info - position and normal
1448 turret_pos = b->subsys->system_info->pnt;
1449 turret_norm = b->subsys->system_info->turret_norm;
1451 // randomly perturb a vector within a cone around the normal
1452 vm_vector_2_matrix(&m, &turret_norm, NULL, NULL);
1453 for(idx=0; idx<particle_count; idx++){
1454 // get a random point in the cone
1455 vm_vec_random_cone(&particle_dir, &turret_norm, wip->b_info.beam_particle_angle, &m);
1456 p_temp = turret_pos;
1457 vm_vec_scale_add(&p_temp, &turret_pos, &particle_dir, wip->b_info.beam_muzzle_radius * frand_range(0.75f, 0.9f));
1459 // transform into world coords
1460 vm_vec_unrotate(&particle_pos, &p_temp, &b->objp->orient);
1461 vm_vec_add2(&particle_pos, &b->objp->pos);
1462 p_temp = particle_dir;
1463 vm_vec_unrotate(&particle_dir, &p_temp, &b->objp->orient);
1465 // now generate some interesting values for the particle
1466 float p_time_ref = wip->b_info.beam_life + ((float)wip->b_info.beam_warmup / 1000.0f);
1467 float p_life = frand_range(p_time_ref * 0.5f, p_time_ref * 0.7f);
1468 float p_vel = (wip->b_info.beam_muzzle_radius / p_life) * frand_range(0.85f, 1.2f);
1469 vm_vec_scale(&particle_dir, -p_vel);
1471 memset(&pinfo, 0, sizeof(particle_info));
1472 pinfo.pos = particle_pos;
1473 pinfo.vel = particle_dir;
1474 pinfo.lifetime = p_life;
1475 pinfo.attached_objnum = -1;
1476 pinfo.attached_sig = 0;
1477 pinfo.rad = wip->b_info.beam_particle_radius;
1479 pinfo.type = PARTICLE_BITMAP;
1480 pinfo.optional_data = wip->b_info.beam_particle_ani;
1481 pinfo.tracer_length = -1.0f;
1482 particle_create(&pinfo);
1486 // render the muzzle glow for a beam weapon
1487 void beam_render_muzzle_glow(beam *b)
1490 weapon_info *wip = &Weapon_info[b->weapon_info_index];
1491 beam_weapon_info *bwi = &Weapon_info[b->weapon_info_index].b_info;
1492 float pct, rand_val;
1494 // if we don't have a glow bitmap
1495 if(bwi->beam_glow_bitmap < 0){
1499 // if the beam is warming up, scale the glow
1500 if(b->warmup_stamp != -1){
1502 pct = BEAM_WARMUP_PCT(b);
1505 // if the beam is warming down
1506 if(b->warmdown_stamp != -1){
1508 pct = 1.0f - BEAM_WARMDOWN_PCT(b);
1511 // otherwise the beam is really firing
1514 rand_val = frand_range(0.90f, 1.0f);
1518 g3_rotate_vertex(&v, &b->last_start);
1519 gr_set_bitmap( bwi->beam_glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.8f * pct);
1521 g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * rand_val, TMAP_FLAG_TEXTURED);
1525 g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * 0.75f * rand_val, TMAP_FLAG_TEXTURED);
1528 g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * 0.45f * rand_val, TMAP_FLAG_TEXTURED);
1531 g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * 0.25f * rand_val, TMAP_FLAG_TEXTURED);
1535 // render all beam weapons
1536 void beam_render_all()
1540 // traverse through all active beams
1541 moveup = GET_FIRST(&Beam_used_list);
1542 while(moveup != END_OF_LIST(&Beam_used_list)){
1543 // each beam type renders a little bit differently
1544 if((moveup->warmup_stamp == -1) && (moveup->warmdown_stamp == -1) && !(moveup->flags & BF_SAFETY)){
1545 // HACK - if this is the first frame the beam is firing, don't render it
1546 if(moveup->life_left >= moveup->life_total - 0.0001f){
1548 moveup = GET_NEXT(moveup);
1552 // render the beam itself
1553 Assert(moveup->weapon_info_index >= 0);
1554 if(moveup->weapon_info_index < 0){
1555 moveup = GET_NEXT(moveup);
1558 beam_render(&Weapon_info[moveup->weapon_info_index].b_info, &moveup->last_start, &moveup->last_shot, moveup->shrink);
1561 // render the muzzle glow
1562 beam_render_muzzle_glow(moveup);
1564 // maybe generate some muzzle particles
1565 beam_generate_muzzle_particles(moveup);
1568 moveup = GET_NEXT(moveup);
1572 // output top and bottom vectors
1573 // fvec == forward vector (eye viewpoint basically. in world coords)
1574 // pos == world coordinate of the point we're calculating "around"
1575 // w == width of the diff between top and bottom around pos
1576 void beam_calc_facing_pts( vector *top, vector *bot, vector *fvec, vector *pos, float w, float z_add )
1583 vm_vec_sub( &rvec, &Eye_position, &temp );
1584 vm_vec_normalize( &rvec );
1586 vm_vec_crossprod(&uvec,fvec,&rvec);
1587 vm_vec_normalize(&uvec);
1589 vm_vec_scale_add( top, &temp, &uvec, w/2.0f );
1590 vm_vec_scale_add( bot, &temp, &uvec, -w/2.0f );
1593 // light scale factor
1594 float blight = 25.5f;
1597 dc_get_arg(ARG_FLOAT);
1598 blight = Dc_arg_float;
1601 // call to add a light source to a small object
1602 void beam_add_light_small(beam *bm, object *objp, vector *pt_override = NULL)
1605 beam_weapon_info *bwi;
1618 Assert(objp != NULL);
1622 Assert(bm->weapon_info_index >= 0);
1623 wip = &Weapon_info[bm->weapon_info_index];
1627 noise = frand_range(1.0f - bwi->sections[0].flicker, 1.0f + bwi->sections[0].flicker);
1629 // widest part of the beam
1630 float light_rad = beam_get_widest(bm) * blight * noise;
1632 // nearest point on the beam, and its distance to the ship
1634 if(pt_override == NULL){
1636 vm_vec_dist_to_line(&objp->pos, &bm->last_start, &bm->last_shot, &near_pt, &dist);
1637 if(dist > light_rad){
1641 near_pt = *pt_override;
1644 // average rgb of the beam
1645 float fr = (float)wip->laser_color_1.red / 255.0f;
1646 float fg = (float)wip->laser_color_1.green / 255.0f;
1647 float fb = (float)wip->laser_color_1.blue / 255.0f;
1649 // add a unique light
1650 // noise *= 0.1f; // a little less noise here, since we want the beam to generally cast a bright light
1651 light_add_point_unique(&near_pt, light_rad * 0.0001f, light_rad, 1.0f, fr, fg, fb, OBJ_INDEX(objp));
1654 // call to add a light source to a large object
1655 void beam_add_light_large(beam *bm, object *objp, vector *pt0, vector *pt1)
1658 beam_weapon_info *bwi;
1671 Assert(objp != NULL);
1675 Assert(bm->weapon_info_index >= 0);
1676 wip = &Weapon_info[bm->weapon_info_index];
1680 noise = frand_range(1.0f - bwi->sections[0].flicker, 1.0f + bwi->sections[0].flicker);
1682 // widest part of the beam
1683 float light_rad = beam_get_widest(bm) * blight * noise;
1685 // average rgb of the beam
1686 float fr = (float)wip->laser_color_1.red / 255.0f;
1687 float fg = (float)wip->laser_color_1.green / 255.0f;
1688 float fb = (float)wip->laser_color_1.blue / 255.0f;
1690 // add a unique light
1691 noise *= 0.1f; // a little less noise here, since we want the beam to generally cast a bright light
1692 light_add_tube(pt0, pt1, 1.0f, light_rad, 1.0f * noise, fr, fg, fb, OBJ_INDEX(objp));
1695 // mark an object as being lit
1696 void beam_add_light(beam *b, int objnum, int source, vector *c_point)
1700 // if we're out of light slots!
1701 if(Beam_light_count >= MAX_BEAM_LIGHT_INFO){
1707 l = &Beam_lights[Beam_light_count++];
1710 l->source = (ubyte)source;
1712 // only type 2 lights (from collisions) need a collision point
1713 if(c_point != NULL){
1714 l->c_point = *c_point;
1716 Assert(source != 2);
1723 // apply lighting from any beams
1724 void beam_apply_lighting()
1729 beam_weapon_info *bwi;
1731 // convert all beam lights into real lights
1732 for(idx=0; idx<Beam_light_count; idx++){
1734 l = &Beam_lights[idx];
1737 if((l->objnum < 0) || (l->objnum >= MAX_OBJECTS) || (l->bm == NULL)){
1741 bwi = &Weapon_info[l->bm->weapon_info_index].b_info;
1743 // different light types
1745 // from the muzzle of the gun
1747 // a few meters in from the of muzzle
1748 vm_vec_sub(&dir, &l->bm->last_start, &l->bm->last_shot);
1749 vm_vec_normalize_quick(&dir);
1750 vm_vec_scale_add(&pt, &l->bm->last_start, &dir, bwi->beam_muzzle_radius * 5.0f);
1752 beam_add_light_small(l->bm, &Objects[l->objnum], &pt);
1755 // from the beam passing by
1758 switch(Objects[l->objnum].type){
1760 Assert(Objects[l->objnum].instance >= 0);
1763 if(Ship_info[Ships[Objects[l->objnum].instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)){
1764 beam_add_light_large(l->bm, &Objects[l->objnum], &l->bm->last_start, &l->bm->last_shot);
1768 beam_add_light_small(l->bm, &Objects[l->objnum]);
1772 // asteroids get small lights
1774 beam_add_light_small(l->bm, &Objects[l->objnum]);
1777 // debris gets small lights
1779 beam_add_light_small(l->bm, &Objects[l->objnum]);
1786 // a few meters from the collision point
1787 vm_vec_sub(&dir, &l->bm->last_start, &l->c_point);
1788 vm_vec_normalize_quick(&dir);
1789 vm_vec_scale_add(&pt, &l->c_point, &dir, bwi->beam_muzzle_radius * 5.0f);
1791 beam_add_light_small(l->bm, &Objects[l->objnum], &pt);
1797 // -----------------------------===========================------------------------------
1798 // BEAM BOOKKEEPING FUNCTIONS
1799 // -----------------------------===========================------------------------------
1802 void beam_delete(beam *b)
1804 // remove from active list and put on free list
1805 list_remove(&Beam_used_list, b);
1806 list_append(&Beam_free_list, b);
1808 // delete our associated object
1809 // Assert(b->objnum >= 0);
1811 obj_delete(b->objnum);
1815 // kill the beam looping sound
1816 if(b->beam_sound_loop != -1){
1817 snd_stop(b->beam_sound_loop);
1818 b->beam_sound_loop = -1;
1823 Assert(Beam_count >= 0);
1824 nprintf(("General", "Recycled beam (%d beams remaining)\n", Beam_count));
1827 // given an object, return its model num
1828 int beam_get_model(object *objp)
1831 Assert(objp->instance >= 0);
1832 if(objp->instance < 0){
1839 return Ships[objp->instance].modelnum;
1842 Assert(Weapons[objp->instance].weapon_info_index >= 0);
1843 if(Weapons[objp->instance].weapon_info_index < 0){
1846 return Weapon_info[Weapons[objp->instance].weapon_info_index].model_num;
1849 Assert(Debris[objp->instance].is_hull);
1850 if(!Debris[objp->instance].is_hull){
1853 return Debris[objp->instance].model_num;
1857 subtype = Asteroids[objp->instance].asteroid_subtype;
1858 Assert(Asteroids[objp->instance].type >= 0);
1859 if(Asteroids[objp->instance].type < 0){
1862 return Asteroid_info[Asteroids[objp->instance].type].model_num[subtype];
1866 // this shouldn't happen too often
1867 mprintf(("Beam couldn't find a good find a good object model/type!! (%d)", objp->type));
1876 // start the warmup phase for the beam
1877 void beam_start_warmup(beam *b)
1879 // set the warmup stamp
1880 b->warmup_stamp = timestamp(Weapon_info[b->weapon_info_index].b_info.beam_warmup);
1882 // start playing warmup sound
1883 if(!(Game_mode & GM_STANDALONE_SERVER) && (Weapon_info[b->weapon_info_index].b_info.beam_warmup_sound >= 0)){
1884 snd_play_3d(&Snds[Weapon_info[b->weapon_info_index].b_info.beam_warmup_sound], &b->last_start, &View_position);
1888 // start the firing phase for the beam, return 0 if the beam failed to start, and should be deleted altogether
1889 int beam_start_firing(beam *b)
1891 // kill the warmup stamp so the rest of the code knows its firing
1892 b->warmup_stamp = -1;
1894 // any special stuff for each weapon type
1896 // re-aim type A and D beam weapons here, otherwise they tend to miss
1915 // determine if we can legitimately start firing, or if we need to take other action
1916 switch(beam_ok_to_fire(b)){
1921 beam_start_warmdown(b);
1925 // start the beam firing sound now, if we haven't already
1926 if((b->beam_sound_loop == -1) && (Weapon_info[b->weapon_info_index].b_info.beam_loop_sound >= 0)){
1927 b->beam_sound_loop = snd_play_3d(&Snds[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);
1930 snd_play_3d(&Snds[SND_BEAM_SHOT], &b->last_start, &View_position);
1937 // start the warmdown phase for the beam
1938 void beam_start_warmdown(beam *b)
1941 b->warmdown_stamp = timestamp(Weapon_info[b->weapon_info_index].b_info.beam_warmdown);
1943 // start the warmdown sound
1944 if(Weapon_info[b->weapon_info_index].b_info.beam_warmdown_sound >= 0){
1945 snd_play_3d(&Snds[Weapon_info[b->weapon_info_index].b_info.beam_warmdown_sound], &b->last_start, &View_position);
1948 // kill the beam looping sound
1949 if(b->beam_sound_loop != -1){
1950 snd_stop(b->beam_sound_loop);
1951 b->beam_sound_loop = -1;
1955 // recalculate beam sounds (looping sounds relative to the player)
1956 void beam_recalc_sounds(beam *b)
1958 beam_weapon_info *bwi;
1961 Assert(b->weapon_info_index >= 0);
1962 if(b->weapon_info_index < 0){
1965 bwi = &Weapon_info[b->weapon_info_index].b_info;
1967 // update the sound position relative to the player
1968 if(b->beam_sound_loop != -1){
1969 // get the point closest to the player's viewing position
1970 switch(vm_vec_dist_to_line(&View_position, &b->last_start, &b->last_shot, &pos, NULL)){
1971 // behind the beam, so use the start pos
1973 pos = b->last_start;
1976 // use the closest point
1978 // already calculated in vm_vec_dist_to_line(...)
1981 // past the beam, so use the shot pos
1987 snd_update_3d_pos(b->beam_sound_loop, &Snds[bwi->beam_loop_sound], &pos);
1992 // -----------------------------===========================------------------------------
1993 // BEAM AIMING FUNCTIONS
1994 // -----------------------------===========================------------------------------
1997 void beam_get_binfo(beam *b, float accuracy, int num_shots)
2002 vector turret_point, turret_norm;
2003 beam_weapon_info *bwi;
2006 // where the shot is originating from (b->last_start gets filled in)
2007 ship_get_global_turret_gun_info(b->objp, b->subsys, &turret_point, &turret_norm, 1, &p2);
2009 // get a model # to work with
2010 model_num = beam_get_model(b->target);
2015 // get beam weapon info
2016 Assert(b->weapon_info_index >= 0);
2017 if(b->weapon_info_index < 0){
2020 bwi = &Weapon_info[b->weapon_info_index].b_info;
2023 skill_level = Game_skill_level;
2024 if(Game_skill_level >= NUM_SKILL_LEVELS){
2025 skill_level = NUM_SKILL_LEVELS - 1;
2027 if(Game_skill_level < 0){
2031 // stuff num shots even though its only used for type D weapons
2032 b->binfo.shot_count = (ubyte)num_shots;
2033 if(b->binfo.shot_count > MAX_BEAM_SHOTS){
2034 b->binfo.shot_count = MAX_BEAM_SHOTS;
2037 // generate the proper amount of directional vectors
2039 // pick an accuracy. beam will be properly aimed at actual fire time
2041 // all we will do is decide whether or not we will hit - type A beam weapons are re-aimed immediately before firing
2042 b->binfo.shot_aim[0] = frand_range(0.0f, 1.0f + bwi->beam_miss_factor[skill_level] * accuracy);
2043 b->binfo.shot_count = 1;
2045 // get random model points, this is useful for big ships, because we never miss when shooting at them
2046 submodel_get_two_random_points(model_num, 0, &b->binfo.dir_a, &b->binfo.dir_b);
2049 // just 2 points in the "slash"
2051 beam_get_octant_points(model_num, b->target, (int)frand_range(0.0f, BEAM_NUM_GOOD_OCTANTS), Beam_good_slash_octants, &oct1, &oct2);
2054 vm_vec_sub(&b->binfo.dir_a, &oct1, &turret_point);
2055 vm_vec_normalize(&b->binfo.dir_a);
2058 vm_vec_sub(&b->binfo.dir_b, &oct2, &turret_point);
2059 vm_vec_normalize(&b->binfo.dir_b);
2062 b->binfo.delta_ang = fl_abs(vm_vec_delta_ang_norm(&b->binfo.dir_a, &b->binfo.dir_b, NULL));
2065 // nothing for this beam - its very special case
2069 // type D beams fire at small ship multiple times
2071 // get a bunch of shot aims
2072 for(idx=0; idx<b->binfo.shot_count; idx++){
2073 // MK, 9/3/99: Added pow() function to make increasingly likely to miss with subsequent shots. 30% more likely with each shot.
2074 float r = ((float) pow(1.3f, idx)) * bwi->beam_miss_factor[skill_level] * accuracy;
2075 b->binfo.shot_aim[idx] = frand_range(0.0f, 1.0f + r);
2079 // type e beams just fire straight
2081 b->binfo.shot_aim[0] = 0.0000001f;
2082 b->binfo.shot_count = 1;
2083 b->binfo.dir_a = turret_norm;
2084 b->binfo.dir_b = turret_norm;
2092 // aim the beam (setup last_start and last_shot - the endpoints). also recalculates collision pairs
2093 void beam_aim(beam *b)
2098 // type C beam weapons have no target
2099 if(b->target == NULL){
2100 Assert(b->type == BEAM_TYPE_C);
2101 if(b->type != BEAM_TYPE_C){
2105 // get a model # to work with
2107 model_num = beam_get_model(b->target);
2113 // setup our initial shot point and aim direction
2116 // where the shot is originating from (b->last_start gets filled in)
2117 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);
2119 // if we're targeting a subsystem - shoot directly at it
2120 if(b->target_subsys != NULL){
2121 // unrotate the center of the subsystem
2122 vm_vec_unrotate(&b->last_shot, &b->target_subsys->system_info->pnt, &b->target->orient);
2123 vm_vec_add2(&b->last_shot, &b->target->pos);
2124 vm_vec_sub(&temp, &b->last_shot, &b->last_start);
2126 vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, 2.0f);
2130 // if we're shooting at a big ship - shoot directly at the model
2131 if((b->target->type == OBJ_SHIP) && (Ship_info[Ships[b->target->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP))){
2132 // rotate into world coords
2133 vm_vec_unrotate(&temp, &b->binfo.dir_a, &b->target->orient);
2134 vm_vec_add2(&temp, &b->target->pos);
2136 // get the shot point
2137 vm_vec_sub(&p2, &temp, &b->last_start);
2138 vm_vec_scale_add(&b->last_shot, &b->last_start, &p2, 2.0f);
2142 // point at the center of the target, then jitter based on shot_aim
2143 b->last_shot = b->target->pos;
2144 beam_jitter_aim(b, b->binfo.shot_aim[0]);
2148 // where the shot is originating from (b->last_start gets filled in)
2149 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);
2151 // set the shot point
2152 vm_vec_scale_add(&b->last_shot, &b->last_start, &b->binfo.dir_a, BEAM_FAR_LENGTH);
2153 Assert(is_valid_vec(&b->last_shot));
2158 temp = b->targeting_laser_offset;
2159 vm_vec_unrotate(&b->last_start, &temp, &b->objp->orient);
2160 vm_vec_add2(&b->last_start, &b->objp->pos);
2161 vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.v.fvec, BEAM_FAR_LENGTH);
2165 // where the shot is originating from (b->last_start gets filled in)
2166 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);
2168 // point at the center of the target, then jitter based on shot_aim
2169 b->last_shot = b->target->pos;
2170 beam_jitter_aim(b, b->binfo.shot_aim[b->shot_index]);
2171 nprintf(("AI", "Frame %i: FIRING\n", Framecount));
2175 // where the shot is originating from (b->last_start gets filled in)
2176 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);
2178 // point directly in the direction of the turret
2179 vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, BEAM_FAR_LENGTH);
2186 // recalculate object pairs
2187 OBJ_RECALC_PAIRS((&Objects[b->objnum]));
2190 // given a model #, and an object, stuff 2 good world coord points
2191 void beam_get_octant_points(int modelnum, object *objp, int oct_index, int oct_array[BEAM_NUM_GOOD_OCTANTS][4], vector *v1, vector *v2)
2193 vector t1, t2, temp;
2194 polymodel *m = model_get(modelnum);
2196 // bad bad bad bad bad bad
2202 Assert((oct_index >= 0) && (oct_index < BEAM_NUM_GOOD_OCTANTS));
2204 // randomly pick octants
2205 t1 = oct_array[oct_index][2] ? m->octants[oct_array[oct_index][0]].max : m->octants[oct_array[oct_index][0]].min;
2206 t2 = oct_array[oct_index][3] ? m->octants[oct_array[oct_index][1]].max : m->octants[oct_array[oct_index][1]].min;
2207 Assert(!vm_vec_same(&t1, &t2));
2209 // get them in world coords
2210 vm_vec_unrotate(&temp, &t1, &objp->orient);
2211 vm_vec_add(v1, &temp, &objp->pos);
2212 vm_vec_unrotate(&temp, &t2, &objp->orient);
2213 vm_vec_add(v2, &temp, &objp->pos);
2216 // throw some jitter into the aim - based upon shot_aim
2217 void beam_jitter_aim(beam *b, float aim)
2219 vector forward, circle;
2221 float subsys_strength;
2223 // if the weapons subsystem is damaged or destroyed
2224 if((b->objp != NULL) && (b->objp->signature == b->sig) && (b->objp->type == OBJ_SHIP) && (b->objp->instance >= 0) && (b->objp->instance < MAX_SHIPS)){
2225 // get subsytem strength
2226 subsys_strength = ship_get_subsystem_strength(&Ships[b->objp->instance], SUBSYSTEM_WEAPONS);
2228 // when subsytem strength is 0, double the aim error factor
2229 aim += aim * (1.0f - subsys_strength);
2232 // shot aim is a direct linear factor of the target model's radius.
2233 // so, pick a random point on the circle
2234 vm_vec_sub(&forward, &b->last_shot, &b->last_start);
2235 vm_vec_normalize_quick(&forward);
2238 vm_vector_2_matrix(&m, &forward, NULL, NULL);
2240 // get a vector on the circle - this should appear to be pretty random
2241 // vm_vec_scale_add(&circle, &b->last_shot, &m.rvec, aim * b->target->radius);
2242 vm_vec_random_in_circle(&circle, &b->last_shot, &m, aim * b->target->radius, 0);
2244 // get the vector pointing to the circle point
2245 vm_vec_sub(&forward, &circle, &b->last_start);
2246 vm_vec_scale_add(&b->last_shot, &b->last_start, &forward, 2.0f);
2250 // -----------------------------===========================------------------------------
2251 // BEAM COLLISION FUNCTIONS
2252 // -----------------------------===========================------------------------------
2254 // collide a beam with a ship, returns 1 if we can ignore all future collisions between the 2 objects
2255 int beam_collide_ship(obj_pair *pair)
2260 mc_info test_collide;
2270 Assert(pair->a->instance >= 0);
2271 Assert(pair->a->type == OBJ_BEAM);
2272 Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2273 b = &Beams[pair->a->instance];
2275 // Don't check collisions for warping out player if past stage 1.
2276 if ( Player->control_mode >= PCM_WARPOUT_STAGE1) {
2277 if ( pair->a == Player_obj ) return 0;
2278 if ( pair->b == Player_obj ) return 0;
2281 // if the "warming up" timestamp has not expired
2282 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
2286 // if the beam is on "safety", don't collide with anything
2287 if(b->flags & BF_SAFETY){
2291 // if the colliding object is the shooting object, return 1 so this is culled
2292 if(pair->b == b->objp){
2296 // try and get a model
2297 model_num = beam_get_model(pair->b);
2309 Assert(pair->b->type == OBJ_SHIP);
2310 Assert(pair->b->instance >= 0);
2311 if((pair->b->type != OBJ_SHIP) || (pair->b->instance < 0)){
2314 shipp = &Ships[pair->b->instance];
2315 sip = &Ship_info[shipp->ship_info_index];
2317 // get the widest portion of the beam
2318 widest = beam_get_widest(b);
2321 test_collide.model_num = model_num;
2322 test_collide.submodel_num = -1;
2323 test_collide.orient = &pair->b->orient;
2324 test_collide.pos = &pair->b->pos;
2325 test_collide.p0 = &b->last_start;
2326 test_collide.p1 = &b->last_shot;
2328 // maybe do a sphereline
2329 if(widest > pair->b->radius * BEAM_AREA_PERCENT){
2330 test_collide.radius = beam_get_widest(b) * 0.5f;
2331 test_collide.flags = MC_CHECK_MODEL | MC_CHECK_SPHERELINE;
2333 test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
2335 model_collide(&test_collide);
2338 if(test_collide.num_hits){
2339 // add to the collision list
2340 beam_add_collision(b, pair->b, &test_collide);
2343 // add this guy to the lighting list
2344 beam_add_light(b, OBJ_INDEX(pair->b), 1, NULL);
2346 // reset timestamp to timeout immediately
2347 pair->next_check_time = timestamp(0);
2352 // collide a beam with an asteroid, returns 1 if we can ignore all future collisions between the 2 objects
2353 int beam_collide_asteroid(obj_pair *pair)
2356 mc_info test_collide;
2365 Assert(pair->a->instance >= 0);
2366 Assert(pair->a->type == OBJ_BEAM);
2367 Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2368 b = &Beams[pair->a->instance];
2370 // if the "warming up" timestamp has not expired
2371 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
2375 // if the beam is on "safety", don't collide with anything
2376 if(b->flags & BF_SAFETY){
2380 // if the colliding object is the shooting object, return 1 so this is culled
2381 if(pair->b == b->objp){
2385 // try and get a model
2386 model_num = beam_get_model(pair->b);
2398 test_collide.model_num = model_num;
2399 test_collide.submodel_num = -1;
2400 test_collide.orient = &pair->b->orient;
2401 test_collide.pos = &pair->b->pos;
2402 test_collide.p0 = &b->last_start;
2403 test_collide.p1 = &b->last_shot;
2404 test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
2405 model_collide(&test_collide);
2408 if(test_collide.num_hits){
2409 // add to the collision list
2410 beam_add_collision(b, pair->b, &test_collide);
2413 // add this guy to the lighting list
2414 beam_add_light(b, OBJ_INDEX(pair->b), 1, NULL);
2416 // reset timestamp to timeout immediately
2417 pair->next_check_time = timestamp(0);
2422 // collide a beam with a missile, returns 1 if we can ignore all future collisions between the 2 objects
2423 int beam_collide_missile(obj_pair *pair)
2426 mc_info test_collide;
2435 Assert(pair->a->instance >= 0);
2436 Assert(pair->a->type == OBJ_BEAM);
2437 Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2438 b = &Beams[pair->a->instance];
2440 // if the "warming up" timestamp has not expired
2441 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
2445 // if the beam is on "safety", don't collide with anything
2446 if(b->flags & BF_SAFETY){
2450 // if the colliding object is the shooting object, return 1 so this is culled
2451 if(pair->b == b->objp){
2455 // try and get a model
2456 model_num = beam_get_model(pair->b);
2467 test_collide.model_num = model_num;
2468 test_collide.submodel_num = -1;
2469 test_collide.orient = &pair->b->orient;
2470 test_collide.pos = &pair->b->pos;
2471 test_collide.p0 = &b->last_start;
2472 test_collide.p1 = &b->last_shot;
2473 test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
2474 model_collide(&test_collide);
2477 if(test_collide.num_hits){
2478 // add to the collision list
2479 beam_add_collision(b, pair->b, &test_collide);
2482 // reset timestamp to timeout immediately
2483 pair->next_check_time = timestamp(0);
2488 // collide a beam with debris, returns 1 if we can ignore all future collisions between the 2 objects
2489 int beam_collide_debris(obj_pair *pair)
2492 mc_info test_collide;
2501 Assert(pair->a->instance >= 0);
2502 Assert(pair->a->type == OBJ_BEAM);
2503 Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2504 b = &Beams[pair->a->instance];
2506 // if the "warming up" timestamp has not expired
2507 if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
2511 // if the beam is on "safety", don't collide with anything
2512 if(b->flags & BF_SAFETY){
2516 // if the colliding object is the shooting object, return 1 so this is culled
2517 if(pair->b == b->objp){
2521 // try and get a model
2522 model_num = beam_get_model(pair->b);
2533 test_collide.model_num = model_num;
2534 test_collide.submodel_num = -1;
2535 test_collide.orient = &pair->b->orient;
2536 test_collide.pos = &pair->b->pos;
2537 test_collide.p0 = &b->last_start;
2538 test_collide.p1 = &b->last_shot;
2539 test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
2540 model_collide(&test_collide);
2543 if(test_collide.num_hits){
2544 // add to the collision list
2545 beam_add_collision(b, pair->b, &test_collide);
2548 // add this guy to the lighting list
2549 beam_add_light(b, OBJ_INDEX(pair->b), 1, NULL);
2551 // reset timestamp to timeout immediately
2552 pair->next_check_time = timestamp(0);
2557 // early-out function for when adding object collision pairs, return 1 if the pair should be ignored
2558 int beam_collide_early_out(object *a, object *b)
2562 float cull_dist, cull_dot;
2563 vector dot_test, dot_test2, dist_test;
2566 Assert(a->instance >= 0);
2567 if(a->instance < 0){
2570 Assert(a->type == OBJ_BEAM);
2571 if(a->type != OBJ_BEAM){
2574 Assert(Beams[a->instance].objnum == OBJ_INDEX(a));
2575 if(Beams[a->instance].objnum != OBJ_INDEX(a)){
2578 bm = &Beams[a->instance];
2579 Assert(bm->weapon_info_index >= 0);
2580 if(bm->weapon_info_index < 0){
2583 bwi = &Weapon_info[bm->weapon_info_index];
2585 // if the second object has an invalid instance, bail
2586 if(b->instance < 0){
2595 // targeting lasers only hit ships
2596 if(bwi->b_info.beam_type == BEAM_TYPE_C){
2601 // targeting lasers only hit ships
2602 if(bwi->b_info.beam_type == BEAM_TYPE_C){
2605 // don't ever collide with non hull pieces
2606 if(!Debris[b->instance].is_hull){
2611 // targeting lasers only hit ships
2612 if(bwi->b_info.beam_type == BEAM_TYPE_C){
2615 // don't ever collide against laser weapons - duh
2616 if(Weapon_info[Weapons[b->instance].weapon_info_index].subtype == WP_LASER){
2622 // get full cull value
2623 beam_get_cull_vals(b, bm, &cull_dot, &cull_dist);
2625 // if the object fails these conditions, bail
2626 vm_vec_sub(&dist_test, &b->pos, &bm->last_start);
2627 dot_test = dist_test;
2628 vm_vec_sub(&dot_test2, &bm->last_shot, &bm->last_start);
2629 vm_vec_normalize_quick(&dot_test);
2630 vm_vec_normalize_quick(&dot_test2);
2631 // cull_dist == DIST SQUARED FOO!
2632 if((vm_vec_dotprod(&dot_test, &dot_test2) < cull_dot) && (vm_vec_mag_squared(&dist_test) > cull_dist)){
2640 // add a collision to the beam for this frame (to be evaluated later)
2641 void beam_add_collision(beam *b, object *hit_object, mc_info *cinfo)
2646 // if we haven't reached the limit for beam collisions, just add
2647 if(b->f_collision_count < MAX_FRAME_COLLISIONS){
2648 bc = &b->f_collisions[b->f_collision_count++];
2649 bc->c_objnum = OBJ_INDEX(hit_object);
2656 // otherwise, we've got to do some checking, ick.
2657 // I guess we can always just remove the farthest item
2659 for(idx=0; idx<MAX_FRAME_COLLISIONS; idx++){
2660 if((bc == NULL) || (b->f_collisions[idx].cinfo.hit_dist > bc->cinfo.hit_dist)){
2661 bc = &b->f_collisions[idx];
2670 bc->c_objnum = OBJ_INDEX(hit_object);
2674 // sort collisions for the frame
2675 int beam_sort_collisions_func(const void *e1, const void *e2)
2677 beam_collision *b1 = (beam_collision*)e1;
2678 beam_collision *b2 = (beam_collision*)e2;
2680 return b1->cinfo.hit_dist < b2->cinfo.hit_dist ? -1 : 1;
2683 // handle a hit on a specific object
2684 void beam_handle_collisions(beam *b)
2687 beam_collision r_coll[MAX_FRAME_COLLISIONS];
2688 int r_coll_count = 0;
2689 beam_weapon_info *bwi;
2693 // early out if we had no collisions
2694 if(b->f_collision_count <= 0){
2698 // get beam weapon info
2699 if((b->weapon_info_index < 0) || (b->weapon_info_index >= Num_weapon_types)){
2703 bwi = &Weapon_info[b->weapon_info_index].b_info;
2704 wi = &Weapon_info[b->weapon_info_index];
2706 // get the widest part of the beam
2707 widest = beam_get_widest(b);
2709 // the first thing we need to do is sort the collisions, from closest to farthest
2710 qsort(b->f_collisions, b->f_collision_count, sizeof(beam_collision), beam_sort_collisions_func);
2712 // now apply all collisions until we reach a ship which "stops" the beam or we reach the end of the list
2713 for(idx=0; idx<b->f_collision_count; idx++){
2717 int target = b->f_collisions[idx].c_objnum;
2719 // if we have an invalid object
2720 if((target < 0) || (target >= MAX_OBJECTS)){
2724 // try and get a model to deal with
2725 model_num = beam_get_model(&Objects[target]);
2731 beam_add_light(b, target, 2, &b->f_collisions[idx].cinfo.hit_point_world);
2733 // add to the recent collision list
2734 r_coll[r_coll_count].c_objnum = target;
2735 r_coll[r_coll_count].c_sig = Objects[target].signature;
2736 r_coll[r_coll_count].c_stamp = -1;
2737 r_coll[r_coll_count].cinfo = b->f_collisions[idx].cinfo;
2739 // if he was already on the recent collision list, copy his timestamp
2740 // also, be sure not to play the impact sound again.
2741 for(s_idx=0; s_idx<b->r_collision_count; s_idx++){
2742 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)){
2744 r_coll[r_coll_count].c_stamp = b->r_collisions[s_idx].c_stamp;
2746 // don't play the impact sound again
2751 // if the damage timestamp has expired or is not set yet, apply damage
2752 if((r_coll[r_coll_count].c_stamp == -1) || timestamp_elapsed(r_coll[r_coll_count].c_stamp)){
2754 r_coll[r_coll_count].c_stamp = timestamp(BEAM_DAMAGE_TIME);
2757 // if no damage - don't even indicate it has been hit
2758 if(wi->damage <= 0){
2762 // increment collision count
2765 // play the impact sound
2767 snd_play_3d( &Snds[wi->impact_snd], &b->f_collisions[idx].cinfo.hit_point_world, &Eye_position );
2771 if(do_damage && !physics_paused){
2772 // maybe draw an explosion
2773 if(wi->impact_weapon_expl_index >= 0){
2774 int ani_handle = weapon_get_expl_handle(wi->impact_weapon_expl_index, &b->f_collisions[idx].cinfo.hit_point_world, wi->impact_explosion_radius);
2775 particle_create( &b->f_collisions[idx].cinfo.hit_point_world, &vmd_zero_vector, 0.0f, wi->impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, ani_handle );
2778 switch(Objects[target].type){
2780 // hit the debris - the debris hit code takes care of checking for MULTIPLAYER_CLIENT, etc
2781 debris_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, Weapon_info[b->weapon_info_index].damage);
2785 // detonate the missile
2786 Assert(Weapon_info[Weapons[Objects[target].instance].weapon_info_index].subtype == WP_MISSILE);
2787 if(!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER){
2788 weapon_hit(&Objects[target], NULL, &Objects[target].pos);
2794 if(!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER){
2795 asteroid_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, Weapon_info[b->weapon_info_index].damage);
2800 // hit the ship - again, the innards of this code handle multiplayer cases
2801 // maybe vaporize ship.
2802 ship_apply_local_damage(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, beam_get_ship_damage(b, &Objects[target]), -1);
2804 // if this is the first hit on the player ship. whack him
2806 beam_apply_whack(b, &Objects[target], &b->f_collisions[idx].cinfo.hit_point_world);
2812 // if the radius of the target is somewhat close to the radius of the beam, "stop" the beam here
2813 // for now : if its smaller than about 1/3 the radius of the ship
2814 if(widest <= (Objects[target].radius * BEAM_AREA_PERCENT) && !beam_will_tool_target(b, &Objects[target])){
2815 // set last_shot so we know where to properly draw the beam
2816 b->last_shot = b->f_collisions[idx].cinfo.hit_point_world;
2817 Assert(is_valid_vec(&b->last_shot));
2819 // done wif the beam
2824 // store the new recent collisions
2825 for(idx=0; idx<r_coll_count; idx++){
2826 b->r_collisions[idx] = r_coll[idx];
2828 b->r_collision_count = r_coll_count;
2831 // for a given object, and a firing beam, determine its critical dot product and range
2832 void beam_get_cull_vals(object *objp, beam *b, float *cull_dot, float *cull_dist)
2835 // debris and asteroids are classified as slow moving small objects
2836 // use cull_dot == potential cone of beam + 10% and 50.0 meters
2839 *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.10f);
2840 *cull_dist = 50.0f * 50.0f;
2843 // treat missiles as fast-moving small objects
2845 *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.5f);
2846 *cull_dist = 300.0f * 300.0f;
2850 // for cap ships, only cull for 90deg or better
2852 if(Ship_info[Ships[objp->instance].ship_info_index].flags & SIF_CAPITAL){
2859 // for large ships, cull at some multiple of the radius
2860 if(Ship_info[Ships[objp->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)){
2861 *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.25f);
2863 *cull_dist = (objp->radius * 1.3f) * (objp->radius * 1.3f);
2867 // for everthing else, cull the same as missiles
2868 *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.5f);
2869 *cull_dist = 300.0f * 300.0f;
2873 // BAD BAD BAD - but this code will cause everything to cull properly
2880 // FIXME - make sure we are truthfull representing the "cone" for all beam types
2881 // get the total possible cone for a given beam in radians
2882 float beam_get_cone_dot(beam *b)
2889 // even though these beams don't move, return a _very_ small value
2890 return (float)cos(fl_radian(50.5f));
2893 return vm_vec_dotprod(&b->binfo.dir_a, &b->binfo.dir_b);
2903 // if it is legal for the beam to fire, or continue firing
2904 int beam_ok_to_fire(beam *b)
2906 // type C beams are ok to fire all the time
2907 if(Weapon_info[b->weapon_info_index].b_info.beam_type == BEAM_TYPE_C){
2911 // if my own object is invalid, stop firing
2912 if(b->objp->signature != b->sig){
2913 mprintf(("BEAM : killing beam because of invalid parent object SIGNATURE!\n"));
2917 // if my own object is a ghost
2918 if(b->objp->type != OBJ_SHIP){
2919 mprintf(("BEAM : killing beam because of invalid parent object TYPE!\n"));
2923 // if the shooting turret is destroyed
2924 if(b->subsys->current_hits <= 0.0f){
2925 mprintf(("BEAM : killing beam because turret has been destroyed!\n"));
2929 // if the beam will be firing out of its FOV, power it down
2930 vector aim_dir, temp;
2931 vector turret_dir, turret_pos;
2932 vm_vec_sub(&aim_dir, &b->last_shot, &b->last_start);
2933 vm_vec_normalize(&aim_dir);
2934 ship_get_global_turret_gun_info(b->objp, b->subsys, &turret_pos, &turret_dir, 1, &temp);
2935 if(vm_vec_dotprod(&aim_dir, &turret_dir) < b->subsys->system_info->turret_fov){
2936 mprintf(("BEAM : powering beam down because of FOV condition!\n"));
2940 // ok to fire/continue firing
2944 // get the width of the widest section of the beam
2945 float beam_get_widest(beam *b)
2948 float widest = -1.0f;
2951 Assert(b->weapon_info_index >= 0);
2952 if(b->weapon_info_index < 0){
2957 for(idx=0; idx<Weapon_info[b->weapon_info_index].b_info.beam_num_sections; idx++){
2958 if(Weapon_info[b->weapon_info_index].b_info.sections[idx].width > widest){
2959 widest = Weapon_info[b->weapon_info_index].b_info.sections[idx].width;
2964 return widest * b->shrink;
2967 // apply a whack to a ship
2968 void beam_apply_whack(beam *b, object *objp, vector *hit_point)
2974 Assert((b != NULL) && (objp != NULL) && (hit_point != NULL));
2975 if((b == NULL) || (objp == NULL) || (hit_point == NULL)){
2978 Assert(b->weapon_info_index >= 0);
2979 wip = &Weapon_info[b->weapon_info_index];
2980 Assert((objp != NULL) && (objp->type == OBJ_SHIP) && (objp->instance >= 0) && (objp->instance < MAX_SHIPS));
2981 if((objp == NULL) || (objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
2984 shipp = &Ships[objp->instance];
2985 if((shipp->ai_index < 0) || (shipp->ai_index >= MAX_AI_INFO)){
2989 // don't whack docking ships
2990 if(Ai_info[shipp->ai_index].ai_flags & AIF_DOCKED){
2994 // determine how big of a whack to apply
2998 if(wip->damage < b_whack_damage){
2999 whack = b_whack_small;
3001 whack = b_whack_big;
3005 vector whack_dir, temp;
3006 vm_vec_dist_to_line(&objp->pos, &b->last_start, &b->last_shot, &temp, &dist);
3007 vm_vec_sub(&whack_dir, &objp->pos, &temp);
3008 vm_vec_normalize(&whack_dir);
3009 vm_vec_scale(&whack_dir, whack);
3012 ship_apply_whack(&whack_dir, hit_point, objp);
3015 // return the amount of damage which should be applied to a ship. basically, filters friendly fire damage
3016 float beam_get_ship_damage(beam *b, object *objp)
3018 // if the beam is on the same team as the object
3019 Assert((objp != NULL) && (b != NULL));
3020 if((objp == NULL) || (b == NULL)){
3023 Assert((objp->type == OBJ_SHIP) && (objp->instance >= 0) && (objp->instance < MAX_SHIPS));
3024 if((objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
3029 if((b->team == Ships[objp->instance].team) && (Weapon_info[b->weapon_info_index].damage > Beam_friendly_cap[Game_skill_level])){
3030 return Beam_friendly_cap[Game_skill_level];
3034 return Weapon_info[b->weapon_info_index].damage;
3037 // if the beam is likely to tool a given target before its lifetime expires
3038 int beam_will_tool_target(beam *b, object *objp)
3040 weapon_info *wip = &Weapon_info[b->weapon_info_index];
3041 float damage_in_a_few_seconds;
3048 // if the object is not a ship, bail
3049 if(objp->type != OBJ_SHIP){
3052 if((objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
3056 // if the beam is going to apply more damage in about 1 and a half than the hull of the ship can take
3057 damage_in_a_few_seconds = (TOOLTIME / (float)BEAM_DAMAGE_TIME) * wip->damage;
3058 if(objp->hull_strength < damage_in_a_few_seconds){
3067 float beam_accuracy = 1.0f;
3070 dc_get_arg(ARG_FLOAT);
3071 beam_accuracy = Dc_arg_float;
3078 for(idx=0; idx<Num_weapon_types; idx++){
3079 if(Weapon_info[idx].wi_flags & WIF_BEAM){
3081 dc_printf("Beam %d : %s\n", b_count, Weapon_info[idx].name);
3085 void beam_test(int whee)
3088 object *orion, *fenris;
3089 ship_subsys *orion_turret, *fenris_turret, *fenris_radar, *orion_radar, *lookup;
3092 nprintf(("General", "Running beam test\n"));
3094 // lookup some stuff
3095 s1 = ship_name_lookup("GTD Orion 1");
3097 orion = &Objects[Ships[s1].objnum];
3098 s2 = ship_name_lookup("GTC Fenris 2");
3100 fenris = &Objects[Ships[s2].objnum];
3103 lookup = GET_FIRST(&Ships[s1].subsys_list);
3104 orion_turret = NULL;
3106 while(lookup != END_OF_LIST(&Ships[s1].subsys_list)){
3108 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret07")){
3109 orion_turret = lookup;
3113 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3114 orion_radar = lookup;
3117 lookup = GET_NEXT(lookup);
3119 Assert(orion_turret != NULL);
3120 Assert(orion_radar != NULL);
3121 lookup = GET_FIRST(&Ships[s2].subsys_list);
3122 fenris_turret = NULL;
3123 fenris_radar = NULL;
3124 while(lookup != END_OF_LIST(&Ships[s2].subsys_list)){
3126 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret07")){
3127 fenris_turret = lookup;
3131 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3132 fenris_radar = lookup;
3135 lookup = GET_NEXT(lookup);
3137 Assert(fenris_turret != NULL);
3138 Assert(fenris_radar != NULL);
3140 memset(&f, 0, sizeof(beam_fire_info));
3141 f.accuracy = beam_accuracy;
3142 f.beam_info_index = -1;
3143 f.beam_info_override = NULL;
3146 f.target_subsys = fenris_turret;
3147 f.turret = orion_turret;
3149 // find the first beam
3151 int beam_first = -1;
3154 for(idx=0; idx<Num_weapon_types; idx++){
3155 if(Weapon_info[idx].wi_flags & WIF_BEAM){
3167 // maybe fire it, if its valid
3168 f.beam_info_index = beam_first + whee - 1;
3169 if(Weapon_info[f.beam_info_index].wi_flags & WIF_BEAM){
3170 HUD_printf("Firing %s\n", Weapon_info[f.beam_info_index].name);
3175 void beam_test_new(int whee)
3178 object *orion, *fenris, *herc2, *herc3, *herc6, *alpha;
3179 ship_subsys *orion_turret, *fenris_turret, *fenris_radar, *orion_radar, *lookup;
3182 nprintf(("General", "Running beam test\n"));
3184 // lookup some stuff
3185 s1 = ship_name_lookup("GTD Orion 1");
3187 orion = &Objects[Ships[s1].objnum];
3188 s2 = ship_name_lookup("GTC Fenris 2");
3190 fenris = &Objects[Ships[s2].objnum];
3191 s3 = ship_name_lookup("GTF Hercules 2");
3193 herc2 = &Objects[Ships[s3].objnum];
3194 s3 = ship_name_lookup("GTF Hercules 3");
3196 herc3 = &Objects[Ships[s3].objnum];
3197 s3 = ship_name_lookup("GTF Hercules 6");
3199 herc6 = &Objects[Ships[s3].objnum];
3200 s3 = ship_name_lookup("Alpha 1");
3202 alpha = &Objects[Ships[s3].objnum];
3205 lookup = GET_FIRST(&Ships[s1].subsys_list);
3206 orion_turret = NULL;
3208 while(lookup != END_OF_LIST(&Ships[s1].subsys_list)){
3210 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret07")){
3211 orion_turret = lookup;
3215 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3216 orion_radar = lookup;
3219 lookup = GET_NEXT(lookup);
3221 Assert(orion_turret != NULL);
3222 Assert(orion_radar != NULL);
3223 lookup = GET_FIRST(&Ships[s2].subsys_list);
3224 fenris_turret = NULL;
3225 fenris_radar = NULL;
3226 while(lookup != END_OF_LIST(&Ships[s2].subsys_list)){
3228 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret03")){
3229 fenris_turret = lookup;
3233 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3234 fenris_radar = lookup;
3237 lookup = GET_NEXT(lookup);
3239 Assert(fenris_turret != NULL);
3240 Assert(fenris_radar != NULL);
3242 memset(&f, 0, sizeof(beam_fire_info));
3243 f.accuracy = beam_accuracy;
3244 f.beam_info_override = NULL;
3247 f.target_subsys = NULL;
3248 f.turret = fenris_turret;
3251 // find the first beam
3253 int beam_first = -1;
3256 for(idx=0; idx<Num_weapon_types; idx++){
3257 if(Weapon_info[idx].wi_flags & WIF_BEAM){
3269 // maybe fire it, if its valid
3270 f.beam_info_index = beam_first + whee - 1;
3271 if(Weapon_info[f.beam_info_index].wi_flags & WIF_BEAM){
3272 HUD_printf("Firing %s\n", Weapon_info[f.beam_info_index].name);