]> icculus.org git repositories - taylor/freespace2.git/blob - src/weapon/weapons.cpp
Initial revision
[taylor/freespace2.git] / src / weapon / weapons.cpp
1 /*
2  * $Logfile: /Freespace2/code/Weapon/Weapons.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Code to handle the weapon systems
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:11  root
11  * Initial revision
12  *
13  * 
14  * 69    9/14/99 3:26a Dave
15  * Fixed laser fogging problem in nebula (D3D)> Fixed multiplayer
16  * respawn-too-early problem. Made a few crash points safe.
17  * 
18  * 68    9/14/99 1:32a Andsager
19  * Better LOD for weapon explosions when behind.  Move point ahead to get
20  * vertex and then find size.
21  * 
22  * 67    9/07/99 1:10p Mikek
23  * Fix code I busted due to adding lifeleft to missiles.
24  * 
25  * 66    9/07/99 12:20a Andsager
26  * LOD less agressive at lower hardware detail level
27  * 
28  * 65    9/06/99 7:21p Dave
29  * Commented out bad lifeleft scaling code in weapon_set_tracking_info()
30  * 
31  * 64    9/06/99 3:23p Andsager
32  * Make fireball and weapon expl ani LOD choice look at resolution of the
33  * bitmap
34  * 
35  * 63    9/06/99 12:46a Andsager
36  * Add weapon_explosion_ani LOD
37  * 
38  * 62    9/05/99 11:24p Mikek
39  * Fixed problems caused by earlier checkin (that was rolled back).
40  * Problem was wp->target_pos was not set for swarmers.
41  * 
42  * More tweaking of missile behavior.  Also add 20% to lifetime of a
43  * locked missile.
44  * 
45  * [Rolled back -- MK] 63    9/05/99 2:23p Mikek
46  * Make aspect seekers a little less likely to miss their target.
47  * Mysterious why they do it so often.  Maybe fix for FS3...
48  * 
49  * [Rolled back -- MK] 62    9/04/99 12:09p Mikek
50  * Limit number of spawned weapons that can home on player based on skill
51  * level.  Works same as for non-spawned weapons.  Only do in single
52  * player.
53  * 
54  * 61    8/27/99 1:34a Andsager
55  * Modify damage by shockwaves for BIG|HUGE ships.  Modify shockwave damge
56  * when weapon blows up.
57  * 
58  * 60    8/24/99 10:47a Jefff
59  * tech room weapon anims.  added tech anim field to weapons.tbl
60  * 
61  * 59    8/16/99 11:58p Andsager
62  * Disable collision on proximity for ships with SIF_DONT_COLLIDE_INVIS
63  * hulls.
64  * 
65  * 58    8/10/99 5:30p Jefff
66  * Added tech_title string to weapon_info.  Changed parser accordingly.
67  * 
68  * 57    8/05/99 2:06a Dave
69  * Whee.
70  * 
71  * 56    8/02/99 5:16p Dave
72  * Bumped up weapon title string length from 32 to 48
73  * 
74  * 55    7/29/99 5:41p Jefff
75  * Sound hooks for cmeasure success
76  * 
77  * 54    7/24/99 1:54p Dave
78  * Hud text flash gauge. Reworked dead popup to use 4 buttons in red-alert
79  * missions.
80  * 
81  * 53    7/19/99 7:20p Dave
82  * Beam tooling. Specialized player-killed-self messages. Fixed d3d nebula
83  * pre-rendering.
84  * 
85  * 52    7/18/99 12:32p Dave
86  * Randomly oriented shockwaves.
87  * 
88  * 51    7/16/99 1:50p Dave
89  * 8 bit aabitmaps. yay.
90  * 
91  * 50    7/15/99 9:20a Andsager
92  * FS2_DEMO initial checkin
93  * 
94  * 49    7/08/99 10:53a Dave
95  * New multiplayer interpolation scheme. Not 100% done yet, but still
96  * better than the old way.
97  * 
98  * 48    7/02/99 4:31p Dave
99  * Much more sophisticated lightning support.
100  * 
101  * 47    7/01/99 5:57p Johnson
102  * Oops. Fixed big ship damage.
103  * 
104  * 46    7/01/99 4:23p Dave
105  * Full support for multiple linked ambient engine sounds. Added "big
106  * damage" flag.
107  * 
108  * 45    6/30/99 5:53p Dave
109  * Put in new anti-camper code.
110  * 
111  * 44    6/22/99 3:24p Danw
112  * Fixed incorrect weapon hit sound culling.
113  * 
114  * 43    6/21/99 7:25p Dave
115  * netplayer pain packet. Added type E unmoving beams.
116  * 
117  * 42    6/14/99 10:45a Dave
118  * Made beam weapons specify accuracy by skill level in the weapons.tbl
119  * 
120  * 41    6/11/99 11:13a Dave
121  * last minute changes before press tour build.
122  * 
123  * 40    6/04/99 2:16p Dave
124  * Put in shrink effect for beam weapons.
125  * 
126  * 39    6/01/99 8:35p Dave
127  * Finished lockarm weapons. Added proper supercap weapons/damage. Added
128  * awacs-set-radius sexpression.
129  * 
130  * 38    6/01/99 3:52p Dave
131  * View footage screen. Fixed xstrings to not display the & symbol. Popup,
132  * dead popup, pxo find player popup, pxo private room popup.
133  * 
134  * 37    5/27/99 6:17p Dave
135  * Added in laser glows.
136  * 
137  * 36    5/20/99 7:00p Dave
138  * Added alternate type names for ships. Changed swarm missile table
139  * entries.
140  * 
141  * 35    5/08/99 8:25p Dave
142  * Upped object pairs. First run of nebula lightning.
143  * 
144  * 34    4/28/99 11:13p Dave
145  * Temporary checkin of artillery code.
146  * 
147  * 33    4/28/99 3:11p Andsager
148  * Stagger turret weapon fire times.  Make turrets smarter when target is
149  * protected or beam protected.  Add weaopn range to weapon info struct.
150  * 
151  * 32    4/22/99 11:06p Dave
152  * Final pass at beam weapons. Solidified a lot of stuff. All that remains
153  * now is to tweak and fix bugs as they come up. No new beam weapon
154  * features.
155  * 
156  * 31    4/19/99 11:01p Dave
157  * More sophisticated targeting laser support. Temporary checkin.
158  * 
159  * 30    4/16/99 5:54p Dave
160  * Support for on/off style "stream" weapons. Real early support for
161  * target-painting lasers.
162  * 
163  * 29    4/07/99 6:22p Dave
164  * Fred and Freespace support for multiple background bitmaps and suns.
165  * Fixed link errors on all subprojects. Moved encrypt_init() to
166  * cfile_init() and lcl_init(), since its safe to call twice.
167  * 
168  * 28    4/02/99 1:35p Dave
169  * Removed weapon hit packet. No good for causing pain.
170  * 
171  * 27    4/02/99 9:55a Dave
172  * Added a few more options in the weapons.tbl for beam weapons. Attempt
173  * at putting "pain" packets into multiplayer.
174  * 
175  * 26    3/31/99 9:26p Dave
176  * Don't load beam textures when in Fred.
177  * 
178  * 25    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  * 24    3/23/99 2:29p Andsager
183  * Fix shockwaves for kamikazi and Fred defined.  Collect together
184  * shockwave_create_info struct.
185  * 
186  * 23    3/23/99 11:03a Dave
187  * Added a few new fields and fixed parsing code for new weapon stuff.
188  * 
189  * 22    3/19/99 9:52a Dave
190  * Checkin to repair massive source safe crash. Also added support for
191  * pof-style nebulae, and some new weapons code.
192  * 
193  * 24    3/15/99 6:45p Daveb
194  * Put in rough nebula bitmap support.
195  * 
196  * 23    3/12/99 3:19p Enricco
197  * Remove spurious Int3()
198  * 
199  * 22    3/11/99 5:53p Dave
200  * More network optimization. Spliced in Dell OEM planet bitmap crap.
201  * 
202  * 21    3/10/99 6:51p Dave
203  * Changed the way we buffer packets for all clients. Optimized turret
204  * fired packets. Did some weapon firing optimizations.
205  * 
206  * 20    2/24/99 4:02p Dave
207  * Fixed weapon locking and homing problems for multiplayer dogfight mode.
208  * 
209  * 19    2/05/99 12:52p Dave
210  * Fixed Glide nondarkening textures.
211  * 
212  * 18    1/29/99 12:47a Dave
213  * Put in sounds for beam weapon. A bunch of interface screens (tech
214  * database stuff).
215  * 
216  * 17    1/27/99 9:56a Dave
217  * Temporary checkin of beam weapons for Dan to make cool sounds.
218  * 
219  * 16    1/25/99 5:03a Dave
220  * First run of stealth, AWACS and TAG missile support. New mission type
221  * :)
222  * 
223  * 15    1/24/99 11:37p Dave
224  * First full rev of beam weapons. Very customizable. Removed some bogus
225  * Int3()'s in low level net code.
226  * 
227  * 14    1/21/99 10:45a Dave
228  * More beam weapon stuff. Put in warmdown time.
229  * 
230  * 13    1/12/99 5:45p Dave
231  * Moved weapon pipeline in multiplayer to almost exclusively client side.
232  * Very good results. Bandwidth goes down, playability goes up for crappy
233  * connections. Fixed object update problem for ship subsystems.
234  * 
235  * 12    1/08/99 2:08p Dave
236  * Fixed software rendering for pofview. Super early support for AWACS and
237  * beam weapons.
238  * 
239  * 11    1/06/99 2:24p Dave
240  * Stubs and release build fixes.
241  * 
242  * 10    12/01/98 6:12p Johnson
243  * Make sure to page in weapon impact animations as xparent textures.
244  * 
245  * 9     11/20/98 4:08p Dave
246  * Fixed flak effect in multiplayer.
247  * 
248  * 8     11/14/98 5:33p Dave
249  * Lots of nebula work. Put in ship contrails.
250  * 
251  * 7     11/05/98 4:18p Dave
252  * First run nebula support. Beefed up localization a bit. Removed all
253  * conditional compiles for foreign versions. Modified mission file
254  * format.
255  * 
256  * 6     10/26/98 9:42a Dave
257  * Early flak gun support.
258  * 
259  * 5     10/23/98 3:51p Dave
260  * Full support for tstrings.tbl and foreign languages. All that remains
261  * is to make it active in Fred.
262  * 
263  * 4     10/07/98 6:27p Dave
264  * Globalized mission and campaign file extensions. Removed Silent Threat
265  * special code. Moved \cache \players and \multidata into the \data
266  * directory.
267  * 
268  * 3     10/07/98 4:49p Andsager
269  * don't do weapon swap (was needed for mission disk)
270  * 
271  * 2     10/07/98 10:54a Dave
272  * Initial checkin.
273  * 
274  * 1     10/07/98 10:51a Dave
275  * 
276  * 314   9/21/98 11:19p Dave
277  * Weapon name fix.
278  * 
279  * 313   9/19/98 4:33p Adam
280  * Changed default values for particle spew (used on Leech Cannon)
281  * 
282  * 312   9/13/98 10:51p Dave
283  * Put in newfangled icons for mission simulator room. New mdisk.vp
284  * checksum and file length.
285  * 
286  * 311   9/13/98 4:29p Andsager
287  * Maintain Weapon_info compataiblity with mission disk
288  * 
289  * 310   9/13/98 4:26p Andsager
290  * 
291  * 309   9/01/98 4:25p Dave
292  * Put in total (I think) backwards compatibility between mission disk
293  * freespace and non mission disk freespace, including pilot files and
294  * campaign savefiles.
295  * 
296  * 308   8/28/98 3:29p Dave
297  * EMP effect done. AI effects may need some tweaking as required.
298  * 
299  * 307   8/25/98 1:49p Dave
300  * First rev of EMP effect. Player side stuff basically done. Next comes
301  * AI code.
302  * 
303  * 306   8/18/98 10:15a Dave
304  * Touchups on the corkscrew missiles. Added particle spewing weapons.
305  * 
306  * 305   8/17/98 5:07p Dave
307  * First rev of corkscrewing missiles.
308  * 
309  * 304   6/30/98 2:23p Dave
310  * Revised object update system. Removed updates for all weapons. Put
311  * button info back into control info packet.
312  * 
313  * 303   6/22/98 8:36a Allender
314  * revamping of homing weapon system.  don't send as object updates
315  * anymore
316  * 
317  * 302   5/24/98 2:25p Allender
318  * be sure that homing missiles die on client when lifeleft gets too
319  * negative (lost packets)
320  * 
321  * 301   5/20/98 5:47p Sandeep
322  * 
323  * 300   5/18/98 1:58a Mike
324  * Make Phoenix not be fired at fighters (but yes bombers).
325  * Improve positioning of ships in guard mode.
326  * Make turrets on player ship not fire near end of support ship docking.
327  * 
328  * $NoKeywords: $
329  */
330
331 #include <stdlib.h>
332
333 #include "pstypes.h"
334 #include "systemvars.h"
335 #include "vecmat.h"
336 #include "tmapper.h"
337 #include "2d.h"
338 #include "3d.h"
339 #include "bmpman.h"
340 #include "model.h"
341 #include "key.h"
342 #include "physics.h"
343 #include "floating.h"
344 #include "model.h"
345 #include "lighting.h"
346 #include "object.h"
347 #include "weapon.h"
348 #include "ship.h"
349 #include "fireballs.h"
350 #include "player.h"
351 #include "hudtarget.h"
352 #include "freespace.h"
353 #include "radar.h"
354 #include "ai.h"
355 #include "sound.h"
356 #include "multi.h"
357 #include "multimsgs.h"
358 #include "linklist.h"
359 #include "timer.h"
360 #include "gamesnd.h"
361 #include "cmeasure.h"
362 #include "shockwave.h"
363 #include "model.h"
364 #include "staticrand.h"
365 #include "swarm.h"
366 #include "multiutil.h"
367 #include "shiphit.h"
368 #include "trails.h"
369 #include "hud.h"
370 #include "objcollide.h"
371 #include "aibig.h"
372 #include "particle.h"
373 #include "asteroid.h"
374 #include "joy_ff.h"
375 #include "multi_obj.h"
376 #include "corkscrew.h"
377 #include "emp.h"
378 #include "localize.h"
379 #include "flak.h"
380 #include "muzzleflash.h"
381
382 #ifndef NDEBUG
383 int Weapon_flyby_sound_enabled = 1;
384 DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
385 #endif
386
387 static int Weapon_flyby_sound_timer;    
388
389 weapon Weapons[MAX_WEAPONS];
390 weapon_info Weapon_info[MAX_WEAPON_TYPES];
391
392 #define         MISSILE_OBJ_USED        (1<<0)                  // flag used in missile_obj struct
393 #define         MAX_MISSILE_OBJS        MAX_WEAPONS             // max number of missiles tracked in missile list
394 missile_obj Missile_objs[MAX_MISSILE_OBJS];     // array used to store missile object indexes
395 missile_obj Missile_obj_list;                                           // head of linked list of missile_obj structs
396
397
398 // WEAPON EXPLOSION INFO
399 #define MAX_weapon_expl_lod                                             4
400 #define MAX_Weapon_expl_info                                    3
401
402 typedef struct weapon_expl_lod {
403         char    filename[MAX_FILENAME_LEN];
404         int     bitmap_id;
405         int     num_frames;
406         int     fps;
407 } weapon_expl_lod;
408
409 typedef struct weapon_expl_info {
410         int                                     lod_count;      
411         weapon_expl_lod         lod[MAX_weapon_expl_lod];
412 } weapon_expl_info;
413
414 weapon_expl_info Weapon_expl_info[MAX_Weapon_expl_info];
415
416 int Num_weapon_expl = 0;
417
418 int Num_weapon_types = 0;
419
420 int Num_weapons = 0;
421 int Weapons_inited = 0;
422
423 int laser_model_inner = -1;
424 int laser_model_outer = -1;
425
426 int missile_model = -1;
427
428 char    *Weapon_names[MAX_WEAPON_TYPES];
429
430 int     First_secondary_index = -1;
431
432 #define MAX_SPAWN_WEAPONS       10                      //      Up to 10 weapons can spawn weapons.
433
434 int     Num_spawn_types;
435 char    Spawn_names[MAX_SPAWN_WEAPONS][NAME_LENGTH];
436
437 int Num_player_weapon_precedence;                               // Number of weapon types in Player_weapon_precedence
438 int Player_weapon_precedence[MAX_WEAPON_TYPES]; // Array of weapon types, precedence list for player weapon selection
439
440 // Used to avoid playing too many impact sounds in too short a time interval.
441 // This will elimate the odd "stereo" effect that occurs when two weapons impact at 
442 // nearly the same time, like from a double laser (also saves sound channels!)
443 #define IMPACT_SOUND_DELTA      50              // in milliseconds
444 int             Weapon_impact_timer;                    // timer, initalized at start of each mission
445
446 // energy suck defines
447 #define ESUCK_DEFAULT_WEAPON_REDUCE                             (10.0f)
448 #define ESUCK_DEFAULT_AFTERBURNER_REDUCE                (10.0f)
449
450 // scale factor for supercaps taking damage from weapons which are not "supercap" weapons
451 #define SUPERCAP_DAMAGE_SCALE                   0.25f
452
453 // scale factor for big ships getting hit by flak
454 #define FLAK_DAMAGE_SCALE                               0.05f
455
456 extern int Max_allowed_player_homers[];
457 extern int compute_num_homing_objects(object *target_objp);
458
459 // 
460 void parse_weapon_expl_tbl()
461 {
462         int     rval, idx;
463         char base_filename[256] = "";
464
465         // open localization
466         lcl_ext_open();
467
468         if ((rval = setjmp(parse_abort)) != 0) {
469                 Error(LOCATION, "Unable to parse weapon_expl.tbl!  Code = %i.\n", rval);
470         }
471         else {
472                 read_file_text(NOX("weapon_expl.tbl"));
473                 reset_parse();          
474         }
475
476         Num_weapon_expl = 0;
477         required_string("#Start");
478         while (required_string_either("#End","$Name:")) {
479                 Assert( Num_weapon_expl < MAX_Weapon_expl_info);
480
481                 // base filename
482                 required_string("$Name:");
483                 stuff_string(base_filename, F_NAME, NULL);
484
485                 // # of lod levels - make sure old fireball.tbl is compatible
486                 Weapon_expl_info[Num_weapon_expl].lod_count = 1;
487                 if(optional_string("$LOD:")){
488                         stuff_int(&Weapon_expl_info[Num_weapon_expl].lod_count);
489                 }
490
491                 // stuff default filename
492                 strcpy(Weapon_expl_info[Num_weapon_expl].lod[0].filename, base_filename);
493
494                 // stuff LOD level filenames
495                 for(idx=1; idx<Weapon_expl_info[Num_weapon_expl].lod_count; idx++){
496                         if(idx >= MAX_weapon_expl_lod){
497                                 break;
498                         }
499
500                         sprintf(Weapon_expl_info[Num_weapon_expl].lod[idx].filename, "%s_%d", base_filename, idx);
501                 }
502
503                 Num_weapon_expl++;
504         }
505         required_string("#End");
506
507         // close localization
508         lcl_ext_close();
509 }
510
511 int get_weapon_expl_info_index(char *filename)
512 {
513         for (int i=0; i<MAX_Weapon_expl_info; i++) {
514                 if ( stricmp(Weapon_expl_info[i].lod[0].filename, filename) == 0) {
515                         return i;
516                 }
517         }
518
519         return -1;
520 }
521 // ----------------------------------------------------------------------
522 // missile_obj_list_init()
523 //
524 // Clear out the Missile_obj_list
525 //
526 void missile_obj_list_init()
527 {
528         int i;
529
530         list_init(&Missile_obj_list);
531         for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
532                 Missile_objs[i].flags = 0;
533         }
534 }
535
536 // ---------------------------------------------------
537 // missile_obj_list_add()
538 //
539 // Function to add a node from the Missile_obj_list.  Only
540 // called from weapon_create()
541 int missile_obj_list_add(int objnum)
542 {
543         int i;
544
545         for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
546                 if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
547                         break;
548         }
549         if ( i == MAX_MISSILE_OBJS ) {
550                 Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
551                 return -1;
552         }
553         
554         Missile_objs[i].flags = 0;
555         Missile_objs[i].objnum = objnum;
556         list_append(&Missile_obj_list, &Missile_objs[i]);
557         Missile_objs[i].flags |= MISSILE_OBJ_USED;
558
559         return i;
560 }
561
562 // ---------------------------------------------------
563 // missle_obj_list_remove()
564 //
565 // Function to remove a node from the Missile_obj_list.  Only
566 // called from weapon_delete()
567 void missle_obj_list_remove(int index)
568 {
569         Assert(index >= 0 && index < MAX_MISSILE_OBJS);
570         list_remove(&Missile_obj_list, &Missile_objs[index]);   
571         Missile_objs[index].flags = 0;
572 }
573
574 // ---------------------------------------------------
575 // missile_obj_list_rebuild()
576 //
577 // Called by the save/restore code to rebuild Missile_obj_list
578 //
579 void missile_obj_list_rebuild()
580 {
581         object *objp;
582
583         missile_obj_list_init();
584
585         for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
586                 if ( objp->type == OBJ_WEAPON && Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE ) {
587                         Weapons[objp->instance].missile_list_index = missile_obj_list_add(OBJ_INDEX(objp));
588                 }
589         }
590 }
591
592 // If this is a player countermeasure, let the player know he evaded a missile
593 void weapon_maybe_alert_cmeasure_success(object *objp)
594 {
595         if ( objp->type == OBJ_CMEASURE ) {
596                 cmeasure *cmp;
597                 cmp = &Cmeasures[objp->instance];
598                 if ( cmp->source_objnum == OBJ_INDEX(Player_obj) ) {
599                         hud_start_text_flash(XSTR("Evaded", 1430), 800);
600                         snd_play(&Snds[SND_MISSILE_EVADED_POPUP]);
601                 } else if ( Objects[cmp->source_objnum].flags & OF_PLAYER_SHIP ) {
602                         send_countermeasure_success_packet( cmp->source_objnum );
603                 }
604         }
605 }
606
607 // ---------------------------------------------------
608 // missile_obj_return_address()
609 //
610 // Called externally to generate an address from an index into
611 // the Missile_objs[] array
612 //
613 missile_obj *missile_obj_return_address(int index)
614 {
615         Assert(index >= 0 && index < MAX_MISSILE_OBJS);
616         return &Missile_objs[index];
617 }
618
619 //      Return the index of Weapon_info[].name that is *name.
620 int weapon_info_lookup(char *name)
621 {
622         int     i;
623
624         for (i=0; i<Num_weapon_types; i++)
625                 if (!stricmp(name, Weapon_info[i].name))
626                         return i;
627
628         return -1;
629 }
630
631 #define DEFAULT_WEAPON_SPAWN_COUNT      10
632
633 //      Parse the weapon flags.
634 void parse_wi_flags(weapon_info *weaponp)
635 {
636         char    weapon_strings[MAX_WEAPON_FLAGS][NAME_LENGTH];
637         int     num_strings;
638
639         required_string("$Flags:");
640
641         num_strings = stuff_string_list(weapon_strings, MAX_WEAPON_FLAGS);
642         
643         for (int i=0; i<num_strings; i++) {
644                 if (!stricmp(NOX("Electronics"), weapon_strings[i]))
645                         weaponp->wi_flags |= WIF_ELECTRONICS;           
646                 else if (!strnicmp(NOX("Spawn"), weapon_strings[i], 5)) {
647                         if (weaponp->spawn_type == -1) {
648                                 int     skip_length, name_length;
649                                 char    *temp_string;
650
651                                 temp_string = weapon_strings[i];
652
653                                 weaponp->wi_flags |= WIF_SPAWN;
654                                 weaponp->spawn_type = (short)Num_spawn_types;
655                                 skip_length = strlen(NOX("Spawn")) + strspn(&temp_string[strlen(NOX("Spawn"))], NOX(" \t"));
656                                 char *num_start = strchr(&temp_string[skip_length], ',');
657                                 if (num_start == NULL) {
658                                         weaponp->spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
659                                         name_length = 999;
660                                 } else {
661                                         weaponp->spawn_count = (short)atoi(num_start+1);
662                                         name_length = num_start - temp_string - skip_length;
663                                 }
664
665                                 strncpy(Spawn_names[Num_spawn_types++], &(weapon_strings[i][skip_length]), name_length);
666                                 Assert(Num_spawn_types < MAX_SPAWN_WEAPONS);
667                         } else
668                                 Warning(LOCATION, "Illegal to have two spawn types for one weapon.\n"
669                                                                                 "Ignoring weapon %s", weapon_strings[i]);
670                 } else if (!stricmp(NOX("Remote Detonate"), weapon_strings[i]))
671                         weaponp->wi_flags |= WIF_REMOTE;
672                 else if (!stricmp(NOX("Puncture"), weapon_strings[i]))
673                         weaponp->wi_flags |= WIF_PUNCTURE;              
674                 else if (!stricmp(NOX("Big Ship"), weapon_strings[i]))
675                         weaponp->wi_flags |= WIF_BIG_ONLY;
676                 else if (!stricmp(NOX("Huge"), weapon_strings[i]))
677                         weaponp->wi_flags |= WIF_HUGE;
678                 else if (!stricmp(NOX("Bomber+"), weapon_strings[i]))
679                         weaponp->wi_flags |= WIF_BOMBER_PLUS;
680                 else if (!stricmp(NOX("child"), weapon_strings[i]))
681                         weaponp->wi_flags |= WIF_CHILD;
682                 else if (!stricmp(NOX("Bomb"), weapon_strings[i]))
683                         weaponp->wi_flags |= WIF_BOMB;
684                 else if (!stricmp(NOX("No Dumbfire"), weapon_strings[i]))
685                         weaponp->wi_flags |= WIF_NO_DUMBFIRE;
686                 else if (!stricmp(NOX("In tech database"), weapon_strings[i]))
687                         weaponp->wi_flags |= WIF_IN_TECH_DATABASE;
688                 else if (!stricmp(NOX("Player allowed"), weapon_strings[i]))
689                         weaponp->wi_flags |= WIF_PLAYER_ALLOWED;                
690                 else if (!stricmp(NOX("Particle Spew"), weapon_strings[i]))
691                         weaponp->wi_flags |= WIF_PARTICLE_SPEW;
692                 else if (!stricmp(NOX("EMP"), weapon_strings[i]))
693                         weaponp->wi_flags |= WIF_EMP;
694                 else if (!stricmp(NOX("Esuck"), weapon_strings[i]))
695                         weaponp->wi_flags |= WIF_ENERGY_SUCK;
696                 else if (!stricmp(NOX("Flak"), weapon_strings[i]))
697                         weaponp->wi_flags |= WIF_FLAK;
698                 else if (!stricmp(NOX("Corkscrew"), weapon_strings[i]))
699                         weaponp->wi_flags |= WIF_CORKSCREW;
700                 else if (!stricmp(NOX("Shudder"), weapon_strings[i]))
701                         weaponp->wi_flags |= WIF_SHUDDER;               
702                 else if (!stricmp(NOX("lockarm"), weapon_strings[i]))
703                         weaponp->wi_flags |= WIF_LOCKARM;               
704                 else if (!stricmp(NOX("beam"), weapon_strings[i]))
705                         weaponp->wi_flags |= WIF_BEAM;
706                 else if (!stricmp(NOX("stream"), weapon_strings[i]))
707                         weaponp->wi_flags |= WIF_STREAM;
708                 else if (!stricmp(NOX("supercap"), weapon_strings[i]))
709                         weaponp->wi_flags |= WIF_SUPERCAP;
710                 else
711                         Warning(LOCATION, "Bogus string in weapon flags: %s\n", weapon_strings[i]);
712         }       
713
714         // SWARM, CORKSCREW and FLAK should be mutually exclusive
715         if(weaponp->wi_flags & WIF_FLAK){
716                 Assert(!(weaponp->wi_flags & WIF_CORKSCREW) && !(weaponp->wi_flags & WIF_SWARM));
717         }
718         if(weaponp->wi_flags & WIF_CORKSCREW){
719                 Assert(!(weaponp->wi_flags & WIF_FLAK) && !(weaponp->wi_flags & WIF_SWARM));
720         }
721         if(weaponp->wi_flags & WIF_SWARM){
722                 Assert(!(weaponp->wi_flags & WIF_CORKSCREW) && !(weaponp->wi_flags & WIF_FLAK));
723         }
724
725         // make sure flak guns are only placed on turrets
726         if(weaponp->wi_flags & WIF_FLAK){
727                 Assert(weaponp->wi_flags & WIF_BIG_ONLY);
728         }
729 }
730
731 // function to parse the information for a specific weapon type.        
732 // return 0 if successful, otherwise return -1
733 #define WEAPONS_MULTITEXT_LENGTH 2048
734
735 int parse_weapon()
736 {
737         char buf[WEAPONS_MULTITEXT_LENGTH];
738         weapon_info *wip;
739         char fname[255] = "";
740         int idx;
741
742         wip = &Weapon_info[Num_weapon_types];
743
744         wip->wi_flags = 0;
745
746         required_string("$Name:");
747         stuff_string(wip->name, F_NAME, NULL);
748         diag_printf ("Weapon name -- %s\n", wip->name);
749
750         // AL 28-3-98: If this is a demo build, we only want to parse weapons that are preceded with
751         //             the '@' symbol
752         #ifdef DEMO // not needed FS2_DEMO (separate table file)
753                 if ( wip->name[0] != '@' ) {
754                         // advance to next weapon, and return -1
755
756                         if ( skip_to_start_of_strings("$Name:", "#End") != 1 ) {
757                                 Int3();
758                         }
759                         return -1;
760                 }
761         #endif
762
763         if ( wip->name[0] == '@' ) {
764                 char old_name[NAME_LENGTH];
765                 strcpy(old_name, wip->name);
766                 strcpy(wip->name, old_name+1);
767         }
768
769         wip->title[0] = 0;
770         if (optional_string("+Title:")) {
771                 stuff_string(wip->title, F_NAME, NULL, WEAPON_TITLE_LEN);
772         }
773
774         wip->desc = NULL;
775         if (optional_string("+Description:")) {
776                 stuff_string(buf, F_MULTITEXT, NULL);
777                 wip->desc = strdup(buf);
778         }
779
780         wip->tech_title[0] = 0;
781         if (optional_string("+Tech Title:")) {
782                 stuff_string(wip->tech_title, F_NAME, NULL, NAME_LENGTH);
783         }
784
785         wip->tech_anim_filename[0] = 0;
786         if (optional_string("+Tech Anim:")) {
787                 stuff_string(wip->tech_anim_filename, F_NAME, NULL, NAME_LENGTH);
788         }
789
790         wip->tech_desc = NULL;
791         if (optional_string("+Tech Description:")) {
792                 stuff_string(buf, F_MULTITEXT, NULL, WEAPONS_MULTITEXT_LENGTH);
793                 wip->tech_desc = strdup(buf);
794         }
795
796         //      Read the model file.  It can be a POF file or none.
797         //      If there is no model file (Model file: = "none") then we use our special
798         //      laser renderer which requires inner, middle and outer information.
799         required_string("$Model file:");
800         stuff_string(wip->pofbitmap_name, F_NAME, NULL);
801         diag_printf ("Model pof file -- %s\n", wip->pofbitmap_name );
802         if ( stricmp(wip->pofbitmap_name, NOX("none")) ) {
803                 wip->model_num = -1;                            
804                 wip->render_type = WRT_POF;
805                 wip->laser_bitmap = -1;
806         } else {
807                 //      No POF or AVI file specified, render as special laser type.
808                 ubyte r,g,b;
809
810                 wip->render_type = WRT_LASER;
811                 wip->model_num = -1;
812
813                 // laser bitmap itself
814                 required_string("@Laser Bitmap:");
815                 stuff_string(wip->pofbitmap_name, F_NAME, NULL);
816                 wip->laser_bitmap = -1;
817                 if(!Fred_running){
818                         wip->laser_bitmap = bm_load( wip->pofbitmap_name );
819                 }
820
821                 // optional laser glow
822                 wip->laser_glow_bitmap = -1;
823                 if(optional_string("@Laser Glow:")){
824                         stuff_string(fname, F_NAME, NULL);              
825                         if(!Fred_running){
826                                 wip->laser_glow_bitmap = bm_load( fname );
827
828                                 // might as well lock it down as an aabitmap now
829                                 if(wip->laser_glow_bitmap >= 0){
830                                         bm_lock(wip->laser_glow_bitmap, 8, BMP_AABITMAP);
831                                         bm_unlock(wip->laser_glow_bitmap);
832                                 }
833                         }
834                 }
835                 
836                 required_string("@Laser Color:");
837                 stuff_byte(&r); stuff_byte(&g); stuff_byte(&b);
838                 gr_init_color( &wip->laser_color_1, r, g, b );
839
840                 // optional string for cycling laser colors
841                 gr_init_color(&wip->laser_color_2, 0, 0, 0);
842                 if(optional_string("@Laser Color2:")){
843                         stuff_byte(&r); stuff_byte(&g); stuff_byte(&b);
844                         gr_init_color( &wip->laser_color_2, r, g, b );
845                 }
846
847                 required_string("@Laser Length:");
848                 stuff_float(&wip->laser_length);
849                 
850                 required_string("@Laser Head Radius:");
851                 stuff_float(&wip->laser_head_radius);
852
853                 required_string("@Laser Tail Radius:");
854                 stuff_float(&wip->laser_tail_radius );
855         }
856
857         required_string("$Mass:");
858         stuff_float( &(wip->mass) );
859         diag_printf ("Weapon mass -- %7.3f\n", wip->mass);
860
861         required_string("$Velocity:");
862         stuff_float( &(wip->max_speed) );
863         diag_printf ("Weapon mass -- %7.3f\n", wip->max_speed);
864
865         required_string("$Fire Wait:");
866         stuff_float( &(wip->fire_wait) );
867         diag_printf ("Weapon fire wait -- %7.3f\n", wip->fire_wait);
868
869         required_string("$Damage:");
870         stuff_float(&wip->damage);
871
872         // secondary weapons require these values
873         if (First_secondary_index != -1) {
874                 required_string("$Blast Force:");
875                 stuff_float( &(wip->blast_force) );
876                 diag_printf ("Weapon blast force -- %7.3f\n", wip->blast_force);
877
878                 required_string("$Inner Radius:");
879                 stuff_float( &(wip->inner_radius) );
880                 if ( wip->inner_radius != 0 ) {
881                         wip->wi_flags |= WIF_AREA_EFFECT;
882                 }
883                 diag_printf ("Weapon inner blast radius -- %7.3f\n", wip->inner_radius);
884
885                 required_string("$Outer Radius:");
886                 stuff_float( &(wip->outer_radius) );
887                 if ( wip->outer_radius != 0 ) {
888                         wip->wi_flags |= WIF_AREA_EFFECT;
889                 }
890                 diag_printf ("Weapon outer blast radius -- %7.3f\n", wip->outer_radius);
891
892                 required_string("$Shockwave Speed:");
893                 stuff_float( &(wip->shockwave_speed) );
894                 if ( wip->shockwave_speed != 0 ) {
895                         wip->wi_flags |= WIF_SHOCKWAVE;
896                 }
897                 diag_printf ("Shockwave speed -- %7.3f\n", wip->shockwave_speed);
898         } 
899         // for primary weapons they're optional
900         else {
901                 if(optional_string("$Blast Force:")){
902                         stuff_float( &(wip->blast_force) );
903                         diag_printf ("Weapon blast force -- %7.3f\n", wip->blast_force);
904                 }
905
906                 if(optional_string("$Inner Radius:")){
907                         stuff_float( &(wip->inner_radius) );
908                         if ( wip->inner_radius != 0 ) {
909                                 wip->wi_flags |= WIF_AREA_EFFECT;
910                         }
911                         diag_printf ("Weapon inner blast radius -- %7.3f\n", wip->inner_radius);
912                 }
913
914                 if(optional_string("$Outer Radius:")){
915                         stuff_float( &(wip->outer_radius) );
916                         if ( wip->outer_radius != 0 ) {
917                                 wip->wi_flags |= WIF_AREA_EFFECT;
918                         }
919                         diag_printf ("Weapon outer blast radius -- %7.3f\n", wip->outer_radius);
920                 }
921
922                 if(optional_string("$Shockwave Speed:")){
923                         stuff_float( &(wip->shockwave_speed) );
924                         if ( wip->shockwave_speed != 0 ) {
925                                 wip->wi_flags |= WIF_SHOCKWAVE;
926                         }
927                         diag_printf ("Shockwave speed -- %7.3f\n", wip->shockwave_speed);
928                 }
929         }
930
931         required_string("$Armor Factor:");
932         stuff_float(&wip->armor_factor);
933
934         required_string("$Shield Factor:");
935         stuff_float(&wip->shield_factor);
936
937         required_string("$Subsystem Factor:");
938         stuff_float(&wip->subsystem_factor);
939
940         required_string("$Lifetime:");
941         stuff_float(&wip->lifetime);
942
943         required_string("$Energy Consumed:");
944         stuff_float(&wip->energy_consumed);
945
946         required_string("$Cargo Size:");
947         stuff_float(&wip->cargo_size);
948
949         int is_homing=0;
950         required_string("$Homing:");
951         stuff_boolean(&is_homing);
952
953         if (is_homing == 1) {
954                 char    temp_type[128];
955
956                 // the following five items only need to be recorded if the weapon is a homing weapon
957                 required_string("+Type:");
958                 stuff_string(temp_type, F_NAME, NULL);
959
960                 if (!stricmp(temp_type, NOX("HEAT"))) {
961                         float   view_cone_angle;
962
963                         wip->wi_flags |= WIF_HOMING_HEAT | WIF_TURNS;
964
965                         required_string("+Turn Time:");                 
966                         stuff_float(&wip->turn_time);
967
968                         required_string("+View Cone:");
969                         stuff_float(&view_cone_angle);
970
971                         wip->fov = (float)cos((float)(ANG_TO_RAD(view_cone_angle/2.0f)));
972
973                 } else if (!stricmp(temp_type, NOX("ASPECT"))) {
974                         wip->wi_flags |= WIF_HOMING_ASPECT | WIF_TURNS;
975
976                         required_string("+Turn Time:");                 
977                         stuff_float(&wip->turn_time);
978
979                         required_string("+Min Lock Time:");                     // minimum time (in seconds) to achieve lock
980                         stuff_float(&wip->min_lock_time);
981
982                         required_string("+Lock Pixels/Sec:");           // pixels/sec moved while locking
983                         stuff_int(&wip->lock_pixels_per_sec);
984
985                         required_string("+Catch-up Pixels/Sec:");       // pixels/sec moved while catching-up for a lock
986                         stuff_int(&wip->catchup_pixels_per_sec);
987
988                         required_string("+Catch-up Penalty:");          // number of extra pixels to move while locking as a penalty for catching up for a lock
989                         stuff_int(&wip->catchup_pixel_penalty);
990                 } else
991                         Error(LOCATION, "Illegal homing type = %s.\nMust be HEAT or ASPECT.\n", temp_type);
992
993         }
994
995         // swarm missiles
996         int s_count;
997         wip->swarm_count = -1;
998         if(optional_string("$Swarm:")){
999                 wip->swarm_count = SWARM_DEFAULT_NUM_MISSILES_FIRED;
1000                 stuff_int(&s_count);
1001                 wip->swarm_count = (short)s_count;
1002
1003                 // flag as being a swarm weapon
1004                 wip->wi_flags |= WIF_SWARM;
1005         }
1006
1007         required_string("$LaunchSnd:");
1008         stuff_int(&wip->launch_snd);
1009
1010         required_string("$ImpactSnd:");
1011         stuff_int(&wip->impact_snd);
1012
1013         if (First_secondary_index != -1) {
1014                 required_string("$FlyBySnd:");
1015                 stuff_int(&wip->flyby_snd);
1016         }
1017
1018         //      Secondary weapons are required to have a rearm rate.
1019         if (First_secondary_index != -1) {
1020                 required_string( "$Rearm Rate:");
1021                 stuff_float( &wip->rearm_rate );
1022                 if (wip->rearm_rate > 0.1f)
1023                         wip->rearm_rate = 1.0f/wip->rearm_rate;
1024                 else
1025                         wip->rearm_rate = 1.0f;
1026         }
1027
1028         wip->weapon_range = 999999999.9f;
1029         if (optional_string("+Weapon Range:")) {
1030                 stuff_float(&wip->weapon_range);
1031         }
1032
1033         wip->spawn_type = -1;
1034         parse_wi_flags(wip);
1035
1036         char trail_name[MAX_FILENAME_LEN] = "";
1037         trail_info *ti = &wip->tr_info;
1038         memset(ti, 0, sizeof(trail_info));
1039         if(optional_string("$Trail:")){ 
1040                 wip->wi_flags |= WIF_TRAIL;             // missile leaves a trail
1041
1042                 required_string("+Start Width:");
1043                 stuff_float(&ti->w_start);
1044
1045                 required_string("+End Width:");
1046                 stuff_float(&ti->w_end);
1047
1048                 required_string("+Start Alpha:");
1049                 stuff_float(&ti->a_start);
1050
1051                 required_string("+End Alpha:");
1052                 stuff_float(&ti->a_end);                
1053
1054                 required_string("+Max Life:");
1055                 stuff_float(&ti->max_life);
1056
1057                 ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
1058
1059                 required_string("+Bitmap:");
1060                 stuff_string(trail_name, F_NAME, NULL);
1061                 ti->bitmap = bm_load(trail_name);
1062                 // wip->delta_time = fl2i(1000.0f*wip->max_life)/(NUM_TRAIL_SECTIONS+1);                // time between sections.  max_life / num_sections basically.
1063         }
1064
1065         // read in filename for icon that is used in weapons selection
1066         wip->icon_filename[0] = 0;
1067         if ( optional_string("$Icon:") ) {
1068                 stuff_string(wip->icon_filename, F_NAME, NULL);
1069         }
1070
1071         // read in filename for animation that is used in weapons selection
1072         wip->anim_filename[0] = 0;
1073         if ( optional_string("$Anim:") ) {
1074                 stuff_string(wip->anim_filename, F_NAME, NULL);
1075         }
1076
1077         wip->impact_weapon_expl_index = -1;
1078         if ( optional_string("$Impact Explosion:") ) {
1079                 char impact_ani_file[FILESPEC_LENGTH];
1080                 stuff_string(impact_ani_file, F_NAME, NULL);
1081                 if ( stricmp(impact_ani_file,NOX("none")))      {
1082                         wip->impact_weapon_expl_index = get_weapon_expl_info_index(impact_ani_file);
1083                         //int num_frames, fps;
1084                         //wip->impact_explosion_ani = bm_load_animation( impact_ani_file, &num_frames, &fps, 1 );
1085
1086                         required_string("$Impact Explosion Radius:");
1087                         stuff_float(&wip->impact_explosion_radius);
1088                 }
1089         }
1090
1091         // muzzle flash
1092         char mflash_string[255] = "";
1093         wip->muzzle_flash = -1;
1094         if( optional_string("$Muzzleflash:") ){
1095                 stuff_string(mflash_string, F_NAME, NULL);
1096
1097                 // look it up
1098                 wip->muzzle_flash = mflash_lookup(mflash_string);
1099
1100                 if(wip->muzzle_flash >= 0){                     
1101                         wip->wi_flags |= WIF_MFLASH;
1102                 }
1103         }
1104
1105         // EMP optional stuff (if WIF_EMP is not set, none of this matters, anyway)
1106         if( optional_string("$EMP Intensity:") ){
1107                 stuff_float(&wip->emp_intensity);
1108         } else {
1109                 wip->emp_intensity = EMP_DEFAULT_INTENSITY;
1110         }
1111         if( optional_string("$EMP Time:") ){
1112                 stuff_float(&wip->emp_time);
1113         } else {
1114                 wip->emp_intensity = EMP_DEFAULT_TIME;
1115         }
1116
1117         // Energy suck optional stuff (if WIF_ENERGY_SUCK is not set, none of this matters anyway)
1118         if( optional_string("$Leech Weapon:") ){
1119                 stuff_float(&wip->weapon_reduce);
1120         } else {
1121                 wip->weapon_reduce = ESUCK_DEFAULT_WEAPON_REDUCE;
1122         }
1123         if( optional_string("$Leech Afterburner:") ){
1124                 stuff_float(&wip->afterburner_reduce);
1125         } else {
1126                 wip->afterburner_reduce = ESUCK_DEFAULT_AFTERBURNER_REDUCE;
1127         }
1128
1129         // beam weapon optional stuff
1130         wip->b_info.beam_type = -1;
1131         wip->b_info.beam_life = -1.0f;
1132         wip->b_info.beam_warmup = -1;
1133         wip->b_info.beam_warmdown = -1;
1134         wip->b_info.beam_muzzle_radius = 0.0f;
1135         wip->b_info.beam_particle_count = -1;
1136         wip->b_info.beam_particle_radius = 0.0f;
1137         wip->b_info.beam_particle_angle = 0.0f;
1138         wip->b_info.beam_particle_ani = -1;     
1139         wip->b_info.beam_loop_sound = -1;
1140         wip->b_info.beam_warmup_sound = -1;
1141         wip->b_info.beam_warmdown_sound = -1;
1142         wip->b_info.beam_num_sections = 0;
1143         wip->b_info.beam_glow_bitmap = -1;
1144         wip->b_info.beam_shots = 0;
1145         wip->b_info.beam_shrink_factor = 0.0f;
1146         wip->b_info.beam_shrink_pct = 0.0f;
1147         if( optional_string("$BeamInfo:")){
1148                 // beam type
1149                 required_string("+Type:");
1150                 stuff_int(&wip->b_info.beam_type);
1151
1152                 // how long it lasts
1153                 required_string("+Life:");
1154                 stuff_float(&wip->b_info.beam_life);
1155
1156                 // warmup time
1157                 required_string("+Warmup:");
1158                 stuff_int(&wip->b_info.beam_warmup);
1159
1160                 // warmdowm time
1161                 required_string("+Warmdown:");
1162                 stuff_int(&wip->b_info.beam_warmdown);
1163
1164                 // muzzle glow radius
1165                 required_string("+Radius:");
1166                 stuff_float(&wip->b_info.beam_muzzle_radius);
1167
1168                 // particle spew count
1169                 required_string("+PCount:");
1170                 stuff_int(&wip->b_info.beam_particle_count);
1171
1172                 // particle radius
1173                 required_string("+PRadius:");
1174                 stuff_float(&wip->b_info.beam_particle_radius);
1175
1176                 // angle off turret normal
1177                 required_string("+PAngle:");
1178                 stuff_float(&wip->b_info.beam_particle_angle);
1179
1180                 // particle bitmap/ani          
1181                 required_string("+PAni:");
1182                 stuff_string(fname, F_NAME, NULL);
1183                 if(!Fred_running){
1184                         int num_frames, fps;
1185                         wip->b_info.beam_particle_ani = bm_load_animation(fname, &num_frames, &fps, 1);
1186                 }
1187
1188                 // magic miss #
1189                 required_string("+Miss Factor:");               
1190                 for(idx=0; idx<NUM_SKILL_LEVELS; idx++){
1191                         wip->b_info.beam_miss_factor[idx] = 0.00001f;
1192                         stuff_float(&wip->b_info.beam_miss_factor[idx]);
1193                 }
1194
1195                 // beam fire sound
1196                 required_string("+BeamSound:");
1197                 stuff_int(&wip->b_info.beam_loop_sound);
1198
1199                 // warmup sound
1200                 required_string("+WarmupSound:");
1201                 stuff_int(&wip->b_info.beam_warmup_sound);
1202
1203                 // warmdown sound
1204                 required_string("+WarmdownSound:");
1205                 stuff_int(&wip->b_info.beam_warmdown_sound);
1206
1207                 // glow bitmap
1208                 required_string("+Muzzleglow:");
1209                 stuff_string(fname, F_NAME, NULL);
1210                 if(!Fred_running){
1211                         wip->b_info.beam_glow_bitmap = bm_load(fname);
1212                 }
1213
1214                 // # of shots (only used for type D beams)
1215                 required_string("+Shots:");
1216                 stuff_int(&wip->b_info.beam_shots);
1217
1218                 // shrinkage
1219                 required_string("+ShrinkFactor:");
1220                 stuff_float(&wip->b_info.beam_shrink_factor);
1221                 required_string("+ShrinkPct:");
1222                 stuff_float(&wip->b_info.beam_shrink_pct);
1223
1224                 // beam sections
1225                 while( optional_string("$Section:") ){
1226                         beam_weapon_section_info i;
1227                         char tex_name[255] = "";
1228                         
1229                         // section width
1230                         required_string("+Width:");
1231                         stuff_float(&i.width);
1232
1233                         // texture
1234                         required_string("+Texture:");
1235                         stuff_string(tex_name, F_NAME, NULL);
1236                         i.texture = -1;
1237                         if(!Fred_running){
1238                                 i.texture = bm_load(tex_name);
1239                                 if(i.texture >= 0){
1240                                         bm_lock(i.texture, 16, BMP_TEX_OTHER);
1241                                         bm_unlock(i.texture);
1242                                 }
1243                         }
1244
1245                         // rgba inner
1246                         required_string("+RGBA Inner:");
1247                         stuff_byte(&i.rgba_inner[0]);
1248                         stuff_byte(&i.rgba_inner[1]);
1249                         stuff_byte(&i.rgba_inner[2]);
1250                         stuff_byte(&i.rgba_inner[3]);
1251
1252                         // rgba outer
1253                         required_string("+RGBA Outer:");
1254                         stuff_byte(&i.rgba_outer[0]);
1255                         stuff_byte(&i.rgba_outer[1]);
1256                         stuff_byte(&i.rgba_outer[2]);
1257                         stuff_byte(&i.rgba_outer[3]);
1258
1259                         // flicker
1260                         required_string("+Flicker:");
1261                         stuff_float(&i.flicker);                        
1262
1263                         // zadd
1264                         required_string("+Zadd:");
1265                         stuff_float(&i.z_add);
1266
1267                         // maybe copy it
1268                         if(wip->b_info.beam_num_sections < MAX_BEAM_SECTIONS - 1){
1269                                 wip->b_info.sections[wip->b_info.beam_num_sections++] = i;
1270                         }
1271                 }               
1272         }
1273
1274         // tag weapon optional stuff
1275         wip->tag_level = -1;
1276         wip->tag_time = -1.0f;
1277         if( optional_string("$Tag:")){
1278                 stuff_int(&wip->tag_level);
1279                 stuff_float(&wip->tag_time);            
1280                 wip->wi_flags |= WIF_TAG;
1281         }       
1282
1283         return 0;
1284 }
1285
1286 // function to parse the information for a specific ship type.  
1287 void parse_cmeasure()
1288 {
1289         cmeasure_info *cmeasurep;
1290
1291         cmeasurep = &Cmeasure_info[Num_cmeasure_types];
1292
1293         required_string("$Name:");
1294         stuff_string(cmeasurep->cmeasure_name, F_NAME, NULL);
1295
1296 /*$Name:                                        Type One
1297 $Velocity:                              20.0                            ;; speed relative to ship, rear-fired until POF info added, MK, 5/22/97
1298 $Fire Wait:                             0.5
1299 $Lifetime Min:                  1.0                             ;; Minimum lifetime
1300 $Lifetime Max:                  2.0                             ;; Maximum lifetime.  Actual lifetime is rand(min..max).
1301 $LaunchSnd:                             counter_1.wav,  .8, 10, 300     ;; countermeasure 1 fired (sound is 3d)
1302 */
1303
1304         required_string("$Velocity:");
1305         stuff_float( &(cmeasurep->max_speed) );
1306
1307         required_string("$Fire Wait:");
1308         stuff_float( &(cmeasurep->fire_wait) );
1309
1310         required_string("$Lifetime Min:");
1311         stuff_float(&cmeasurep->life_min);
1312
1313         required_string("$Lifetime Max:");
1314         stuff_float(&cmeasurep->life_max);
1315
1316         required_string("$LaunchSnd:");
1317         stuff_int(&cmeasurep->launch_sound);
1318
1319         required_string("$Model:");
1320         stuff_string(cmeasurep->pof_name, F_NAME, NULL);
1321         cmeasurep->model_num = -1;              
1322 }
1323
1324
1325 //      For all weapons that spawn weapons, given an index at weaponp->spawn_type,
1326 // convert the strings in Spawn_names to indices in the Weapon_types array.
1327 void translate_spawn_types()
1328 {
1329         int     i,j;
1330
1331         for (i=0; i<Num_weapon_types; i++)
1332                 if (Weapon_info[i].spawn_type != -1) {
1333                         int     spawn_type = Weapon_info[i].spawn_type;
1334
1335                         for (j=0; j<Num_weapon_types; j++)
1336                                 if (!stricmp(Spawn_names[spawn_type], Weapon_info[j].name)) {
1337                                         Weapon_info[i].spawn_type = (short)j;
1338                                         if (i == j){
1339                                                 Warning(LOCATION, "Weapon %s spawns itself.  Infinite recursion?\n", Weapon_info[i].name);
1340                                         }
1341                                 }
1342                 }
1343 }
1344
1345 void parse_weaponstbl()
1346 {
1347         // open localization
1348         lcl_ext_open();
1349
1350         read_file_text("weapons.tbl");
1351         reset_parse();
1352
1353         Num_weapon_types = 0;
1354         First_secondary_index = -1;
1355         Num_spawn_types = 0;
1356         
1357         required_string("#Primary Weapons");
1358         while (required_string_either("#End", "$Name:")) {
1359                 Assert( Num_weapon_types < MAX_WEAPON_TYPES );
1360                 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
1361                 if ( parse_weapon() ) {
1362                         continue;
1363                 }
1364                 Weapon_info[Num_weapon_types].subtype = WP_LASER;
1365                 Num_weapon_types++;
1366         }
1367         required_string("#End");
1368
1369         required_string("#Secondary Weapons");
1370         First_secondary_index = Num_weapon_types;
1371         while (required_string_either("#End", "$Name:")) {
1372                 Assert( Num_weapon_types < MAX_WEAPON_TYPES );
1373                 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
1374                 if ( parse_weapon() ) {
1375                         continue;
1376                 }
1377                 Weapon_info[Num_weapon_types].subtype = WP_MISSILE;
1378                 Num_weapon_types++;
1379         }
1380         required_string("#End");
1381
1382         required_string("#Beam Weapons");
1383         while (required_string_either("#End", "$Name:")) {
1384                 Assert( Num_weapon_types < MAX_WEAPON_TYPES );
1385                 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
1386                 if ( parse_weapon() ) {
1387                         continue;
1388                 }
1389                 Weapon_info[Num_weapon_types].subtype = WP_BEAM;
1390                 Num_weapon_types++;
1391         }
1392         required_string("#End");
1393
1394         required_string("#Countermeasures");
1395         while (required_string_either("#End", "$Name:")) {
1396                 Assert( Num_cmeasure_types < MAX_CMEASURE_TYPES );
1397                 parse_cmeasure();
1398                 Num_cmeasure_types++;
1399         }
1400
1401         required_string("#End");
1402
1403         // Read in a list of weapon_info indicies that are an ordering of the player weapon precedence.
1404         // This list is used to select an alternate weapon when a particular weapon is not available
1405         // during weapon selection.
1406         required_string("$Player Weapon Precedence:");
1407         Num_player_weapon_precedence = stuff_int_list(Player_weapon_precedence, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
1408
1409         translate_spawn_types();
1410
1411         // close localization
1412         lcl_ext_close();
1413 }
1414
1415 void create_weapon_names()
1416 {
1417         int     i;
1418
1419         for (i=0; i<Num_weapon_types; i++)
1420                 Weapon_names[i] = Weapon_info[i].name;
1421 }
1422
1423 // This will get called once at game startup
1424 void weapon_init()
1425 {
1426         int rval;
1427
1428         if ( !Weapons_inited ) {
1429
1430                 // parse weapon_exp.tbl
1431                 parse_weapon_expl_tbl();
1432
1433                 // parse weapons.tbl
1434                 if ((rval = setjmp(parse_abort)) != 0) {
1435                         Error(LOCATION, "Error parsing 'weapons.tbl'\r\nError code = %i.\r\n", rval);
1436                 } else {                        
1437                         parse_weaponstbl();
1438                         create_weapon_names();
1439                         Weapons_inited = 1;
1440                 }
1441         }
1442
1443         weapon_level_init();
1444 }
1445
1446
1447 // This will get called at the start of each level.
1448 void weapon_level_init()
1449 {
1450         int i;
1451
1452         // Reset everything between levels
1453         Num_weapons = 0;
1454         for (i=0; i<MAX_WEAPONS; i++)   {
1455                 Weapons[i].objnum = -1;
1456                 Weapons[i].weapon_info_index = -1;
1457         }
1458
1459         trail_level_init();             // reset all missile trails
1460
1461         swarm_level_init();
1462         missile_obj_list_init();
1463         
1464         cscrew_level_init();
1465
1466         // emp effect
1467         emp_level_init();
1468
1469         Weapon_flyby_sound_timer = timestamp(0);
1470         Weapon_impact_timer = 1;        // inited each level, used to reduce impact sounds
1471 }
1472
1473 MONITOR( NumWeaponsRend );      
1474
1475 float weapon_glow_scale_f = 2.3f;
1476 float weapon_glow_scale_r = 2.3f;
1477 float weapon_glow_scale_l = 1.5f;
1478 float weapon_glow_alpha_d3d = 0.85f;
1479 float weapon_glow_alpha_glide = 0.99f;
1480 void weapon_render(object *obj)
1481 {
1482         int num;
1483         weapon_info *wip;
1484         weapon *wp;
1485         color c;
1486
1487         MONITOR_INC(NumWeaponsRend, 1);
1488
1489         Assert(obj->type == OBJ_WEAPON);
1490
1491         num = obj->instance;
1492         wp = &Weapons[num];
1493         wip = &Weapon_info[Weapons[num].weapon_info_index];
1494
1495         switch (wip->render_type) {
1496                 case WRT_LASER: {
1497                         // turn off fogging for good measure
1498                         gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
1499
1500                         if (wip->laser_bitmap >= 0) {                                   
1501                                 gr_set_color_fast(&wip->laser_color_1);
1502                                 gr_set_bitmap(wip->laser_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.99999f);
1503
1504                                 vector headp;
1505                                 vm_vec_scale_add(&headp, &obj->pos, &obj->orient.fvec, wip->laser_length);
1506                                 wp->weapon_flags &= ~WF_CONSIDER_FOR_FLYBY_SOUND;
1507                                 if ( g3_draw_laser(&headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius) ) {
1508                                         wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
1509                                 }
1510                         }                       
1511
1512                         // maybe draw laser glow bitmap
1513                         if(wip->laser_glow_bitmap >= 0){
1514                                 // get the laser color
1515                                 weapon_get_laser_color(&c, obj);
1516
1517                                 vector headp2;                  
1518                                 vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.fvec, wip->laser_length * weapon_glow_scale_l);
1519                                 gr_set_bitmap(wip->laser_glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, gr_screen.mode == GR_DIRECT3D ? weapon_glow_alpha_d3d : weapon_glow_alpha_glide);
1520                                 g3_draw_laser_rgb(&headp2, wip->laser_head_radius * weapon_glow_scale_f, &obj->pos, wip->laser_tail_radius * weapon_glow_scale_r, c.red, c.green, c.blue);
1521                         }                                               
1522                         break;
1523                 }
1524
1525                 case WRT_POF:   {
1526                                 uint render_flags = MR_NORMAL|MR_IS_MISSILE|MR_NO_LIGHTING;
1527
1528                                 model_clear_instance(wip->model_num);
1529
1530                                 if ( (wip->wi_flags & WIF_THRUSTER) && (wp->thruster_bitmap > -1) ) {
1531                                         float   ft;
1532
1533                                         //      Add noise to thruster geometry.
1534                                         //ft = obj->phys_info.forward_thrust;                                   
1535                                         ft = 1.0f;              // Always use 1.0f for missiles                                 
1536                                         ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
1537                                         if (ft > 1.0f)
1538                                                 ft = 1.0f;
1539
1540                                         model_set_thrust( wip->model_num, ft, wp->thruster_bitmap, wp->thruster_glow_bitmap, wp->thruster_glow_noise);
1541                                         render_flags |= MR_SHOW_THRUSTERS;
1542                                 }
1543
1544                                 model_render(wip->model_num, &obj->orient, &obj->pos, render_flags);
1545
1546                                 // render a missile plume as well
1547                                 /*
1548                                 static int plume = -1;  
1549                                 extern float Interp_thrust_twist;
1550                                 extern float Interp_thrust_twist2;
1551                                 if(plume == -1){
1552                                         plume = model_load("plume01.pof", -1, NULL);
1553                                 }
1554                                 if(plume != -1){
1555                                         Interp_thrust_twist = tw;
1556                                         Interp_thrust_twist2 = tw2;
1557                                         model_set_alpha(plume_alpha);
1558                                         model_render(plume, &obj->orient, &obj->pos, MR_ALL_XPARENT);
1559                                         Interp_thrust_twist = -1.0f;
1560                                         Interp_thrust_twist2 = -1.0f;
1561                                 }
1562                                 */
1563                         }
1564                         break;
1565
1566                 default:
1567                         Warning(LOCATION, "Unknown weapon rendering type = %i\n", wip->render_type);
1568         }
1569 }
1570
1571 void weapon_delete(object *obj)
1572 {
1573         weapon *wp;
1574         int num;
1575
1576         num = obj->instance;
1577
1578         Assert( Weapons[num].objnum == OBJ_INDEX(obj));
1579         wp = &Weapons[num];
1580
1581         Assert(wp->weapon_info_index >= 0);
1582         wp->weapon_info_index = -1;
1583         if (wp->swarm_index >= 0) {
1584                 swarm_delete(wp->swarm_index);
1585                 wp->swarm_index = -1;
1586         }
1587
1588         if(wp->cscrew_index >= 0) {
1589                 cscrew_delete(wp->cscrew_index);
1590                 wp->cscrew_index = -1;
1591         }
1592
1593         if (wp->missile_list_index >= 0) {
1594                 missle_obj_list_remove(wp->missile_list_index);
1595                 wp->missile_list_index = -1;
1596         }
1597
1598         if (wp->flak_index >= 0){
1599                 flak_delete(wp->flak_index);
1600                 wp->flak_index = -1;
1601         }
1602
1603         if (wp->trail_num > -1) {
1604                 trail_object_died(wp->trail_num);
1605         }
1606
1607         wp->objnum = -1;
1608         Num_weapons--;
1609         Assert(Num_weapons >= 0);
1610 }
1611
1612 // Check if missile is newly locked onto the Player, maybe play a launch warning
1613 void weapon_maybe_play_warning(weapon *wp)
1614 {
1615         if ( wp->homing_object == Player_obj ) {
1616                 if ( !(wp->weapon_flags & WF_LOCK_WARNING_PLAYED) ) {
1617                         wp->weapon_flags |= WF_LOCK_WARNING_PLAYED;
1618                         if ( Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_HEAT ) {
1619                                 snd_play(&Snds[SND_HEATLOCK_WARN]);
1620                         } else {
1621                                 Assert(Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_ASPECT);
1622                                 snd_play(&Snds[SND_ASPECTLOCK_WARN]);
1623                         }
1624                 }
1625         }
1626 }
1627
1628 #define CMEASURE_DETONATE_DISTANCE              40.0f
1629
1630 //      Detonate all missiles near this countermeasure.
1631 void detonate_nearby_missiles(cmeasure *cmp)
1632 {
1633         missile_obj     *mop;
1634         vector          cmeasure_pos;
1635
1636         cmeasure_pos = Objects[cmp->objnum].pos;
1637
1638         mop = GET_FIRST(&Missile_obj_list);
1639         while(mop != END_OF_LIST(&Missile_obj_list)) {
1640                 object  *objp;
1641                 weapon  *wp;
1642
1643                 objp = &Objects[mop->objnum];
1644                 wp = &Weapons[objp->instance];
1645
1646                 if (wp->team != cmp->team) {
1647                         if ( Missiontime - wp->creation_time > F1_0/2) {
1648                                 if (vm_vec_dist_quick(&cmeasure_pos, &objp->pos) < CMEASURE_DETONATE_DISTANCE) {
1649                                         if (wp->lifeleft > 0.2f) { 
1650                                                 //nprintf(("Jim", "Frame %i: Cmeasure #%i detonating weapon #%i\n", Framecount, cmp-Cmeasures, wp-Weapons));
1651                                                 wp->lifeleft = 0.2f;
1652                                                 // nprintf(("AI", "Frame %i: Flagging weapon %i for detonation.\n", Framecount, wp-Weapons));
1653                                         }
1654                                 }
1655                         }
1656                 }
1657
1658                 mop = mop->next;
1659         }
1660 }
1661
1662 //      Find an object for weapon #num (object *weapon_objp) to home on due to heat.
1663 void find_homing_object(object *weapon_objp, int num)
1664 {
1665         object          *objp, *old_homing_objp;
1666         weapon_info     *wip;
1667         weapon          *wp;
1668         float                   best_dist;
1669
1670         wp = &Weapons[num];
1671
1672         wip = &Weapon_info[Weapons[num].weapon_info_index];
1673
1674         best_dist = 99999.9f;
1675
1676         // save the old homing object so that multiplayer servers can give the right information
1677         // to clients if the object changes
1678         old_homing_objp = wp->homing_object;
1679
1680         wp->homing_object = &obj_used_list;
1681
1682         //      Scan all objects, find a weapon to home on.
1683         for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
1684                 if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_CMEASURE)) {
1685                         if (objp->type == OBJ_CMEASURE)
1686                                 if (Cmeasures[objp->instance].flags & CMF_DUD_HEAT)
1687                                         continue;
1688
1689                         int homing_object_team = obj_team(objp);
1690                         if ( (homing_object_team != wp->team) || (homing_object_team == TEAM_TRAITOR) ) {
1691                                 float           dist;
1692                                 float           dot;
1693                                 vector  vec_to_object;
1694
1695                                 // AL 2-17-98: If ship is immune to sensors, can't home on it (Sandeep says so)!
1696                                 if ( objp->type == OBJ_SHIP ) {
1697                                         if ( Ships[objp->instance].flags & SF_HIDDEN_FROM_SENSORS ) {
1698                                                 continue;
1699                                         }
1700
1701                                         //      MK, 9/4/99.
1702                                         //      If this is a player object, make sure there aren't already too many homers.
1703                                         //      Only in single player.  In multiplayer, we don't want to restrict it in dogfight on team vs. team.
1704                                         //      For co-op, it's probably also OK.
1705                                         if (!( Game_mode & GM_MULTIPLAYER )) {
1706                                                 int     num_homers = compute_num_homing_objects(objp);
1707                                                 if (Max_allowed_player_homers[Game_skill_level] < num_homers)
1708                                                         continue;
1709                                         }
1710                                 }
1711
1712                                 dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
1713
1714                                 if (objp->type == OBJ_CMEASURE)
1715                                         dist *= 0.5f;
1716
1717                                 dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.fvec);
1718
1719                                 if (dot > wip->fov) {
1720                                         if (dist < best_dist) {
1721                                                 best_dist = dist;
1722                                                 wp->homing_object = objp;
1723                                                 wp->target_sig = objp->signature;
1724
1725                                                 weapon_maybe_alert_cmeasure_success(objp);
1726                                         }
1727                                 }
1728                         }
1729                 }
1730         }
1731
1732 //      if (wp->homing_object->type == OBJ_CMEASURE)
1733 //              nprintf(("AI", "Frame %i: Weapon #%i homing on cmeasure #%i\n", Framecount, num, objp-Objects));
1734
1735         if (wp->homing_object == Player_obj)
1736                 weapon_maybe_play_warning(wp);
1737
1738         // if the old homing object is different that the new one, send a packet to clients
1739         if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
1740                 send_homing_weapon_info( num );
1741         }
1742 }
1743
1744 //      Scan all countermeasures.  Maybe make weapon_objp home on it.
1745 void find_homing_object_cmeasures_1(object *weapon_objp)
1746 {
1747         object  *objp;
1748         weapon  *wp;
1749         weapon_info     *wip;
1750         float           best_dot, dist, dot;
1751
1752         wp = &Weapons[weapon_objp->instance];
1753         wip = &Weapon_info[wp->weapon_info_index];
1754
1755         best_dot = wip->fov;                    //      Note, setting to this avoids comparison below.
1756
1757         for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
1758                 if (objp->type == OBJ_CMEASURE) {
1759                         vector  vec_to_object;
1760                         dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
1761
1762                         if (dist < MAX_CMEASURE_TRACK_DIST) {
1763                                 float   chance;
1764                                 if (wip->wi_flags & WIF_HOMING_ASPECT) {
1765                                         chance = 1.0f/2.0f;     //      aspect seeker this likely to chase a countermeasure
1766                                 } else {
1767                                         chance = 1.0f/1.5f;     //      heat seeker this likely to chase a countermeasure
1768                                 }
1769                                 if ((objp->signature != wp->cmeasure_ignore_objnum) && (objp->signature != wp->cmeasure_chase_objnum)) {
1770                                         if (frand() < chance) {
1771                                                 wp->cmeasure_ignore_objnum = objp->signature;   //      Don't process this countermeasure again.
1772                                                 //nprintf(("Jim", "Frame %i: Weapon #%i ignoring cmeasure #%i\n", Framecount, OBJ_INDEX(weapon_objp), objp->signature));
1773                                         } else  {
1774                                                 wp->cmeasure_chase_objnum = objp->signature;    //      Don't process this countermeasure again.
1775                                                 //nprintf(("Jim", "Frame %i: Weapon #%i CHASING cmeasure #%i\n", Framecount, OBJ_INDEX(weapon_objp), objp->signature));
1776                                         }
1777                                 }
1778                                 
1779                                 if (objp->signature != wp->cmeasure_ignore_objnum) {
1780
1781                                         dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.fvec);
1782
1783                                         if (dot > best_dot) {
1784                                                 //nprintf(("Jim", "Frame %i: Weapon #%i homing on cmeasure #%i\n", Framecount, weapon_objp-Objects, objp->signature));
1785                                                 best_dot = dot;
1786                                                 wp->homing_object = objp;
1787                                                 weapon_maybe_alert_cmeasure_success(objp);
1788                                         }
1789                                 }
1790                         }
1791                 }
1792         }
1793 }
1794
1795
1796 //      Someone launched countermeasures.
1797 //      For all heat-seeking homing objects, see if should favor tracking a countermeasure instead.
1798 void find_homing_object_cmeasures()
1799 {
1800         object  *weapon_objp;
1801
1802         // nprintf(("AI", "Scanning for countermeasures in frame %i\n", Framecount));
1803
1804         if (Cmeasures_homing_check == 0)
1805                 return;
1806
1807         if (Cmeasures_homing_check <= 0)
1808                 Cmeasures_homing_check = 1;
1809
1810         Cmeasures_homing_check--;
1811
1812         for (weapon_objp = GET_FIRST(&obj_used_list); weapon_objp != END_OF_LIST(&obj_used_list); weapon_objp = GET_NEXT(weapon_objp) ) {
1813                 if (weapon_objp->type == OBJ_WEAPON) {
1814                         weapon_info     *wip = &Weapon_info[Weapons[weapon_objp->instance].weapon_info_index];
1815
1816                         if (wip->wi_flags & WIF_HOMING)
1817                                 find_homing_object_cmeasures_1(weapon_objp);
1818                 }
1819         }
1820
1821 }
1822
1823 //      Find object with signature "sig" and make weapon home on it.
1824 void find_homing_object_by_sig(object *weapon_objp, int sig)
1825 {
1826         ship_obj                *sop;
1827         weapon          *wp;
1828         object          *old_homing_objp;
1829
1830         wp = &Weapons[weapon_objp->instance];
1831
1832         // save the old object so that multiplayer masters know whether to send a homing update packet
1833         old_homing_objp = wp->homing_object;
1834
1835         sop = GET_FIRST(&Ship_obj_list);
1836         while(sop != END_OF_LIST(&Ship_obj_list)) {
1837                 object  *objp;
1838
1839                 objp = &Objects[sop->objnum];
1840                 if (objp->signature == sig) {
1841                         wp->homing_object = objp;
1842                         wp->target_sig = objp->signature;
1843                         break;
1844                 }
1845
1846                 sop = sop->next;
1847         }
1848
1849         // if the old homing object is different that the new one, send a packet to clients
1850         if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
1851                 send_homing_weapon_info( weapon_objp->instance );
1852         }
1853
1854 }
1855
1856 //      Make weapon num home.  It's also object *obj.
1857 void weapon_home(object *obj, int num, float frame_time)
1858 {
1859         weapon          *wp;
1860         weapon_info     *wip;
1861         object          *hobjp;
1862
1863         Assert(obj->type == OBJ_WEAPON);
1864         Assert(obj->instance == num);
1865         wp = &Weapons[num];
1866         wip = &Weapon_info[wp->weapon_info_index];
1867         hobjp = Weapons[num].homing_object;
1868
1869         //      If not 1/2 second gone by, don't home yet.
1870         if ((hobjp == &obj_used_list) || ( f2fl(Missiontime - wp->creation_time) < 0.25f )) {
1871                 //      If this is a heat seeking homing missile and 1/2 second has elapsed since firing
1872                 //      and we don't have a target (else we wouldn't be inside the IF), find a new target.
1873                 if (wip->wi_flags & WIF_HOMING_HEAT)
1874                         if ( f2fl(Missiontime - wp->creation_time) > 0.5f )
1875                                 find_homing_object(obj, num);
1876
1877                 if (obj->phys_info.speed > wip->max_speed) {
1878                         obj->phys_info.speed -= frame_time * 4;
1879                         vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.fvec, obj->phys_info.speed);
1880                 } else if ((obj->phys_info.speed < wip->max_speed/4) && (wip->wi_flags & WIF_HOMING_HEAT)) {
1881                         obj->phys_info.speed = wip->max_speed/4;
1882                         vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.fvec, obj->phys_info.speed);
1883                 }
1884
1885 /*      Removed code that makes bombs drop for a bit.  They looked odd and it was confusing.  People wondered where their weapons went.
1886                 //      Make bombs drop down for first second of life.
1887                 if (wip->wi_flags & WIF_BOMB) {
1888                         if (wip->lifetime - wp->lifeleft < 0.5f) {
1889                                 float   time_scale = wip->lifetime - wp->lifeleft;
1890                                 vm_vec_scale_add2(&obj->phys_info.desired_vel, &obj->orient.uvec, (time_scale - 0.5f) * max(10.0f, obj->phys_info.speed/2.0f));
1891                         }
1892                 }
1893 */
1894                 return;
1895         }
1896
1897         // AL 4-8-98: If orgiginal target for aspect lock missile is lost, stop homing
1898         if (wip->wi_flags & WIF_HOMING_ASPECT) {
1899                 if ( wp->target_sig > 0 ) {
1900                         if ( wp->homing_object->signature != wp->target_sig ) {
1901                                 wp->homing_object = &obj_used_list;
1902                                 return;
1903                         }
1904                 }
1905         }
1906
1907         // AL 4-13-98: Stop homing on a subsystem if parent ship has changed
1908         if (wip->wi_flags & WIF_HOMING_HEAT) {
1909                 if ( wp->target_sig > 0 ) {
1910                         if ( wp->homing_object->signature != wp->target_sig ) {
1911                                 wp->homing_subsys = NULL;
1912                         }
1913                 }
1914         }
1915
1916 /*
1917         if (hobjp->type == OBJ_NONE) {
1918                 find_homing_object(obj, num);
1919                 return;
1920         }
1921 */
1922
1923         switch (hobjp->type) {
1924         case OBJ_NONE:
1925                 if (wip->wi_flags & WIF_HOMING_ASPECT)
1926                         find_homing_object_by_sig(obj, wp->target_sig);
1927                 else
1928                         find_homing_object(obj, num);
1929                 return;
1930                 break;
1931         case OBJ_SHIP:
1932                 if (hobjp->signature != wp->target_sig) {
1933                         if (wip->wi_flags & WIF_HOMING_ASPECT)
1934                                 find_homing_object_by_sig(obj, wp->target_sig);
1935                         else
1936                                 find_homing_object(obj, num);
1937                         return;
1938                 }
1939                 break;
1940         case OBJ_WEAPON:
1941                 // only allowed to home on bombs
1942                 Assert(Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags & WIF_BOMB);
1943                 if (wip->wi_flags & WIF_HOMING_ASPECT)
1944                         find_homing_object_by_sig(obj, wp->target_sig);
1945                 else
1946                         find_homing_object(obj, num);
1947                 break;
1948         case OBJ_CMEASURE:
1949                 break;
1950         default:
1951                 return;
1952         }
1953
1954         //      See if this weapon is the nearest homing object to the object it is homing on.
1955         //      If so, update some fields in the target object's ai_info.
1956         if (hobjp != &obj_used_list) {
1957                 float   dist;
1958
1959                 dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
1960
1961                 if (hobjp->type == OBJ_SHIP) {
1962                         ai_info *aip;
1963
1964                         aip = &Ai_info[Ships[hobjp->instance].ai_index];
1965
1966                         if ((aip->nearest_locked_object == -1) || (dist < aip->nearest_locked_distance)) {
1967                                 aip->nearest_locked_object = obj-Objects;
1968                                 aip->nearest_locked_distance = dist;
1969                         }
1970                 }
1971         }
1972
1973         //      If the object it is homing on is still valid, home some more!
1974         if (hobjp != &obj_used_list) {
1975                 float           old_dot, vel;
1976                 vector  vec_to_goal;
1977                 vector  target_pos;     // position of what the homing missile is seeking
1978
1979                 vm_vec_zero(&target_pos);
1980
1981                 // the homing missile may be seeking a subsystem on a ship.  If so, we need to calculate the
1982                 // world coordinates of that subsystem so the homing missile can seek it out.
1983                 //      For now, March 7, 1997, MK, heat seeking homing missiles will be able to home on
1984                 //      any subsystem.  Probably makes sense for them to only home on certain kinds of subsystems.
1985                 if ( wp->homing_subsys != NULL ) {
1986                         get_subsystem_world_pos(hobjp, Weapons[num].homing_subsys, &target_pos);
1987                         wp->homing_pos = target_pos;    // store the homing position in weapon data
1988                         Assert( !vm_is_vec_nan(&wp->homing_pos) );
1989                 } else {
1990                         float   fov;
1991                         float   dist;
1992
1993                         dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
1994                         if (hobjp->type == OBJ_CMEASURE) {
1995                                 if (dist < CMEASURE_DETONATE_DISTANCE) {
1996                                         cmeasure        *cmp;
1997
1998                                         cmp = &Cmeasures[hobjp->instance];
1999
2000                                         //      Make this missile detonate soon.  Not right away, not sure why.  Seems better.
2001                                         if (cmp->team != wp->team) {
2002                                                 detonate_nearby_missiles(cmp);
2003                                                 //nprintf(("AI", "Frame %i: Weapon %i hit cmeasure, will die!\n", Framecount, wp-Weapons));
2004                                                 return;
2005                                         }
2006                                 }
2007                         }
2008
2009                         fov = 0.8f;
2010                         if (wip->fov > 0.8f)
2011                                 fov = wip->fov;
2012
2013                         int pick_homing_point = 0;
2014                         if ( IS_VEC_NULL(&wp->homing_pos) ) {
2015                                 pick_homing_point = 1;
2016                         }
2017
2018                         //      Update homing position if it hasn't been set, you're within 500 meters, or every half second, approximately.
2019                         //      For large objects, don't lead them.
2020                         if (hobjp->radius < 40.0f) {
2021                                 target_pos = hobjp->pos;
2022                                 wp->homing_pos = target_pos;
2023                         } else if ( pick_homing_point || (dist < 500.0f) || (rand_chance(flFrametime, 2.0f)) ) {
2024
2025                                 if (hobjp->type == OBJ_SHIP) {
2026                                         if ( !pick_homing_point ) {
2027                                                 // ensure that current attack point is only updated in world coords (ie not pick a different vertex)
2028                                                 wp->pick_big_attack_point_timestamp = 0;
2029                                         }
2030
2031                                         if ( pick_homing_point ) {
2032                                                 // If *any* player is parent of homing missile, then use position where lock indicator is
2033                                                 if ( Objects[obj->parent].flags & OF_PLAYER_SHIP ) {
2034                                                         player *pp;
2035
2036                                                         // determine the player
2037                                                         pp = Player;
2038                                                         if ( Game_mode & GM_MULTIPLAYER ) {
2039                                                                 int pnum;
2040
2041                                                                 pnum = multi_find_player_by_object( &Objects[obj->parent] );
2042                                                                 if ( pnum != -1 ){
2043                                                                         pp = Net_players[pnum].player;
2044                                                                 }
2045                                                         }
2046
2047                                                         // If player has apect lock, we don't want to find a homing point on the closest
2048                                                         // octant... setting the timestamp to 0 ensures this.
2049                                                         if (wip->wi_flags & WIF_HOMING_ASPECT) {
2050                                                                 wp->pick_big_attack_point_timestamp = 0;
2051                                                         } else {
2052                                                                 wp->pick_big_attack_point_timestamp = 1;
2053                                                         }
2054
2055                                                         if ( pp && pp->locking_subsys ) {
2056                                                                 wp->big_attack_point = pp->locking_subsys->system_info->pnt;
2057                                                         } else {
2058                                                                 vm_vec_zero(&wp->big_attack_point);
2059                                                         }
2060                                                 }
2061                                         }
2062
2063                                         ai_big_pick_attack_point(hobjp, obj, &target_pos, fov);
2064
2065                                 } else {
2066                                         target_pos = hobjp->pos;
2067                                 }
2068
2069                                 wp->homing_pos = target_pos;
2070                                 Assert( !vm_is_vec_nan(&wp->homing_pos) );
2071                                 // nprintf(("AI", "Attack point = %7.3f %7.3f %7.3f\n", target_pos.x, target_pos.y, target_pos.z));
2072                         } else
2073                                 target_pos = wp->homing_pos;
2074                 }
2075
2076                 //      Couldn't find a lock.
2077                 if (IS_VEC_NULL(&target_pos))
2078                         return;
2079
2080                 //      Cause aspect seeking weapon to home at target's predicted position.
2081                 //      But don't use predicted position if dot product small or negative.
2082                 //      If do this, with a ship headed towards missile, could choose a point behind missile.``1
2083                 float   dist_to_target, time_to_target;
2084                 
2085                 dist_to_target = vm_vec_normalized_dir(&vec_to_goal, &target_pos, &obj->pos);
2086                 time_to_target = dist_to_target/wip->max_speed;
2087
2088                 vector  tvec;
2089                 tvec = obj->phys_info.vel;
2090                 vm_vec_normalize(&tvec);
2091
2092                 old_dot = vm_vec_dot(&tvec, &vec_to_goal);
2093
2094                 //      If a weapon has missed its target, detonate it.
2095                 //      This solves the problem of a weapon circling the center of a subsystem that has been blown away.
2096                 //      Problem: It does not do impact damage, just proximity damage.
2097                 if ((dist_to_target < flFrametime * obj->phys_info.speed * 4.0f + 10.0f) && (old_dot < 0.0f)) {
2098                         int kill_missile = TRUE;
2099                         if (wp->homing_object) {
2100                                 if (wp->homing_object->type == OBJ_SHIP) {
2101                                         ship *shipp = &Ships[wp->homing_object->instance];
2102                                         if (Ship_info[shipp->ship_info_index].flags & SIF_DONT_COLLIDE_INVIS) {
2103                                                 kill_missile = FALSE;
2104                                         }
2105                                 }
2106                         }
2107                         
2108                         if (kill_missile && (wp->lifeleft > 0.01f)) {
2109                                 wp->lifeleft = 0.01f;
2110                         }
2111                 }
2112
2113                 //      Only lead target if more than one second away.  Otherwise can miss target.  I think this
2114                 //      is what's causing Harbingers to miss the super destroyer. -- MK, 4/15/98
2115                 if ((wip->wi_flags & WIF_HOMING_ASPECT) && (old_dot > 0.1f) && (time_to_target > 0.1f))
2116                         vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, min(time_to_target, 2.0f));
2117
2118                 //nprintf(("AI", "Dot = %7.3f, dist = %7.3f, time_to = %6.3f, deg/sec = %7.3f\n", old_dot, dist_to_target, time_to_target, angles/flFrametime));
2119
2120                 // nprintf(("AI", "Weapon %i, lifeleft = %7.3f, dist = %7.3f, dot = %6.3f\n", num, Weapons[num].lifeleft, vm_vec_dist_quick(&obj->pos, &Weapons[num].homing_object->pos), old_dot));
2121
2122                 //      If a HEAT seeking (rather than ASPECT seeking) homing missile, verify that target is in viewcone.
2123                 if (wip->wi_flags & WIF_HOMING_HEAT) {
2124                         if ((old_dot < wip->fov) && (dist_to_target > wip->inner_radius*1.1f)) {        //      Delay finding new target one frame to allow detonation.
2125                                 find_homing_object(obj, num);
2126                                 return;                 //      Maybe found a new homing object.  Return, process more next frame.
2127                         } else  //      Subtract out life based on how far from target this missile points.
2128                                 if (wip->fov < 0.95f) {
2129                                         wp->lifeleft -= flFrametime * (0.95f - old_dot);
2130                                         //Should only happen when time is compressed.
2131                                         //if (flFrametime * (1.0f - old_dot) > 1.0f)
2132                                         //      Int3();
2133                                 }
2134                 } else if (wip->wi_flags & WIF_HOMING_ASPECT) { //      subtract life as if max turn is 90 degrees.
2135                         if (wip->fov < 0.95f)
2136                                 wp->lifeleft -= flFrametime * (0.95f - old_dot);
2137                 } else
2138                         Assert(0);      //      Hmm, a homing missile, but not aspect or heat?
2139
2140
2141                 //      Control speed based on dot product to goal.  If close to straight ahead, move
2142                 //      at max speed, else move slower based on how far from ahead.
2143                 if (old_dot < 0.90f) {
2144                         obj->phys_info.speed = max(0.2f, old_dot* (float) fabs(old_dot));
2145                         if (obj->phys_info.speed < wip->max_speed*0.75f)
2146                                 obj->phys_info.speed = wip->max_speed*0.75f;
2147                 } else
2148                         obj->phys_info.speed = wip->max_speed;
2149
2150                 //      For first second of weapon's life, it doesn't fly at top speed.  It ramps up.
2151                 if (Missiontime - wp->creation_time < i2f(1)) {
2152                         float   t;
2153
2154                         t = f2fl(Missiontime - wp->creation_time);
2155                         obj->phys_info.speed *= t*t;
2156                 }
2157
2158                 Assert( obj->phys_info.speed > 0.0f );
2159
2160                 vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.fvec, obj->phys_info.speed);
2161
2162                 // turn the missile towards the target only if non-swarm.  Homing swarm missiles choose
2163                 // a different vector to turn towards, this is done in swarm_update_direction().
2164 //              if ( !(wip->wi_flags & WIF_SWARM) ) {
2165                 if ( wp->swarm_index < 0 ) {
2166                         // nprintf(("AI", "Dot, dist = %7.3f, %7.3f, target pos = %7.3f %7.3f %7.3f\n", old_dot, vm_vec_dist_quick(&obj->pos, &target_pos), target_pos.x, target_pos.y, target_pos.z));
2167                         ai_turn_towards_vector(&target_pos, obj, frame_time, wip->turn_time, NULL, NULL, 0.0f, 0, NULL);
2168                         vel = vm_vec_mag(&obj->phys_info.desired_vel);
2169
2170                         vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.fvec, vel);
2171
2172                 }
2173
2174 /*              //      If this weapon shot past its target, make it detonate.
2175                 if ((old_dot < 0.0f) && (dist_to_target < 50.0f)) {
2176                         if (wp->lifeleft > 0.01f)
2177                                 wp->lifeleft = 0.01f;
2178                 }
2179 */      }
2180 }
2181
2182 // as Mike K did with ships -- break weapon into process_pre and process_post for code to execute
2183 // before and after physics movement
2184
2185 void weapon_process_pre( object *obj, float frame_time)
2186 {
2187         // if the object is a corkscrew style weapon, process it now
2188         if((obj->type == OBJ_WEAPON) && (Weapons[obj->instance].cscrew_index >= 0)){
2189                 cscrew_process_pre(obj);
2190         }
2191
2192         // if the weapon is a flak weapon, maybe detonate it early
2193         if((obj->type == OBJ_WEAPON) && (Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags & WIF_FLAK) && (Weapons[obj->instance].flak_index >= 0)){
2194                 flak_maybe_detonate(obj);               
2195         }
2196 }
2197
2198 int     Homing_hits = 0, Homing_misses = 0;
2199
2200
2201 MONITOR( NumWeapons );  
2202
2203 // maybe play a "whizz sound" if close enough to view position
2204 void weapon_maybe_play_flyby_sound(object *weapon_objp, weapon *wp)
2205 {       
2206         // do a quick out if not a laser
2207         if ( Weapon_info[wp->weapon_info_index].subtype != WP_LASER ) {
2208                 return;
2209         }
2210
2211         // don't play flyby sounds too close together
2212         if ( !timestamp_elapsed(Weapon_flyby_sound_timer) ) {
2213                 return;
2214         }
2215
2216         if ( !(wp->weapon_flags & WF_PLAYED_FLYBY_SOUND) && (wp->weapon_flags & WF_CONSIDER_FOR_FLYBY_SOUND) ) {
2217                 float           dist, dot, radius;
2218
2219                 dist = vm_vec_dist_quick(&weapon_objp->pos, &Eye_position);
2220
2221                 if ( Viewer_obj ) {
2222                         radius = Viewer_obj->radius;
2223                 } else {
2224                         radius = 0.0f;
2225                 }
2226
2227                 if ( (dist > radius) && (dist < 55) ) {
2228                         vector  vec_to_weapon;
2229
2230                         vm_vec_sub(&vec_to_weapon, &weapon_objp->pos, &Eye_position);
2231                         vm_vec_normalize(&vec_to_weapon);
2232
2233                         // ensure laser is in front of eye
2234                         dot = vm_vec_dot(&vec_to_weapon, &Eye_matrix.fvec);
2235                         if ( dot < 0.1 ) {
2236                                 return;
2237                         }
2238
2239                         // ensure that laser is moving in similar direction to fvec
2240                         dot = vm_vec_dot(&vec_to_weapon, &weapon_objp->orient.fvec);
2241                         
2242 //                      nprintf(("Alan", "Weapon dot: %.2f\n", dot));
2243                         if ( (dot < -0.80) && (dot > -0.98) ) {
2244                                 snd_play_3d( &Snds[SND_WEAPON_FLYBY], &weapon_objp->pos, &Eye_position );
2245                                 Weapon_flyby_sound_timer = timestamp(200);
2246                                 wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
2247                         }
2248                 }
2249         }
2250 }
2251
2252 // process a weapon after physics movement.  MWA reorders some of the code on 8/13 for multiplayer.  When
2253 // adding something to this function, decide whether or not a client in a multiplayer game needs to do
2254 // what is normally done in a single player game.  Things like plotting an object on a radar, effect
2255 // for exhaust are things that are done on all machines.  Things which calculate weapon targets, new
2256 // velocities, etc, are server only functions and should go after the if ( !MULTIPLAYER_MASTER ) statement
2257 // See Allender if you cannot decide what to do.
2258 void weapon_process_post(object * obj, float frame_time)
2259 {
2260         int                     num;    
2261         weapon_info     *wip;
2262         weapon          *wp;
2263
2264         MONITOR_INC( NumWeapons, 1 );   
2265         
2266         Assert(obj->type == OBJ_WEAPON);
2267
2268         num = obj->instance;
2269
2270 #ifndef NDEBUG
2271         int objnum = OBJ_INDEX(obj);
2272         Assert( Weapons[num].objnum == objnum );
2273 #endif
2274
2275         wp = &Weapons[num];
2276
2277         wp->lifeleft -= frame_time;
2278         wip = &Weapon_info[wp->weapon_info_index];
2279
2280         // check life left.  Multiplayer client code will go through here as well.  We must be careful in weapon_hit
2281         // when killing a missile that spawn child weapons!!!!
2282         if ( wp->lifeleft < 0.0f ) {
2283                 if ( wip->subtype & WP_MISSILE ) {
2284                         if(Game_mode & GM_MULTIPLAYER){                         
2285                                 if ( !MULTIPLAYER_CLIENT || (MULTIPLAYER_CLIENT && (wp->lifeleft < -2.0f)) || (MULTIPLAYER_CLIENT && (wip->wi_flags & WIF_CHILD))) {                                    // don't call this function multiplayer client -- host will send this packet to us
2286                                         // nprintf(("AI", "Frame %i: Weapon %i detonated, dist = %7.3f!\n", Framecount, obj-Objects));
2287                                         weapon_detonate(obj);                                   
2288                                 }
2289                         } else {
2290                                 // nprintf(("AI", "Frame %i: Weapon %i detonated, dist = %7.3f!\n", Framecount, obj-Objects));
2291                                 weapon_detonate(obj);                                                                   
2292                         }
2293                         if (wip->wi_flags & WIF_HOMING) {
2294                                 Homing_misses++;
2295                                 // nprintf(("AI", "Miss!  Hits = %i/%i\n", Homing_hits, (Homing_hits + Homing_misses)));
2296                         }
2297                 } else {
2298                         obj->flags |= OF_SHOULD_BE_DEAD;
2299 //                      demo_do_flag_dead(OBJ_INDEX(obj));
2300                 }
2301
2302                 return;
2303         }
2304
2305         // plot homing missiles on the radar
2306         if (wip->wi_flags & WIF_HOMING) {
2307                 if ( hud_gauge_active(HUD_RADAR) ) {
2308                         radar_plot_object( obj );
2309                 }
2310         }
2311
2312         // trail missiles
2313         if ((wip->wi_flags & WIF_TRAIL) && !(wip->wi_flags & WIF_CORKSCREW)) {
2314                 if ( wp->trail_num > -1 )       {
2315                         if (trail_stamp_elapsed(wp->trail_num)) {
2316
2317                                 trail_add_segment( wp->trail_num, &obj->pos );
2318                                 
2319                                 trail_set_stamp(wp->trail_num);
2320                         } else {
2321                                 trail_set_segment( wp->trail_num, &obj->pos );
2322                         }
2323
2324                 }
2325         }
2326
2327         if ( wip->wi_flags & WIF_THRUSTER )     {
2328                 ship_do_weapon_thruster_frame( wp, obj, flFrametime );  
2329         }
2330
2331         // maybe play a "whizz sound" if close enough to view position
2332         #ifndef NDEBUG
2333         if ( Weapon_flyby_sound_enabled ) {
2334                 weapon_maybe_play_flyby_sound(obj, wp);
2335         }
2336         #else
2337                 weapon_maybe_play_flyby_sound(obj, wp);
2338         #endif  
2339         
2340         //      If our target is still valid, then update some info.
2341         if (wp->target_num != -1) {
2342                 if (Objects[wp->target_num].signature == wp->target_sig) {
2343                         float           cur_dist;
2344                         vector  v0;
2345
2346                         vm_vec_avg(&v0, &obj->pos, &obj->last_pos);
2347
2348                         cur_dist = vm_vec_dist_quick(&v0, &Objects[wp->target_num].pos);
2349
2350                         if (cur_dist < wp->nearest_dist) {
2351                                 wp->nearest_dist = cur_dist;
2352                         } else if (cur_dist > wp->nearest_dist + 1.0f) {
2353                                 float           dot;
2354                                 vector  tvec;
2355                                 ai_info *parent_aip;
2356                                 float           lead_scale = 0.0f;
2357
2358                                 parent_aip = NULL;
2359                                 if (obj->parent != Player_obj-Objects) {
2360                                         parent_aip = &Ai_info[Ships[Objects[obj->parent].instance].ai_index];
2361                                         lead_scale = parent_aip->lead_scale;
2362                                 }
2363
2364                                 vm_vec_normalized_dir(&tvec, &v0, &Objects[wp->target_num].pos);
2365                                 dot = vm_vec_dot(&tvec, &Objects[wp->target_num].orient.fvec);
2366                                 // nprintf(("AI", "Miss dot = %7.3f, dist = %7.3f, lead_scale = %7.3f\n", dot, cur_dist, lead_scale));
2367                                 wp->target_num = -1;
2368
2369                                 //      Learn!  If over-shooting or under-shooting, compensate.
2370                                 //      Really need to compensate for left/right errors.  This does no good against someone circling
2371                                 //      in a plane perpendicular to the attacker's forward vector.
2372                                 if (parent_aip != NULL) {
2373                                         if (cur_dist > 100.0f)
2374                                                 parent_aip->lead_scale = 0.0f;
2375
2376                                         if (dot < -0.1f){
2377                                                 parent_aip->lead_scale += cur_dist/2000.0f;
2378                                         } else if (dot > 0.1f) {
2379                                                 parent_aip->lead_scale -= cur_dist/2000.0f;
2380                                         }
2381                                         
2382                                         if (fl_abs(parent_aip->lead_scale) > 1.0f){
2383                                                 parent_aip->lead_scale *= 0.9f;
2384                                         }
2385                                 }
2386                         }
2387                 }
2388         }
2389
2390         if(wip->wi_flags & WIF_PARTICLE_SPEW){
2391                 weapon_maybe_spew_particle(obj);
2392         }
2393
2394         // a single player or multiplayer server function -- it affects actual weapon movement.
2395         if (wip->wi_flags & WIF_HOMING) {
2396                 weapon_home(obj, num, frame_time);
2397
2398 /*              if (wip->wi_flags & WIF_BOMB) {
2399                         if (wip->lifetime - obj->lifeleft < 1.0f) {
2400                                 
2401                         }
2402                 }
2403 */              
2404                 // If this is a swarm type missile, 
2405 //              if ( wip->wi_flags & WIF_SWARM ) 
2406                 if ( wp->swarm_index >= 0 ) {
2407                         swarm_update_direction(obj, frame_time);
2408                 }
2409
2410                 if( wp->cscrew_index >= 0) {
2411                         cscrew_process_post(obj);                       
2412                 }
2413         }
2414 }
2415
2416 //      Update weapon tracking information.
2417 void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_objnum, int target_is_locked, ship_subsys *target_subsys)
2418 {
2419         int                     ai_index;
2420         object          *parent_objp;
2421         weapon          *wp;
2422         weapon_info     *wip;
2423         int targeting_same = 0;
2424
2425         if ( weapon_objnum < 0 ) {
2426                 return;
2427         }
2428
2429         Assert(Objects[weapon_objnum].type == OBJ_WEAPON);
2430
2431         wp = &Weapons[Objects[weapon_objnum].instance];
2432         wip = &Weapon_info[wp->weapon_info_index];
2433         parent_objp = &Objects[parent_objnum];
2434
2435         Assert(parent_objp->type == OBJ_SHIP);
2436         ai_index = Ships[parent_objp->instance].ai_index;
2437
2438         if ( ai_index >= 0 ) {
2439                 int target_team = -1;
2440                 if ( target_objnum >= 0 ) {
2441                         int obj_type = Objects[target_objnum].type;
2442                         if ( (obj_type == OBJ_SHIP) || (obj_type == OBJ_WEAPON) ) {
2443                                 target_team = obj_team(&Objects[target_objnum]);
2444                         }
2445                 }
2446         
2447                 // determining if we're targeting the same team
2448                 if(Ships[parent_objp->instance].team == target_team){
2449                         targeting_same = 1;
2450                 } else {
2451                         targeting_same = 0;
2452                 }
2453
2454                 if ((target_objnum != -1) && (!targeting_same || ((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT) && (target_team == TEAM_TRAITOR))) ) {
2455                         wp->target_num = target_objnum;
2456                         wp->target_sig = Objects[target_objnum].signature;
2457                         wp->nearest_dist = 99999.0f;
2458                         if ( (wip->wi_flags & WIF_HOMING_ASPECT) && target_is_locked) {
2459                                 wp->homing_object = &Objects[target_objnum];
2460                                 wp->homing_subsys = target_subsys;
2461                                 weapon_maybe_play_warning(wp);
2462                         } else if ( wip->wi_flags & WIF_HOMING_HEAT ) {
2463                                 //      Make a heat seeking missile try to home.  If the target is outside the view cone, it will
2464                                 //      immediately drop it and try to find one in its view cone.
2465                                 if (target_objnum != -1) {
2466                                         wp->homing_object = &Objects[target_objnum];
2467                                         weapon_maybe_play_warning(wp);
2468                                 } else
2469                                         wp->homing_object = &obj_used_list;
2470
2471                                 wp->homing_subsys = target_subsys;
2472                         }
2473                 } else {
2474                         wp->target_num = -1;
2475                         wp->target_sig = -1;
2476                 }
2477
2478                 //      If missile is locked on target, increase its lifetime by 20% since missiles can be fired at limit of range
2479                 //      as defined by velocity*lifeleft, but missiles often slow down a bit, plus can be fired at a moving away target.
2480                 //      Confusing to many players when their missiles run out of gas before getting to target.  
2481                 // DB - removed 7:14 pm 9/6/99. was totally messing up lifetimes for all weapons.
2482                 //      MK, 7:11 am, 9/7/99.  Put it back in, but with a proper check here to make sure it's an aspect seeker and
2483                 //      put a sanity check in the color changing laser code that was broken by this code.
2484                 if (target_is_locked && (wp->target_num != -1) && (wip->wi_flags & WIF_HOMING_ASPECT) ) {
2485                         wp->lifeleft *= 1.2f;
2486                 }
2487
2488                 ai_update_danger_weapon(target_objnum, weapon_objnum);          
2489         }
2490 }
2491
2492
2493 // weapon_create() will create a weapon object
2494 //
2495 // Returns:  index of weapon in the Objects[] array, -1 if the weapon object was not created
2496 int Weapons_created = 0;
2497 int weapon_create( vector * pos, matrix * orient, int weapon_id, int parent_objnum, int secondary_flag, int group_id, int is_locked )
2498 {
2499         int                     n, objnum;
2500         int num_deleted;
2501         object          *objp, *parent_objp;
2502         weapon          *wp;
2503         weapon_info     *wip;
2504
2505         Assert(weapon_id >= 0 && weapon_id < Num_weapon_types);
2506
2507         // beam weapons should never come through here!
2508         Assert(!(Weapon_info[weapon_id].wi_flags & WIF_BEAM));
2509
2510         num_deleted = 0;
2511         if (Num_weapons >= MAX_WEAPONS-5) {
2512
2513                 //No, do remove for AI ships -- MK, 3/12/98  // don't need to try and delete weapons for ai ships
2514                 //if ( !(Objects[parent_objnum].flags & OF_PLAYER_SHIP) )
2515                 //      return -1;
2516
2517                 num_deleted = collide_remove_weapons();
2518                 nprintf(("WARNING", "Deleted %d weapons because of lack of slots\n", num_deleted));
2519                 if (num_deleted == 0){
2520                         return -1;
2521                 }
2522         }
2523
2524         for (n=0; n<MAX_WEAPONS; n++ ){
2525                 if (Weapons[n].weapon_info_index < 0){
2526                         break;
2527                 }
2528         }
2529
2530         if (n == MAX_WEAPONS) {
2531                 // if we supposedly deleted weapons above, what happened here!!!!
2532                 if (num_deleted){
2533                         Int3();                         // get allender -- something funny is going on!!!
2534                 }
2535
2536                 return -1;
2537         }
2538
2539         Weapons_created++;
2540         objnum = obj_create( OBJ_WEAPON, parent_objnum, n, orient, pos, 2.0f, OF_RENDERS | OF_COLLIDES | OF_PHYSICS );
2541         Assert(objnum >= 0);
2542         Assert(First_secondary_index != -1);
2543         objp = &Objects[objnum];
2544
2545         parent_objp = NULL;
2546         if(parent_objnum >= 0){
2547                 parent_objp = &Objects[parent_objnum];
2548         }
2549
2550         // Create laser n!
2551         wp = &Weapons[n];
2552         wip = &Weapon_info[weapon_id];
2553
2554         // check if laser or dumbfire missile
2555         // set physics flag to allow optimization
2556         if ((wip->subtype == WP_LASER) || ((wip->subtype == WP_MISSILE) && !(wip->wi_flags & WIF_HOMING))) {
2557                 // set physics flag
2558                 objp->phys_info.flags |= PF_CONST_VEL;
2559         }
2560
2561         wp->weapon_info_index = weapon_id;
2562         wp->lifeleft = wip->lifetime;
2563
2564         wp->objnum = objnum;
2565         wp->homing_object = &obj_used_list;             //      Assume not homing on anything.
2566         wp->homing_subsys = NULL;
2567         wp->creation_time = Missiontime;
2568         wp->group_id = group_id;
2569
2570         // we don't necessarily need a parent
2571         if(parent_objp != NULL){
2572                 Assert(parent_objp->type == OBJ_SHIP);  //      Get Mike, a non-ship has fired a weapon!
2573                 Assert((parent_objp->instance >= 0) && (parent_objp->instance < MAX_SHIPS));
2574                 wp->team = Ships[parent_objp->instance].team;
2575                 wp->species = Ship_info[Ships[parent_objp->instance].ship_info_index].species;
2576         } else {
2577                 wp->team = 0;
2578                 wp->species = 0;
2579         }
2580         wp->turret_subsys = NULL;
2581         vm_vec_zero(&wp->homing_pos);
2582         wp->weapon_flags = 0;
2583         wp->target_sig = -1;
2584         wp->cmeasure_ignore_objnum = -1;
2585         wp->cmeasure_chase_objnum = -1;
2586
2587         // Init the thruster info
2588         wp->thruster_bitmap = -1;
2589         wp->thruster_frame = 0.0f;
2590         wp->thruster_glow_bitmap = -1;
2591         wp->thruster_glow_noise = 1.0f;
2592         wp->thruster_glow_frame = 0.0f;
2593
2594         if ( wip->wi_flags & WIF_SWARM ) {
2595                 wp->swarm_index = (short)swarm_create();
2596         } else {
2597                 wp->swarm_index = -1;
2598         }               
2599
2600         // if this is a particle spewing weapon, setup some stuff
2601         if(wip->wi_flags & WIF_PARTICLE_SPEW){
2602                 wp->particle_spew_time = -1;            
2603         }
2604
2605         // assign the network signature.  The starting sig is sent to all clients, so this call should
2606         // result in the same net signature numbers getting assigned to every player in the game
2607         if ( Game_mode & GM_MULTIPLAYER ) {
2608                 if(wip->subtype == WP_MISSILE){
2609                         Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
2610
2611                         // for weapons that respawn, add the number of respawnable weapons to the net signature pool
2612                         // to reserve N signatures for the spawned weapons
2613                         if ( wip->wi_flags & WIF_SPAWN ){
2614                                 multi_set_network_signature( (ushort)(Objects[objnum].net_signature + wip->spawn_count), MULTI_SIG_NON_PERMANENT );
2615                         }
2616                 } else {
2617                         Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
2618                 }
2619                 // for multiplayer clients, when creating lasers, add some more life to the lasers.  This helps
2620                 // to overcome some problems associated with lasers dying on client machine before they get message
2621                 // from server saying it hit something.
2622                 // removed 1/13/98 -- MWA if ( MULTIPLAYER_CLIENT && (wip->subtype == WP_LASER) )
2623                 //      removed 1/13/98 -- MWA  wp->lifeleft += 1.5f;
2624         }
2625
2626         //      Make remote detonate missiles look like they're getting detonated by firer simply by giving them variable lifetimes.
2627         if (!(Objects[parent_objnum].flags & OF_PLAYER_SHIP) && (wip->wi_flags & WIF_REMOTE)) {
2628                 float rand_val;
2629
2630                 if ( Game_mode & GM_NORMAL ){
2631                         rand_val = frand();
2632                 } else {
2633                         rand_val = static_randf(Objects[objnum].net_signature);
2634                 }
2635
2636                 wp->lifeleft = wp->lifeleft/2.0f + rand_val * wp->lifeleft/2.0f;
2637         }
2638
2639         objp->phys_info.mass = wip->mass;
2640         objp->phys_info.side_slip_time_const = 0.0f;
2641         objp->phys_info.rotdamp = 0.0f;
2642         vm_vec_zero(&objp->phys_info.max_vel);
2643         objp->phys_info.max_vel.z = wip->max_speed;
2644         vm_vec_zero(&objp->phys_info.max_rotvel);
2645         objp->shields[0] = wip->damage;
2646         if (wip->wi_flags & WIF_BOMB){
2647                 objp->hull_strength = 50.0f;
2648         } else {
2649                 objp->hull_strength = 0.0f;
2650         }
2651
2652         if ( wip->subtype == WP_MISSILE ){
2653                 objp->radius = model_get_radius(wip->model_num);
2654         } else if ( wip->subtype == WP_LASER ) {
2655                 objp->radius = wip->laser_head_radius;
2656         }
2657
2658         //      Set desired velocity and initial velocity.
2659         //      For lasers, velocity is always the same.
2660         //      For missiles, it is a small amount plus the firing ship's velocity.
2661         //      For missiles, the velocity trends towards some goal.
2662         //      Note: If you change how speed works here, such as adding in speed of parent ship, you'll need to change the AI code
2663         //      that predicts collision points.  See Mike Kulas or Dave Andsager.  (Or see ai_get_weapon_speed().)
2664         if (!(wip->wi_flags & WIF_HOMING)) {
2665                 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.fvec, objp->phys_info.max_vel.z );
2666                 objp->phys_info.vel = objp->phys_info.desired_vel;
2667                 objp->phys_info.speed = vm_vec_mag(&objp->phys_info.desired_vel);
2668         } else {                
2669                 //      For weapons that home, set velocity to sum of forward component of parent's velocity and 1/4 weapon's max speed.
2670                 //      Note that it is important to extract the forward component of the parent's velocity to factor out sliding, else
2671                 //      the missile will not be moving forward.
2672                 if(parent_objp != NULL){
2673                         vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.fvec, vm_vec_dot(&parent_objp->phys_info.vel, &parent_objp->orient.fvec) + objp->phys_info.max_vel.z/4 );
2674                 } else {
2675                         vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.fvec, objp->phys_info.max_vel.z/4 );
2676                 }
2677                 objp->phys_info.vel = objp->phys_info.desired_vel;
2678                 objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
2679         }
2680
2681         // create the corkscrew
2682         if ( wip->wi_flags & WIF_CORKSCREW ) {
2683                 wp->cscrew_index = (short)cscrew_create(objp);
2684         } else {
2685                 wp->cscrew_index = -1;
2686         }
2687
2688         // if this is a flak weapon shell, make it so
2689         // NOTE : this function will change some fundamental things about the weapon object
2690         if ( wip->wi_flags & WIF_FLAK ){
2691                 flak_create(wp);
2692         } else {
2693                 wp->flak_index = -1;
2694         }       
2695
2696         wp->missile_list_index = -1;
2697         // If this is a missile, then add it to the Missile_obj_list
2698         if ( wip->subtype == WP_MISSILE ) {
2699                 wp->missile_list_index = missile_obj_list_add(objnum);
2700         }
2701
2702         if (wip->wi_flags & WIF_TRAIL /*&& !(wip->wi_flags & WIF_CORKSCREW) */) {
2703                 wp->trail_num = trail_create(wip->tr_info);             
2704
2705                 if ( wp->trail_num > -1 )       {
2706                         // Add two segments.  One to stay at launch pos, one to move.
2707                         trail_add_segment( wp->trail_num, &objp->pos );
2708                         trail_add_segment( wp->trail_num, &objp->pos );
2709                 }
2710         }
2711
2712         // Ensure weapon flyby sound doesn't get played for player lasers
2713         if ( parent_objp == Player_obj ) {
2714                 wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
2715         }
2716
2717         wp->pick_big_attack_point_timestamp = timestamp(1);
2718
2719         //      Set detail levels for POF-type weapons.
2720         if (Weapon_info[wp->weapon_info_index].model_num != -1) {
2721                 polymodel * pm;
2722                 int     i;
2723                 pm = model_get(Weapon_info[wp->weapon_info_index].model_num);
2724
2725                 for (i=0; i<pm->n_detail_levels; i++){
2726                         pm->detail_depth[i] = (objp->radius*20.0f + 20.0f) * i;
2727                 }
2728         }
2729
2730                 // if the weapon was fired locked
2731         if(is_locked){
2732                 wp->weapon_flags |= WF_LOCKED_WHEN_FIRED;
2733         }
2734
2735         Num_weapons++;
2736         return objnum;
2737 }
2738
2739 //      Spawn child weapons from object *objp.
2740 void spawn_child_weapons(object *objp)
2741 {
2742         int     i;
2743         int     child_id;
2744         int     parent_num;
2745         ushort starting_sig;
2746         weapon  *wp;
2747         weapon_info     *wip;
2748
2749         Assert(objp->type == OBJ_WEAPON);
2750         Assert((objp->instance >= 0) && (objp->instance < MAX_WEAPONS));
2751
2752         wp = &Weapons[objp->instance];
2753         Assert((wp->weapon_info_index >= 0) && (wp->weapon_info_index < MAX_WEAPON_TYPES));
2754         wip = &Weapon_info[wp->weapon_info_index];
2755
2756         child_id = wip->spawn_type;
2757
2758         parent_num = objp->parent;
2759
2760         if ((Objects[parent_num].type != objp->parent_type) || (Objects[parent_num].signature != objp->parent_sig)) {
2761                 mprintf(("Warning: Parent of spawn weapon does not exist.  Not spawning.\n"));
2762                 return;
2763         }
2764
2765         starting_sig = 0;
2766         if ( Game_mode & GM_MULTIPLAYER ) {             
2767                 // get the next network signature and save it.  Set the next usable network signature to be
2768                 // the passed in objects signature + 1.  We "reserved" N of these slots when we created objp
2769                 // for it's spawned children.
2770                 starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
2771                 multi_set_network_signature( objp->net_signature, MULTI_SIG_NON_PERMANENT );
2772         }
2773
2774         for (i=0; i<wip->spawn_count; i++) {
2775                 int             weapon_objnum;
2776                 vector  tvec, pos;
2777                 matrix  orient;
2778
2779                 // for multiplayer, use the static randvec functions based on the network signatures to provide
2780                 // the randomness so that it is the same on all machines.
2781                 if ( Game_mode & GM_MULTIPLAYER ){
2782                         static_randvec(objp->net_signature + i, &tvec);
2783                 } else {
2784                         vm_vec_rand_vec_quick(&tvec);
2785                 }
2786                 vm_vec_scale_add(&pos, &objp->pos, &tvec, objp->radius);
2787
2788                 vm_vector_2_matrix(&orient, &tvec, NULL, NULL);
2789                 weapon_objnum = weapon_create(&pos, &orient, child_id, parent_num, 1, -1, wp->weapon_flags & WF_LOCKED_WHEN_FIRED);
2790
2791                 //      Assign a little randomness to lifeleft so they don't all disappear at the same time.
2792                 if (weapon_objnum != -1) {
2793                         float rand_val;
2794
2795                         if ( Game_mode & GM_NORMAL ){
2796                                 rand_val = frand();
2797                         } else {
2798                                 rand_val = static_randf(objp->net_signature + i);
2799                         }
2800
2801                         Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f;
2802                 }
2803
2804         }
2805
2806         // in multiplayer, reset the next network signature to the one that was saved.
2807         if ( Game_mode & GM_MULTIPLAYER ){
2808                 multi_set_network_signature( starting_sig, MULTI_SIG_NON_PERMANENT );
2809         }
2810 }
2811
2812 // -----------------------------------------------------------------------
2813 // weapon_hit_do_sound()
2814 //
2815 // Play a sound effect when a weapon hits a ship
2816 //
2817 // To elimate the "stereo" effect of two lasers hitting at nearly
2818 // the same time, and to reduce the number of sound channels used,
2819 // only play one impact sound if IMPACT_SOUND_DELTA has elapsed
2820 //
2821 // Note: Uses Weapon_impact_timer global for timer variable
2822 //
2823 void weapon_hit_do_sound(object *hit_obj, weapon_info *wip, vector *hitpos)
2824 {
2825         int     is_hull_hit;
2826         float shield_str;
2827
2828         // If non-missiles (namely lasers) expire without hitting a ship, don't play impact sound
2829         if      ( wip->subtype != WP_MISSILE ) {                
2830                 if ( !hit_obj ) {
2831                         // flak weapons make sounds             
2832                         if(wip->wi_flags & WIF_FLAK){
2833                                 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );                           
2834                         }
2835                         return;
2836                 }
2837
2838                 switch(hit_obj->type) {
2839                 case OBJ_SHIP:
2840                         // do nothing
2841                         break;
2842
2843                 case OBJ_ASTEROID:
2844                         if ( timestamp_elapsed(Weapon_impact_timer) ) {
2845                                 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
2846                                 Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
2847                         }
2848                         return;
2849                         break;
2850
2851                 default:
2852                         return;
2853                 }
2854         }
2855
2856         if ( hit_obj == NULL ) {
2857                 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
2858                 return;
2859         }
2860
2861         if ( timestamp_elapsed(Weapon_impact_timer) ) {
2862
2863                 is_hull_hit = 1;
2864                 if ( hit_obj->type == OBJ_SHIP ) {
2865                         shield_str = ship_quadrant_shield_strength(hit_obj, hitpos);
2866                 } else {
2867                         shield_str = 0.0f;
2868                 }
2869
2870                 // play a shield hit if shields are above 10% max in this quadrant
2871                 if ( shield_str > 0.1f ) {
2872                         is_hull_hit = 0;
2873                 }
2874
2875                 if ( !is_hull_hit ) {
2876                         // Play a shield impact sound effect
2877                         if ( hit_obj == Player_obj ) {
2878                                 snd_play_3d( &Snds[SND_SHIELD_HIT_YOU], hitpos, &Eye_position );
2879                                 // AL 12-15-97: Add missile impact sound even when shield is hit
2880                                 if ( wip->subtype == WP_MISSILE ) {
2881                                         snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
2882                                 }
2883                         } else {
2884                                 snd_play_3d( &Snds[SND_SHIELD_HIT], hitpos, &Eye_position );
2885                         }
2886                 } else {
2887                         // Play a hull impact sound effect
2888                         switch ( wip->subtype ) {
2889                                 case WP_LASER:
2890                                         if ( hit_obj == Player_obj )
2891                                                 snd_play_3d( &Snds[SND_PLAYER_HIT_LASER], hitpos, &Eye_position );
2892                                         else {
2893                                                 if ( wip->impact_snd != -1 ) {
2894                                                         snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
2895                                                 }
2896                                         }
2897                                         break;
2898                                 case WP_MISSILE:
2899                                         if ( hit_obj == Player_obj ) 
2900                                                 snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
2901                                         else {
2902                                                 if ( wip->impact_snd != -1 ) {
2903                                                         snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
2904                                                 }
2905                                         }
2906                                         break;
2907                                 default:        
2908                                         nprintf(("Warning","WARNING ==> Cannot determine sound to play for weapon impact\n"));
2909                                         break;
2910                         } // end switch
2911                 }
2912
2913                 Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
2914         }
2915 }
2916
2917 // distrupt any subsystems that fall into damage sphere of this Electronics missile
2918 //
2919 // input:       ship_obj                =>              pointer to ship that holds subsystem
2920 //                              blast_pos       =>              world pos of weapon blast
2921 //                              wi_index                =>              weapon info index of weapon causing blast
2922 void weapon_do_electronics_affect(object *ship_objp, vector *blast_pos, int wi_index)
2923 {
2924         weapon_info                     *wip;
2925         ship                                    *shipp;
2926         ship_subsys                     *ss;
2927         model_subsystem *psub;
2928         vector                          subsys_world_pos;
2929         float                                   dist;
2930
2931         shipp = &Ships[ship_objp->instance];
2932         wip = &Weapon_info[wi_index];
2933
2934         for ( ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss) ) {
2935                 psub = ss->system_info;
2936
2937                 // convert subsys point to world coords
2938                 vm_vec_unrotate(&subsys_world_pos, &psub->pnt, &ship_objp->orient);
2939                 vm_vec_add2(&subsys_world_pos, &ship_objp->pos);
2940
2941                 // see if subsys point is within damage sphere
2942                 dist = vm_vec_dist_quick(blast_pos, &subsys_world_pos); 
2943                 if ( dist < wip->outer_radius ) {
2944                         ship_subsys_set_disrupted(ss, fl2i(6000.0f + frand()*4000.0f));
2945                 }
2946         }
2947 }
2948
2949 //      ----------------------------------------------------------------------
2950 //      weapon_area_calc_damage()
2951 //
2952 // Calculate teh damage for an object based on the location of an area-effect
2953 // explosion.
2954 //
2955 // input:               objp                    =>              object pointer ship receiving blast effect
2956 //                                      pos                     =>              world pos of blast center
2957 //                                      inner_rad       =>              smallest radius at which full damage is done
2958 //                                      outer_rad       =>              radius at which no damage is done
2959 //                                      max_blast       =>              maximum blast possible from explosion
2960 //                                      max_damage      =>              maximum damage possible from explosion
2961 //                                      blast                   =>              OUTPUT PARAMETER: receives blast value from explosion
2962 //                                      damage          =>              OUTPUT PARAMETER: receives damage value from explosion
2963 //                                      limit                   =>              a limit on the area, needed for shockwave damage
2964 //
2965 //      returns:                no damage occurred      =>              -1
2966 //                                      damage occured                  =>              0
2967 //
2968 int weapon_area_calc_damage(object *objp, vector *pos, float inner_rad, float outer_rad, float max_blast, float max_damage, float *blast, float *damage, float limit)
2969 {
2970         float                   dist, max_dist, min_dist;
2971
2972         // only blast ships and asteroids
2973         if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID)) {
2974                 return -1;
2975         }
2976
2977         max_dist = objp->radius + outer_rad;
2978         dist = vm_vec_dist_quick(&objp->pos, pos);      
2979         if ( (dist > max_dist) || (dist > (limit+objp->radius)) ) {
2980                 return -1;      // spheres don't intersect at all
2981         }
2982
2983         if ( dist < (inner_rad+objp->radius) ) {
2984                 // damage is maximum within inner radius
2985                 *damage = max_damage;
2986                 *blast = max_blast;
2987         } else {
2988                 float dist_to_outer_rad_squared, total_dist_squared;
2989                 min_dist = dist - objp->radius;
2990                 Assert(min_dist < outer_rad);
2991                 dist_to_outer_rad_squared = (outer_rad-min_dist)*(outer_rad-min_dist);
2992                 total_dist_squared = (inner_rad-outer_rad)*(inner_rad-outer_rad);
2993                 // AL 2-24-98: drop off damage relative to square of distance
2994                 Assert(dist_to_outer_rad_squared <= total_dist_squared);
2995                 *damage = max_damage * dist_to_outer_rad_squared/total_dist_squared;
2996
2997
2998 //              *damage = (min_dist - outer_rad) * max_damage/(inner_rad - outer_rad);
2999                 *blast =  (min_dist - outer_rad) * max_blast /(inner_rad - outer_rad);
3000         }
3001
3002         // nprintf(("AI", "Frame %i: Damage = %7.3f, %7.3f meters away.\n", Framecount, *damage, dist));
3003
3004         return 0;
3005 }
3006
3007 //      ----------------------------------------------------------------------
3008 //      weapon_area_apply_blast()
3009 //
3010 // Apply the blast effects of an explosion to a ship
3011 //
3012 // input:       force_apply_pos =>              world pos of where force is applied to object
3013 //                              ship_obj                                =>              object pointer of ship receiving the blast
3014 //                              blast_pos                       =>              world pos of blast center
3015 //                              blast                                   =>              force of blast
3016 //                              make_shockwave          =>              boolean, whether to create a shockwave or not
3017 //
3018 void weapon_area_apply_blast(vector *force_apply_pos, object *ship_obj, vector *blast_pos, float blast, int make_shockwave)
3019 {
3020         #define SHAKE_CONST 3000
3021         vector          force, vec_blast_to_ship, vec_ship_to_impact;
3022         polymodel               *pm;
3023
3024         // apply blast force based on distance from center of explosion
3025         vm_vec_sub(&vec_blast_to_ship, &ship_obj->pos, blast_pos);
3026         vm_vec_normalize_safe(&vec_blast_to_ship);
3027         vm_vec_copy_scale(&force, &vec_blast_to_ship, blast );
3028
3029
3030         vm_vec_sub(&vec_ship_to_impact, blast_pos, &ship_obj->pos);
3031
3032         pm = model_get(Ships[ship_obj->instance].modelnum);
3033         Assert ( pm != NULL );
3034
3035         if (make_shockwave) {
3036                 physics_apply_shock (&force, blast, &ship_obj->phys_info, &ship_obj->orient, &pm->mins, &pm->maxs, pm->rad);
3037                 if (ship_obj == Player_obj) {
3038                         joy_ff_play_vector_effect(&vec_blast_to_ship, blast * 2.0f);
3039                 }
3040         } else {
3041                 ship_apply_whack( &force, &vec_ship_to_impact, ship_obj);
3042         }
3043 }
3044
3045 //      ----------------------------------------------------------------------
3046 //      weapon_do_area_effect()
3047 //
3048 // Do the area effect for a weapon
3049 //
3050 // input:       wobjp                   =>              object pointer to weapon causing explosion
3051 //                              pos                     =>              world pos of explosion center
3052 //                              other_obj       =>              object pointer to ship that weapon impacted on (can be NULL)
3053 //
3054 void weapon_do_area_effect(object *wobjp, vector *pos, object *other_obj)
3055 {
3056         weapon_info     *wip;
3057         weapon *wp;
3058         object          *objp;
3059         float                   damage, blast;
3060
3061         wip = &Weapon_info[Weapons[wobjp->instance].weapon_info_index]; 
3062         wp = &Weapons[wobjp->instance];
3063         Assert(wip->inner_radius != 0); 
3064
3065         // only blast ships and asteroids
3066         for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
3067                 if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) ) {
3068                         continue;
3069                 }
3070         
3071                 if ( objp->type == OBJ_SHIP ) {
3072                         // don't blast navbuoys
3073                         if ( ship_get_SIF(objp->instance) & SIF_NAVBUOY ) {
3074                                 continue;
3075                         }
3076                 }
3077
3078                 if ( weapon_area_calc_damage(objp, pos, wip->inner_radius, wip->outer_radius, wip->blast_force, wip->damage, &blast, &damage, wip->outer_radius) == -1 ){
3079                         continue;
3080                 }
3081
3082                 // scale damage
3083                 damage *= weapon_get_damage_scale(wip, wobjp, other_obj);               
3084
3085                 switch ( objp->type ) {
3086                 case OBJ_SHIP:
3087                         ship_apply_global_damage(objp, wobjp, pos, damage);
3088                         weapon_area_apply_blast(NULL, objp, pos, blast, 0);                     
3089                         break;
3090                 case OBJ_ASTEROID:
3091                         asteroid_hit(objp, NULL, NULL, damage);
3092                         break;
3093                 default:
3094                         Int3();
3095                         break;
3096                 }       
3097
3098         }       // end for
3099
3100         // if this weapon has the "Electronics" flag set, then disrupt subsystems in sphere
3101         if ( (other_obj != NULL) && (wip->wi_flags & WIF_ELECTRONICS) ) {
3102                 if ( other_obj->type == OBJ_SHIP ) {
3103                         weapon_do_electronics_affect(other_obj, pos, Weapons[wobjp->instance].weapon_info_index);
3104                 }
3105         }
3106 }
3107
3108
3109 //      ----------------------------------------------------------------------
3110 //      weapon_hit()
3111 //
3112 // This function is called when a weapon hits something (or, in the case of
3113 // missiles explodes for any particular reason)
3114 //
3115 void weapon_hit( object * weapon_obj, object * other_obj, vector * hitpos )
3116 {
3117         Assert(weapon_obj != NULL);
3118         if(weapon_obj == NULL){
3119                 return;
3120         }
3121         Assert((weapon_obj->type == OBJ_WEAPON) && (weapon_obj->instance >= 0) && (weapon_obj->instance < MAX_WEAPONS));
3122         if((weapon_obj->type != OBJ_WEAPON) || (weapon_obj->instance < 0) || (weapon_obj->instance >= MAX_WEAPONS)){
3123                 return;
3124         }
3125
3126         int                     num = weapon_obj->instance;
3127         int                     weapon_type = Weapons[num].weapon_info_index;
3128         object          *weapon_parent_objp;
3129         weapon_info     *wip;
3130         // int np_index;
3131
3132         Assert((weapon_type >= 0) && (weapon_type < MAX_WEAPONS));
3133         if((weapon_type < 0) || (weapon_type >= MAX_WEAPONS)){
3134                 return;
3135         }
3136         wip = &Weapon_info[weapon_type];
3137         weapon_parent_objp = &Objects[weapon_obj->parent];
3138
3139         // if this is the player ship, and is a laser hit, skip it. wait for player "pain" to take care of it
3140         // if( ((wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT) && (Player_obj != NULL) && (other_obj == Player_obj) ){
3141         if((other_obj != Player_obj) || (wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT){
3142                 weapon_hit_do_sound(other_obj, wip, hitpos);
3143         }
3144
3145         if ( wip->impact_weapon_expl_index > -1 )       {
3146                 int expl_ani_handle = weapon_get_expl_handle(wip->impact_weapon_expl_index, hitpos, wip->impact_explosion_radius);
3147                 particle_create( hitpos, &vmd_zero_vector, 0.0f, wip->impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, expl_ani_handle );
3148         }
3149
3150         weapon_obj->flags |= OF_SHOULD_BE_DEAD;
3151
3152         int sw_flag = SW_WEAPON;
3153
3154         // check if this is an area effect weapon
3155         if ( wip->wi_flags & WIF_AREA_EFFECT ) {
3156         // if ( wip->subtype & WP_MISSILE && wip->wi_flags & WIF_AREA_EFFECT ) {
3157                 if ( wip->wi_flags & WIF_SHOCKWAVE ) {
3158                         float actual_damage = wip->damage;
3159                         // Shockwaves caused by weapons hitting weapons are 1/4 as powerful
3160                         if ( ((other_obj) && (other_obj->type == OBJ_WEAPON)) || (Weapons[num].weapon_flags & WF_DESTROYED_BY_WEAPON)) {
3161                                 actual_damage /= 4.0f;
3162                                 sw_flag |= SW_WEAPON_KILL;
3163                         }
3164                         shockwave_create_info sci;
3165                         sci.blast = wip->blast_force;
3166                         sci.damage = actual_damage;
3167                         sci.inner_rad = wip->inner_radius;
3168                         sci.outer_rad = wip->outer_radius;
3169                         sci.speed = wip->shockwave_speed;
3170                         sci.rot_angle = 0.0f;
3171
3172                         shockwave_create(OBJ_INDEX(weapon_obj), hitpos, &sci, sw_flag);
3173 //                      snd_play_3d( &Snds[SND_SHOCKWAVE_IMPACT], hitpos, &Eye_position );
3174                 }
3175                 else {
3176                         weapon_do_area_effect(weapon_obj, hitpos, other_obj);
3177                 }
3178         }
3179
3180         // check if this is an EMP weapon
3181         if(wip->wi_flags & WIF_EMP){
3182                 emp_apply(&weapon_obj->pos, wip->inner_radius, wip->outer_radius, wip->emp_intensity, wip->emp_time);
3183         }       
3184
3185         // spawn weapons - note the change from FS 1 multiplayer.
3186         if (wip->wi_flags & WIF_SPAWN){
3187                 spawn_child_weapons(weapon_obj);
3188         }       
3189 }
3190
3191 void weapon_detonate(object *objp)
3192 {
3193         Assert(objp != NULL);
3194         if(objp == NULL){
3195                 return;
3196         }
3197         Assert((objp->type == OBJ_WEAPON) && (objp->instance >= 0));
3198         if((objp->type != OBJ_WEAPON) || (objp->instance < 0)){
3199                 return;
3200         }       
3201
3202         // send a detonate packet in multiplayer
3203         if(MULTIPLAYER_MASTER){
3204                 send_weapon_detonate_packet(objp);
3205         }
3206
3207         // call weapon hit
3208         weapon_hit(objp, NULL, &objp->pos);
3209 }
3210
3211 //      Return the Weapon_info[] index of the weapon with name *name.
3212 int weapon_name_lookup(char *name)
3213 {
3214         int     i;
3215
3216         for ( i=0; i < Num_weapon_types; i++) {
3217                 if (!stricmp(name, Weapon_info[i].name)) {
3218                         return i;
3219                 }
3220         }
3221
3222         return -1;
3223 }
3224
3225 // Group_id:  If you should quad lasers, they should all have the same group id.  
3226 // This will be used to optimize lighting, since each group only needs to cast one light.
3227 // Call this to get a new group id, then pass it to each weapon_create call for all the
3228 // weapons in the group.   Number will be between 0 and WEAPON_MAX_GROUP_IDS and will
3229 // get reused.
3230 int weapon_create_group_id()
3231 {
3232         static int current_id = 0;
3233
3234         int n = current_id;
3235         
3236         current_id++;
3237         if ( current_id >= WEAPON_MAX_GROUP_IDS )       {
3238                 current_id = 0;
3239         }
3240         return n;
3241 }
3242
3243
3244 void weapons_page_in()
3245 {
3246         int i, j, idx;
3247
3248         // Page in bitmaps for all weapons
3249         for (i=0; i<Num_weapon_types; i++ )     {
3250                 weapon_info *wip = &Weapon_info[i];
3251
3252                 wip->wi_flags &= (~WIF_THRUSTER);               // Assume no thrusters
3253                 
3254                 switch( wip->render_type )      {
3255                         case WRT_POF:
3256                                 {
3257                                         wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
3258
3259                                         polymodel *pm = model_get( wip->model_num );
3260
3261                                         // If it has a model, and the model pof has thrusters, then set
3262                                         // the flags
3263                                         if ( pm->n_thrusters > 0 )      {
3264                                                 //mprintf(( "Weapon %s has thrusters!\n", wip->name ));
3265                                                 wip->wi_flags |= WIF_THRUSTER;
3266                                         }
3267                 
3268                                         for (j=0; j<pm->n_textures; j++ )       {
3269                                                 int bitmap_num = pm->original_textures[j];
3270
3271                                                 if ( bitmap_num > -1 )  {
3272                                                         // if we're in Glide (and maybe later with D3D), use nondarkening textures
3273                                                         if(gr_screen.mode == GR_GLIDE){
3274                                                                 bm_page_in_nondarkening_texture( bitmap_num );
3275                                                         } else {
3276                                                                 bm_page_in_texture( bitmap_num );
3277                                                         }
3278                                                 }
3279                                         }
3280                                 }
3281                                 break;
3282
3283                         case WRT_LASER:
3284                                 {
3285                                         bm_page_in_texture( wip->laser_bitmap );
3286
3287                                         if(wip->laser_glow_bitmap >= 0){
3288                                                 bm_page_in_texture(wip->laser_glow_bitmap);
3289                                         }
3290                                 }
3291                                 break;
3292
3293                         default:
3294                                 Int3(); // Invalid weapon rendering type.
3295                 }
3296
3297                 // If this has an impact vclip page it in.
3298 //              if ( wip->impact_explosion_ani > -1 )   {
3299 //                      int nframes, fps;
3300 //                      bm_get_info( wip->impact_explosion_ani, NULL, NULL, NULL, &nframes, &fps );
3301 //                      bm_page_in_xparent_texture( wip->impact_explosion_ani, nframes );
3302 //              }
3303
3304                 // trail bitmaps
3305                 if ( (wip->wi_flags & WIF_TRAIL) && (wip->tr_info.bitmap > -1) )        {
3306                         bm_page_in_texture( wip->tr_info.bitmap );
3307                 }
3308
3309                 // if this is a beam weapon, page in its stuff
3310                 if(wip->wi_flags & WIF_BEAM){
3311                         // all beam sections
3312                         for(idx=0; idx<wip->b_info.beam_num_sections; idx++){
3313                                 if((idx < MAX_BEAM_SECTIONS) && (wip->b_info.sections[idx].texture >= 0)){
3314                                         bm_page_in_texture(wip->b_info.sections[idx].texture);
3315                                 }
3316                         }
3317
3318                         // muzzle glow
3319                         if(wip->b_info.beam_glow_bitmap >= 0){
3320                                 bm_page_in_texture(wip->b_info.beam_glow_bitmap);
3321                         }
3322
3323                         // particle ani
3324                         if(wip->b_info.beam_particle_ani >= 0){
3325                                 int nframes, fps;
3326                                 bm_get_info( wip->b_info.beam_particle_ani, NULL, NULL, NULL, &nframes, &fps );
3327                                 bm_page_in_texture( wip->b_info.beam_particle_ani, nframes );
3328                         }
3329                 }
3330         }
3331
3332         // explosion ani's
3333         for (i=0; i<Num_weapon_expl; i++) {
3334                 int bitmap_handle, nframes, fps;
3335
3336                 for (j=0; j<Weapon_expl_info[i].lod_count; j++) {
3337                         //load ani
3338                         bitmap_handle = bm_load_animation(Weapon_expl_info[i].lod[j].filename, &nframes, &fps, 1);
3339                         Weapon_expl_info[i].lod[j].bitmap_id = bitmap_handle;
3340                         Weapon_expl_info[i].lod[j].fps = fps;
3341                         Weapon_expl_info[i].lod[j].num_frames = nframes;
3342
3343                         // page it in
3344                         bm_page_in_xparent_texture(bitmap_handle, nframes);
3345                 }
3346         }
3347
3348         // Counter measures
3349         for (i=0; i<Num_cmeasure_types; i++ )   {
3350                 cmeasure_info *cmeasurep;
3351
3352                 cmeasurep = &Cmeasure_info[i];
3353         
3354                 cmeasurep->model_num = model_load( cmeasurep->pof_name, 0, NULL );
3355
3356                 polymodel *pm = model_get( cmeasurep->model_num );
3357
3358                 for (j=0; j<pm->n_textures; j++ )       {
3359                         int bitmap_num = pm->original_textures[j];
3360
3361                         if ( bitmap_num > -1 )  {
3362                                 bm_page_in_texture( bitmap_num );
3363                         }
3364                 }
3365                 Assert( cmeasurep->model_num > -1 );
3366         }
3367
3368 }
3369
3370 // call to get the "color" of the laser at the given moment (since glowing lasers can cycle colors)
3371 void weapon_get_laser_color(color *c, object *objp)
3372 {
3373         weapon *wep;
3374         weapon_info *winfo;
3375         float pct;
3376
3377         // sanity
3378         if(c == NULL){
3379                 return;
3380         }
3381
3382         // sanity
3383         Assert(objp->type == OBJ_WEAPON);
3384         Assert(objp->instance >= 0);
3385         Assert(Weapons[objp->instance].weapon_info_index >= 0);
3386         if((objp->type != OBJ_WEAPON) || (objp->instance < 0) || (Weapons[objp->instance].weapon_info_index < 0)){
3387                 return;
3388         }
3389         wep = &Weapons[objp->instance];
3390         winfo = &Weapon_info[wep->weapon_info_index];
3391
3392         // if we're a one-color laser
3393         if((winfo->laser_color_2.red == 0) && (winfo->laser_color_2.green == 0) && (winfo->laser_color_2.blue == 0)){
3394                 *c = winfo->laser_color_1;
3395         }
3396
3397         // lifetime pct
3398         pct = 1.0f - (wep->lifeleft / winfo->lifetime);
3399         if(pct > 0.5f){
3400                 pct = 0.5f;
3401         } else if (pct < 0.0f)
3402                 pct = 0.0f;
3403
3404         pct *= 2.0f;
3405         
3406         // otherwise interpolate between the colors
3407         gr_init_color( c, (int)((float)winfo->laser_color_1.red + (((float)winfo->laser_color_2.red - (float)winfo->laser_color_1.red) * pct)), 
3408                                                         (int)((float)winfo->laser_color_1.green + (((float)winfo->laser_color_2.green - (float)winfo->laser_color_1.green) * pct)), 
3409                                                         (int)((float)winfo->laser_color_1.blue + (((float)winfo->laser_color_2.blue - (float)winfo->laser_color_1.blue) * pct)) );
3410 }
3411
3412 // default weapon particle spew data
3413 int Weapon_particle_spew_count = 1;
3414 int Weapon_particle_spew_time = 25;
3415 float Weapon_particle_spew_vel = 0.4f;
3416 float Weapon_particle_spew_radius = 2.0f;
3417 float Weapon_particle_spew_lifetime = 0.15f;
3418 float Weapon_particle_spew_scale = 0.8f;
3419
3420 // for weapons flagged as particle spewers, spew particles. wheee
3421 void weapon_maybe_spew_particle(object *obj)
3422 {
3423         weapon *wp;
3424         int idx;
3425         vector direct, direct_temp, particle_pos;
3426         vector null_vec = ZERO_VECTOR;
3427         vector vel;
3428         float ang;
3429
3430         // check some stuff
3431         Assert(obj->type == OBJ_WEAPON);
3432         Assert(obj->instance >= 0);
3433         Assert(Weapons[obj->instance].weapon_info_index >= 0);
3434         Assert(Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags & WIF_PARTICLE_SPEW);
3435         
3436         wp = &Weapons[obj->instance];   
3437
3438         // if the weapon's particle timestamp has elapse`d
3439         if((wp->particle_spew_time == -1) || timestamp_elapsed(wp->particle_spew_time)){
3440                 // reset the timestamp
3441                 wp->particle_spew_time = timestamp(Weapon_particle_spew_time);
3442
3443                 // spew some particles
3444                 for(idx=0; idx<Weapon_particle_spew_count; idx++){
3445                         // get the backward vector of the weapon
3446                         direct = obj->orient.fvec;
3447                         vm_vec_negate(&direct);
3448
3449                         //      randomly perturb x, y and z
3450                         
3451                         // uvec
3452                         ang = fl_radian(frand_range(-90.0f, 90.0f));
3453                         vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.fvec);                     
3454                         direct = direct_temp;
3455                         vm_vec_scale(&direct, Weapon_particle_spew_scale);
3456
3457                         // rvec
3458                         ang = fl_radian(frand_range(-90.0f, 90.0f));
3459                         vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.rvec);                     
3460                         direct = direct_temp;
3461                         vm_vec_scale(&direct, Weapon_particle_spew_scale);
3462
3463                         // fvec
3464                         ang = fl_radian(frand_range(-90.0f, 90.0f));
3465                         vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.uvec);                     
3466                         direct = direct_temp;
3467                         vm_vec_scale(&direct, Weapon_particle_spew_scale);
3468
3469                         // get a velovity vector of some percentage of the weapon's velocity
3470                         vel = obj->phys_info.vel;
3471                         vm_vec_scale(&vel, Weapon_particle_spew_vel);
3472
3473                         // emit the particle
3474                         vm_vec_add(&particle_pos, &obj->pos, &direct);
3475                         particle_create(&particle_pos, &vel, Weapon_particle_spew_lifetime, Weapon_particle_spew_radius, PARTICLE_SMOKE);
3476                 }
3477         }
3478 }
3479
3480 // debug console functionality
3481 void pspew_display_dcf()
3482 {
3483         dc_printf("Particle spew settings\n\n");
3484         dc_printf("Particle spew count (pspew_count) : %d\n", Weapon_particle_spew_count);
3485         dc_printf("Particle spew time (pspew_time) : %d\n", Weapon_particle_spew_time);
3486         dc_printf("Particle spew velocity (pspew_vel) : %f\n", Weapon_particle_spew_vel);
3487         dc_printf("Particle spew size (pspew_size) : %f\n", Weapon_particle_spew_radius);
3488         dc_printf("Particle spew lifetime (pspew_life) : %f\n", Weapon_particle_spew_lifetime);
3489         dc_printf("Particle spew scale (psnew_scale) : %f\n", Weapon_particle_spew_scale);
3490 }
3491
3492 DCF(pspew_count, "Number of particles spewed at a time")
3493 {       
3494         dc_get_arg(ARG_INT);
3495         if(Dc_arg_type & ARG_INT){
3496                 Weapon_particle_spew_count = Dc_arg_int;
3497         }
3498
3499         pspew_display_dcf();
3500 }
3501
3502 DCF(pspew_time, "Time between particle spews")
3503 {       
3504         dc_get_arg(ARG_INT);
3505         if(Dc_arg_type & ARG_INT){
3506                 Weapon_particle_spew_time = Dc_arg_int;
3507         }
3508
3509         pspew_display_dcf();
3510 }
3511
3512 DCF(pspew_vel, "Relative velocity of particles (0.0 - 1.0)")
3513 {       
3514         dc_get_arg(ARG_FLOAT);
3515         if(Dc_arg_type & ARG_FLOAT){
3516                 Weapon_particle_spew_vel = Dc_arg_float;
3517         }
3518
3519         pspew_display_dcf();
3520 }
3521
3522 DCF(pspew_size, "Size of spewed particles")
3523 {       
3524         dc_get_arg(ARG_FLOAT);
3525         if(Dc_arg_type & ARG_FLOAT){
3526                 Weapon_particle_spew_radius = Dc_arg_float;
3527         }
3528
3529         pspew_display_dcf();
3530 }
3531
3532 DCF(pspew_life, "Lifetime of spewed particles")
3533 {       
3534         dc_get_arg(ARG_FLOAT);
3535         if(Dc_arg_type & ARG_FLOAT){
3536                 Weapon_particle_spew_lifetime = Dc_arg_float;
3537         }
3538
3539         pspew_display_dcf();
3540 }
3541
3542 DCF(pspew_scale, "How far away particles are from the weapon path")
3543 {       
3544         dc_get_arg(ARG_FLOAT);
3545         if(Dc_arg_type & ARG_FLOAT){
3546                 Weapon_particle_spew_scale = Dc_arg_float;
3547         }
3548
3549         pspew_display_dcf();
3550 }
3551
3552 // return a scale factor for damage which should be applied for 2 collisions
3553 float weapon_get_damage_scale(weapon_info *wip, object *wep, object *target)
3554 {
3555         weapon *wp;     
3556         int from_player = 0;
3557         float total_scale = 1.0f;
3558         float hull_pct;
3559         int is_big_damage_ship = 0;
3560
3561         // sanity
3562         if((wip == NULL) || (wep == NULL) || (target == NULL)){
3563                 return 1.0f;
3564         }
3565
3566         // don't scale any damage if its not a weapon   
3567         if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){
3568                 return 1.0f;
3569         }
3570         wp = &Weapons[wep->instance];
3571
3572         // was the weapon fired by the player
3573         from_player = 0;
3574         if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags & OF_PLAYER_SHIP)){
3575                 from_player = 1;
3576         }
3577                 
3578         // if this is a lockarm weapon, and it was fired unlocked
3579         if((wip->wi_flags & WIF_LOCKARM) && !(wp->weapon_flags & WF_LOCKED_WHEN_FIRED)){                
3580                 total_scale *= 0.1f;
3581         }
3582         
3583         // if the hit object was a ship
3584         if(target->type == OBJ_SHIP){
3585                 ship *shipp;
3586                 ship_info *sip;
3587
3588                 // get some info on the ship
3589                 Assert((target->instance >= 0) && (target->instance < MAX_SHIPS));
3590                 if((target->instance < 0) || (target->instance >= MAX_SHIPS)){
3591                         return total_scale;
3592                 }
3593                 shipp = &Ships[target->instance];
3594                 sip = &Ship_info[Ships[target->instance].ship_info_index];
3595
3596                 // get hull pct of the ship currently
3597                 hull_pct = target->hull_strength / sip->initial_hull_strength;
3598
3599                 // if it has hit a supercap ship and is not a supercap class weapon
3600                 if((sip->flags & SIF_SUPERCAP) && !(wip->wi_flags & WIF_SUPERCAP)){
3601                         // if the supercap is around 3/4 damage, apply nothing
3602                         if(hull_pct <= 0.75f){
3603                                 return 0.0f;
3604                         } else {
3605                                 total_scale *= SUPERCAP_DAMAGE_SCALE;
3606                         }
3607                 }
3608
3609                 // determine if this is a big damage ship
3610                 is_big_damage_ship = (sip->flags & SIF_BIG_DAMAGE);
3611
3612                 // if this is a large ship, and is being hit by flak
3613                 if(is_big_damage_ship && (wip->wi_flags & WIF_FLAK)){
3614                         total_scale *= FLAK_DAMAGE_SCALE;
3615                 }
3616                 
3617                 // if the player is firing small weapons at a big ship
3618                 if( from_player && is_big_damage_ship && !(wip->wi_flags & (WIF_HURTS_BIG_SHIPS)) ){
3619
3620                         // if its a laser weapon
3621                         if(wip->subtype == WP_LASER){
3622                                 total_scale *= 0.01f;
3623                         } else {
3624                                 total_scale *= 0.05f;
3625                         }
3626                 }
3627
3628                 // if the weapon is a small weapon being fired at a big ship
3629                 if( is_big_damage_ship && !(wip->wi_flags & (WIF_HURTS_BIG_SHIPS)) ){
3630                         if(hull_pct > 0.1f){
3631                                 total_scale *= hull_pct;
3632                         } else {
3633                                 return 0.0f;
3634                         }
3635                 }
3636         }
3637         
3638         return total_scale;
3639 }
3640
3641 int weapon_get_expl_handle(int weapon_expl_index, vector *pos, float size)
3642 {
3643         weapon_expl_info *wei = &Weapon_expl_info[weapon_expl_index];
3644
3645         if (wei->lod_count == 1) {
3646                 return wei->lod[0].bitmap_id;
3647         }
3648
3649         // now we have to do some work
3650         vertex v;
3651         int x, y, w, h, bm_size;
3652         int must_stop = 0;
3653         int best_lod = 1;
3654         int behind = 0;
3655
3656         // start the frame
3657         extern float Viewer_zoom;
3658         extern int G3_count;
3659
3660         if(!G3_count){
3661                 g3_start_frame(1);
3662                 must_stop = 1;
3663         }
3664         g3_set_view_matrix(&Eye_position, &Eye_matrix, Viewer_zoom);
3665
3666         // get extents of the rotated bitmap
3667         g3_rotate_vertex(&v, pos);
3668
3669         // if vertex is behind, find size if in front, then drop down 1 LOD
3670         if (v.codes & CC_BEHIND) {
3671                 float dist = vm_vec_dist_quick(&Eye_position, pos);
3672                 vector temp;
3673
3674                 behind = 1;
3675                 vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.fvec, dist);
3676                 g3_rotate_vertex(&v, &temp);
3677
3678                 // if still behind, bail and go with default
3679                 if (v.codes & CC_BEHIND) {
3680                         behind = 0;
3681                 }
3682         }
3683
3684         if (!g3_get_bitmap_dims(wei->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
3685                 if (Detail.hardware_textures == 4) {
3686                         // straight LOD
3687                         if(w <= bm_size/8){
3688                                 best_lod = 3;
3689                         } else if(w <= bm_size/2){
3690                                 best_lod = 2;
3691                         } else if(w <= 1.3f*bm_size){
3692                                 best_lod = 1;
3693                         } else {
3694                                 best_lod = 0;
3695                         }
3696                 } else {
3697                         // less aggressive LOD for lower detail settings
3698                         if(w <= bm_size/8){
3699                                 best_lod = 3;
3700                         } else if(w <= bm_size/3){
3701                                 best_lod = 2;
3702                         } else if(w <= (1.15f*bm_size)){
3703                                 best_lod = 1;
3704                         } else {
3705                                 best_lod = 0;
3706                         }               
3707                 }
3708         }
3709
3710         // if it's behind, bump up LOD by 1
3711         if (behind) {
3712                 best_lod++;
3713         }
3714
3715         // end the frame
3716         if(must_stop){
3717                 g3_end_frame();
3718         }
3719
3720         best_lod = min(best_lod, wei->lod_count - 1);
3721         return wei->lod[best_lod].bitmap_id;
3722 }