]> icculus.org git repositories - taylor/freespace2.git/blob - src/weapon/beam.cpp
Initial revision
[taylor/freespace2.git] / src / weapon / beam.cpp
1 /*
2  * $Logfile: /Freespace2/code/Weapon/Beam.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * all sorts of cool stuff about ships
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:11  root
11  * Initial revision
12  *
13  * 
14  * 68    9/09/99 11:40p Dave
15  * Handle an Assert() in beam code. Added supernova sounds. Play the right
16  * 2 end movies properly, based upon what the player did in the mission.
17  * 
18  * 67    9/09/99 2:36p Mikek
19  * Put back in the "1.0f +" in BEAM_TYPE_A aiming.  Not the best way to
20  * correct this.
21  * 
22  * 65    9/08/99 10:29p Dave
23  * Make beam sound pausing and unpausing much safer.
24  * 
25  * 64    9/06/99 12:46a Andsager
26  * Add weapon_explosion_ani LOD
27  * 
28  * 63    9/03/99 5:12p Mikek
29  * Change miss_factor code, making it a lot more likely for beams to miss
30  * and also making them increasingly likely to miss with each subsequent
31  * shot in the multi-shot burst.
32  * 
33  * 62    8/30/99 5:01p Dave
34  * Made d3d do less state changing in the nebula. Use new chat server for
35  * PXO.
36  * 
37  * 61    8/28/99 7:29p Dave
38  * Fixed wingmen persona messaging. Make sure locked turrets don't count
39  * towards the # attacking a player.
40  * 
41  * 60    8/27/99 9:07p Dave
42  * LOD explosions. Improved beam weapon accuracy.
43  * 
44  * 59    8/26/99 10:15a Dave
45  * Don't apply beam whacks to docked ships.
46  * 
47  * 58    7/31/99 1:16p Dave
48  * Use larger font for 1024 HUD flash text box. Make beam weapons aware of
49  * weapon subsystem damage on firing ship.
50  * 
51  * 57    7/22/99 4:00p Dave
52  * Fixed beam weapon muzzle glow rendering. Externalized hud shield info.
53  * 
54  * 56    7/19/99 7:20p Dave
55  * Beam tooling. Specialized player-killed-self messages. Fixed d3d nebula
56  * pre-rendering.
57  * 
58  * 55    7/15/99 9:20a Andsager
59  * FS2_DEMO initial checkin
60  * 
61  * 54    7/09/99 5:54p Dave
62  * Seperated cruiser types into individual types. Added tons of new
63  * briefing icons. Campaign screen.
64  * 
65  * 53    7/08/99 10:53a Dave
66  * New multiplayer interpolation scheme. Not 100% done yet, but still
67  * better than the old way.
68  * 
69  * 52    7/02/99 10:51p Dave
70  * Limit friendly beam fire damage. :(
71  * 
72  * 51    7/01/99 11:44a Dave
73  * Updated object sound system to allow multiple obj sounds per ship.
74  * Added hit-by-beam sound. Added killed by beam sound.
75  * 
76  * 50    6/29/99 7:39p Dave
77  * Lots of small bug fixes.
78  * 
79  * 49    6/29/99 2:53p Dave
80  * Re-enabled beam lighting.
81  * 
82  * 48    6/25/99 3:04p Dave
83  * Removed Int3().
84  * 
85  * 47    6/24/99 3:00p Dave
86  * Stupid bug.
87  * 
88  * 46    6/23/99 4:49p Dave
89  * Temporarily removed beam lighting.
90  * 
91  * 45    6/21/99 7:25p Dave
92  * netplayer pain packet. Added type E unmoving beams.
93  * 
94  * 44    6/18/99 5:16p Dave
95  * Added real beam weapon lighting. Fixed beam weapon sounds. Added MOTD
96  * dialog to PXO screen.
97  * 
98  * 43    6/14/99 10:45a Dave
99  * Made beam weapons specify accuracy by skill level in the weapons.tbl
100  * 
101  * 42    6/09/99 2:55p Andsager
102  * Allow multiple asteroid subtypes (of large, medium, small) and follow
103  * family.
104  * 
105  * 41    6/08/99 6:02p Jamesa
106  * Handle case where type B beam start and end directions are really
107  * close.
108  * 
109  * 40    6/04/99 2:16p Dave
110  * Put in shrink effect for beam weapons.
111  * 
112  * 39    5/14/99 11:47a Andsager
113  * Added  beam_get_weapon_info_index(object *bm)
114  * 
115  * 38    5/12/99 10:43a Andsager
116  * Increase max beam length
117  * 
118  * 37    5/08/99 8:25p Dave
119  * Upped object pairs. First run of nebula lightning.
120  * 
121  * 36    5/05/99 9:02p Dave
122  * Fixed D3D aabitmap rendering. Spiffed up nebula effect a bit (added
123  * rotations, tweaked values, made bitmap selection more random). Fixed
124  * D3D beam weapon clipping problem. Added D3d frame dumping.
125  * 
126  * 35    5/03/99 9:07a Dave
127  * Pirate Bob. Changed beam test code a bit.
128  * 
129  * 34    4/27/99 12:16a Dave
130  * Fixed beam weapon muzzle glow problem. Fixed premature timeout on the
131  * pxo server list screen. Fixed secondary firing for hosts on a
132  * standalone. Fixed wacky multiplayer weapon "shuddering" problem.
133  * 
134  * 33    4/25/99 6:12p Johnson
135  * Fixed bug where multi-shot beams were getting the shot count from the
136  * wrong place.
137  * 
138  * 32    4/25/99 3:36p Dave
139  * Fixed nebula table code. Tweaked beam weapon explosion stuff.
140  * 
141  * 31    4/23/99 2:33p Johnson
142  * Allow beams to shoot at missiles properly.
143  * 
144  * 30    4/23/99 12:01p Johnson
145  * Added SIF_HUGE_SHIP
146  * 
147  * 29    4/22/99 11:06p Dave
148  * Final pass at beam weapons. Solidified a lot of stuff. All that remains
149  * now is to tweak and fix bugs as they come up. No new beam weapon
150  * features.
151  * 
152  * 28    4/21/99 6:15p Dave
153  * Did some serious housecleaning in the beam code. Made it ready to go
154  * for anti-fighter "pulse" weapons. Fixed collision pair creation. Added
155  * a handy macro for recalculating collision pairs for a given object.
156  * 
157  * 27    4/20/99 6:39p Dave
158  * Almost done with artillery targeting. Added support for downloading
159  * images on the PXO screen.
160  * 
161  * 26    4/19/99 11:01p Dave
162  * More sophisticated targeting laser support. Temporary checkin.
163  * 
164  * 25    4/19/99 4:54p Johnson
165  * Removed infinite loop from beam_render_all()   oops :)
166  * 
167  * 24    4/16/99 5:54p Dave
168  * Support for on/off style "stream" weapons. Real early support for
169  * target-painting lasers.
170  * 
171  * 23    4/04/99 2:13p Dave
172  * Put in area effect beam weapons. May be a bit too expensive though.
173  * Dave
174  * 22    4/02/99 9:55a Dave
175  * Added a few more options in the weapons.tbl for beam weapons. Attempt
176  * at putting "pain" packets into multiplayer.
177  * 
178  * 21    3/31/99 8:24p Dave
179  * Beefed up all kinds of stuff, incluging beam weapons, nebula effects
180  * and background nebulae. Added per-ship non-dimming pixel colors.
181  * 
182  * 20    3/08/99 7:03p Dave
183  * First run of new object update system. Looks very promising.
184  * 
185  * 19    3/04/99 6:09p Dave
186  * Added in sexpressions for firing beams and checking for if a ship is
187  * tagged.
188  * 
189  * 18    3/02/99 9:25p Dave
190  * Added a bunch of model rendering debug code. Started work on fixing
191  * beam weapon wacky firing.
192  * 
193  * 17    2/21/99 1:48p Dave
194  * Some code for monitoring datarate for multiplayer in detail.
195  * 
196  * 16    2/11/99 3:08p Dave
197  * PXO refresh button. Very preliminary squad war support.
198  * 
199  * 15    2/05/99 3:23p Mattf
200  * Made beams a little more forgiving when checking object types.
201  * 
202  * 14    2/05/99 12:52p Dave
203  * Fixed Glide nondarkening textures.
204  * 
205  * 13    2/04/99 6:29p Dave
206  * First full working rev of FS2 PXO support.  Fixed Glide lighting
207  * problems.
208  * 
209  * 12    1/30/99 9:02p Dave
210  * Fixed neb stuff.
211  * 
212  * 11    1/30/99 1:29a Dave
213  * Fixed nebula thumbnail problem. Full support for 1024x768 choose pilot
214  * screen.  Fixed beam weapon death messages.
215  * 
216  * 10    1/29/99 7:08p Johnson
217  * Put in fix for beam weapons which are on dying ships.
218  * 
219  * 9     1/29/99 12:47a Dave
220  * Put in sounds for beam weapon. A bunch of interface screens (tech
221  * database stuff).
222  * 
223  * 8     1/27/99 9:56a Dave
224  * Temporary checkin of beam weapons for Dan to make cool sounds.
225  * 
226  * 7     1/24/99 11:37p Dave
227  * First full rev of beam weapons. Very customizable. Removed some bogus
228  * Int3()'s in low level net code.
229  * 
230  * 6     1/21/99 2:06p Dave
231  * Final checkin for multiplayer testing.
232  * 
233  * 5     1/21/99 10:45a Dave
234  * More beam weapon stuff. Put in warmdown time.
235  * 
236  * 4     1/14/99 12:48a Dave
237  * Todo list bug fixes. Made a pass at putting briefing icons back into
238  * FRED. Sort of works :(
239  * 
240  * 3     1/12/99 12:53a Dave
241  * More work on beam weapons - made collision detection very efficient -
242  * collide against all object types properly - made 3 movement types
243  * smooth. Put in test code to check for possible non-darkening pixels on
244  * object textures.
245  * 
246  * 2     1/08/99 2:08p Dave
247  * Fixed software rendering for pofview. Super early support for AWACS and
248  * beam weapons.
249  * 
250  * 
251  * $NoKeywords: $
252  */
253
254 #include "linklist.h"
255 #include "object.h"
256 #include "objcollide.h"
257 #include "ship.h"
258 #include "freespace.h"
259 #include "3d.h"
260 #include "model.h"
261 #include "alphacolors.h"
262 #include "timer.h"
263 #include "fireballs.h"
264 #include "debris.h"
265 #include "asteroid.h"
266 #include "multi.h"
267 #include "multimsgs.h"
268 #include "bmpman.h"
269 #include "particle.h"
270 #include "shiphit.h"
271 #include "flak.h"
272 #include "3d.h"
273 #include "gamesnd.h"
274 #include "beam.h"
275 #include "hudmessage.h"
276 #include "key.h"
277 #include "lighting.h"
278
279 // ------------------------------------------------------------------------------------------------
280 // BEAM WEAPON DEFINES/VARS
281 //
282
283 // use this to extend a beam to "infinity"
284 #define BEAM_FAR_LENGTH                         30000.0f
285
286 // this is the constant which defines when a beam is an "area" beam. meaning, when we switch on sphereline checking and when 
287 // a beam gets "stopped" by an object. It is a percentage of the object radius which the beam must be wider than
288 #define BEAM_AREA_PERCENT                       0.4f
289
290 // randomness factor - all beam weapon aiming is adjusted by +/- some factor within this range
291 #define BEAM_RANDOM_FACTOR                      0.4f
292
293 #define MAX_BEAMS                                               40
294 #define BEAM_DAMAGE_TIME                        170                     // apply damage 
295 #define MAX_SHOT_POINTS                         30
296 #define SHOT_POINT_TIME                         200                     // 5 arcs a second
297
298 #define TOOLTIME                                                1500.0f
299
300 // max # of collisions we'll allow per frame
301 #define MAX_FRAME_COLLISIONS            5
302
303 // beam flag defines
304 #define BF_SAFETY                                               (1<<0)          // if this is set, don't collide or render for this frame. lifetime still increases though
305 #define BF_SHRINK                                               (1<<1)          // if this is set, the beam is in the warmdown phase
306
307 // beam struct (the actual weapon/object)
308 typedef struct beam {
309         // low-level data
310         int             objnum;                                 // our own objnum
311         int             weapon_info_index;
312         int             sig;                                            // signature for the shooting object
313         object  *objp;                                  // the shooting object (who owns the turret that I am being fired from)
314         object  *target;                                        // target object
315         ship_subsys *target_subsys;     // targeted subsys
316         int             target_sig;                             // target sig
317         ship_subsys *subsys;                            // subsys its being fired from
318         beam            *next, *prev;                   // link list stuff
319         vector  targeting_laser_offset;
320         int             framecount;                             // how many frames the beam has been active
321         int             flags;                                  // see BF_* defines     
322         float           shrink;                                 // shrink factor        
323
324         // beam info    
325         int             warmup_stamp;                   // timestamp for "warming up"
326         int             warmdown_stamp;         // timestamp for "warming down"
327         int             type;                                           // see BEAM_TYPE_* defines in beam.h
328         float           life_left;                              // in seconds
329         float           life_total;                             // total life   
330         // this vector has very special meaning. BEFORE performing collision checks, it basically implies a "direction". meaning
331         // the vector between it and last_start is where the beam will be aiming. AFTER performing collision checks, it is the
332         // literal world collision point on the object (or meaningless, if we hit nothing). The function beam_move_all_pre() is
333         // responsible for then setting it up pre-collision time
334         vector  last_shot;                              
335         vector  last_start;                             
336         int             shot_index;                             // for type D beam weapons
337
338         // recent collisions
339         beam_collision r_collisions[MAX_FRAME_COLLISIONS];                                      // recent collisions
340         int r_collision_count;                                                                                                          // # of recent collisions
341
342         // collision info for this frame
343         beam_collision f_collisions[MAX_FRAME_COLLISIONS];                                      // collisions for the current frame
344         int f_collision_count;                                                                                                          // # of collisions we recorded this frame
345
346         // looping sound info, HANDLE
347         int             beam_sound_loop;                // -1 if none
348
349         // team 
350         char            team;
351
352         // exactly how the beam will behave. by passing this is multiplayer from server to client, we can ensure that
353         // everything looks the same
354         beam_info binfo;
355 } beam;
356
357 beam Beams[MAX_BEAMS];                          // all beams
358 beam Beam_free_list;                                    // free beams
359 beam Beam_used_list;                                    // used beams
360 int Beam_count = 0;                                     // how many beams are in use
361
362 // octant indices. These are "good" pairs of octants to use for beam target
363 #define BEAM_NUM_GOOD_OCTANTS                   8
364 int Beam_good_slash_octants[BEAM_NUM_GOOD_OCTANTS][4] = {               
365         { 2, 5, 1, 0 },                                 // octant, octant, min/max pt, min/max pt
366         { 7, 0, 1, 0 },
367         { 1, 6, 1, 0 },                                 
368         { 6, 1, 0, 1 },
369         { 5, 2, 0, 1 }, 
370         { 0, 7, 0, 1 },         
371         { 7, 1, 1, 0 },
372         { 6, 0, 1, 0 },
373 };
374 int Beam_good_shot_octants[BEAM_NUM_GOOD_OCTANTS][4] = {                
375         { 5, 0, 1, 0 },                                 // octant, octant, min/max pt, min/max pt
376         { 7, 2, 1, 0 },
377         { 7, 1, 1, 0 },                                 
378         { 6, 0, 1, 0 },
379         { 7, 3, 1, 0 }, 
380         { 6, 2, 1, 0 },
381         { 5, 1, 1, 0 },
382         { 4, 0, 1, 0 },
383 };
384
385 // damage cap values for friendly beam fire
386 float Beam_friendly_cap[NUM_SKILL_LEVELS] = { 0.0f, 5.0f, 10.0f, 20.0f, 30.0f };
387
388 // beam lighting effects
389 int Beam_lighting = 1;
390
391 // debug stuff - keep track of how many collision tests we perform a second and how many we toss a second
392 #define BEAM_TEST_STAMP_TIME            4000    // every 4 seconds
393 int Beam_test_stamp = -1;
394 int Beam_test_ints = 0;
395 int Beam_test_ship = 0;
396 int Beam_test_ast = 0;
397 int Beam_test_framecount = 0;
398
399 // beam warmup completion %
400 #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 ) 
401
402 // beam warmdown completion %           
403 #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 ) 
404
405 // timestamp for spewing muzzle particles
406 int Beam_muzzle_stamp = -1;
407
408 // link into the physics paused system
409 extern int physics_paused;
410
411 // beam lighting info
412 #define MAX_BEAM_LIGHT_INFO             100
413 typedef struct beam_light_info {
414         beam *bm;                                       // beam casting the light
415         int objnum;                                     // object getting light cast on it
416         ubyte source;                           // 0 to light the shooter, 1 for lighting any ship the beam passes, 2 to light the collision ship
417         vector c_point;                 // collision point for type 2 lights
418 } beam_light_info;
419
420 beam_light_info Beam_lights[MAX_BEAM_LIGHT_INFO];
421 int Beam_light_count = 0;
422
423 float b_whack_small = 500.0f;
424 float b_whack_big = 1500.0f;
425 float b_whack_damage = 150.0f;
426 DCF(b_whack_small, "")
427 {
428         dc_get_arg(ARG_FLOAT);
429         b_whack_small = Dc_arg_float;
430 }
431 DCF(b_whack_big, "")
432 {
433         dc_get_arg(ARG_FLOAT);
434         b_whack_big = Dc_arg_float;
435 }
436 DCF(b_whack_damage, "")
437 {
438         dc_get_arg(ARG_FLOAT);
439         b_whack_damage = Dc_arg_float;
440 }
441
442
443 // ------------------------------------------------------------------------------------------------
444 // BEAM WEAPON FORWARD DECLARATIONS
445 //
446
447 // delete a beam
448 void beam_delete(beam *b);
449
450 // handle a hit on a specific object
451 void beam_handle_collisions(beam *b);
452
453 // fills in binfo
454 void beam_get_binfo(beam *b, float accuracy, int num_shots);
455
456 // aim the beam (setup last_start and last_shot - the endpoints). also recalculates object collision info
457 void beam_aim(beam *b);
458
459 // type A functions
460 void beam_type_a_move(beam *b);
461
462 // type B functions
463 void beam_type_b_move(beam *b);
464
465 // type C functions
466 void beam_type_c_move(beam *b);
467
468 // type D functions
469 void beam_type_d_move(beam *b);
470 void beam_type_d_get_status(beam *b, int *shot_index, int *fire_wait);
471
472 // type e functions
473 void beam_type_e_move(beam *b);
474
475 // given a model #, and an object, stuff 2 good world coord points
476 void beam_get_octant_points(int modelnum, object *objp, int oct_index, int oct_array[BEAM_NUM_GOOD_OCTANTS][4], vector *v1, vector *v2);
477
478 // given an object, return its model num
479 int beam_get_model(object *objp);
480
481 // for a given object, and a firing beam, determine its critical dot product and range
482 void beam_get_cull_vals(object *objp, beam *b, float *cull_dot, float *cull_dist);
483
484 // get the total possible cone for a given beam in radians
485 float beam_get_cone_dot(beam *b);
486
487 // for rendering the beam effect
488 // output top and bottom vectors
489 // fvec == forward vector (eye viewpoint basically. in world coords)
490 // pos == world coordinate of the point we're calculating "around"
491 // w == width of the diff between top and bottom around pos
492 void beam_calc_facing_pts(vector *top, vector *bot, vector *fvec, vector *pos, float w, float z_add);
493
494 // render the muzzle glow for a beam weapon
495 void beam_render_muzzle_glow(beam *b);
496
497 // generate particles for the muzzle glow
498 void beam_generate_muzzle_particles(beam *b);
499
500 // throw some jitter into the aim - based upon shot_aim
501 void beam_jitter_aim(beam *b, float aim);
502
503 // if it is legal for the beam to continue firing
504 // returns -1 if the beam should stop firing immediately
505 // returns 0 if the beam should go to warmdown
506 // returns 1 if the beam can continue along its way
507 int beam_ok_to_fire(beam *b);
508
509 // start the warmup phase for the beam
510 void beam_start_warmup(beam *b);
511
512 // start the firing phase for the beam, return 0 if the beam failed to start, and should be deleted altogether
513 int beam_start_firing(beam *b);
514
515 // start the warmdown phase for the beam
516 void beam_start_warmdown(beam *b);
517
518 // add a collision to the beam for this frame (to be evaluated later)
519 void beam_add_collision(beam *b, object *hit_object, mc_info *cinfo);
520
521 // sort collisions for the frame
522 int beam_sort_collisions_func(const void *e1, const void *e2);
523
524 // get the width of the widest section of the beam
525 float beam_get_widest(beam *b);
526
527 // mark an object as being lit
528 void beam_add_light(beam *b, int objnum, int source, vector *c_point);
529
530 // apply lighting from any beams
531 void beam_apply_lighting();
532
533 // recalculate beam sounds (looping sounds relative to the player)
534 void beam_recalc_sounds(beam *b);
535
536 // apply a whack to a ship
537 void beam_apply_whack(beam *b, object *objp, vector *hit_point);
538
539 // return the amount of damage which should be applied to a ship. basically, filters friendly fire damage 
540 float beam_get_ship_damage(beam *b, object *objp);
541
542 // if the beam is likely to tool a given target before its lifetime expires
543 int beam_will_tool_target(beam *b, object *objp);
544
545
546 // ------------------------------------------------------------------------------------------------
547 // BEAM WEAPON FUNCTIONS
548 //
549
550 // init at game startup
551 void beam_init()
552 {
553         // clear the beams
554         list_init( &Beam_free_list );
555         list_init( &Beam_used_list );
556 }
557
558 // initialize beam weapons for this level
559 void beam_level_init()
560 {
561         // intialize beams
562         int idx;
563
564         Beam_count = 0;
565         list_init( &Beam_free_list );
566         list_init( &Beam_used_list );
567         memset(Beams, 0, sizeof(beam) * MAX_BEAMS);
568
569         // Link all object slots into the free list
570         for (idx=0; idx<MAX_BEAMS; idx++)       {
571                 Beams[idx].objnum = -1;
572                 list_append(&Beam_free_list, &Beams[idx] );
573         }
574
575         // reset muzzle particle spew timestamp
576         Beam_muzzle_stamp = -1;
577 }
578
579 // shutdown beam weapons for this level
580 void beam_level_close()
581 {
582         // clear the beams
583         list_init( &Beam_free_list );
584         list_init( &Beam_used_list );
585 }
586
587 // fire a beam, returns nonzero on success. the innards of the code handle all the rest, foo
588 int beam_fire(beam_fire_info *fire_info)
589 {
590         beam *new_item;
591         weapon_info *wip;
592         ship *firing_ship;
593         int objnum;             
594
595         // sanity check
596         if(fire_info == NULL){
597                 Int3();
598                 return -1;
599         }
600
601         // if we're out of beams, bail
602         if(Beam_count >= MAX_BEAMS){
603                 return -1;
604         }
605
606         // for now, only allow ship targets
607         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)){
608                 return -1;
609         }       
610
611         // make sure the beam_info_index is valid
612         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));
613         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)){
614                 return -1;
615         }
616         wip = &Weapon_info[fire_info->beam_info_index]; 
617
618         // make sure a ship is firing this
619         Assert((fire_info->shooter->type == OBJ_SHIP) && (fire_info->shooter->instance >= 0) && (fire_info->shooter->instance < MAX_SHIPS));
620         if((fire_info->shooter->type != OBJ_SHIP) || (fire_info->shooter->instance < 0) && (fire_info->shooter->instance >= MAX_SHIPS)){
621                 return -1;
622         }
623         firing_ship = &Ships[fire_info->shooter->instance];
624
625         // get a free beam
626         new_item = GET_FIRST(&Beam_free_list);
627         Assert( new_item != &Beam_free_list );          // shouldn't have the dummy element
628         if(new_item == &Beam_free_list){
629                 return -1;
630         }
631
632         // remove from the free list
633         list_remove( &Beam_free_list, new_item );
634         
635         // insert onto the end of used list
636         list_append( &Beam_used_list, new_item );
637
638         // increment counter
639         Beam_count++;   
640
641         // fill in some values
642         new_item->warmup_stamp = -1;
643         new_item->warmdown_stamp = -1;
644         new_item->weapon_info_index = fire_info->beam_info_index;       
645         new_item->objp = fire_info->shooter;
646         new_item->sig = fire_info->shooter->signature;
647         new_item->subsys = fire_info->turret;   
648         new_item->life_left = wip->b_info.beam_life;    
649         new_item->life_total = wip->b_info.beam_life;
650         new_item->r_collision_count = 0;
651         new_item->f_collision_count = 0;
652         new_item->target = fire_info->target;
653         new_item->target_subsys = fire_info->target_subsys;
654         new_item->target_sig = fire_info->target->signature;    
655         new_item->beam_sound_loop = -1;
656         new_item->type = wip->b_info.beam_type;
657         new_item->targeting_laser_offset = fire_info->targeting_laser_offset;
658         new_item->framecount = 0;
659         new_item->flags = 0;
660         new_item->shot_index = 0;
661         new_item->shrink = 1.0f;        
662         new_item->team = (char)firing_ship->team;
663         
664         // if the targeted subsystem is not NULL, force it to be a type A beam
665         if(new_item->target_subsys != NULL){
666                 new_item->type = BEAM_TYPE_A;
667         }
668
669         // type D weapons can only fire at small ships and missiles
670         if(new_item->type == BEAM_TYPE_D){
671                 // if its a targeted ship, get the target ship
672                 if((fire_info->target != NULL) && (fire_info->target->type == OBJ_SHIP) && (fire_info->target->instance >= 0)){         
673                         ship *target_ship = &Ships[fire_info->target->instance];
674                         
675                         // maybe force to be a type A
676                         if(Ship_info[target_ship->ship_info_index].flags & (SIF_BIG_SHIP | SIF_CAPITAL | SIF_SUPERCAP | SIF_FREIGHTER | SIF_DRYDOCK | SIF_CARGO)){
677                                 new_item->type = BEAM_TYPE_A;
678                         }
679                 }
680         }
681         
682         // ----------------------------------------------------------------------
683         // THIS IS THE CRITICAL POINT FOR MULTIPLAYER
684         // beam_get_binfo(...) determines exactly how the beam will behave over the course of its life
685         // it fills in binfo, which we can pass to clients in multiplayer       
686         if(fire_info->beam_info_override != NULL){
687                 new_item->binfo = *fire_info->beam_info_override;
688         } else {
689                 beam_get_binfo(new_item, fire_info->accuracy, wip->b_info.beam_shots);                  // to fill in b_info    - the set of directional aim vectors
690         }       
691
692         // create the associated object
693         objnum = obj_create(OBJ_BEAM, -1, new_item - Beams, &vmd_identity_matrix, &vmd_zero_vector, 1.0f, OF_COLLIDES);
694         if(objnum < 0){
695                 Int3();
696                 beam_delete(new_item);
697                 nprintf(("General", "obj_create() failed for beam weapon! bah!\n"));
698                 return -1;
699         }
700         new_item->objnum = objnum;
701
702         // this sets up all info for the first frame the beam fires
703         beam_aim(new_item);                                             // to fill in shot_point, etc.  
704
705         // check to see if its legal to fire at this guy
706         if(beam_ok_to_fire(new_item) != 1){
707                 beam_delete(new_item);
708                 mprintf(("Killing beam at initial fire because of illegal targeting!!!\n"));
709                 return -1;
710         }
711
712         // if we're a multiplayer master - send a packet
713         if(MULTIPLAYER_MASTER){
714                 send_beam_fired_packet(fire_info->shooter, fire_info->turret, fire_info->target, fire_info->beam_info_index, &new_item->binfo);
715         }
716
717         // start the warmup phase
718         beam_start_warmup(new_item);            
719
720         return objnum;
721 }
722
723 // fire a targeting beam, returns objnum on success. a much much simplified version of a beam weapon
724 // targeting lasers last _one_ frame. For a continuous stream - they must be created every frame.
725 // this allows it to work smoothly in multiplayer (detect "trigger down". every frame just create a targeting laser firing straight out of the
726 // object. this way you get all the advantages of nice rendering and collisions).
727 // NOTE : only references beam_info_index and shooter
728 int beam_fire_targeting(beam_fire_info *fire_info)
729 {
730         beam *new_item;
731         weapon_info *wip;
732         int objnum;     
733         ship *firing_ship;
734
735         // sanity check
736         if(fire_info == NULL){
737                 Int3();
738                 return -1;
739         }
740
741         // if we're out of beams, bail
742         if(Beam_count >= MAX_BEAMS){
743                 return -1;
744         }
745         
746         // make sure the beam_info_index is valid
747         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));
748         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)){
749                 return -1;
750         }
751         wip = &Weapon_info[fire_info->beam_info_index]; 
752
753         // make sure a ship is firing this
754         Assert((fire_info->shooter->type == OBJ_SHIP) && (fire_info->shooter->instance >= 0) && (fire_info->shooter->instance < MAX_SHIPS));
755         if((fire_info->shooter->type != OBJ_SHIP) || (fire_info->shooter->instance < 0) && (fire_info->shooter->instance >= MAX_SHIPS)){
756                 return -1;
757         }
758         firing_ship = &Ships[fire_info->shooter->instance];
759
760
761         // get a free beam
762         new_item = GET_FIRST(&Beam_free_list);
763         Assert( new_item != &Beam_free_list );          // shouldn't have the dummy element
764
765         // remove from the free list
766         list_remove( &Beam_free_list, new_item );
767         
768         // insert onto the end of used list
769         list_append( &Beam_used_list, new_item );
770
771         // increment counter
772         Beam_count++;
773
774         // maybe allocate some extra data based on the beam type
775         Assert(wip->b_info.beam_type == BEAM_TYPE_C);
776         if(wip->b_info.beam_type != BEAM_TYPE_C){
777                 return -1;
778         }
779
780         // fill in some values
781         new_item->warmup_stamp = -1;
782         new_item->warmdown_stamp = -1;
783         new_item->weapon_info_index = fire_info->beam_info_index;       
784         new_item->objp = fire_info->shooter;
785         new_item->sig = NULL;
786         new_item->subsys = NULL;
787         new_item->life_left = 0;        
788         new_item->life_total = 0;
789         new_item->r_collision_count = 0;
790         new_item->f_collision_count = 0;
791         new_item->target = NULL;
792         new_item->target_subsys = NULL;
793         new_item->target_sig = NULL;    
794         new_item->beam_sound_loop = -1;
795         new_item->type = BEAM_TYPE_C;   
796         new_item->targeting_laser_offset = fire_info->targeting_laser_offset;
797         new_item->framecount = 0;
798         new_item->flags = 0;
799         new_item->shot_index = 0;
800         new_item->team = (char)firing_ship->team;
801
802         // type c is a very special weapon type - binfo has no meaning
803
804         // create the associated object
805         objnum = obj_create(OBJ_BEAM, -1, new_item - Beams, &vmd_identity_matrix, &vmd_zero_vector, 1.0f, OF_COLLIDES);
806         if(objnum < 0){
807                 Int3();
808                 beam_delete(new_item);
809                 nprintf(("General", "obj_create() failed for beam weapon! bah!\n"));
810                 return -1;
811         }
812         new_item->objnum = objnum;      
813
814         // this sets up all info for the first frame the beam fires
815         beam_aim(new_item);                                             // to fill in shot_point, etc.          
816         
817         return objnum;
818 }
819
820 // return an object index of the guy who's firing this beam
821 int beam_get_parent(object *bm)
822 {
823         beam *b;
824
825         // get a handle to the beam
826         Assert(bm->type == OBJ_BEAM);
827         Assert(bm->instance >= 0);      
828         if(bm->type != OBJ_BEAM){
829                 return -1;
830         }
831         if(bm->instance < 0){
832                 return -1;
833         }
834         b = &Beams[bm->instance];
835
836         Assert(b->objp != NULL);
837         if(b->objp == NULL){
838                 return -1;
839         }
840
841         // if the object handle is invalid
842         if(b->objp->signature != b->sig){
843                 return -1;
844         }
845
846         // return the handle
847         return OBJ_INDEX(b->objp);
848 }
849
850 // return weapon_info_index of beam
851 int beam_get_weapon_info_index(object *bm)
852 {
853         Assert(bm->type == OBJ_BEAM);
854         if (bm->type != OBJ_BEAM) {
855                 return -1;
856         }
857
858         Assert(bm->instance >= 0 && bm->instance < MAX_BEAMS);
859         if (bm->instance < 0) {
860                 return -1;
861         }
862
863         // return weapon_info_index
864         return Beams[bm->instance].weapon_info_index;
865 }
866
867
868
869 // given a beam object, get the # of collisions which happened during the last collision check (typically, last frame)
870 int beam_get_num_collisions(int objnum)
871 {       
872         // sanity checks
873         if((objnum < 0) || (objnum >= MAX_OBJECTS)){
874                 Int3();
875                 return -1;
876         }
877         if((Objects[objnum].instance < 0) || (Objects[objnum].instance >= MAX_BEAMS)){
878                 Int3();
879                 return -1;
880         }
881         if((Beams[Objects[objnum].instance].objnum != objnum) || (Beams[Objects[objnum].instance].objnum < 0)){
882                 Int3();
883                 return -1;
884         }
885
886         // return the # of recent collisions
887         return Beams[Objects[objnum].instance].r_collision_count;
888 }
889
890 // stuff collision info, returns 1 on success
891 int beam_get_collision(int objnum, int num, int *collision_objnum, mc_info **cinfo)
892 {
893         // sanity checks
894         if((objnum < 0) || (objnum >= MAX_OBJECTS)){
895                 Int3();
896                 return 0;
897         }
898         if((Objects[objnum].instance < 0) || (Objects[objnum].instance >= MAX_BEAMS)){
899                 Int3();
900                 return 0;
901         }
902         if((Beams[Objects[objnum].instance].objnum != objnum) || (Beams[Objects[objnum].instance].objnum < 0)){
903                 Int3();
904                 return 0;
905         }
906         if(num >= Beams[Objects[objnum].instance].r_collision_count){
907                 Int3();
908                 return 0;
909         }
910
911         // return - success
912         *cinfo = &Beams[Objects[objnum].instance].r_collisions[num].cinfo;
913         *collision_objnum = Beams[Objects[objnum].instance].r_collisions[num].c_objnum;
914         return 1;
915 }
916
917 // pause all looping beam sounds
918 void beam_pause_sounds()
919 {
920         beam *moveup = NULL;
921
922         // set all beam volumes to 0    
923         moveup = GET_FIRST(&Beam_used_list);
924         if(moveup == NULL){
925                 return;
926         }
927         while(moveup != END_OF_LIST(&Beam_used_list)){                          
928                 // set the volume to 0, if he has a looping beam sound
929                 if(moveup->beam_sound_loop >= 0){
930                         snd_set_volume(moveup->beam_sound_loop, 0.0f);
931                 }
932
933                 // next beam
934                 moveup = GET_NEXT(moveup);
935         }
936 }
937
938 // unpause looping beam sounds
939 void beam_unpause_sounds()
940 {
941         beam *moveup = NULL;
942
943         // recalc all beam sounds
944         moveup = GET_FIRST(&Beam_used_list);
945         if(moveup == NULL){
946                 return;
947         }
948         while(moveup != END_OF_LIST(&Beam_used_list)){                          
949                 beam_recalc_sounds(moveup);
950
951                 // next beam
952                 moveup = GET_NEXT(moveup);
953         }
954 }
955
956
957 // -----------------------------===========================------------------------------
958 // BEAM MOVEMENT FUNCTIONS
959 // -----------------------------===========================------------------------------
960
961 // move a type A beam weapon
962 void beam_type_a_move(beam *b)
963 {
964         vector dir;
965         vector temp, temp2;     
966
967         // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
968         // get the "originating point" of the beam for this frame. essentially bashes last_start
969         ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2);
970
971         // if the "warming up" timestamp has not expired
972         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
973                 return;
974         }
975
976         // put the "last_shot" point arbitrarily far away
977         vm_vec_sub(&dir, &b->last_shot, &b->last_start);
978         vm_vec_normalize_quick(&dir);
979         vm_vec_scale_add(&b->last_shot, &b->last_start, &dir, BEAM_FAR_LENGTH);
980         Assert(is_valid_vec(&b->last_shot));
981 }
982
983 // move a type B beam weapon
984 #define BEAM_T(b)                                               ( ((b->binfo.delta_ang / b->life_total) * (b->life_total - b->life_left)) / b->binfo.delta_ang )
985 void beam_type_b_move(beam *b)
986 {               
987         vector actual_dir;
988         vector temp, temp2;
989         float dot_save; 
990
991         // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
992         // get the "originating point" of the beam for this frame. essentially bashes last_start
993         ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2);
994
995         // if the "warming up" timestamp has not expired
996         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
997                 return;
998         }       
999
1000         // if the two direction vectors are _really_ close together, just use the original direction
1001         dot_save = vm_vec_dot(&b->binfo.dir_a, &b->binfo.dir_b);
1002         if((double)dot_save >= 0.999999999){
1003                 actual_dir = b->binfo.dir_a;
1004         } 
1005         // otherwise move towards the dir       we calculated when firing this beam     
1006         else {
1007                 vm_vec_interp_constant(&actual_dir, &b->binfo.dir_a, &b->binfo.dir_b, BEAM_T(b));
1008         }
1009
1010         // now recalculate shot_point to be shooting through our new point
1011         vm_vec_scale_add(&b->last_shot, &b->last_start, &actual_dir, BEAM_FAR_LENGTH);
1012         int is_valid = is_valid_vec(&b->last_shot);
1013         Assert(is_valid);
1014         if(!is_valid){
1015                 actual_dir = b->binfo.dir_a;
1016                 vm_vec_scale_add(&b->last_shot, &b->last_start, &actual_dir, BEAM_FAR_LENGTH);
1017         }
1018 }
1019
1020 // type C functions
1021 void beam_type_c_move(beam *b)
1022 {       
1023         vector temp;
1024
1025         // ugh
1026         if(b->objp == NULL){
1027                 Int3();
1028                 return;
1029         }
1030
1031         // type c beams only last one frame so we never have to "move" them.                    
1032         temp = b->targeting_laser_offset;
1033         vm_vec_unrotate(&b->last_start, &temp, &b->objp->orient);
1034         vm_vec_add2(&b->last_start, &b->objp->pos);     
1035         vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.fvec, BEAM_FAR_LENGTH);
1036 }
1037
1038 // type D functions
1039 void beam_type_d_move(beam *b)
1040 {
1041         int shot_index, fire_wait;
1042         vector temp, temp2, dir;        
1043
1044         // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
1045         // get the "originating point" of the beam for this frame. essentially bashes last_start
1046         ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2);
1047
1048         // if the "warming up" timestamp has not expired
1049         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
1050                 return;
1051         }       
1052
1053         // determine what stage of the beam we're in
1054         beam_type_d_get_status(b, &shot_index, &fire_wait);
1055
1056         // if we've changed shot index
1057         if(shot_index != b->shot_index){
1058                 // set the new index
1059                 b->shot_index = shot_index;
1060
1061                 // re-aim
1062                 beam_aim(b);
1063         }
1064
1065         // if we're in the fire wait stage
1066         b->flags &= ~BF_SAFETY;
1067         if(fire_wait){
1068                 b->flags |= BF_SAFETY;
1069         }
1070
1071         // put the "last_shot" point arbitrarily far away
1072         vm_vec_sub(&dir, &b->last_shot, &b->last_start);
1073         vm_vec_normalize_quick(&dir);
1074         vm_vec_scale_add(&b->last_shot, &b->last_start, &dir, BEAM_FAR_LENGTH);
1075         Assert(is_valid_vec(&b->last_shot));
1076 }
1077 void beam_type_d_get_status(beam *b, int *shot_index, int *fire_wait)
1078 {       
1079         float shot_time = b->life_total / (float)b->binfo.shot_count;
1080         float beam_time = b->life_total - b->life_left;
1081
1082         // determine what "shot" we're on       
1083         *shot_index = (int)(beam_time / shot_time);
1084         Assert(*shot_index < b->binfo.shot_count);
1085         if(*shot_index >= b->binfo.shot_count){
1086                 *shot_index = b->binfo.shot_count - 1;
1087         }       
1088
1089         // determine if its the firing or waiting section of the shot (fire happens first, THEN wait)   
1090         *fire_wait = 0;
1091         if(beam_time > ((shot_time * (*shot_index)) + (shot_time * 0.5f))){
1092                 *fire_wait = 1;
1093         }       
1094 }
1095
1096 // type e functions
1097 void beam_type_e_move(beam *b)
1098 {
1099         vector temp, turret_norm;       
1100
1101         // LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
1102         // get the "originating point" of the beam for this frame. essentially bashes last_start
1103         ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &turret_norm, 1, &temp);
1104
1105         // if the "warming up" timestamp has not expired
1106         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
1107                 return;
1108         }       
1109
1110         // put the "last_shot" point arbitrarily far away
1111         vm_vec_scale_add(&b->last_shot, &b->last_start, &turret_norm, BEAM_FAR_LENGTH); 
1112         Assert(is_valid_vec(&b->last_shot));
1113 }
1114
1115 // pre-move (before collision checking - but AFTER ALL OTHER OBJECTS HAVE BEEN MOVED)
1116 void beam_move_all_pre()
1117 {       
1118         beam *b;        
1119         beam *moveup;           
1120
1121         // zero lights for this frame yet
1122         Beam_light_count = 0;
1123
1124         // traverse through all active beams
1125         moveup = GET_FIRST(&Beam_used_list);
1126         while(moveup != END_OF_LIST(&Beam_used_list)){                          
1127                 // get the beam
1128                 b = moveup;
1129
1130                 // unset collision info
1131                 b->f_collision_count = 0;
1132                 
1133                 if(!physics_paused){
1134                         // move the beam
1135                         switch(b->type){
1136                         // type A beam weapons don't move
1137                         case BEAM_TYPE_A :                      
1138                                 beam_type_a_move(b);
1139                                 break;
1140
1141                         // type B beam weapons move across the target somewhat randomly
1142                         case BEAM_TYPE_B :
1143                                 beam_type_b_move(b);
1144                                 break;                          
1145
1146                         // type C beam weapons are attached to a fighter - pointing forward
1147                         case BEAM_TYPE_C:
1148                                 beam_type_c_move(b);
1149                                 break;
1150
1151                         // type D
1152                         case BEAM_TYPE_D:
1153                                 beam_type_d_move(b);
1154                                 break;
1155
1156                         // type E
1157                         case BEAM_TYPE_E:
1158                                 beam_type_e_move(b);
1159                                 break;
1160
1161                         // illegal beam type
1162                         default :
1163                                 Int3();
1164                         }
1165                 }
1166
1167                 // next
1168                 moveup = GET_NEXT(moveup);
1169         }
1170 }
1171
1172 // post-collision time processing for beams
1173 void beam_move_all_post()
1174 {
1175         beam *moveup;   
1176         beam *next_one; 
1177         int bf_status;  
1178         beam_weapon_info *bwi;
1179
1180         // traverse through all active beams
1181         moveup = GET_FIRST(&Beam_used_list);
1182         while(moveup != END_OF_LIST(&Beam_used_list)){                          
1183                  bwi = &Weapon_info[moveup->weapon_info_index].b_info;
1184
1185                  // check the status of the beam
1186                 bf_status = beam_ok_to_fire(moveup);
1187
1188                 // if we're warming up
1189                 if(moveup->warmup_stamp != -1){
1190                         next_one = GET_NEXT(moveup);                    
1191
1192                         // should we be stopping?
1193                         if(bf_status < 0){
1194                                 beam_delete(moveup);
1195                         } else {
1196                                 // if the warming up timestamp has expired, start firing
1197                                 if(timestamp_elapsed(moveup->warmup_stamp)){                                                    
1198                                         // start firing
1199                                         if(!beam_start_firing(moveup)){
1200                                                 beam_delete(moveup);                                                                                            
1201                                         }                       
1202                                         
1203                                         // add a muzzle light for the shooter
1204                                         beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
1205                                 } 
1206                         }
1207
1208                         // next
1209                         moveup = next_one;
1210                         continue;
1211                 } 
1212                 // if we're warming down
1213                 else if(moveup->warmdown_stamp != -1){                  
1214                         next_one = GET_NEXT(moveup);
1215
1216                         // should we be stopping?
1217                         if(bf_status < 0){
1218                                 beam_delete(moveup);
1219                         } else {
1220                                 // if we're done warming down, the beam is finished
1221                                 if(timestamp_elapsed(moveup->warmdown_stamp)){                          
1222                                         beam_delete(moveup);                            
1223                                 }                       
1224                         }
1225
1226                         // next
1227                         moveup = next_one;
1228                         continue;
1229                 }
1230                 
1231                 // otherwise, we're firing away.........                
1232
1233                 // add a muzzle light for the shooter
1234                 beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
1235
1236                 // subtract out the life left for the beam
1237                 if(!physics_paused){
1238                         moveup->life_left -= flFrametime;                                               
1239                 }
1240
1241                 // if we're past the shrink point, start shrinking the beam
1242                 if(moveup->life_left <= (moveup->life_total * bwi->beam_shrink_factor)){
1243                         moveup->flags |= BF_SHRINK;
1244                 }
1245
1246                 // if we're shrinking the beam
1247                 if(moveup->flags & BF_SHRINK){
1248                         moveup->shrink -= bwi->beam_shrink_pct * flFrametime;
1249                         if(moveup->shrink < 0.1f){
1250                                 moveup->shrink = 0.1f;
1251                         }
1252                 }               
1253
1254                 // stop shooting?
1255                 if(bf_status <= 0){
1256                         next_one = GET_NEXT(moveup);
1257
1258                         // if beam should abruptly stop
1259                         if(bf_status == -1){                            
1260                                 beam_delete(moveup);                                                    
1261                         }
1262                         // if the beam should just power down
1263                         else {                  
1264                                 beam_start_warmdown(moveup);
1265                         }
1266                         
1267                         // next beam
1268                         moveup = next_one;
1269                         continue;
1270                 }                               
1271
1272                 // increment framecount
1273                 moveup->framecount++;           
1274                 
1275                 // type c weapons live for one frame only
1276                 if(moveup->type == BEAM_TYPE_C){
1277                         if(moveup->framecount > 1){
1278                                 next_one = GET_NEXT(moveup);
1279                         
1280                                 beam_delete(moveup);                                                    
1281                                 moveup = next_one;
1282                                 continue;
1283                         }
1284                 }
1285                 // done firing, so go into the warmdown phase
1286                 else {
1287                         if((moveup->life_left <= 0.0f) && (moveup->warmdown_stamp == -1)){
1288                                 beam_start_warmdown(moveup);
1289                                 
1290                                 moveup = GET_NEXT(moveup);                      
1291                                 continue;
1292                         }                               
1293                 }               
1294
1295                 // handle any collisions which occured collision (will take care of applying damage to all objects which got hit)
1296                 beam_handle_collisions(moveup);                                         
1297
1298                 // recalculate beam sounds
1299                 beam_recalc_sounds(moveup);
1300
1301                 // next item
1302                 moveup = GET_NEXT(moveup);
1303         }
1304
1305         // apply all beam lighting
1306         beam_apply_lighting();
1307
1308         // process beam culling info
1309 #ifndef NDEBUG
1310         /*
1311         if(Beam_test_stamp == -1){
1312                 Beam_test_stamp = timestamp(BEAM_TEST_STAMP_TIME);
1313                 Beam_test_ints = 0;
1314                 Beam_test_framecount = 0;
1315         } else {
1316                 if(timestamp_elapsed(Beam_test_stamp)){                 
1317                         // report the results
1318                         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));
1319
1320                         // reset vars
1321                         Beam_test_stamp = timestamp(BEAM_TEST_STAMP_TIME);
1322                         Beam_test_ints = 0;
1323                         Beam_test_ship = 0;
1324                         Beam_test_ast = 0;
1325                         Beam_test_framecount = 0;
1326                 } else {
1327                         Beam_test_framecount++;
1328                 }
1329         }
1330         */
1331 #endif
1332 }
1333
1334 // -----------------------------===========================------------------------------
1335 // BEAM RENDERING FUNCTIONS
1336 // -----------------------------===========================------------------------------
1337
1338 // render a beam weapon
1339 #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);
1340 #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);
1341 #define P_VERTICES()            do { for(idx=0; idx<4; idx++){ g3_project_vertex(verts[idx]); } } while(0);
1342 int poly_beam = 0;
1343 void beam_render(beam_weapon_info *bwi, vector *start, vector *shot, float shrink)
1344 {               
1345         int idx, s_idx;
1346         vertex h1[4];                           // halves of a beam section     
1347         vertex *verts[4] = { &h1[0], &h1[1], &h1[2], &h1[3] };  
1348         vector fvec, top1, bottom1, top2, bottom2;
1349         float scale;    
1350
1351         // bogus weapon info index
1352         if(bwi == NULL){
1353                 return;
1354         }
1355
1356         // if the beam start and endpoints are the same
1357         if(vm_vec_same(start, shot)){
1358                 return;
1359         }
1360
1361         // get beam direction
1362         vm_vec_sub(&fvec, shot, start);
1363         vm_vec_normalize_quick(&fvec);          
1364
1365         // turn off backface culling
1366         gr_set_cull(0);
1367         
1368         // draw all sections    
1369         for(s_idx=0; s_idx<bwi->beam_num_sections; s_idx++){
1370                 // calculate the beam points
1371                 scale = frand_range(1.0f - bwi->sections[s_idx].flicker, 1.0f + bwi->sections[s_idx].flicker);
1372                 beam_calc_facing_pts(&top1, &bottom1, &fvec, start, bwi->sections[s_idx].width * scale * shrink, bwi->sections[s_idx].z_add);   
1373                 beam_calc_facing_pts(&top2, &bottom2, &fvec, shot, bwi->sections[s_idx].width * scale * scale * shrink, bwi->sections[s_idx].z_add);                            
1374                 R_VERTICES();                                                                                                                           // rotate and project the vertices
1375                 P_VERTICES();                                           
1376                 STUFF_VERTICES();               // stuff the beam with creamy goodness (texture coords)
1377
1378                 // set the right texture with additive alpha, and draw the poly
1379                 gr_set_bitmap(bwi->sections[s_idx].texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.9999f);              
1380                 g3_draw_poly( 4, verts, TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT);                        
1381         }               
1382         
1383         // turn backface culling back on
1384         gr_set_cull(1); 
1385 }
1386
1387 // generate particles for the muzzle glow
1388 int hack_time = 100;
1389 DCF(h_time, "")
1390 {
1391         dc_get_arg(ARG_INT);
1392         hack_time = Dc_arg_int;
1393 }
1394 void beam_generate_muzzle_particles(beam *b)
1395 {
1396         int particle_count;
1397         int idx;
1398         weapon_info *wip;
1399         vector turret_norm, turret_pos, particle_pos, particle_dir, p_temp;
1400         matrix m;
1401         particle_info pinfo;
1402
1403         // if our hack stamp has expired
1404         if(!((Beam_muzzle_stamp == -1) || timestamp_elapsed(Beam_muzzle_stamp))){
1405                 return;
1406         }
1407
1408         // never generate anything past about 1/5 of the beam fire time 
1409         if(b->warmup_stamp == -1){
1410                 return;
1411         }
1412
1413         // get weapon info
1414         wip = &Weapon_info[b->weapon_info_index];
1415
1416         // no specified particle for this beam weapon
1417         if(wip->b_info.beam_particle_ani < 0){
1418                 return;
1419         }
1420         
1421         // reset the hack stamp
1422         Beam_muzzle_stamp = timestamp(hack_time);
1423
1424         // randomly generate 10 to 20 particles
1425         particle_count = (int)frand_range(0.0f, (float)wip->b_info.beam_particle_count);
1426
1427         // get turret info - position and normal        
1428         turret_pos = b->subsys->system_info->pnt;
1429         turret_norm = b->subsys->system_info->turret_norm;      
1430
1431         // randomly perturb a vector within a cone around the normal
1432         vm_vector_2_matrix(&m, &turret_norm, NULL, NULL);
1433         for(idx=0; idx<particle_count; idx++){
1434                 // get a random point in the cone
1435                 vm_vec_random_cone(&particle_dir, &turret_norm, wip->b_info.beam_particle_angle, &m);
1436                 p_temp = turret_pos;
1437                 vm_vec_scale_add(&p_temp, &turret_pos, &particle_dir, wip->b_info.beam_muzzle_radius * frand_range(0.75f, 0.9f));
1438
1439                 // transform into world coords          
1440                 vm_vec_unrotate(&particle_pos, &p_temp, &b->objp->orient);
1441                 vm_vec_add2(&particle_pos, &b->objp->pos);
1442                 p_temp = particle_dir;
1443                 vm_vec_unrotate(&particle_dir, &p_temp, &b->objp->orient);
1444
1445                 // now generate some interesting values for the particle
1446                 float p_time_ref = wip->b_info.beam_life + ((float)wip->b_info.beam_warmup / 1000.0f);          
1447                 float p_life = frand_range(p_time_ref * 0.5f, p_time_ref * 0.7f);
1448                 float p_vel = (wip->b_info.beam_muzzle_radius / p_life) * frand_range(0.85f, 1.2f);
1449                 vm_vec_scale(&particle_dir, -p_vel);
1450
1451                 memset(&pinfo, 0, sizeof(particle_info));
1452                 pinfo.pos = particle_pos;
1453                 pinfo.vel = particle_dir;
1454                 pinfo.lifetime = p_life;
1455                 pinfo.attached_objnum = -1;
1456                 pinfo.attached_sig = 0;
1457                 pinfo.rad = wip->b_info.beam_particle_radius;
1458                 pinfo.reverse = 1;
1459                 pinfo.type = PARTICLE_BITMAP;
1460                 pinfo.optional_data = wip->b_info.beam_particle_ani;
1461                 pinfo.tracer_length = -1.0f;            
1462                 particle_create(&pinfo);
1463         }
1464 }
1465
1466 // render the muzzle glow for a beam weapon
1467 void beam_render_muzzle_glow(beam *b)
1468 {
1469         vertex v;
1470         weapon_info *wip = &Weapon_info[b->weapon_info_index];
1471         beam_weapon_info *bwi = &Weapon_info[b->weapon_info_index].b_info;
1472         float pct, rand_val;
1473
1474         // if we don't have a glow bitmap
1475         if(bwi->beam_glow_bitmap < 0){
1476                 return;
1477         }
1478
1479         // if the beam is warming up, scale the glow
1480         if(b->warmup_stamp != -1){              
1481                 // get warmup pct
1482                 pct = BEAM_WARMUP_PCT(b);
1483                 rand_val = 1.0f;
1484         } else
1485         // if the beam is warming down
1486         if(b->warmdown_stamp != -1){
1487                 // get warmup pct
1488                 pct = 1.0f - BEAM_WARMDOWN_PCT(b);
1489                 rand_val = 1.0f;
1490         } 
1491         // otherwise the beam is really firing
1492         else {
1493                 pct = 1.0f;
1494                 rand_val = frand_range(0.90f, 1.0f);
1495         }
1496
1497         // draw the bitmap
1498         g3_rotate_vertex(&v, &b->last_start);
1499         gr_set_bitmap( bwi->beam_glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.8f * pct); 
1500         // draw 1 bitmap
1501         g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * rand_val, TMAP_FLAG_TEXTURED);
1502         
1503         // maybe draw more
1504         if(pct > 0.3f){
1505                 g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * 0.75f * rand_val, TMAP_FLAG_TEXTURED);
1506         }
1507         if(pct > 0.5f){
1508                 g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * 0.45f * rand_val, TMAP_FLAG_TEXTURED);
1509         }
1510         if(pct > 0.7f){
1511                 g3_draw_bitmap(&v, 0, wip->b_info.beam_muzzle_radius * pct * 0.25f * rand_val, TMAP_FLAG_TEXTURED);
1512         }
1513 }
1514
1515 // render all beam weapons
1516 void beam_render_all()
1517 {
1518         beam *moveup;           
1519
1520         // traverse through all active beams
1521         moveup = GET_FIRST(&Beam_used_list);
1522         while(moveup != END_OF_LIST(&Beam_used_list)){                          
1523                 // each beam type renders a little bit differently
1524                 if((moveup->warmup_stamp == -1) && (moveup->warmdown_stamp == -1) && !(moveup->flags & BF_SAFETY)){
1525                         // HACK -  if this is the first frame the beam is firing, don't render it
1526                         if(moveup->life_left >= moveup->life_total - 0.0001f){
1527
1528                                 moveup = GET_NEXT(moveup);
1529                                 continue;
1530                         }                       
1531
1532                         // render the beam itself
1533                         Assert(moveup->weapon_info_index >= 0);
1534                         if(moveup->weapon_info_index < 0){
1535                                 moveup = GET_NEXT(moveup);
1536                                 continue;
1537                         }
1538                         beam_render(&Weapon_info[moveup->weapon_info_index].b_info, &moveup->last_start, &moveup->last_shot, moveup->shrink);
1539                 }
1540
1541                 // render the muzzle glow
1542                 beam_render_muzzle_glow(moveup);                
1543
1544                 // maybe generate some muzzle particles
1545                 beam_generate_muzzle_particles(moveup);
1546
1547                 // next item
1548                 moveup = GET_NEXT(moveup);
1549         }       
1550 }
1551
1552 // output top and bottom vectors
1553 // fvec == forward vector (eye viewpoint basically. in world coords)
1554 // pos == world coordinate of the point we're calculating "around"
1555 // w == width of the diff between top and bottom around pos
1556 void beam_calc_facing_pts( vector *top, vector *bot, vector *fvec, vector *pos, float w, float z_add )
1557 {
1558         vector uvec, rvec;
1559         vector temp;    
1560
1561         temp = *pos;
1562
1563         vm_vec_sub( &rvec, &Eye_position, &temp );
1564         vm_vec_normalize( &rvec );      
1565
1566         vm_vec_crossprod(&uvec,fvec,&rvec);
1567         vm_vec_normalize(&uvec);
1568
1569         vm_vec_scale_add( top, &temp, &uvec, w/2.0f );
1570         vm_vec_scale_add( bot, &temp, &uvec, -w/2.0f ); 
1571 }
1572
1573 // light scale factor
1574 float blight = 25.5f;
1575 DCF(blight, "")
1576 {
1577         dc_get_arg(ARG_FLOAT);
1578         blight = Dc_arg_float;
1579 }
1580
1581 // call to add a light source to a small object
1582 void beam_add_light_small(beam *bm, object *objp, vector *pt_override = NULL)
1583 {
1584         weapon_info *wip;
1585         beam_weapon_info *bwi;
1586         float noise;
1587
1588         // no lighting 
1589         if(!Beam_lighting){
1590                 return;
1591         }
1592
1593         // sanity
1594         Assert(bm != NULL);
1595         if(bm == NULL){
1596                 return;
1597         }
1598         Assert(objp != NULL);
1599         if(objp == NULL){
1600                 return;
1601         }
1602         Assert(bm->weapon_info_index >= 0);
1603         wip = &Weapon_info[bm->weapon_info_index];
1604         bwi = &wip->b_info;
1605
1606         // some noise
1607         noise = frand_range(1.0f - bwi->sections[0].flicker, 1.0f + bwi->sections[0].flicker);
1608
1609         // widest part of the beam
1610         float light_rad = beam_get_widest(bm) * blight * noise; 
1611
1612         // nearest point on the beam, and its distance to the ship
1613         vector near_pt;
1614         if(pt_override == NULL){
1615                 float dist;
1616                 vm_vec_dist_to_line(&objp->pos, &bm->last_start, &bm->last_shot, &near_pt, &dist);
1617                 if(dist > light_rad){
1618                         return;
1619                 }
1620         } else {
1621                 near_pt = *pt_override;
1622         }
1623
1624         // average rgb of the beam      
1625         float fr = (float)wip->laser_color_1.red / 255.0f;
1626         float fg = (float)wip->laser_color_1.green / 255.0f;
1627         float fb = (float)wip->laser_color_1.blue / 255.0f;
1628
1629         // add a unique light
1630         // noise *= 0.1f;                       // a little less noise here, since we want the beam to generally cast a bright light
1631         light_add_point_unique(&near_pt, light_rad * 0.0001f, light_rad, 1.0f, fr, fg, fb, OBJ_INDEX(objp));
1632 }
1633
1634 // call to add a light source to a large object
1635 void beam_add_light_large(beam *bm, object *objp, vector *pt0, vector *pt1)
1636 {
1637         weapon_info *wip;
1638         beam_weapon_info *bwi;
1639         float noise;
1640
1641         // no lighting 
1642         if(!Beam_lighting){
1643                 return;
1644         }
1645
1646         // sanity
1647         Assert(bm != NULL);
1648         if(bm == NULL){
1649                 return;
1650         }
1651         Assert(objp != NULL);
1652         if(objp == NULL){
1653                 return;
1654         }
1655         Assert(bm->weapon_info_index >= 0);
1656         wip = &Weapon_info[bm->weapon_info_index];
1657         bwi = &wip->b_info;
1658
1659         // some noise
1660         noise = frand_range(1.0f - bwi->sections[0].flicker, 1.0f + bwi->sections[0].flicker);
1661
1662         // widest part of the beam
1663         float light_rad = beam_get_widest(bm) * blight * noise;         
1664
1665         // average rgb of the beam      
1666         float fr = (float)wip->laser_color_1.red / 255.0f;
1667         float fg = (float)wip->laser_color_1.green / 255.0f;
1668         float fb = (float)wip->laser_color_1.blue / 255.0f;
1669
1670         // add a unique light
1671         noise *= 0.1f;                  // a little less noise here, since we want the beam to generally cast a bright light
1672         light_add_tube(pt0, pt1, 1.0f, light_rad, 1.0f * noise, fr, fg, fb, OBJ_INDEX(objp));
1673 }
1674
1675 // mark an object as being lit
1676 void beam_add_light(beam *b, int objnum, int source, vector *c_point)
1677 {
1678         beam_light_info *l;
1679
1680         // if we're out of light slots!
1681         if(Beam_light_count >= MAX_BEAM_LIGHT_INFO){
1682                 // Int3();
1683                 return;
1684         }
1685
1686         // otherwise add it
1687         l = &Beam_lights[Beam_light_count++];
1688         l->bm = b;
1689         l->objnum = objnum;
1690         l->source = (ubyte)source;
1691         
1692         // only type 2 lights (from collisions) need a collision point
1693         if(c_point != NULL){
1694                 l->c_point = *c_point;
1695         } else {
1696                 Assert(source != 2);
1697                 if(source == 2){
1698                         Beam_light_count--;
1699                 }
1700         }
1701 }
1702
1703 // apply lighting from any beams
1704 void beam_apply_lighting()
1705 {
1706         int idx;
1707         beam_light_info *l;
1708         vector pt, dir;
1709         beam_weapon_info *bwi;
1710
1711         // convert all beam lights into real lights
1712         for(idx=0; idx<Beam_light_count; idx++){
1713                 // get the light
1714                 l = &Beam_lights[idx];          
1715
1716                 // bad object
1717                 if((l->objnum < 0) || (l->objnum >= MAX_OBJECTS) || (l->bm == NULL)){
1718                         continue;
1719                 }
1720
1721                 bwi = &Weapon_info[l->bm->weapon_info_index].b_info;
1722
1723                 // different light types
1724                 switch(l->source){
1725                 // from the muzzle of the gun
1726                 case 0:
1727                         // a few meters in from the of muzzle                   
1728                         vm_vec_sub(&dir, &l->bm->last_start, &l->bm->last_shot);
1729                         vm_vec_normalize_quick(&dir);
1730                         vm_vec_scale_add(&pt, &l->bm->last_start, &dir, bwi->beam_muzzle_radius * 5.0f);
1731
1732                         beam_add_light_small(l->bm, &Objects[l->objnum], &pt);
1733                         break;
1734
1735                 // from the beam passing by
1736                 case 1:
1737                         // object type
1738                         switch(Objects[l->objnum].type){
1739                         case OBJ_SHIP:
1740                                 Assert(Objects[l->objnum].instance >= 0);
1741
1742                                 // large ships
1743                                 if(Ship_info[Ships[Objects[l->objnum].instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)){
1744                                         beam_add_light_large(l->bm, &Objects[l->objnum], &l->bm->last_start, &l->bm->last_shot);
1745                                 }
1746                                 // small ships
1747                                 else {
1748                                         beam_add_light_small(l->bm, &Objects[l->objnum]);
1749                                 }
1750                                 break;
1751
1752                         // asteroids get small lights
1753                         case OBJ_ASTEROID:
1754                                 beam_add_light_small(l->bm, &Objects[l->objnum]);
1755                                 break;
1756
1757                         // debris gets small lights
1758                         case OBJ_DEBRIS:
1759                                 beam_add_light_small(l->bm, &Objects[l->objnum]);
1760                                 break;
1761                         }
1762                         break;
1763
1764                 // from a collision
1765                 case 2:
1766                         // a few meters from the collision point                        
1767                         vm_vec_sub(&dir, &l->bm->last_start, &l->c_point);
1768                         vm_vec_normalize_quick(&dir);
1769                         vm_vec_scale_add(&pt, &l->c_point, &dir, bwi->beam_muzzle_radius * 5.0f);
1770
1771                         beam_add_light_small(l->bm, &Objects[l->objnum], &pt);
1772                         break;
1773                 }
1774         }       
1775 }
1776
1777 // -----------------------------===========================------------------------------
1778 // BEAM BOOKKEEPING FUNCTIONS
1779 // -----------------------------===========================------------------------------
1780
1781 // delete a beam
1782 void beam_delete(beam *b)
1783 {
1784         // remove from active list and put on free list
1785         list_remove(&Beam_used_list, b);
1786         list_append(&Beam_free_list, b);
1787
1788         // delete our associated object
1789         // Assert(b->objnum >= 0);
1790         if(b->objnum >= 0){
1791                 obj_delete(b->objnum);
1792         }
1793         b->objnum = -1;
1794
1795         // kill the beam looping sound
1796         if(b->beam_sound_loop != -1){
1797                 snd_stop(b->beam_sound_loop);
1798                 b->beam_sound_loop = -1;
1799         }       
1800
1801         // subtract one
1802         Beam_count--;
1803         Assert(Beam_count >= 0);
1804         nprintf(("General", "Recycled beam (%d beams remaining)\n", Beam_count));
1805 }
1806
1807 // given an object, return its model num
1808 int beam_get_model(object *objp)
1809 {
1810         int subtype;
1811         Assert(objp->instance >= 0);
1812         if(objp->instance < 0){
1813                 return -1;
1814         }
1815
1816         subtype = 0;
1817         switch(objp->type){
1818         case OBJ_SHIP:          
1819                 return Ships[objp->instance].modelnum;
1820
1821         case OBJ_WEAPON:
1822                 Assert(Weapons[objp->instance].weapon_info_index >= 0);
1823                 if(Weapons[objp->instance].weapon_info_index < 0){
1824                         return -1;
1825                 }
1826                 return Weapon_info[Weapons[objp->instance].weapon_info_index].model_num;
1827
1828         case OBJ_DEBRIS:
1829                 Assert(Debris[objp->instance].is_hull);
1830                 if(!Debris[objp->instance].is_hull){
1831                         return -1;
1832                 }
1833                 return Debris[objp->instance].model_num;                
1834
1835 #ifndef FS2_DEMO
1836         case OBJ_ASTEROID:
1837                 subtype = Asteroids[objp->instance].asteroid_subtype;
1838                 Assert(Asteroids[objp->instance].type >= 0);
1839                 if(Asteroids[objp->instance].type < 0){
1840                         return -1;
1841                 }
1842                 return Asteroid_info[Asteroids[objp->instance].type].model_num[subtype];
1843 #endif
1844
1845         default:
1846                 // this shouldn't happen too often
1847                 mprintf(("Beam couldn't find a good find a good object model/type!! (%d)", objp->type));
1848                 return -1;
1849         }
1850
1851         // can't happen
1852         Int3();
1853         return -1;
1854 }
1855
1856 // start the warmup phase for the beam
1857 void beam_start_warmup(beam *b)
1858 {
1859         // set the warmup stamp
1860         b->warmup_stamp = timestamp(Weapon_info[b->weapon_info_index].b_info.beam_warmup);
1861
1862         // start playing warmup sound
1863         if(!(Game_mode & GM_STANDALONE_SERVER) && (Weapon_info[b->weapon_info_index].b_info.beam_warmup_sound >= 0)){           
1864                 snd_play_3d(&Snds[Weapon_info[b->weapon_info_index].b_info.beam_warmup_sound], &b->last_start, &View_position);
1865         }
1866 }
1867
1868 // start the firing phase for the beam, return 0 if the beam failed to start, and should be deleted altogether
1869 int beam_start_firing(beam *b)
1870 {
1871         // kill the warmup stamp so the rest of the code knows its firing
1872         b->warmup_stamp = -1;   
1873
1874         // any special stuff for each weapon type
1875         switch(b->type){
1876         // re-aim type A and D beam weapons here, otherwise they tend to miss           
1877         case BEAM_TYPE_A:
1878         case BEAM_TYPE_D:
1879                 beam_aim(b);
1880                 break;
1881         
1882         case BEAM_TYPE_B:
1883                 break;
1884
1885         case BEAM_TYPE_C:
1886                 break;  
1887
1888         case BEAM_TYPE_E:
1889                 break;
1890
1891         default:
1892                 Int3();
1893         }
1894
1895         // determine if we can legitimately start firing, or if we need to take other action
1896         switch(beam_ok_to_fire(b)){
1897         case -1 :
1898                 return 0;
1899
1900         case 0 :                        
1901                 beam_start_warmdown(b);
1902                 return 1;
1903         }                               
1904                         
1905         // start the beam firing sound now, if we haven't already               
1906         if((b->beam_sound_loop == -1) && (Weapon_info[b->weapon_info_index].b_info.beam_loop_sound >= 0)){                              
1907                 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);
1908
1909                 // "shot" sound
1910                 snd_play_3d(&Snds[SND_BEAM_SHOT], &b->last_start, &View_position);
1911         }       
1912
1913         // success
1914         return 1;
1915 }
1916
1917 // start the warmdown phase for the beam
1918 void beam_start_warmdown(beam *b)
1919 {
1920         // timestamp
1921         b->warmdown_stamp = timestamp(Weapon_info[b->weapon_info_index].b_info.beam_warmdown);                  
1922
1923         // start the warmdown sound
1924         if(Weapon_info[b->weapon_info_index].b_info.beam_warmdown_sound >= 0){                          
1925                 snd_play_3d(&Snds[Weapon_info[b->weapon_info_index].b_info.beam_warmdown_sound], &b->last_start, &View_position);
1926         }
1927
1928         // kill the beam looping sound 
1929         if(b->beam_sound_loop != -1){
1930                 snd_stop(b->beam_sound_loop);
1931                 b->beam_sound_loop = -1;
1932         }                                               
1933 }
1934
1935 // recalculate beam sounds (looping sounds relative to the player)
1936 void beam_recalc_sounds(beam *b)
1937 {
1938         beam_weapon_info *bwi;
1939         vector pos;     
1940
1941         Assert(b->weapon_info_index >= 0);
1942         if(b->weapon_info_index < 0){
1943                 return;
1944         }
1945         bwi = &Weapon_info[b->weapon_info_index].b_info;
1946
1947         // update the sound position relative to the player
1948         if(b->beam_sound_loop != -1){
1949                 // get the point closest to the player's viewing position
1950                 switch(vm_vec_dist_to_line(&View_position, &b->last_start, &b->last_shot, &pos, NULL)){
1951                 // behind the beam, so use the start pos
1952                 case -1:
1953                         pos = b->last_start;
1954                         break;
1955
1956                 // use the closest point
1957                 case 0:
1958                         // already calculated in vm_vec_dist_to_line(...)
1959                         break;
1960
1961                 // past the beam, so use the shot pos
1962                 case 1:
1963                         pos = b->last_shot;
1964                         break;
1965                 }
1966
1967                 snd_update_3d_pos(b->beam_sound_loop, &Snds[bwi->beam_loop_sound], &pos);
1968         }
1969 }
1970
1971
1972 // -----------------------------===========================------------------------------
1973 // BEAM AIMING FUNCTIONS
1974 // -----------------------------===========================------------------------------
1975
1976 // fills in binfo
1977 void beam_get_binfo(beam *b, float accuracy, int num_shots)
1978 {
1979         vector p2;
1980         int model_num, idx;     
1981         vector oct1, oct2;
1982         vector turret_point, turret_norm;
1983         beam_weapon_info *bwi;
1984         int skill_level;
1985
1986         // where the shot is originating from (b->last_start gets filled in)
1987         ship_get_global_turret_gun_info(b->objp, b->subsys, &turret_point, &turret_norm, 1, &p2);
1988
1989         // get a model # to work with
1990         model_num = beam_get_model(b->target);  
1991         if(model_num < 0){
1992                 return;
1993         }       
1994
1995         // get beam weapon info
1996         Assert(b->weapon_info_index >= 0);
1997         if(b->weapon_info_index < 0){
1998                 return;
1999         }
2000         bwi = &Weapon_info[b->weapon_info_index].b_info;
2001
2002         // skill level
2003         skill_level = Game_skill_level;
2004         if(Game_skill_level >= NUM_SKILL_LEVELS){
2005                 skill_level = NUM_SKILL_LEVELS - 1;
2006         }
2007         if(Game_skill_level < 0){
2008                 skill_level = 0;
2009         }
2010
2011         // stuff num shots even though its only used for type D weapons
2012         b->binfo.shot_count = (ubyte)num_shots;
2013         if(b->binfo.shot_count > MAX_BEAM_SHOTS){
2014                 b->binfo.shot_count = MAX_BEAM_SHOTS;
2015         }
2016
2017         // generate the proper amount of directional vectors    
2018         switch(b->type){        
2019         // pick an accuracy. beam will be properly aimed at actual fire time
2020         case BEAM_TYPE_A:               
2021                 // all we will do is decide whether or not we will hit - type A beam weapons are re-aimed immediately before firing
2022                 b->binfo.shot_aim[0] = frand_range(0.0f, 1.0f + bwi->beam_miss_factor[skill_level] * accuracy);
2023                 b->binfo.shot_count = 1;
2024
2025                 // get random model points, this is useful for big ships, because we never miss when shooting at them                   
2026                 submodel_get_two_random_points(model_num, 0, &b->binfo.dir_a, &b->binfo.dir_b);
2027                 break;  
2028
2029         // just 2 points in the "slash"
2030         case BEAM_TYPE_B:                                                       
2031                 beam_get_octant_points(model_num, b->target, (int)frand_range(0.0f, BEAM_NUM_GOOD_OCTANTS), Beam_good_slash_octants, &oct1, &oct2);             
2032
2033                 // point 1
2034                 vm_vec_sub(&b->binfo.dir_a, &oct1, &turret_point);
2035                 vm_vec_normalize(&b->binfo.dir_a);
2036
2037                 // point 2
2038                 vm_vec_sub(&b->binfo.dir_b, &oct2, &turret_point);
2039                 vm_vec_normalize(&b->binfo.dir_b);      
2040                 
2041                 // delta angle
2042                 b->binfo.delta_ang = fl_abs(vm_vec_delta_ang_norm(&b->binfo.dir_a, &b->binfo.dir_b, NULL));
2043                 break;
2044
2045         // nothing for this beam - its very special case
2046         case BEAM_TYPE_C:
2047                 break;
2048
2049         // type D beams fire at small ship multiple times
2050         case BEAM_TYPE_D:
2051                 // get a bunch of shot aims
2052                 for(idx=0; idx<b->binfo.shot_count; idx++){
2053                         //      MK, 9/3/99: Added pow() function to make increasingly likely to miss with subsequent shots.  30% more likely with each shot.
2054                         float r = ((float) pow(1.3f, idx)) * bwi->beam_miss_factor[skill_level] * accuracy;
2055                         b->binfo.shot_aim[idx] = frand_range(0.0f, 1.0f + r);
2056                 }
2057                 break;
2058
2059         // type e beams just fire straight
2060         case BEAM_TYPE_E:
2061                 b->binfo.shot_aim[0] = 0.0000001f;
2062                 b->binfo.shot_count = 1;
2063                 b->binfo.dir_a = turret_norm;
2064                 b->binfo.dir_b = turret_norm;
2065                 break;
2066
2067         default:
2068                 break;
2069         }
2070 }
2071
2072 // aim the beam (setup last_start and last_shot - the endpoints). also recalculates collision pairs
2073 void beam_aim(beam *b)
2074 {
2075         vector temp, p2;
2076         int model_num;  
2077         
2078         // type C beam weapons have no target
2079         if(b->target == NULL){
2080                 Assert(b->type == BEAM_TYPE_C);
2081                 if(b->type != BEAM_TYPE_C){
2082                         return;
2083                 }
2084         }
2085         // get a model # to work with
2086         else {
2087                 model_num = beam_get_model(b->target);  
2088                 if(model_num < 0){
2089                         return;
2090                 }       
2091         }
2092
2093         // setup our initial shot point and aim direction
2094         switch(b->type){
2095         case BEAM_TYPE_A:       
2096                 // where the shot is originating from (b->last_start gets filled in)
2097                 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);
2098
2099                 // if we're targeting a subsystem - shoot directly at it
2100                 if(b->target_subsys != NULL){                   
2101                         // unrotate the center of the subsystem
2102                         vm_vec_unrotate(&b->last_shot, &b->target_subsys->system_info->pnt, &b->target->orient);
2103                         vm_vec_add2(&b->last_shot, &b->target->pos);             
2104                         vm_vec_sub(&temp, &b->last_shot, &b->last_start);
2105                         
2106                         vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, 2.0f);
2107                         break;
2108                 }
2109
2110                 // if we're shooting at a big ship - shoot directly at the model
2111                 if((b->target->type == OBJ_SHIP) && (Ship_info[Ships[b->target->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP))){
2112                         // rotate into world coords
2113                         vm_vec_unrotate(&temp, &b->binfo.dir_a, &b->target->orient);
2114                         vm_vec_add2(&temp, &b->target->pos);
2115
2116                         // get the shot point
2117                         vm_vec_sub(&p2, &temp, &b->last_start);
2118                         vm_vec_scale_add(&b->last_shot, &b->last_start, &p2, 2.0f);
2119                         break;
2120                 }
2121                 
2122                 // point at the center of the target, then jitter based on shot_aim
2123                 b->last_shot = b->target->pos;          
2124                 beam_jitter_aim(b, b->binfo.shot_aim[0]);
2125                 break;  
2126
2127         case BEAM_TYPE_B:               
2128                 // where the shot is originating from (b->last_start gets filled in)
2129                 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);                             
2130
2131                 // set the shot point
2132                 vm_vec_scale_add(&b->last_shot, &b->last_start, &b->binfo.dir_a, BEAM_FAR_LENGTH);
2133                 Assert(is_valid_vec(&b->last_shot));            
2134                 break;
2135
2136         case BEAM_TYPE_C:
2137                 // start point
2138                 temp = b->targeting_laser_offset;       
2139                 vm_vec_unrotate(&b->last_start, &temp, &b->objp->orient);
2140                 vm_vec_add2(&b->last_start, &b->objp->pos);
2141                 vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.fvec, BEAM_FAR_LENGTH);                
2142                 break;
2143
2144         case BEAM_TYPE_D:                               
2145                 // where the shot is originating from (b->last_start gets filled in)
2146                 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);             
2147                 
2148                 // point at the center of the target, then jitter based on shot_aim
2149                 b->last_shot = b->target->pos;          
2150                 beam_jitter_aim(b, b->binfo.shot_aim[b->shot_index]);
2151                 nprintf(("AI", "Frame %i: FIRING\n", Framecount));
2152                 break;
2153
2154         case BEAM_TYPE_E:
2155                 // where the shot is originating from (b->last_start gets filled in)
2156                 ship_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2);             
2157
2158                 // point directly in the direction of the turret
2159                 vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, BEAM_FAR_LENGTH);
2160                 break;
2161
2162         default:
2163                 Int3();
2164         }               
2165
2166         // recalculate object pairs
2167         OBJ_RECALC_PAIRS((&Objects[b->objnum]));
2168 }
2169
2170 // given a model #, and an object, stuff 2 good world coord points
2171 void beam_get_octant_points(int modelnum, object *objp, int oct_index, int oct_array[BEAM_NUM_GOOD_OCTANTS][4], vector *v1, vector *v2)
2172 {       
2173         vector t1, t2, temp;
2174         polymodel *m = model_get(modelnum);
2175
2176         // bad bad bad bad bad bad
2177         if(m == NULL){
2178                 Int3();
2179                 return;
2180         }
2181
2182         Assert((oct_index >= 0) && (oct_index < BEAM_NUM_GOOD_OCTANTS));
2183
2184         // randomly pick octants        
2185         t1 = oct_array[oct_index][2] ? m->octants[oct_array[oct_index][0]].max : m->octants[oct_array[oct_index][0]].min;
2186         t2 = oct_array[oct_index][3] ? m->octants[oct_array[oct_index][1]].max : m->octants[oct_array[oct_index][1]].min;
2187         Assert(!vm_vec_same(&t1, &t2));
2188
2189         // get them in world coords
2190         vm_vec_unrotate(&temp, &t1, &objp->orient);
2191         vm_vec_add(v1, &temp, &objp->pos);
2192         vm_vec_unrotate(&temp, &t2, &objp->orient);
2193         vm_vec_add(v2, &temp, &objp->pos);
2194 }
2195
2196 // throw some jitter into the aim - based upon shot_aim
2197 void beam_jitter_aim(beam *b, float aim)
2198 {
2199         vector forward, circle;
2200         matrix m;
2201         float subsys_strength;
2202
2203         // if the weapons subsystem is damaged or destroyed
2204         if((b->objp != NULL) && (b->objp->signature == b->sig) && (b->objp->type == OBJ_SHIP) && (b->objp->instance >= 0) && (b->objp->instance < MAX_SHIPS)){
2205                 // get subsytem strength
2206                 subsys_strength = ship_get_subsystem_strength(&Ships[b->objp->instance], SUBSYSTEM_WEAPONS);
2207                 
2208                 // when subsytem strength is 0, double the aim error factor
2209                 aim += aim * (1.0f - subsys_strength);
2210         }
2211
2212         // shot aim is a direct linear factor of the target model's radius.
2213         // so, pick a random point on the circle
2214         vm_vec_sub(&forward, &b->last_shot, &b->last_start);
2215         vm_vec_normalize_quick(&forward);
2216         
2217         // vector
2218         vm_vector_2_matrix(&m, &forward, NULL, NULL);
2219
2220         // get a vector on the circle - this should appear to be pretty random
2221         // vm_vec_scale_add(&circle, &b->last_shot, &m.rvec, aim * b->target->radius);
2222         vm_vec_random_in_circle(&circle, &b->last_shot, &m, aim * b->target->radius, 0);
2223         
2224         // get the vector pointing to the circle point
2225         vm_vec_sub(&forward, &circle, &b->last_start);  
2226         vm_vec_scale_add(&b->last_shot, &b->last_start, &forward, 2.0f);
2227 }
2228
2229
2230 // -----------------------------===========================------------------------------
2231 // BEAM COLLISION FUNCTIONS
2232 // -----------------------------===========================------------------------------
2233
2234 // collide a beam with a ship, returns 1 if we can ignore all future collisions between the 2 objects
2235 int beam_collide_ship(obj_pair *pair)
2236 {
2237         beam *b;                
2238         ship *shipp;
2239         ship_info *sip;
2240         mc_info test_collide;           
2241         int model_num;  
2242         float widest;
2243
2244         // bogus
2245         if(pair == NULL){
2246                 return 0;
2247         }
2248
2249         // get the beam
2250         Assert(pair->a->instance >= 0);
2251         Assert(pair->a->type == OBJ_BEAM);
2252         Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2253         b = &Beams[pair->a->instance];
2254
2255         // Don't check collisions for warping out player if past stage 1.
2256         if ( Player->control_mode >= PCM_WARPOUT_STAGE1)        {
2257                 if ( pair->a == Player_obj ) return 0;
2258                 if ( pair->b == Player_obj ) return 0;
2259         }
2260
2261         // if the "warming up" timestamp has not expired
2262         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){               
2263                 return 0;
2264         }
2265
2266         // if the beam is on "safety", don't collide with anything
2267         if(b->flags & BF_SAFETY){
2268                 return 0;
2269         }
2270         
2271         // if the colliding object is the shooting object, return 1 so this is culled
2272         if(pair->b == b->objp){
2273                 return 1;
2274         }       
2275
2276         // try and get a model
2277         model_num = beam_get_model(pair->b);
2278         if(model_num < 0){
2279                 // Int3();
2280                 return 1;
2281         }
2282         
2283 #ifndef NDEBUG
2284         Beam_test_ints++;
2285         Beam_test_ship++;
2286 #endif
2287
2288         // bad
2289         Assert(pair->b->type == OBJ_SHIP);
2290         Assert(pair->b->instance >= 0);
2291         if((pair->b->type != OBJ_SHIP) || (pair->b->instance < 0)){
2292                 return 1;
2293         }
2294         shipp = &Ships[pair->b->instance];
2295         sip = &Ship_info[shipp->ship_info_index];
2296
2297         // get the widest portion of the beam
2298         widest = beam_get_widest(b);
2299
2300         // do the collision             
2301         test_collide.model_num = model_num;
2302         test_collide.submodel_num = -1;
2303         test_collide.orient = &pair->b->orient;
2304         test_collide.pos = &pair->b->pos;
2305         test_collide.p0 = &b->last_start;
2306         test_collide.p1 = &b->last_shot;
2307
2308         // maybe do a sphereline
2309         if(widest > pair->b->radius * BEAM_AREA_PERCENT){
2310                 test_collide.radius = beam_get_widest(b) * 0.5f;
2311                 test_collide.flags = MC_CHECK_MODEL | MC_CHECK_SPHERELINE;
2312         } else {        
2313                 test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;     
2314         }
2315         model_collide(&test_collide);
2316
2317         // if we got a hit
2318         if(test_collide.num_hits){
2319                 // add to the collision list
2320                 beam_add_collision(b, pair->b, &test_collide);          
2321         }       
2322
2323         // add this guy to the lighting list
2324         beam_add_light(b, OBJ_INDEX(pair->b), 1, NULL);
2325
2326         // reset timestamp to timeout immediately
2327         pair->next_check_time = timestamp(0);
2328                 
2329         return 0;
2330 }
2331
2332 // collide a beam with an asteroid, returns 1 if we can ignore all future collisions between the 2 objects
2333 int beam_collide_asteroid(obj_pair *pair)
2334 {
2335         beam *b;                
2336         mc_info test_collide;           
2337         int model_num;
2338
2339         // bogus
2340         if(pair == NULL){
2341                 return 0;
2342         }
2343
2344         // get the beam
2345         Assert(pair->a->instance >= 0);
2346         Assert(pair->a->type == OBJ_BEAM);
2347         Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2348         b = &Beams[pair->a->instance];
2349
2350         // if the "warming up" timestamp has not expired
2351         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
2352                 return 0;
2353         }
2354
2355         // if the beam is on "safety", don't collide with anything
2356         if(b->flags & BF_SAFETY){
2357                 return 0;
2358         }
2359         
2360         // if the colliding object is the shooting object, return 1 so this is culled
2361         if(pair->b == b->objp){
2362                 return 1;
2363         }       
2364
2365         // try and get a model
2366         model_num = beam_get_model(pair->b);
2367         if(model_num < 0){
2368                 Int3();
2369                 return 1;
2370         }       
2371
2372 #ifndef NDEBUG
2373         Beam_test_ints++;
2374         Beam_test_ast++;
2375 #endif
2376
2377         // do the collision             
2378         test_collide.model_num = model_num;
2379         test_collide.submodel_num = -1;
2380         test_collide.orient = &pair->b->orient;
2381         test_collide.pos = &pair->b->pos;
2382         test_collide.p0 = &b->last_start;
2383         test_collide.p1 = &b->last_shot;        
2384         test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
2385         model_collide(&test_collide);
2386
2387         // if we got a hit
2388         if(test_collide.num_hits){
2389                 // add to the collision list
2390                 beam_add_collision(b, pair->b, &test_collide);
2391         }       
2392
2393         // add this guy to the lighting list
2394         beam_add_light(b, OBJ_INDEX(pair->b), 1, NULL);
2395
2396         // reset timestamp to timeout immediately
2397         pair->next_check_time = timestamp(0);
2398                 
2399         return 0;       
2400 }
2401
2402 // collide a beam with a missile, returns 1 if we can ignore all future collisions between the 2 objects
2403 int beam_collide_missile(obj_pair *pair)
2404 {
2405         beam *b;        
2406         mc_info test_collide;           
2407         int model_num;
2408
2409         // bogus
2410         if(pair == NULL){
2411                 return 0;
2412         }
2413
2414         // get the beam
2415         Assert(pair->a->instance >= 0);
2416         Assert(pair->a->type == OBJ_BEAM);
2417         Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2418         b = &Beams[pair->a->instance];
2419
2420         // if the "warming up" timestamp has not expired
2421         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
2422                 return 0;
2423         }
2424
2425         // if the beam is on "safety", don't collide with anything
2426         if(b->flags & BF_SAFETY){
2427                 return 0;
2428         }
2429         
2430         // if the colliding object is the shooting object, return 1 so this is culled
2431         if(pair->b == b->objp){
2432                 return 1;
2433         }       
2434
2435         // try and get a model
2436         model_num = beam_get_model(pair->b);
2437         if(model_num < 0){
2438                 //Int3();
2439                 return 1;
2440         }
2441
2442 #ifndef NDEBUG
2443         Beam_test_ints++;
2444 #endif
2445
2446         // do the collision             
2447         test_collide.model_num = model_num;
2448         test_collide.submodel_num = -1;
2449         test_collide.orient = &pair->b->orient;
2450         test_collide.pos = &pair->b->pos;
2451         test_collide.p0 = &b->last_start;
2452         test_collide.p1 = &b->last_shot;
2453         test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
2454         model_collide(&test_collide);
2455
2456         // if we got a hit
2457         if(test_collide.num_hits){
2458                 // add to the collision list
2459                 beam_add_collision(b, pair->b, &test_collide);
2460         }
2461
2462         // reset timestamp to timeout immediately
2463         pair->next_check_time = timestamp(0);
2464
2465         return 0;
2466 }
2467
2468 // collide a beam with debris, returns 1 if we can ignore all future collisions between the 2 objects
2469 int beam_collide_debris(obj_pair *pair)
2470 {       
2471         beam *b;        
2472         mc_info test_collide;           
2473         int model_num;
2474
2475         // bogus
2476         if(pair == NULL){
2477                 return 0;
2478         }
2479
2480         // get the beam
2481         Assert(pair->a->instance >= 0);
2482         Assert(pair->a->type == OBJ_BEAM);
2483         Assert(Beams[pair->a->instance].objnum == OBJ_INDEX(pair->a));
2484         b = &Beams[pair->a->instance];
2485
2486         // if the "warming up" timestamp has not expired
2487         if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
2488                 return 0;
2489         }
2490
2491         // if the beam is on "safety", don't collide with anything
2492         if(b->flags & BF_SAFETY){
2493                 return 0;
2494         }
2495         
2496         // if the colliding object is the shooting object, return 1 so this is culled
2497         if(pair->b == b->objp){
2498                 return 1;
2499         }       
2500
2501         // try and get a model
2502         model_num = beam_get_model(pair->b);
2503         if(model_num < 0){
2504                 // Int3();
2505                 return 1;
2506         }       
2507
2508 #ifndef NDEBUG
2509         Beam_test_ints++;
2510 #endif
2511
2512         // do the collision             
2513         test_collide.model_num = model_num;
2514         test_collide.submodel_num = -1;
2515         test_collide.orient = &pair->b->orient;
2516         test_collide.pos = &pair->b->pos;
2517         test_collide.p0 = &b->last_start;
2518         test_collide.p1 = &b->last_shot;
2519         test_collide.flags = MC_CHECK_MODEL | MC_CHECK_RAY;
2520         model_collide(&test_collide);
2521
2522         // if we got a hit
2523         if(test_collide.num_hits){
2524                 // add to the collision list
2525                 beam_add_collision(b, pair->b, &test_collide);
2526         }       
2527
2528         // add this guy to the lighting list
2529         beam_add_light(b, OBJ_INDEX(pair->b), 1, NULL);
2530
2531         // reset timestamp to timeout immediately
2532         pair->next_check_time = timestamp(0);
2533
2534         return 0;
2535 }
2536
2537 // early-out function for when adding object collision pairs, return 1 if the pair should be ignored
2538 int beam_collide_early_out(object *a, object *b)
2539 {
2540         beam *bm;
2541         weapon_info *bwi;
2542         float cull_dist, cull_dot;
2543         vector dot_test, dot_test2, dist_test;  
2544                 
2545         // get the beam
2546         Assert(a->instance >= 0);
2547         if(a->instance < 0){
2548                 return 1;
2549         }
2550         Assert(a->type == OBJ_BEAM);
2551         if(a->type != OBJ_BEAM){
2552                 return 1;
2553         }
2554         Assert(Beams[a->instance].objnum == OBJ_INDEX(a));
2555         if(Beams[a->instance].objnum != OBJ_INDEX(a)){
2556                 return 1;
2557         }       
2558         bm = &Beams[a->instance];
2559         Assert(bm->weapon_info_index >= 0);
2560         if(bm->weapon_info_index < 0){
2561                 return 1;
2562         }
2563         bwi = &Weapon_info[bm->weapon_info_index];
2564
2565         // if the second object has an invalid instance, bail
2566         if(b->instance < 0){
2567                 return 1;
2568         }
2569
2570         // baseline bails
2571         switch(b->type){
2572         case OBJ_SHIP:
2573                 break;
2574         case OBJ_ASTEROID:
2575                 // targeting lasers only hit ships
2576                 if(bwi->b_info.beam_type == BEAM_TYPE_C){
2577                         return 1;
2578                 }
2579                 break;
2580         case OBJ_DEBRIS:
2581                 // targeting lasers only hit ships
2582                 if(bwi->b_info.beam_type == BEAM_TYPE_C){
2583                         return 1;
2584                 }
2585                 // don't ever collide with non hull pieces
2586                 if(!Debris[b->instance].is_hull){
2587                         return 1;
2588                 }
2589                 break;
2590         case OBJ_WEAPON:
2591                 // targeting lasers only hit ships
2592                 if(bwi->b_info.beam_type == BEAM_TYPE_C){
2593                         return 1;
2594                 }
2595                 // don't ever collide against laser weapons - duh
2596                 if(Weapon_info[Weapons[b->instance].weapon_info_index].subtype == WP_LASER){
2597                         return 1;
2598                 }
2599                 break;
2600         }
2601
2602         // get full cull value
2603         beam_get_cull_vals(b, bm, &cull_dot, &cull_dist);
2604
2605         // if the object fails these conditions, bail
2606         vm_vec_sub(&dist_test, &b->pos, &bm->last_start);
2607         dot_test = dist_test;
2608         vm_vec_sub(&dot_test2, &bm->last_shot, &bm->last_start);
2609         vm_vec_normalize_quick(&dot_test);
2610         vm_vec_normalize_quick(&dot_test2);
2611         // cull_dist == DIST SQUARED FOO!
2612         if((vm_vec_dotprod(&dot_test, &dot_test2) < cull_dot) && (vm_vec_mag_squared(&dist_test) > cull_dist)){
2613                 return 1;
2614         }
2615         
2616         // don't cull
2617         return 0;
2618 }
2619
2620 // add a collision to the beam for this frame (to be evaluated later)
2621 void beam_add_collision(beam *b, object *hit_object, mc_info *cinfo)
2622 {
2623         beam_collision *bc;
2624         int idx;
2625
2626         // if we haven't reached the limit for beam collisions, just add
2627         if(b->f_collision_count < MAX_FRAME_COLLISIONS){
2628                 bc = &b->f_collisions[b->f_collision_count++];
2629                 bc->c_objnum = OBJ_INDEX(hit_object);
2630                 bc->cinfo = *cinfo;
2631
2632                 // done
2633                 return;
2634         }               
2635
2636         // otherwise, we've got to do some checking, ick. 
2637         // I guess we can always just remove the farthest item
2638         bc = NULL;
2639         for(idx=0; idx<MAX_FRAME_COLLISIONS; idx++){
2640                 if((bc == NULL) || (b->f_collisions[idx].cinfo.hit_dist > bc->cinfo.hit_dist)){
2641                         bc = &b->f_collisions[idx];
2642                 }
2643         }
2644
2645         // copy in
2646         Assert(bc != NULL);
2647         if(bc == NULL){
2648                 return;
2649         }
2650         bc->c_objnum = OBJ_INDEX(hit_object);
2651         bc->cinfo = *cinfo;
2652 }
2653
2654 // sort collisions for the frame
2655 int beam_sort_collisions_func(const void *e1, const void *e2)
2656 {
2657         beam_collision *b1 = (beam_collision*)e1;
2658         beam_collision *b2 = (beam_collision*)e2;
2659
2660         return b1->cinfo.hit_dist < b2->cinfo.hit_dist ? -1 : 1;
2661 }
2662
2663 // handle a hit on a specific object
2664 void beam_handle_collisions(beam *b)
2665 {       
2666         int idx, s_idx;
2667         beam_collision r_coll[MAX_FRAME_COLLISIONS];
2668         int r_coll_count = 0;
2669         beam_weapon_info *bwi;
2670         weapon_info *wi;
2671         float widest;   
2672
2673         // early out if we had no collisions
2674         if(b->f_collision_count <= 0){
2675                 return;
2676         }
2677
2678         // get beam weapon info
2679         if((b->weapon_info_index < 0) || (b->weapon_info_index >= Num_weapon_types)){
2680                 Int3();
2681                 return;
2682         }
2683         bwi = &Weapon_info[b->weapon_info_index].b_info;
2684         wi = &Weapon_info[b->weapon_info_index];
2685
2686         // get the widest part of the beam
2687         widest = beam_get_widest(b);
2688
2689         // the first thing we need to do is sort the collisions, from closest to farthest
2690         qsort(b->f_collisions, b->f_collision_count, sizeof(beam_collision), beam_sort_collisions_func);
2691
2692         // now apply all collisions until we reach a ship which "stops" the beam or we reach the end of the list
2693         for(idx=0; idx<b->f_collision_count; idx++){    
2694                 int model_num = -1;
2695                 int do_damage = 0;
2696                 int first_hit = 1;
2697                 int target = b->f_collisions[idx].c_objnum;
2698
2699                 // if we have an invalid object
2700                 if((target < 0) || (target >= MAX_OBJECTS)){
2701                         continue;
2702                 }
2703
2704                 // try and get a model to deal with             
2705                 model_num = beam_get_model(&Objects[target]);
2706                 if(model_num < 0){
2707                         continue;
2708                 }
2709
2710                 // add lighting
2711                 beam_add_light(b, target, 2, &b->f_collisions[idx].cinfo.hit_point_world);
2712
2713                 // add to the recent collision list
2714                 r_coll[r_coll_count].c_objnum = target;
2715                 r_coll[r_coll_count].c_sig = Objects[target].signature;
2716                 r_coll[r_coll_count].c_stamp = -1;
2717                 r_coll[r_coll_count].cinfo = b->f_collisions[idx].cinfo;
2718                 
2719                 // if he was already on the recent collision list, copy his timestamp
2720                 // also, be sure not to play the impact sound again.
2721                 for(s_idx=0; s_idx<b->r_collision_count; s_idx++){
2722                         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)){
2723                                 // timestamp
2724                                 r_coll[r_coll_count].c_stamp = b->r_collisions[s_idx].c_stamp;
2725
2726                                 // don't play the impact sound again
2727                                 first_hit = 0;
2728                         }
2729                 }
2730                                 
2731                 // if the damage timestamp has expired or is not set yet, apply damage
2732                 if((r_coll[r_coll_count].c_stamp == -1) || timestamp_elapsed(r_coll[r_coll_count].c_stamp)){
2733                         do_damage = 1;
2734                         r_coll[r_coll_count].c_stamp = timestamp(BEAM_DAMAGE_TIME);
2735                 }
2736
2737                 // if no damage - don't even indicate it has been hit
2738                 if(wi->damage <= 0){
2739                         do_damage = 0;
2740                 }
2741
2742                 // increment collision count
2743                 r_coll_count++;         
2744
2745                 // play the impact sound
2746                 if(first_hit){
2747                         snd_play_3d( &Snds[wi->impact_snd], &b->f_collisions[idx].cinfo.hit_point_world, &Eye_position );
2748                 }
2749                 
2750                 // do damage
2751                 if(do_damage && !physics_paused){               
2752                         // maybe draw an explosion
2753                         if(wi->impact_weapon_expl_index >= 0){
2754                                 int ani_handle = weapon_get_expl_handle(wi->impact_weapon_expl_index, &b->f_collisions[idx].cinfo.hit_point_world, wi->impact_explosion_radius);
2755                                 particle_create( &b->f_collisions[idx].cinfo.hit_point_world, &vmd_zero_vector, 0.0f, wi->impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, ani_handle );
2756                         }
2757
2758                         switch(Objects[target].type){
2759                         case OBJ_DEBRIS:
2760                                 // hit the debris - the debris hit code takes care of checking for MULTIPLAYER_CLIENT, etc
2761                                 debris_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, Weapon_info[b->weapon_info_index].damage);
2762                                 break;
2763
2764                         case OBJ_WEAPON:
2765                                 // detonate the missile
2766                                 Assert(Weapon_info[Weapons[Objects[target].instance].weapon_info_index].subtype == WP_MISSILE);
2767                                 if(!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER){
2768                                         weapon_hit(&Objects[target], NULL, &Objects[target].pos);
2769                                 }
2770                                 break;
2771
2772                         case OBJ_ASTEROID:
2773                                 // hit the asteroid
2774                                 if(!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER){
2775                                         asteroid_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, Weapon_info[b->weapon_info_index].damage);
2776                                 }
2777                                 break;
2778
2779                         case OBJ_SHIP:                          
2780                                 // hit the ship - again, the innards of this code handle multiplayer cases
2781                                 // maybe vaporize ship.
2782                                 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);
2783
2784                                 // if this is the first hit on the player ship. whack him                               
2785                                 if(do_damage){
2786                                         beam_apply_whack(b, &Objects[target], &b->f_collisions[idx].cinfo.hit_point_world);
2787                                 }
2788                                 break;
2789                         }                                                                       
2790                 }                               
2791
2792                 // if the radius of the target is somewhat close to the radius of the beam, "stop" the beam here
2793                 // for now : if its smaller than about 1/3 the radius of the ship
2794                 if(widest <= (Objects[target].radius * BEAM_AREA_PERCENT) && !beam_will_tool_target(b, &Objects[target])){      
2795                         // set last_shot so we know where to properly draw the beam             
2796                         b->last_shot = b->f_collisions[idx].cinfo.hit_point_world;
2797                         Assert(is_valid_vec(&b->last_shot));            
2798
2799                         // done wif the beam
2800                         break;
2801                 }
2802         }
2803
2804         // store the new recent collisions
2805         for(idx=0; idx<r_coll_count; idx++){
2806                 b->r_collisions[idx] = r_coll[idx];
2807         }
2808         b->r_collision_count = r_coll_count;
2809 }
2810
2811 // for a given object, and a firing beam, determine its critical dot product and range
2812 void beam_get_cull_vals(object *objp, beam *b, float *cull_dot, float *cull_dist)
2813 {
2814         switch(objp->type){
2815         // debris and asteroids are classified as slow moving small objects
2816         // use cull_dot == potential cone of beam + 10% and 50.0 meters
2817         case OBJ_DEBRIS:
2818         case OBJ_ASTEROID:
2819                 *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.10f);
2820                 *cull_dist = 50.0f * 50.0f;
2821                 return;
2822
2823         // treat missiles as fast-moving small objects
2824         case OBJ_WEAPON:
2825                 *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.5f);
2826                 *cull_dist = 300.0f * 300.0f;
2827                 return;
2828
2829         case OBJ_SHIP:
2830                 // for cap ships, only cull for 90deg or better
2831                 /*
2832                 if(Ship_info[Ships[objp->instance].ship_info_index].flags & SIF_CAPITAL){
2833                         *cull_dot = 0.0f;
2834                         *cull_dist = 0.0f;
2835                         return;
2836                 }
2837                 */
2838
2839                 // for large ships, cull at some multiple of the radius
2840                 if(Ship_info[Ships[objp->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)){
2841                         *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.25f);
2842                         
2843                         *cull_dist = (objp->radius * 1.3f) * (objp->radius * 1.3f);
2844                         return;
2845                 }
2846
2847                 // for everthing else, cull the same as missiles
2848                 *cull_dot = 1.0f - ((1.0f - beam_get_cone_dot(b)) * 1.5f);
2849                 *cull_dist = 300.0f * 300.0f;
2850                 return;
2851         }
2852
2853         // BAD BAD BAD - but this code will cause everything to cull properly
2854         Int3();
2855         *cull_dot = 1.0f;
2856         *cull_dist = 0.0f;
2857         return;
2858 }
2859
2860 // FIXME - make sure we are truthfull representing the "cone" for all beam types
2861 // get the total possible cone for a given beam in radians
2862 float beam_get_cone_dot(beam *b)
2863 {
2864         switch(b->type){
2865         case BEAM_TYPE_A:
2866         case BEAM_TYPE_C:
2867         case BEAM_TYPE_D:
2868         case BEAM_TYPE_E:
2869                 // even though these beams don't move, return a _very_ small value
2870                 return (float)cos(fl_radian(50.5f));    
2871                 
2872         case BEAM_TYPE_B:
2873                 return vm_vec_dotprod(&b->binfo.dir_a, &b->binfo.dir_b);
2874
2875         default:
2876                 Int3();
2877         }
2878
2879         Int3();
2880         return 0.0f;
2881 }
2882
2883 // if it is legal for the beam to fire, or continue firing
2884 int beam_ok_to_fire(beam *b)
2885 {
2886         // type C beams are ok to fire all the time
2887         if(Weapon_info[b->weapon_info_index].b_info.beam_type == BEAM_TYPE_C){
2888                 return 1;
2889         }
2890
2891         // if my own object is invalid, stop firing
2892         if(b->objp->signature != b->sig){
2893                 mprintf(("BEAM : killing beam because of invalid parent object SIGNATURE!\n"));
2894                 return -1;
2895         }
2896
2897         // if my own object is a ghost
2898         if(b->objp->type != OBJ_SHIP){
2899                 mprintf(("BEAM : killing beam because of invalid parent object TYPE!\n"));
2900                 return -1;
2901         }       
2902
2903         // if the shooting turret is destroyed  
2904         if(b->subsys->current_hits <= 0.0f){            
2905                 mprintf(("BEAM : killing beam because turret has been destroyed!\n"));
2906                 return -1;
2907         }               
2908
2909         // if the beam will be firing out of its FOV, power it down
2910         vector aim_dir, temp;
2911         vector turret_dir, turret_pos;
2912         vm_vec_sub(&aim_dir, &b->last_shot, &b->last_start);
2913         vm_vec_normalize(&aim_dir);
2914         ship_get_global_turret_gun_info(b->objp, b->subsys, &turret_pos, &turret_dir, 1, &temp);
2915         if(vm_vec_dotprod(&aim_dir, &turret_dir) < b->subsys->system_info->turret_fov){
2916                 mprintf(("BEAM : powering beam down because of FOV condition!\n"));
2917                 return 0;
2918         }
2919
2920         // ok to fire/continue firing
2921         return 1;
2922 }
2923
2924 // get the width of the widest section of the beam
2925 float beam_get_widest(beam *b)
2926 {
2927         int idx;
2928         float widest = -1.0f;
2929
2930         // sanity
2931         Assert(b->weapon_info_index >= 0);
2932         if(b->weapon_info_index < 0){
2933                 return -1.0f;
2934         }
2935
2936         // lookup
2937         for(idx=0; idx<Weapon_info[b->weapon_info_index].b_info.beam_num_sections; idx++){
2938                 if(Weapon_info[b->weapon_info_index].b_info.sections[idx].width > widest){
2939                         widest = Weapon_info[b->weapon_info_index].b_info.sections[idx].width;
2940                 }
2941         }
2942
2943         // return       
2944         return widest * b->shrink;
2945 }  
2946
2947 // apply a whack to a ship
2948 void beam_apply_whack(beam *b, object *objp, vector *hit_point)
2949 {
2950         weapon_info *wip;       
2951         ship *shipp;
2952
2953         // sanity
2954         Assert((b != NULL) && (objp != NULL) && (hit_point != NULL));
2955         if((b == NULL) || (objp == NULL) || (hit_point == NULL)){
2956                 return;
2957         }       
2958         Assert(b->weapon_info_index >= 0);
2959         wip = &Weapon_info[b->weapon_info_index];       
2960         Assert((objp != NULL) && (objp->type == OBJ_SHIP) && (objp->instance >= 0) && (objp->instance < MAX_SHIPS));
2961         if((objp == NULL) || (objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
2962                 return;
2963         }
2964         shipp = &Ships[objp->instance];
2965         if((shipp->ai_index < 0) || (shipp->ai_index >= MAX_AI_INFO)){
2966                 return;
2967         }
2968
2969         // don't whack docking ships
2970         if(Ai_info[shipp->ai_index].ai_flags & AIF_DOCKED){
2971                 return;
2972         }
2973
2974         // determine how big of a whack to apply
2975         float whack;
2976         float dist;
2977
2978         if(wip->damage < b_whack_damage){
2979                 whack = b_whack_small;
2980         } else {
2981                 whack = b_whack_big;
2982         }
2983
2984         // whack direction
2985         vector whack_dir, temp;
2986         vm_vec_dist_to_line(&objp->pos, &b->last_start, &b->last_shot, &temp, &dist);
2987         vm_vec_sub(&whack_dir, &objp->pos, &temp);
2988         vm_vec_normalize(&whack_dir);
2989         vm_vec_scale(&whack_dir, whack);
2990
2991         // apply the whack
2992         ship_apply_whack(&whack_dir, hit_point, objp);
2993 }
2994
2995 // return the amount of damage which should be applied to a ship. basically, filters friendly fire damage 
2996 float beam_get_ship_damage(beam *b, object *objp)
2997 {       
2998         // if the beam is on the same team as the object
2999         Assert((objp != NULL) && (b != NULL));
3000         if((objp == NULL) || (b == NULL)){
3001                 return 0.0f;
3002         }
3003         Assert((objp->type == OBJ_SHIP) && (objp->instance >= 0) && (objp->instance < MAX_SHIPS));
3004         if((objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
3005                 return 0.0f;
3006         }
3007
3008         // same team. yikes
3009         if((b->team == Ships[objp->instance].team) && (Weapon_info[b->weapon_info_index].damage > Beam_friendly_cap[Game_skill_level])){
3010                 return Beam_friendly_cap[Game_skill_level];
3011         }
3012
3013         // normal damage
3014         return Weapon_info[b->weapon_info_index].damage;
3015 }
3016
3017 // if the beam is likely to tool a given target before its lifetime expires
3018 int beam_will_tool_target(beam *b, object *objp)
3019 {
3020         weapon_info *wip = &Weapon_info[b->weapon_info_index];
3021         float damage_in_a_few_seconds;
3022
3023         // sanity
3024         if(objp == NULL){
3025                 return 0;
3026         }
3027
3028         // if the object is not a ship, bail
3029         if(objp->type != OBJ_SHIP){
3030                 return 0;
3031         }
3032         if((objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
3033                 return 0;
3034         }
3035
3036         // if the beam is going to apply more damage in about 1 and a half than the hull of the ship can take
3037         damage_in_a_few_seconds = (TOOLTIME / (float)BEAM_DAMAGE_TIME) * wip->damage;
3038         if(objp->hull_strength < damage_in_a_few_seconds){
3039
3040                 // tooled
3041                 return 1;
3042         }
3043
3044         return 0;
3045 }
3046
3047 float beam_accuracy = 1.0f;
3048 DCF(b_aim, "")
3049 {
3050         dc_get_arg(ARG_FLOAT);
3051         beam_accuracy = Dc_arg_float;
3052 }
3053 DCF(beam_list, "")
3054 {
3055         int idx;
3056         int b_count = 0;
3057
3058         for(idx=0; idx<Num_weapon_types; idx++){
3059                 if(Weapon_info[idx].wi_flags & WIF_BEAM){                       
3060                         b_count++;
3061                         dc_printf("Beam %d : %s\n", b_count, Weapon_info[idx].name);
3062                 }
3063         }
3064 }
3065 void beam_test(int whee)
3066 {
3067         int s1, s2;
3068         object *orion, *fenris;
3069         ship_subsys *orion_turret, *fenris_turret, *fenris_radar, *orion_radar, *lookup;
3070         beam_fire_info f;
3071
3072         nprintf(("General", "Running beam test\n"));
3073
3074         // lookup some stuff 
3075         s1 = ship_name_lookup("GTD Orion 1");
3076         Assert(s1 >= 0);
3077         orion = &Objects[Ships[s1].objnum];
3078         s2 = ship_name_lookup("GTC Fenris 2");
3079         Assert(s2 >= 0);
3080         fenris = &Objects[Ships[s2].objnum];            
3081
3082         // get beam weapons
3083         lookup = GET_FIRST(&Ships[s1].subsys_list);
3084         orion_turret = NULL;
3085         orion_radar = NULL;
3086         while(lookup != END_OF_LIST(&Ships[s1].subsys_list)){
3087                 // turret               
3088                 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret07")){
3089                         orion_turret = lookup;                  
3090                 }
3091
3092                 // radar
3093                 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3094                         orion_radar = lookup;
3095                 }
3096
3097                 lookup = GET_NEXT(lookup);
3098         }
3099         Assert(orion_turret != NULL);
3100         Assert(orion_radar != NULL);
3101         lookup = GET_FIRST(&Ships[s2].subsys_list);
3102         fenris_turret = NULL;
3103         fenris_radar = NULL;
3104         while(lookup != END_OF_LIST(&Ships[s2].subsys_list)){
3105                 // turret
3106                 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret07")){
3107                         fenris_turret = lookup;                 
3108                 }
3109
3110                 // radar
3111                 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3112                         fenris_radar = lookup;
3113                 }
3114
3115                 lookup = GET_NEXT(lookup);
3116         }
3117         Assert(fenris_turret != NULL);  
3118         Assert(fenris_radar != NULL);
3119
3120         memset(&f, 0, sizeof(beam_fire_info));
3121         f.accuracy = beam_accuracy;
3122         f.beam_info_index = -1;
3123         f.beam_info_override = NULL;
3124         f.shooter = orion;
3125         f.target = fenris;
3126         f.target_subsys = fenris_turret;
3127         f.turret = orion_turret;
3128
3129         // find the first beam
3130         int idx;        
3131         int beam_first = -1;
3132         int beam_count = 0;
3133
3134         for(idx=0; idx<Num_weapon_types; idx++){
3135                 if(Weapon_info[idx].wi_flags & WIF_BEAM){                       
3136                         beam_count++;
3137                         if(beam_count > 1){
3138                                 beam_first = idx;
3139                                 break;
3140                         }
3141                 }
3142         }       
3143         if(beam_first < 0){
3144                 return;
3145         }
3146         
3147         // maybe fire it, if its valid
3148         f.beam_info_index = beam_first + whee - 1;
3149         if(Weapon_info[f.beam_info_index].wi_flags & WIF_BEAM){
3150                 HUD_printf("Firing %s\n", Weapon_info[f.beam_info_index].name);
3151                 beam_fire(&f);
3152         }
3153 }
3154
3155 void beam_test_new(int whee)
3156 {
3157         int s1, s2, s3;
3158         object *orion, *fenris, *herc2, *herc3, *herc6, *alpha;
3159         ship_subsys *orion_turret, *fenris_turret, *fenris_radar, *orion_radar, *lookup;
3160         beam_fire_info f;
3161
3162         nprintf(("General", "Running beam test\n"));
3163
3164         // lookup some stuff 
3165         s1 = ship_name_lookup("GTD Orion 1");
3166         Assert(s1 >= 0);
3167         orion = &Objects[Ships[s1].objnum];
3168         s2 = ship_name_lookup("GTC Fenris 2");
3169         Assert(s2 >= 0);
3170         fenris = &Objects[Ships[s2].objnum];    
3171         s3 = ship_name_lookup("GTF Hercules 2");
3172         Assert(s3 >= 0);
3173         herc2 = &Objects[Ships[s3].objnum];
3174         s3 = ship_name_lookup("GTF Hercules 3");
3175         Assert(s3 >= 0);
3176         herc3 = &Objects[Ships[s3].objnum];
3177         s3 = ship_name_lookup("GTF Hercules 6");
3178         Assert(s3 >= 0);
3179         herc6 = &Objects[Ships[s3].objnum];
3180         s3 = ship_name_lookup("Alpha 1");
3181         Assert(s3 >= 0);
3182         alpha = &Objects[Ships[s3].objnum];     
3183
3184         // get beam weapons
3185         lookup = GET_FIRST(&Ships[s1].subsys_list);
3186         orion_turret = NULL;
3187         orion_radar = NULL;
3188         while(lookup != END_OF_LIST(&Ships[s1].subsys_list)){
3189                 // turret               
3190                 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret07")){
3191                         orion_turret = lookup;                  
3192                 }
3193
3194                 // radar
3195                 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3196                         orion_radar = lookup;
3197                 }
3198
3199                 lookup = GET_NEXT(lookup);
3200         }
3201         Assert(orion_turret != NULL);
3202         Assert(orion_radar != NULL);
3203         lookup = GET_FIRST(&Ships[s2].subsys_list);
3204         fenris_turret = NULL;
3205         fenris_radar = NULL;
3206         while(lookup != END_OF_LIST(&Ships[s2].subsys_list)){
3207                 // turret
3208                 if((lookup->system_info->type == SUBSYSTEM_TURRET) && !stricmp(lookup->system_info->subobj_name, "turret03")){
3209                         fenris_turret = lookup;                 
3210                 }
3211
3212                 // radar
3213                 if(lookup->system_info->type == SUBSYSTEM_RADAR){
3214                         fenris_radar = lookup;
3215                 }
3216
3217                 lookup = GET_NEXT(lookup);
3218         }
3219         Assert(fenris_turret != NULL);  
3220         Assert(fenris_radar != NULL);
3221
3222         memset(&f, 0, sizeof(beam_fire_info));
3223         f.accuracy = beam_accuracy;     
3224         f.beam_info_override = NULL;
3225         f.shooter = fenris;
3226         f.target = alpha;
3227         f.target_subsys = NULL;
3228         f.turret = fenris_turret;
3229         f.num_shots = 3;
3230
3231         // find the first beam
3232         int idx;        
3233         int beam_first = -1;
3234         int beam_count = 0;
3235
3236         for(idx=0; idx<Num_weapon_types; idx++){
3237                 if(Weapon_info[idx].wi_flags & WIF_BEAM){
3238                         beam_count++;
3239                         if(beam_count > 1){
3240                                 beam_first = idx;
3241                                 break;
3242                         }                       
3243                 }
3244         }       
3245         if(beam_first < 0){
3246                 return;
3247         }
3248         
3249         // maybe fire it, if its valid
3250         f.beam_info_index = beam_first + whee - 1;
3251         if(Weapon_info[f.beam_info_index].wi_flags & WIF_BEAM){
3252                 HUD_printf("Firing %s\n", Weapon_info[f.beam_info_index].name);
3253                 beam_fire(&f);
3254         }
3255 }