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