2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Weapon/Weapons.cpp $
15 * Code to handle the weapon systems
18 * Revision 1.13 2005/10/01 22:04:58 taylor
19 * fix FS1 (de)briefing voices, the directory names are different in FS1
20 * hard code the table values so that the fs1.vp file isn't needed
21 * hard code a mission fix for sm2-08a since a have no idea how to fix it otherwise
22 * generally cleanup some FS1 code
23 * fix volume sliders in the options screen that never went all the way up
25 * Revision 1.12 2005/08/12 08:48:11 taylor
26 * don't scale weapon damage for FS1, this way you can still kill a capship with lasers rather than bombs
28 * Revision 1.11 2004/09/20 01:31:45 theoddone33
31 * Revision 1.10 2003/06/11 18:30:33 taylor
34 * Revision 1.9 2003/05/25 02:30:44 taylor
37 * Revision 1.8 2002/06/21 03:04:12 relnev
40 * Revision 1.7 2002/06/17 06:33:11 relnev
41 * ryan's struct patch for gcc 2.95
43 * Revision 1.6 2002/06/09 04:41:30 relnev
44 * added copyright header
46 * Revision 1.5 2002/06/05 08:05:29 relnev
47 * stub/warning removal.
49 * reworked the sound code.
51 * Revision 1.4 2002/06/01 03:32:00 relnev
52 * fix texture loading mistake.
54 * enable some d3d stuff for opengl also
56 * Revision 1.3 2002/05/28 08:52:03 relnev
57 * implemented two assembly stubs.
59 * cleaned up a few warnings.
61 * added a little demo hackery to make it progress a little farther.
63 * Revision 1.2 2002/05/07 03:16:53 theoddone33
64 * The Great Newline Fix
66 * Revision 1.1.1.1 2002/05/03 03:28:11 root
70 * 69 9/14/99 3:26a Dave
71 * Fixed laser fogging problem in nebula (D3D)> Fixed multiplayer
72 * respawn-too-early problem. Made a few crash points safe.
74 * 68 9/14/99 1:32a Andsager
75 * Better LOD for weapon explosions when behind. Move point ahead to get
76 * vertex and then find size.
78 * 67 9/07/99 1:10p Mikek
79 * Fix code I busted due to adding lifeleft to missiles.
81 * 66 9/07/99 12:20a Andsager
82 * LOD less agressive at lower hardware detail level
84 * 65 9/06/99 7:21p Dave
85 * Commented out bad lifeleft scaling code in weapon_set_tracking_info()
87 * 64 9/06/99 3:23p Andsager
88 * Make fireball and weapon expl ani LOD choice look at resolution of the
91 * 63 9/06/99 12:46a Andsager
92 * Add weapon_explosion_ani LOD
94 * 62 9/05/99 11:24p Mikek
95 * Fixed problems caused by earlier checkin (that was rolled back).
96 * Problem was wp->target_pos was not set for swarmers.
98 * More tweaking of missile behavior. Also add 20% to lifetime of a
101 * [Rolled back -- MK] 63 9/05/99 2:23p Mikek
102 * Make aspect seekers a little less likely to miss their target.
103 * Mysterious why they do it so often. Maybe fix for FS3...
105 * [Rolled back -- MK] 62 9/04/99 12:09p Mikek
106 * Limit number of spawned weapons that can home on player based on skill
107 * level. Works same as for non-spawned weapons. Only do in single
110 * 61 8/27/99 1:34a Andsager
111 * Modify damage by shockwaves for BIG|HUGE ships. Modify shockwave damge
112 * when weapon blows up.
114 * 60 8/24/99 10:47a Jefff
115 * tech room weapon anims. added tech anim field to weapons.tbl
117 * 59 8/16/99 11:58p Andsager
118 * Disable collision on proximity for ships with SIF_DONT_COLLIDE_INVIS
121 * 58 8/10/99 5:30p Jefff
122 * Added tech_title string to weapon_info. Changed parser accordingly.
124 * 57 8/05/99 2:06a Dave
127 * 56 8/02/99 5:16p Dave
128 * Bumped up weapon title string length from 32 to 48
130 * 55 7/29/99 5:41p Jefff
131 * Sound hooks for cmeasure success
133 * 54 7/24/99 1:54p Dave
134 * Hud text flash gauge. Reworked dead popup to use 4 buttons in red-alert
137 * 53 7/19/99 7:20p Dave
138 * Beam tooling. Specialized player-killed-self messages. Fixed d3d nebula
141 * 52 7/18/99 12:32p Dave
142 * Randomly oriented shockwaves.
144 * 51 7/16/99 1:50p Dave
145 * 8 bit aabitmaps. yay.
147 * 50 7/15/99 9:20a Andsager
148 * FS2_DEMO initial checkin
150 * 49 7/08/99 10:53a Dave
151 * New multiplayer interpolation scheme. Not 100% done yet, but still
152 * better than the old way.
154 * 48 7/02/99 4:31p Dave
155 * Much more sophisticated lightning support.
157 * 47 7/01/99 5:57p Johnson
158 * Oops. Fixed big ship damage.
160 * 46 7/01/99 4:23p Dave
161 * Full support for multiple linked ambient engine sounds. Added "big
164 * 45 6/30/99 5:53p Dave
165 * Put in new anti-camper code.
167 * 44 6/22/99 3:24p Danw
168 * Fixed incorrect weapon hit sound culling.
170 * 43 6/21/99 7:25p Dave
171 * netplayer pain packet. Added type E unmoving beams.
173 * 42 6/14/99 10:45a Dave
174 * Made beam weapons specify accuracy by skill level in the weapons.tbl
176 * 41 6/11/99 11:13a Dave
177 * last minute changes before press tour build.
179 * 40 6/04/99 2:16p Dave
180 * Put in shrink effect for beam weapons.
182 * 39 6/01/99 8:35p Dave
183 * Finished lockarm weapons. Added proper supercap weapons/damage. Added
184 * awacs-set-radius sexpression.
186 * 38 6/01/99 3:52p Dave
187 * View footage screen. Fixed xstrings to not display the & symbol. Popup,
188 * dead popup, pxo find player popup, pxo private room popup.
190 * 37 5/27/99 6:17p Dave
191 * Added in laser glows.
193 * 36 5/20/99 7:00p Dave
194 * Added alternate type names for ships. Changed swarm missile table
197 * 35 5/08/99 8:25p Dave
198 * Upped object pairs. First run of nebula lightning.
200 * 34 4/28/99 11:13p Dave
201 * Temporary checkin of artillery code.
203 * 33 4/28/99 3:11p Andsager
204 * Stagger turret weapon fire times. Make turrets smarter when target is
205 * protected or beam protected. Add weaopn range to weapon info struct.
207 * 32 4/22/99 11:06p Dave
208 * Final pass at beam weapons. Solidified a lot of stuff. All that remains
209 * now is to tweak and fix bugs as they come up. No new beam weapon
212 * 31 4/19/99 11:01p Dave
213 * More sophisticated targeting laser support. Temporary checkin.
215 * 30 4/16/99 5:54p Dave
216 * Support for on/off style "stream" weapons. Real early support for
217 * target-painting lasers.
219 * 29 4/07/99 6:22p Dave
220 * Fred and Freespace support for multiple background bitmaps and suns.
221 * Fixed link errors on all subprojects. Moved encrypt_init() to
222 * cfile_init() and lcl_init(), since its safe to call twice.
224 * 28 4/02/99 1:35p Dave
225 * Removed weapon hit packet. No good for causing pain.
227 * 27 4/02/99 9:55a Dave
228 * Added a few more options in the weapons.tbl for beam weapons. Attempt
229 * at putting "pain" packets into multiplayer.
231 * 26 3/31/99 9:26p Dave
232 * Don't load beam textures when in Fred.
234 * 25 3/31/99 8:24p Dave
235 * Beefed up all kinds of stuff, incluging beam weapons, nebula effects
236 * and background nebulae. Added per-ship non-dimming pixel colors.
238 * 24 3/23/99 2:29p Andsager
239 * Fix shockwaves for kamikazi and Fred defined. Collect together
240 * shockwave_create_info struct.
242 * 23 3/23/99 11:03a Dave
243 * Added a few new fields and fixed parsing code for new weapon stuff.
245 * 22 3/19/99 9:52a Dave
246 * Checkin to repair massive source safe crash. Also added support for
247 * pof-style nebulae, and some new weapons code.
249 * 24 3/15/99 6:45p Daveb
250 * Put in rough nebula bitmap support.
252 * 23 3/12/99 3:19p Enricco
253 * Remove spurious Int3()
255 * 22 3/11/99 5:53p Dave
256 * More network optimization. Spliced in Dell OEM planet bitmap crap.
258 * 21 3/10/99 6:51p Dave
259 * Changed the way we buffer packets for all clients. Optimized turret
260 * fired packets. Did some weapon firing optimizations.
262 * 20 2/24/99 4:02p Dave
263 * Fixed weapon locking and homing problems for multiplayer dogfight mode.
265 * 19 2/05/99 12:52p Dave
266 * Fixed Glide nondarkening textures.
268 * 18 1/29/99 12:47a Dave
269 * Put in sounds for beam weapon. A bunch of interface screens (tech
272 * 17 1/27/99 9:56a Dave
273 * Temporary checkin of beam weapons for Dan to make cool sounds.
275 * 16 1/25/99 5:03a Dave
276 * First run of stealth, AWACS and TAG missile support. New mission type
279 * 15 1/24/99 11:37p Dave
280 * First full rev of beam weapons. Very customizable. Removed some bogus
281 * Int3()'s in low level net code.
283 * 14 1/21/99 10:45a Dave
284 * More beam weapon stuff. Put in warmdown time.
286 * 13 1/12/99 5:45p Dave
287 * Moved weapon pipeline in multiplayer to almost exclusively client side.
288 * Very good results. Bandwidth goes down, playability goes up for crappy
289 * connections. Fixed object update problem for ship subsystems.
291 * 12 1/08/99 2:08p Dave
292 * Fixed software rendering for pofview. Super early support for AWACS and
295 * 11 1/06/99 2:24p Dave
296 * Stubs and release build fixes.
298 * 10 12/01/98 6:12p Johnson
299 * Make sure to page in weapon impact animations as xparent textures.
301 * 9 11/20/98 4:08p Dave
302 * Fixed flak effect in multiplayer.
304 * 8 11/14/98 5:33p Dave
305 * Lots of nebula work. Put in ship contrails.
307 * 7 11/05/98 4:18p Dave
308 * First run nebula support. Beefed up localization a bit. Removed all
309 * conditional compiles for foreign versions. Modified mission file
312 * 6 10/26/98 9:42a Dave
313 * Early flak gun support.
315 * 5 10/23/98 3:51p Dave
316 * Full support for tstrings.tbl and foreign languages. All that remains
317 * is to make it active in Fred.
319 * 4 10/07/98 6:27p Dave
320 * Globalized mission and campaign file extensions. Removed Silent Threat
321 * special code. Moved \cache \players and \multidata into the \data
324 * 3 10/07/98 4:49p Andsager
325 * don't do weapon swap (was needed for mission disk)
327 * 2 10/07/98 10:54a Dave
330 * 1 10/07/98 10:51a Dave
332 * 314 9/21/98 11:19p Dave
335 * 313 9/19/98 4:33p Adam
336 * Changed default values for particle spew (used on Leech Cannon)
338 * 312 9/13/98 10:51p Dave
339 * Put in newfangled icons for mission simulator room. New mdisk.vp
340 * checksum and file length.
342 * 311 9/13/98 4:29p Andsager
343 * Maintain Weapon_info compataiblity with mission disk
345 * 310 9/13/98 4:26p Andsager
347 * 309 9/01/98 4:25p Dave
348 * Put in total (I think) backwards compatibility between mission disk
349 * freespace and non mission disk freespace, including pilot files and
350 * campaign savefiles.
352 * 308 8/28/98 3:29p Dave
353 * EMP effect done. AI effects may need some tweaking as required.
355 * 307 8/25/98 1:49p Dave
356 * First rev of EMP effect. Player side stuff basically done. Next comes
359 * 306 8/18/98 10:15a Dave
360 * Touchups on the corkscrew missiles. Added particle spewing weapons.
362 * 305 8/17/98 5:07p Dave
363 * First rev of corkscrewing missiles.
365 * 304 6/30/98 2:23p Dave
366 * Revised object update system. Removed updates for all weapons. Put
367 * button info back into control info packet.
369 * 303 6/22/98 8:36a Allender
370 * revamping of homing weapon system. don't send as object updates
373 * 302 5/24/98 2:25p Allender
374 * be sure that homing missiles die on client when lifeleft gets too
375 * negative (lost packets)
377 * 301 5/20/98 5:47p Sandeep
379 * 300 5/18/98 1:58a Mike
380 * Make Phoenix not be fired at fighters (but yes bombers).
381 * Improve positioning of ships in guard mode.
382 * Make turrets on player ship not fire near end of support ship docking.
390 #include "systemvars.h"
399 #include "floating.h"
401 #include "lighting.h"
405 #include "fireballs.h"
407 #include "hudtarget.h"
408 #include "freespace.h"
413 #include "multimsgs.h"
414 #include "linklist.h"
417 #include "cmeasure.h"
418 #include "shockwave.h"
420 #include "staticrand.h"
422 #include "multiutil.h"
426 #include "objcollide.h"
428 #include "particle.h"
429 #include "asteroid.h"
431 #include "multi_obj.h"
432 #include "corkscrew.h"
434 #include "localize.h"
436 #include "muzzleflash.h"
439 int Weapon_flyby_sound_enabled = 1;
440 DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
443 static int Weapon_flyby_sound_timer;
445 weapon Weapons[MAX_WEAPONS];
446 weapon_info Weapon_info[MAX_WEAPON_TYPES];
448 #define MISSILE_OBJ_USED (1<<0) // flag used in missile_obj struct
449 #define MAX_MISSILE_OBJS MAX_WEAPONS // max number of missiles tracked in missile list
450 missile_obj Missile_objs[MAX_MISSILE_OBJS]; // array used to store missile object indexes
451 missile_obj Missile_obj_list; // head of linked list of missile_obj structs
454 // WEAPON EXPLOSION INFO
455 #define MAX_weapon_expl_lod 4
456 #define MAX_Weapon_expl_info 3
458 typedef struct weapon_expl_lod {
459 char filename[MAX_FILENAME_LEN];
465 typedef struct weapon_expl_info {
467 weapon_expl_lod lod[MAX_weapon_expl_lod];
470 weapon_expl_info Weapon_expl_info[MAX_Weapon_expl_info];
472 int Num_weapon_expl = 0;
474 int Num_weapon_types = 0;
477 int Weapons_inited = 0;
479 int laser_model_inner = -1;
480 int laser_model_outer = -1;
482 int missile_model = -1;
484 char *Weapon_names[MAX_WEAPON_TYPES];
486 int First_secondary_index = -1;
488 #define MAX_SPAWN_WEAPONS 10 // Up to 10 weapons can spawn weapons.
491 char Spawn_names[MAX_SPAWN_WEAPONS][NAME_LENGTH];
493 int Num_player_weapon_precedence; // Number of weapon types in Player_weapon_precedence
494 int Player_weapon_precedence[MAX_WEAPON_TYPES]; // Array of weapon types, precedence list for player weapon selection
496 // Used to avoid playing too many impact sounds in too short a time interval.
497 // This will elimate the odd "stereo" effect that occurs when two weapons impact at
498 // nearly the same time, like from a double laser (also saves sound channels!)
499 #define IMPACT_SOUND_DELTA 50 // in milliseconds
500 int Weapon_impact_timer; // timer, initalized at start of each mission
502 // energy suck defines
503 #define ESUCK_DEFAULT_WEAPON_REDUCE (10.0f)
504 #define ESUCK_DEFAULT_AFTERBURNER_REDUCE (10.0f)
506 // scale factor for supercaps taking damage from weapons which are not "supercap" weapons
507 #define SUPERCAP_DAMAGE_SCALE 0.25f
509 // scale factor for big ships getting hit by flak
510 #define FLAK_DAMAGE_SCALE 0.05f
512 extern int Max_allowed_player_homers[];
513 extern int compute_num_homing_objects(object *target_objp);
516 void parse_weapon_expl_tbl()
520 char base_filename[256] = "";
526 read_file_text(NOX("weapon_expl.tbl"));
530 required_string("#Start");
531 while (required_string_either("#End","$Name:")) {
532 SDL_assert( Num_weapon_expl < MAX_Weapon_expl_info);
535 required_string("$Name:");
536 stuff_string(base_filename, F_NAME, NULL);
538 // # of lod levels - make sure old fireball.tbl is compatible
539 Weapon_expl_info[Num_weapon_expl].lod_count = 1;
540 if(optional_string("$LOD:")){
541 stuff_int(&Weapon_expl_info[Num_weapon_expl].lod_count);
544 // stuff default filename
545 SDL_strlcpy(Weapon_expl_info[Num_weapon_expl].lod[0].filename, base_filename, MAX_FILENAME_LEN);
547 // stuff LOD level filenames
548 for(idx=1; idx<Weapon_expl_info[Num_weapon_expl].lod_count; idx++){
549 if(idx >= MAX_weapon_expl_lod){
553 SDL_snprintf(Weapon_expl_info[Num_weapon_expl].lod[idx].filename, MAX_FILENAME_LEN, "%s_%d", base_filename, idx);
558 required_string("#End");
559 } catch (parse_error_t rval) {
560 Error(LOCATION, "Unable to parse weapon_expl.tbl! Code = %i.\n", (int)rval);
563 // close localization
566 // hard coded value for FS1
569 Weapon_expl_info[Num_weapon_expl].lod_count = 1;
570 SDL_strlcpy(Weapon_expl_info[Num_weapon_expl].lod[0].filename, "ExpMissileHit1", MAX_FILENAME_LEN);
576 int get_weapon_expl_info_index(char *filename)
578 for (int i=0; i<MAX_Weapon_expl_info; i++) {
579 if ( SDL_strcasecmp(Weapon_expl_info[i].lod[0].filename, filename) == 0) {
586 // ----------------------------------------------------------------------
587 // missile_obj_list_init()
589 // Clear out the Missile_obj_list
591 void missile_obj_list_init()
595 list_init(&Missile_obj_list);
596 for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
597 Missile_objs[i].flags = 0;
601 // ---------------------------------------------------
602 // missile_obj_list_add()
604 // Function to add a node from the Missile_obj_list. Only
605 // called from weapon_create()
606 int missile_obj_list_add(int objnum)
610 for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
611 if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
614 if ( i == MAX_MISSILE_OBJS ) {
615 Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
619 Missile_objs[i].flags = 0;
620 Missile_objs[i].objnum = objnum;
621 list_append(&Missile_obj_list, &Missile_objs[i]);
622 Missile_objs[i].flags |= MISSILE_OBJ_USED;
627 // ---------------------------------------------------
628 // missle_obj_list_remove()
630 // Function to remove a node from the Missile_obj_list. Only
631 // called from weapon_delete()
632 void missle_obj_list_remove(int index)
634 SDL_assert(index >= 0 && index < MAX_MISSILE_OBJS);
635 list_remove(&Missile_obj_list, &Missile_objs[index]);
636 Missile_objs[index].flags = 0;
639 // ---------------------------------------------------
640 // missile_obj_list_rebuild()
642 // Called by the save/restore code to rebuild Missile_obj_list
644 void missile_obj_list_rebuild()
648 missile_obj_list_init();
650 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
651 if ( objp->type == OBJ_WEAPON && Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE ) {
652 Weapons[objp->instance].missile_list_index = missile_obj_list_add(OBJ_INDEX(objp));
657 // If this is a player countermeasure, let the player know he evaded a missile
658 void weapon_maybe_alert_cmeasure_success(object *objp)
660 if ( objp->type == OBJ_CMEASURE ) {
662 cmp = &Cmeasures[objp->instance];
663 if ( cmp->source_objnum == OBJ_INDEX(Player_obj) ) {
664 hud_start_text_flash(XSTR("Evaded", 1430), 800);
665 snd_play(&Snds[SND_MISSILE_EVADED_POPUP]);
666 } else if ( Objects[cmp->source_objnum].flags & OF_PLAYER_SHIP ) {
667 send_countermeasure_success_packet( cmp->source_objnum );
672 // ---------------------------------------------------
673 // missile_obj_return_address()
675 // Called externally to generate an address from an index into
676 // the Missile_objs[] array
678 missile_obj *missile_obj_return_address(int index)
680 SDL_assert(index >= 0 && index < MAX_MISSILE_OBJS);
681 return &Missile_objs[index];
684 // Return the index of Weapon_info[].name that is *name.
685 int weapon_info_lookup(const char *name)
690 // fix the stupid table stuff - it's non-fatal but annoying error messages
691 if (!strcmp(name, "Disruptor Missile")) {
693 // this one fixes the same issue with a SilentThreat mission
694 } else if (!strcmp(name, "Shield Breaker")) {
699 for (i=0; i<Num_weapon_types; i++)
700 if (!SDL_strcasecmp(name, Weapon_info[i].name))
706 #define DEFAULT_WEAPON_SPAWN_COUNT 10
708 // Parse the weapon flags.
709 void parse_wi_flags(weapon_info *weaponp)
711 char weapon_strings[MAX_WEAPON_FLAGS][NAME_LENGTH];
714 required_string("$Flags:");
716 num_strings = stuff_string_list(weapon_strings, MAX_WEAPON_FLAGS);
718 for (int i=0; i<num_strings; i++) {
719 if (!SDL_strcasecmp(NOX("Electronics"), weapon_strings[i]))
720 weaponp->wi_flags |= WIF_ELECTRONICS;
721 else if (!SDL_strncasecmp(NOX("Spawn"), weapon_strings[i], 5)) {
722 if (weaponp->spawn_type == -1) {
723 int skip_length, name_length;
726 temp_string = weapon_strings[i];
728 weaponp->wi_flags |= WIF_SPAWN;
729 weaponp->spawn_type = (short)Num_spawn_types;
730 skip_length = strlen(NOX("Spawn")) + strspn(&temp_string[strlen(NOX("Spawn"))], NOX(" \t"));
731 char *num_start = SDL_strchr(&temp_string[skip_length], ',');
732 if (num_start == NULL) {
733 weaponp->spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
734 name_length = NAME_LENGTH;
736 weaponp->spawn_count = (short)atoi(num_start+1);
737 name_length = SDL_min(num_start - temp_string - skip_length + 1, NAME_LENGTH);
740 SDL_strlcpy(Spawn_names[Num_spawn_types++], &(weapon_strings[i][skip_length]), name_length);
741 SDL_assert(Num_spawn_types < MAX_SPAWN_WEAPONS);
743 Warning(LOCATION, "Illegal to have two spawn types for one weapon.\n"
744 "Ignoring weapon %s", weapon_strings[i]);
745 } else if (!SDL_strcasecmp(NOX("Remote Detonate"), weapon_strings[i]))
746 weaponp->wi_flags |= WIF_REMOTE;
747 else if (!SDL_strcasecmp(NOX("Puncture"), weapon_strings[i]))
748 weaponp->wi_flags |= WIF_PUNCTURE;
749 else if (!SDL_strcasecmp(NOX("Big Ship"), weapon_strings[i]))
750 weaponp->wi_flags |= WIF_BIG_ONLY;
751 else if (!SDL_strcasecmp(NOX("Huge"), weapon_strings[i]))
752 weaponp->wi_flags |= WIF_HUGE;
753 else if (!SDL_strcasecmp(NOX("Bomber+"), weapon_strings[i]))
754 weaponp->wi_flags |= WIF_BOMBER_PLUS;
755 else if (!SDL_strcasecmp(NOX("child"), weapon_strings[i]))
756 weaponp->wi_flags |= WIF_CHILD;
757 else if (!SDL_strcasecmp(NOX("Bomb"), weapon_strings[i]))
758 weaponp->wi_flags |= WIF_BOMB;
759 else if (!SDL_strcasecmp(NOX("No Dumbfire"), weapon_strings[i]))
760 weaponp->wi_flags |= WIF_NO_DUMBFIRE;
761 else if (!SDL_strcasecmp(NOX("In tech database"), weapon_strings[i]))
762 weaponp->wi_flags |= WIF_IN_TECH_DATABASE;
763 else if (!SDL_strcasecmp(NOX("Player allowed"), weapon_strings[i]))
764 weaponp->wi_flags |= WIF_PLAYER_ALLOWED;
765 else if (!SDL_strcasecmp(NOX("Particle Spew"), weapon_strings[i]))
766 weaponp->wi_flags |= WIF_PARTICLE_SPEW;
767 else if (!SDL_strcasecmp(NOX("EMP"), weapon_strings[i]))
768 weaponp->wi_flags |= WIF_EMP;
769 else if (!SDL_strcasecmp(NOX("Esuck"), weapon_strings[i]))
770 weaponp->wi_flags |= WIF_ENERGY_SUCK;
771 else if (!SDL_strcasecmp(NOX("Flak"), weapon_strings[i]))
772 weaponp->wi_flags |= WIF_FLAK;
773 else if (!SDL_strcasecmp(NOX("Corkscrew"), weapon_strings[i]))
774 weaponp->wi_flags |= WIF_CORKSCREW;
775 else if (!SDL_strcasecmp(NOX("Shudder"), weapon_strings[i]))
776 weaponp->wi_flags |= WIF_SHUDDER;
777 else if (!SDL_strcasecmp(NOX("lockarm"), weapon_strings[i]))
778 weaponp->wi_flags |= WIF_LOCKARM;
779 else if (!SDL_strcasecmp(NOX("beam"), weapon_strings[i]))
780 weaponp->wi_flags |= WIF_BEAM;
781 else if (!SDL_strcasecmp(NOX("stream"), weapon_strings[i]))
782 weaponp->wi_flags |= WIF_STREAM;
783 else if (!SDL_strcasecmp(NOX("supercap"), weapon_strings[i]))
784 weaponp->wi_flags |= WIF_SUPERCAP;
786 else if (!SDL_strcasecmp(NOX("Swarm"), weapon_strings[i])) {
787 weaponp->wi_flags |= WIF_SWARM;
788 weaponp->swarm_count = SWARM_DEFAULT_NUM_MISSILES_FIRED;
789 } else if (!SDL_strcasecmp(NOX("No Ship"), weapon_strings[i]))
790 weaponp->wi_flags |= WIF_CHILD;
793 Warning(LOCATION, "Bogus string in weapon flags: %s\n", weapon_strings[i]);
796 // SWARM, CORKSCREW and FLAK should be mutually exclusive
797 if(weaponp->wi_flags & WIF_FLAK){
798 SDL_assert(!(weaponp->wi_flags & WIF_CORKSCREW) && !(weaponp->wi_flags & WIF_SWARM));
800 if(weaponp->wi_flags & WIF_CORKSCREW){
801 SDL_assert(!(weaponp->wi_flags & WIF_FLAK) && !(weaponp->wi_flags & WIF_SWARM));
803 if(weaponp->wi_flags & WIF_SWARM){
804 SDL_assert(!(weaponp->wi_flags & WIF_CORKSCREW) && !(weaponp->wi_flags & WIF_FLAK));
807 // make sure flak guns are only placed on turrets
808 if(weaponp->wi_flags & WIF_FLAK){
809 SDL_assert(weaponp->wi_flags & WIF_BIG_ONLY);
813 // function to parse the information for a specific weapon type.
814 // return 0 if successful, otherwise return -1
815 #define WEAPONS_MULTITEXT_LENGTH 2048
819 char buf[WEAPONS_MULTITEXT_LENGTH];
821 char fname[255] = "";
824 wip = &Weapon_info[Num_weapon_types];
828 required_string("$Name:");
829 stuff_string(wip->name, F_NAME, NULL);
830 diag_printf ("Weapon name -- %s\n", wip->name);
832 // AL 28-3-98: If this is a demo build, we only want to parse weapons that are preceded with
834 #ifdef DEMO // not needed FS2_DEMO (separate table file)
835 if ( wip->name[0] != '@' ) {
836 // advance to next weapon, and return -1
838 if ( skip_to_start_of_strings("$Name:", "#End") != 1 ) {
845 if ( wip->name[0] == '@' ) {
846 char old_name[NAME_LENGTH];
847 SDL_strlcpy(old_name, wip->name, SDL_arraysize(old_name));
848 SDL_strlcpy(wip->name, old_name+1, SDL_arraysize(wip->name));
852 if (optional_string("+Title:")) {
853 stuff_string(wip->title, F_NAME, NULL, WEAPON_TITLE_LEN);
857 if (optional_string("+Description:")) {
858 stuff_string(buf, F_MULTITEXT, NULL);
859 wip->desc = strdup(buf);
862 wip->tech_title[0] = 0;
863 if (optional_string("+Tech Title:")) {
864 stuff_string(wip->tech_title, F_NAME, NULL, NAME_LENGTH);
867 wip->tech_anim_filename[0] = 0;
868 if (optional_string("+Tech Anim:")) {
869 stuff_string(wip->tech_anim_filename, F_NAME, NULL, NAME_LENGTH);
872 wip->tech_desc = NULL;
873 if (optional_string("+Tech Description:")) {
874 stuff_string(buf, F_MULTITEXT, NULL, WEAPONS_MULTITEXT_LENGTH);
875 wip->tech_desc = strdup(buf);
878 // Read the model file. It can be a POF file or none.
879 // If there is no model file (Model file: = "none") then we use our special
880 // laser renderer which requires inner, middle and outer information.
881 required_string("$Model file:");
882 stuff_string(wip->pofbitmap_name, F_NAME, NULL);
883 diag_printf ("Model pof file -- %s\n", wip->pofbitmap_name );
884 if ( SDL_strcasecmp(wip->pofbitmap_name, NOX("none")) ) {
886 wip->render_type = WRT_POF;
887 wip->laser_bitmap = -1;
889 // No POF or AVI file specified, render as special laser type.
892 wip->render_type = WRT_LASER;
895 // laser bitmap itself
896 required_string("@Laser Bitmap:");
897 stuff_string(wip->pofbitmap_name, F_NAME, NULL);
898 wip->laser_bitmap = -1;
900 wip->laser_bitmap = bm_load( wip->pofbitmap_name );
903 // optional laser glow
904 wip->laser_glow_bitmap = -1;
905 if(optional_string("@Laser Glow:")){
906 stuff_string(fname, F_NAME, NULL);
908 wip->laser_glow_bitmap = bm_load( fname );
910 // might as well lock it down as an aabitmap now
911 if(wip->laser_glow_bitmap >= 0){
912 bm_lock(wip->laser_glow_bitmap, 8, BMP_AABITMAP);
913 bm_unlock(wip->laser_glow_bitmap);
918 required_string("@Laser Color:");
919 stuff_byte(&r); stuff_byte(&g); stuff_byte(&b);
920 gr_init_color( &wip->laser_color_1, r, g, b );
922 // optional string for cycling laser colors
923 gr_init_color(&wip->laser_color_2, 0, 0, 0);
924 if(optional_string("@Laser Color2:")){
925 stuff_byte(&r); stuff_byte(&g); stuff_byte(&b);
926 gr_init_color( &wip->laser_color_2, r, g, b );
929 required_string("@Laser Length:");
930 stuff_float(&wip->laser_length);
932 required_string("@Laser Head Radius:");
933 stuff_float(&wip->laser_head_radius);
935 required_string("@Laser Tail Radius:");
936 stuff_float(&wip->laser_tail_radius );
939 required_string("$Mass:");
940 stuff_float( &(wip->mass) );
941 diag_printf ("Weapon mass -- %7.3f\n", wip->mass);
943 required_string("$Velocity:");
944 stuff_float( &(wip->max_speed) );
945 diag_printf ("Weapon mass -- %7.3f\n", wip->max_speed);
947 required_string("$Fire Wait:");
948 stuff_float( &(wip->fire_wait) );
949 diag_printf ("Weapon fire wait -- %7.3f\n", wip->fire_wait);
951 required_string("$Damage:");
952 stuff_float(&wip->damage);
954 // secondary weapons require these values
955 if (First_secondary_index != -1) {
956 required_string("$Blast Force:");
957 stuff_float( &(wip->blast_force) );
958 diag_printf ("Weapon blast force -- %7.3f\n", wip->blast_force);
960 required_string("$Inner Radius:");
961 stuff_float( &(wip->inner_radius) );
962 if ( wip->inner_radius != 0 ) {
963 wip->wi_flags |= WIF_AREA_EFFECT;
965 diag_printf ("Weapon inner blast radius -- %7.3f\n", wip->inner_radius);
967 required_string("$Outer Radius:");
968 stuff_float( &(wip->outer_radius) );
969 if ( wip->outer_radius != 0 ) {
970 wip->wi_flags |= WIF_AREA_EFFECT;
972 diag_printf ("Weapon outer blast radius -- %7.3f\n", wip->outer_radius);
974 required_string("$Shockwave Speed:");
975 stuff_float( &(wip->shockwave_speed) );
976 if ( wip->shockwave_speed != 0 ) {
977 wip->wi_flags |= WIF_SHOCKWAVE;
979 diag_printf ("Shockwave speed -- %7.3f\n", wip->shockwave_speed);
981 // for primary weapons they're optional
983 if(optional_string("$Blast Force:")){
984 stuff_float( &(wip->blast_force) );
985 diag_printf ("Weapon blast force -- %7.3f\n", wip->blast_force);
988 if(optional_string("$Inner Radius:")){
989 stuff_float( &(wip->inner_radius) );
990 if ( wip->inner_radius != 0 ) {
991 wip->wi_flags |= WIF_AREA_EFFECT;
993 diag_printf ("Weapon inner blast radius -- %7.3f\n", wip->inner_radius);
996 if(optional_string("$Outer Radius:")){
997 stuff_float( &(wip->outer_radius) );
998 if ( wip->outer_radius != 0 ) {
999 wip->wi_flags |= WIF_AREA_EFFECT;
1001 diag_printf ("Weapon outer blast radius -- %7.3f\n", wip->outer_radius);
1004 if(optional_string("$Shockwave Speed:")){
1005 stuff_float( &(wip->shockwave_speed) );
1006 if ( wip->shockwave_speed != 0 ) {
1007 wip->wi_flags |= WIF_SHOCKWAVE;
1009 diag_printf ("Shockwave speed -- %7.3f\n", wip->shockwave_speed);
1013 required_string("$Armor Factor:");
1014 stuff_float(&wip->armor_factor);
1016 required_string("$Shield Factor:");
1017 stuff_float(&wip->shield_factor);
1019 required_string("$Subsystem Factor:");
1020 stuff_float(&wip->subsystem_factor);
1022 required_string("$Lifetime:");
1023 stuff_float(&wip->lifetime);
1025 required_string("$Energy Consumed:");
1026 stuff_float(&wip->energy_consumed);
1028 required_string("$Cargo Size:");
1029 stuff_float(&wip->cargo_size);
1032 required_string("$Homing:");
1033 stuff_boolean(&is_homing);
1035 if (is_homing == 1) {
1036 char temp_type[128];
1038 // the following five items only need to be recorded if the weapon is a homing weapon
1039 required_string("+Type:");
1040 stuff_string(temp_type, F_NAME, NULL);
1042 if (!SDL_strcasecmp(temp_type, NOX("HEAT"))) {
1043 float view_cone_angle;
1045 wip->wi_flags |= WIF_HOMING_HEAT | WIF_TURNS;
1047 required_string("+Turn Time:");
1048 stuff_float(&wip->turn_time);
1050 required_string("+View Cone:");
1051 stuff_float(&view_cone_angle);
1053 wip->fov = (float)cos((float)(ANG_TO_RAD(view_cone_angle/2.0f)));
1055 } else if (!SDL_strcasecmp(temp_type, NOX("ASPECT"))) {
1056 wip->wi_flags |= WIF_HOMING_ASPECT | WIF_TURNS;
1058 required_string("+Turn Time:");
1059 stuff_float(&wip->turn_time);
1061 required_string("+Min Lock Time:"); // minimum time (in seconds) to achieve lock
1062 stuff_float(&wip->min_lock_time);
1064 required_string("+Lock Pixels/Sec:"); // pixels/sec moved while locking
1065 stuff_int(&wip->lock_pixels_per_sec);
1067 required_string("+Catch-up Pixels/Sec:"); // pixels/sec moved while catching-up for a lock
1068 stuff_int(&wip->catchup_pixels_per_sec);
1070 required_string("+Catch-up Penalty:"); // number of extra pixels to move while locking as a penalty for catching up for a lock
1071 stuff_int(&wip->catchup_pixel_penalty);
1073 Error(LOCATION, "Illegal homing type = %s.\nMust be HEAT or ASPECT.\n", temp_type);
1079 wip->swarm_count = -1;
1080 if(optional_string("$Swarm:")){
1081 wip->swarm_count = SWARM_DEFAULT_NUM_MISSILES_FIRED;
1082 stuff_int(&s_count);
1083 wip->swarm_count = (short)s_count;
1085 // flag as being a swarm weapon
1086 wip->wi_flags |= WIF_SWARM;
1089 required_string("$LaunchSnd:");
1090 stuff_int(&wip->launch_snd);
1092 required_string("$ImpactSnd:");
1093 stuff_int(&wip->impact_snd);
1095 if (First_secondary_index != -1) {
1096 required_string("$FlyBySnd:");
1097 stuff_int(&wip->flyby_snd);
1100 // Secondary weapons are required to have a rearm rate.
1101 if (First_secondary_index != -1) {
1102 required_string( "$Rearm Rate:");
1103 stuff_float( &wip->rearm_rate );
1104 if (wip->rearm_rate > 0.1f)
1105 wip->rearm_rate = 1.0f/wip->rearm_rate;
1107 wip->rearm_rate = 1.0f;
1110 wip->weapon_range = 999999999.9f;
1111 if (optional_string("+Weapon Range:")) {
1112 stuff_float(&wip->weapon_range);
1115 wip->spawn_type = -1;
1116 parse_wi_flags(wip);
1118 char trail_name[MAX_FILENAME_LEN] = "";
1119 trail_info *ti = &wip->tr_info;
1120 memset(ti, 0, sizeof(trail_info));
1122 if(optional_string("$Trail:")){
1123 wip->wi_flags |= WIF_TRAIL; // missile leaves a trail
1125 required_string("+Start Width:");
1126 stuff_float(&ti->w_start);
1128 required_string("+End Width:");
1129 stuff_float(&ti->w_end);
1131 required_string("+Start Alpha:");
1132 stuff_float(&ti->a_start);
1134 required_string("+End Alpha:");
1135 stuff_float(&ti->a_end);
1137 required_string("+Max Life:");
1138 stuff_float(&ti->max_life);
1140 ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
1142 required_string("+Bitmap:");
1143 stuff_string(trail_name, F_NAME, NULL);
1144 ti->bitmap = bm_load(trail_name);
1145 // wip->delta_time = fl2i(1000.0f*wip->max_life)/(NUM_TRAIL_SECTIONS+1); // time between sections. max_life / num_sections basically.
1148 // seemed easier to separate this out from above
1150 required_string("$Trail:");
1151 stuff_boolean(&has_trail);
1153 if (has_trail == 1) {
1154 wip->wi_flags |= WIF_TRAIL; // missile leaves a trail
1156 required_string("+Head Width:");
1157 stuff_float(&ti->w_start);
1159 required_string("+Tail Width:");
1160 stuff_float(&ti->w_end);
1165 required_string("+Life:");
1166 stuff_float(&ti->max_life);
1168 ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
1170 required_string("+Bitmap:");
1171 stuff_string(trail_name, F_NAME, NULL);
1172 ti->bitmap = bm_load(trail_name);
1176 // read in filename for icon that is used in weapons selection
1177 wip->icon_filename[0] = 0;
1178 if ( optional_string("$Icon:") ) {
1179 stuff_string(wip->icon_filename, F_NAME, NULL);
1182 // read in filename for animation that is used in weapons selection
1183 wip->anim_filename[0] = 0;
1184 if ( optional_string("$Anim:") ) {
1185 stuff_string(wip->anim_filename, F_NAME, NULL);
1188 wip->impact_weapon_expl_index = -1;
1189 if ( optional_string("$Impact Explosion:") ) {
1190 char impact_ani_file[FILESPEC_LENGTH];
1191 stuff_string(impact_ani_file, F_NAME, NULL);
1192 if ( SDL_strcasecmp(impact_ani_file,NOX("none"))) {
1193 wip->impact_weapon_expl_index = get_weapon_expl_info_index(impact_ani_file);
1194 //int num_frames, fps;
1195 //wip->impact_explosion_ani = bm_load_animation( impact_ani_file, &num_frames, &fps, 1 );
1197 required_string("$Impact Explosion Radius:");
1198 stuff_float(&wip->impact_explosion_radius);
1203 char mflash_string[255] = "";
1204 wip->muzzle_flash = -1;
1205 if( optional_string("$Muzzleflash:") ){
1206 stuff_string(mflash_string, F_NAME, NULL);
1209 wip->muzzle_flash = mflash_lookup(mflash_string);
1211 if(wip->muzzle_flash >= 0){
1212 wip->wi_flags |= WIF_MFLASH;
1216 // EMP optional stuff (if WIF_EMP is not set, none of this matters, anyway)
1217 if( optional_string("$EMP Intensity:") ){
1218 stuff_float(&wip->emp_intensity);
1220 wip->emp_intensity = EMP_DEFAULT_INTENSITY;
1222 if( optional_string("$EMP Time:") ){
1223 stuff_float(&wip->emp_time);
1225 wip->emp_intensity = EMP_DEFAULT_TIME;
1228 // Energy suck optional stuff (if WIF_ENERGY_SUCK is not set, none of this matters anyway)
1229 if( optional_string("$Leech Weapon:") ){
1230 stuff_float(&wip->weapon_reduce);
1232 wip->weapon_reduce = ESUCK_DEFAULT_WEAPON_REDUCE;
1234 if( optional_string("$Leech Afterburner:") ){
1235 stuff_float(&wip->afterburner_reduce);
1237 wip->afterburner_reduce = ESUCK_DEFAULT_AFTERBURNER_REDUCE;
1240 // beam weapon optional stuff
1241 wip->b_info.beam_type = -1;
1242 wip->b_info.beam_life = -1.0f;
1243 wip->b_info.beam_warmup = -1;
1244 wip->b_info.beam_warmdown = -1;
1245 wip->b_info.beam_muzzle_radius = 0.0f;
1246 wip->b_info.beam_particle_count = -1;
1247 wip->b_info.beam_particle_radius = 0.0f;
1248 wip->b_info.beam_particle_angle = 0.0f;
1249 wip->b_info.beam_particle_ani = -1;
1250 wip->b_info.beam_loop_sound = -1;
1251 wip->b_info.beam_warmup_sound = -1;
1252 wip->b_info.beam_warmdown_sound = -1;
1253 wip->b_info.beam_num_sections = 0;
1254 wip->b_info.beam_glow_bitmap = -1;
1255 wip->b_info.beam_shots = 0;
1256 wip->b_info.beam_shrink_factor = 0.0f;
1257 wip->b_info.beam_shrink_pct = 0.0f;
1258 if( optional_string("$BeamInfo:")){
1260 required_string("+Type:");
1261 stuff_int(&wip->b_info.beam_type);
1263 // how long it lasts
1264 required_string("+Life:");
1265 stuff_float(&wip->b_info.beam_life);
1268 required_string("+Warmup:");
1269 stuff_int(&wip->b_info.beam_warmup);
1272 required_string("+Warmdown:");
1273 stuff_int(&wip->b_info.beam_warmdown);
1275 // muzzle glow radius
1276 required_string("+Radius:");
1277 stuff_float(&wip->b_info.beam_muzzle_radius);
1279 // particle spew count
1280 required_string("+PCount:");
1281 stuff_int(&wip->b_info.beam_particle_count);
1284 required_string("+PRadius:");
1285 stuff_float(&wip->b_info.beam_particle_radius);
1287 // angle off turret normal
1288 required_string("+PAngle:");
1289 stuff_float(&wip->b_info.beam_particle_angle);
1291 // particle bitmap/ani
1292 required_string("+PAni:");
1293 stuff_string(fname, F_NAME, NULL);
1295 int num_frames, fps;
1296 wip->b_info.beam_particle_ani = bm_load_animation(fname, &num_frames, &fps, 1);
1300 required_string("+Miss Factor:");
1301 for(idx=0; idx<NUM_SKILL_LEVELS; idx++){
1302 wip->b_info.beam_miss_factor[idx] = 0.00001f;
1303 stuff_float(&wip->b_info.beam_miss_factor[idx]);
1307 required_string("+BeamSound:");
1308 stuff_int(&wip->b_info.beam_loop_sound);
1311 required_string("+WarmupSound:");
1312 stuff_int(&wip->b_info.beam_warmup_sound);
1315 required_string("+WarmdownSound:");
1316 stuff_int(&wip->b_info.beam_warmdown_sound);
1319 required_string("+Muzzleglow:");
1320 stuff_string(fname, F_NAME, NULL);
1322 wip->b_info.beam_glow_bitmap = bm_load(fname);
1325 // # of shots (only used for type D beams)
1326 required_string("+Shots:");
1327 stuff_int(&wip->b_info.beam_shots);
1330 required_string("+ShrinkFactor:");
1331 stuff_float(&wip->b_info.beam_shrink_factor);
1332 required_string("+ShrinkPct:");
1333 stuff_float(&wip->b_info.beam_shrink_pct);
1336 while( optional_string("$Section:") ){
1337 beam_weapon_section_info i;
1338 char tex_name[255] = "";
1341 required_string("+Width:");
1342 stuff_float(&i.width);
1345 required_string("+Texture:");
1346 stuff_string(tex_name, F_NAME, NULL);
1349 i.texture = bm_load(tex_name);
1351 bm_lock(i.texture, 16, BMP_TEX_OTHER);
1352 bm_unlock(i.texture);
1357 required_string("+RGBA Inner:");
1358 stuff_byte(&i.rgba_inner[0]);
1359 stuff_byte(&i.rgba_inner[1]);
1360 stuff_byte(&i.rgba_inner[2]);
1361 stuff_byte(&i.rgba_inner[3]);
1364 required_string("+RGBA Outer:");
1365 stuff_byte(&i.rgba_outer[0]);
1366 stuff_byte(&i.rgba_outer[1]);
1367 stuff_byte(&i.rgba_outer[2]);
1368 stuff_byte(&i.rgba_outer[3]);
1371 required_string("+Flicker:");
1372 stuff_float(&i.flicker);
1375 required_string("+Zadd:");
1376 stuff_float(&i.z_add);
1379 if(wip->b_info.beam_num_sections < MAX_BEAM_SECTIONS - 1){
1380 wip->b_info.sections[wip->b_info.beam_num_sections++] = i;
1385 // tag weapon optional stuff
1386 wip->tag_level = -1;
1387 wip->tag_time = -1.0f;
1388 if( optional_string("$Tag:")){
1389 stuff_int(&wip->tag_level);
1390 stuff_float(&wip->tag_time);
1391 wip->wi_flags |= WIF_TAG;
1397 // function to parse the information for a specific ship type.
1398 void parse_cmeasure()
1400 cmeasure_info *cmeasurep;
1402 cmeasurep = &Cmeasure_info[Num_cmeasure_types];
1404 required_string("$Name:");
1405 stuff_string(cmeasurep->cmeasure_name, F_NAME, NULL);
1408 $Velocity: 20.0 ;; speed relative to ship, rear-fired until POF info added, MK, 5/22/97
1410 $Lifetime Min: 1.0 ;; Minimum lifetime
1411 $Lifetime Max: 2.0 ;; Maximum lifetime. Actual lifetime is rand(min..max).
1412 $LaunchSnd: counter_1.wav, .8, 10, 300 ;; countermeasure 1 fired (sound is 3d)
1415 required_string("$Velocity:");
1416 stuff_float( &(cmeasurep->max_speed) );
1418 required_string("$Fire Wait:");
1419 stuff_float( &(cmeasurep->fire_wait) );
1421 required_string("$Lifetime Min:");
1422 stuff_float(&cmeasurep->life_min);
1424 required_string("$Lifetime Max:");
1425 stuff_float(&cmeasurep->life_max);
1427 required_string("$LaunchSnd:");
1428 stuff_int(&cmeasurep->launch_sound);
1430 required_string("$Model:");
1431 stuff_string(cmeasurep->pof_name, F_NAME, NULL);
1432 cmeasurep->model_num = -1;
1436 // For all weapons that spawn weapons, given an index at weaponp->spawn_type,
1437 // convert the strings in Spawn_names to indices in the Weapon_types array.
1438 void translate_spawn_types()
1442 for (i=0; i<Num_weapon_types; i++)
1443 if (Weapon_info[i].spawn_type != -1) {
1444 int spawn_type = Weapon_info[i].spawn_type;
1446 for (j=0; j<Num_weapon_types; j++)
1447 if (!SDL_strcasecmp(Spawn_names[spawn_type], Weapon_info[j].name)) {
1448 Weapon_info[i].spawn_type = (short)j;
1450 Warning(LOCATION, "Weapon %s spawns itself. Infinite recursion?\n", Weapon_info[i].name);
1456 void parse_weaponstbl()
1458 // open localization
1461 read_file_text("weapons.tbl");
1464 Num_weapon_types = 0;
1465 First_secondary_index = -1;
1466 Num_spawn_types = 0;
1468 required_string("#Primary Weapons");
1469 while (required_string_either("#End", "$Name:")) {
1470 SDL_assert( Num_weapon_types < MAX_WEAPON_TYPES );
1471 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
1472 if ( parse_weapon() ) {
1475 Weapon_info[Num_weapon_types].subtype = WP_LASER;
1478 required_string("#End");
1480 required_string("#Secondary Weapons");
1481 First_secondary_index = Num_weapon_types;
1482 while (required_string_either("#End", "$Name:")) {
1483 SDL_assert( Num_weapon_types < MAX_WEAPON_TYPES );
1484 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
1485 if ( parse_weapon() ) {
1488 Weapon_info[Num_weapon_types].subtype = WP_MISSILE;
1491 required_string("#End");
1493 required_string("#Beam Weapons");
1494 while (required_string_either("#End", "$Name:")) {
1495 SDL_assert( Num_weapon_types < MAX_WEAPON_TYPES );
1496 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
1497 if ( parse_weapon() ) {
1500 Weapon_info[Num_weapon_types].subtype = WP_BEAM;
1503 required_string("#End");
1505 required_string("#Countermeasures");
1506 while (required_string_either("#End", "$Name:")) {
1507 SDL_assert( Num_cmeasure_types < MAX_CMEASURE_TYPES );
1509 Num_cmeasure_types++;
1512 required_string("#End");
1514 // Read in a list of weapon_info indicies that are an ordering of the player weapon precedence.
1515 // This list is used to select an alternate weapon when a particular weapon is not available
1516 // during weapon selection.
1517 required_string("$Player Weapon Precedence:");
1518 Num_player_weapon_precedence = stuff_int_list(Player_weapon_precedence, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
1520 translate_spawn_types();
1522 // close localization
1526 void create_weapon_names()
1530 for (i=0; i<Num_weapon_types; i++)
1531 Weapon_names[i] = Weapon_info[i].name;
1534 // This will get called once at game startup
1537 if ( !Weapons_inited ) {
1538 #if !(defined(FS2_DEMO) || defined(FS1_DEMO))
1539 // parse weapon_exp.tbl
1540 parse_weapon_expl_tbl();
1542 // parse weapons.tbl
1545 create_weapon_names();
1547 } catch (parse_error_t rval) {
1548 Error(LOCATION, "Error parsing 'weapons.tbl'\r\nError code = %i.\r\n", (int)rval);
1552 weapon_level_init();
1556 // This will get called at the start of each level.
1557 void weapon_level_init()
1561 // Reset everything between levels
1563 for (i=0; i<MAX_WEAPONS; i++) {
1564 Weapons[i].objnum = -1;
1565 Weapons[i].weapon_info_index = -1;
1568 trail_level_init(); // reset all missile trails
1571 missile_obj_list_init();
1573 cscrew_level_init();
1578 Weapon_flyby_sound_timer = timestamp(0);
1579 Weapon_impact_timer = 1; // inited each level, used to reduce impact sounds
1582 MONITOR( NumWeaponsRend );
1584 static const float weapon_glow_scale_f = 2.3f;
1585 static const float weapon_glow_scale_r = 2.3f;
1586 static const float weapon_glow_scale_l = 1.5f;
1587 static const float weapon_glow_alpha = 0.85f;
1589 void weapon_render(object *obj)
1596 MONITOR_INC(NumWeaponsRend, 1);
1598 SDL_assert(obj->type == OBJ_WEAPON);
1600 num = obj->instance;
1602 wip = &Weapon_info[Weapons[num].weapon_info_index];
1604 switch (wip->render_type) {
1606 // turn off fogging for good measure
1607 gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0, -1.0f, -1.0f);
1609 if (wip->laser_bitmap >= 0) {
1610 gr_set_color_fast(&wip->laser_color_1);
1611 gr_set_bitmap(wip->laser_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.99999f, -1, -1);
1614 vm_vec_scale_add(&headp, &obj->pos, &obj->orient.v.fvec, wip->laser_length);
1615 wp->weapon_flags &= ~WF_CONSIDER_FOR_FLYBY_SOUND;
1616 if ( g3_draw_laser(&headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius) ) {
1617 wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
1621 // maybe draw laser glow bitmap
1623 if(wip->laser_glow_bitmap >= 0){
1625 if ( (Detail.weapon_detail >= 1) && (wip->laser_glow_bitmap >= 0) ) {
1627 // get the laser color
1628 weapon_get_laser_color(&c, obj);
1630 vector headp2 = ZERO_VECTOR;
1631 vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.v.fvec, wip->laser_length * weapon_glow_scale_l);
1632 gr_set_bitmap(wip->laser_glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, weapon_glow_alpha, -1, -1);
1633 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);
1639 uint render_flags = MR_NORMAL|MR_IS_MISSILE|MR_NO_LIGHTING;
1641 model_clear_instance(wip->model_num);
1643 if ( (wip->wi_flags & WIF_THRUSTER) && (wp->thruster_bitmap > -1) ) {
1646 // Add noise to thruster geometry.
1647 //ft = obj->phys_info.forward_thrust;
1648 ft = 1.0f; // Always use 1.0f for missiles
1649 ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
1653 model_set_thrust( wip->model_num, ft, wp->thruster_bitmap, wp->thruster_glow_bitmap, wp->thruster_glow_noise);
1654 render_flags |= MR_SHOW_THRUSTERS;
1657 model_render(wip->model_num, &obj->orient, &obj->pos, render_flags);
1659 // render a missile plume as well
1661 static int plume = -1;
1662 extern float Interp_thrust_twist;
1663 extern float Interp_thrust_twist2;
1665 plume = model_load("plume01.pof", -1, NULL);
1668 Interp_thrust_twist = tw;
1669 Interp_thrust_twist2 = tw2;
1670 model_set_alpha(plume_alpha);
1671 model_render(plume, &obj->orient, &obj->pos, MR_ALL_XPARENT);
1672 Interp_thrust_twist = -1.0f;
1673 Interp_thrust_twist2 = -1.0f;
1680 Warning(LOCATION, "Unknown weapon rendering type = %i\n", wip->render_type);
1684 void weapon_delete(object *obj)
1689 num = obj->instance;
1691 SDL_assert( Weapons[num].objnum == OBJ_INDEX(obj));
1694 SDL_assert(wp->weapon_info_index >= 0);
1695 wp->weapon_info_index = -1;
1696 if (wp->swarm_index >= 0) {
1697 swarm_delete(wp->swarm_index);
1698 wp->swarm_index = -1;
1701 if(wp->cscrew_index >= 0) {
1702 cscrew_delete(wp->cscrew_index);
1703 wp->cscrew_index = -1;
1706 if (wp->missile_list_index >= 0) {
1707 missle_obj_list_remove(wp->missile_list_index);
1708 wp->missile_list_index = -1;
1711 if (wp->flak_index >= 0){
1712 flak_delete(wp->flak_index);
1713 wp->flak_index = -1;
1716 if (wp->trail_num > -1) {
1717 trail_object_died(wp->trail_num);
1722 SDL_assert(Num_weapons >= 0);
1725 // Check if missile is newly locked onto the Player, maybe play a launch warning
1726 void weapon_maybe_play_warning(weapon *wp)
1728 if ( wp->homing_object == Player_obj ) {
1729 if ( !(wp->weapon_flags & WF_LOCK_WARNING_PLAYED) ) {
1730 wp->weapon_flags |= WF_LOCK_WARNING_PLAYED;
1731 if ( Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_HEAT ) {
1732 snd_play(&Snds[SND_HEATLOCK_WARN]);
1734 SDL_assert(Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_ASPECT);
1735 snd_play(&Snds[SND_ASPECTLOCK_WARN]);
1741 #define CMEASURE_DETONATE_DISTANCE 40.0f
1743 // Detonate all missiles near this countermeasure.
1744 void detonate_nearby_missiles(cmeasure *cmp)
1747 vector cmeasure_pos;
1749 cmeasure_pos = Objects[cmp->objnum].pos;
1751 mop = GET_FIRST(&Missile_obj_list);
1752 while(mop != END_OF_LIST(&Missile_obj_list)) {
1756 objp = &Objects[mop->objnum];
1757 wp = &Weapons[objp->instance];
1759 if (wp->team != cmp->team) {
1760 if ( Missiontime - wp->creation_time > F1_0/2) {
1761 if (vm_vec_dist_quick(&cmeasure_pos, &objp->pos) < CMEASURE_DETONATE_DISTANCE) {
1762 if (wp->lifeleft > 0.2f) {
1763 //nprintf(("Jim", "Frame %i: Cmeasure #%i detonating weapon #%i\n", Framecount, cmp-Cmeasures, wp-Weapons));
1764 wp->lifeleft = 0.2f;
1765 // nprintf(("AI", "Frame %i: Flagging weapon %i for detonation.\n", Framecount, wp-Weapons));
1775 // Find an object for weapon #num (object *weapon_objp) to home on due to heat.
1776 void find_homing_object(object *weapon_objp, int num)
1778 object *objp, *old_homing_objp;
1785 wip = &Weapon_info[Weapons[num].weapon_info_index];
1787 best_dist = 99999.9f;
1789 // save the old homing object so that multiplayer servers can give the right information
1790 // to clients if the object changes
1791 old_homing_objp = wp->homing_object;
1793 wp->homing_object = &obj_used_list;
1795 // Scan all objects, find a weapon to home on.
1796 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
1797 if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_CMEASURE)) {
1798 if (objp->type == OBJ_CMEASURE)
1799 if (Cmeasures[objp->instance].flags & CMF_DUD_HEAT)
1802 int homing_object_team = obj_team(objp);
1803 if ( (homing_object_team != wp->team) || (homing_object_team == TEAM_TRAITOR) ) {
1806 vector vec_to_object;
1808 // AL 2-17-98: If ship is immune to sensors, can't home on it (Sandeep says so)!
1809 if ( objp->type == OBJ_SHIP ) {
1810 if ( Ships[objp->instance].flags & SF_HIDDEN_FROM_SENSORS ) {
1815 // If this is a player object, make sure there aren't already too many homers.
1816 // Only in single player. In multiplayer, we don't want to restrict it in dogfight on team vs. team.
1817 // For co-op, it's probably also OK.
1818 if (!( Game_mode & GM_MULTIPLAYER )) {
1819 int num_homers = compute_num_homing_objects(objp);
1820 if (Max_allowed_player_homers[Game_skill_level] < num_homers)
1825 dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
1827 if (objp->type == OBJ_CMEASURE)
1830 dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.v.fvec);
1832 if (dot > wip->fov) {
1833 if (dist < best_dist) {
1835 wp->homing_object = objp;
1836 wp->target_sig = objp->signature;
1838 weapon_maybe_alert_cmeasure_success(objp);
1845 // if (wp->homing_object->type == OBJ_CMEASURE)
1846 // nprintf(("AI", "Frame %i: Weapon #%i homing on cmeasure #%i\n", Framecount, num, objp-Objects));
1848 if (wp->homing_object == Player_obj)
1849 weapon_maybe_play_warning(wp);
1851 // if the old homing object is different that the new one, send a packet to clients
1852 if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
1853 send_homing_weapon_info( num );
1857 // Scan all countermeasures. Maybe make weapon_objp home on it.
1858 void find_homing_object_cmeasures_1(object *weapon_objp)
1863 float best_dot, dist, dot;
1865 wp = &Weapons[weapon_objp->instance];
1866 wip = &Weapon_info[wp->weapon_info_index];
1868 best_dot = wip->fov; // Note, setting to this avoids comparison below.
1870 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
1871 if (objp->type == OBJ_CMEASURE) {
1872 vector vec_to_object;
1873 dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
1875 if (dist < MAX_CMEASURE_TRACK_DIST) {
1877 if (wip->wi_flags & WIF_HOMING_ASPECT) {
1878 chance = 1.0f/2.0f; // aspect seeker this likely to chase a countermeasure
1880 chance = 1.0f/1.5f; // heat seeker this likely to chase a countermeasure
1882 if ((objp->signature != wp->cmeasure_ignore_objnum) && (objp->signature != wp->cmeasure_chase_objnum)) {
1883 if (frand() < chance) {
1884 wp->cmeasure_ignore_objnum = objp->signature; // Don't process this countermeasure again.
1885 //nprintf(("Jim", "Frame %i: Weapon #%i ignoring cmeasure #%i\n", Framecount, OBJ_INDEX(weapon_objp), objp->signature));
1887 wp->cmeasure_chase_objnum = objp->signature; // Don't process this countermeasure again.
1888 //nprintf(("Jim", "Frame %i: Weapon #%i CHASING cmeasure #%i\n", Framecount, OBJ_INDEX(weapon_objp), objp->signature));
1892 if (objp->signature != wp->cmeasure_ignore_objnum) {
1894 dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.v.fvec);
1896 if (dot > best_dot) {
1897 //nprintf(("Jim", "Frame %i: Weapon #%i homing on cmeasure #%i\n", Framecount, weapon_objp-Objects, objp->signature));
1899 wp->homing_object = objp;
1900 weapon_maybe_alert_cmeasure_success(objp);
1909 // Someone launched countermeasures.
1910 // For all heat-seeking homing objects, see if should favor tracking a countermeasure instead.
1911 void find_homing_object_cmeasures()
1913 object *weapon_objp;
1915 // nprintf(("AI", "Scanning for countermeasures in frame %i\n", Framecount));
1917 if (Cmeasures_homing_check == 0)
1920 if (Cmeasures_homing_check <= 0)
1921 Cmeasures_homing_check = 1;
1923 Cmeasures_homing_check--;
1925 for (weapon_objp = GET_FIRST(&obj_used_list); weapon_objp != END_OF_LIST(&obj_used_list); weapon_objp = GET_NEXT(weapon_objp) ) {
1926 if (weapon_objp->type == OBJ_WEAPON) {
1927 weapon_info *wip = &Weapon_info[Weapons[weapon_objp->instance].weapon_info_index];
1929 if (wip->wi_flags & WIF_HOMING)
1930 find_homing_object_cmeasures_1(weapon_objp);
1936 // Find object with signature "sig" and make weapon home on it.
1937 void find_homing_object_by_sig(object *weapon_objp, int sig)
1941 object *old_homing_objp;
1943 wp = &Weapons[weapon_objp->instance];
1945 // save the old object so that multiplayer masters know whether to send a homing update packet
1946 old_homing_objp = wp->homing_object;
1948 sop = GET_FIRST(&Ship_obj_list);
1949 while(sop != END_OF_LIST(&Ship_obj_list)) {
1952 objp = &Objects[sop->objnum];
1953 if (objp->signature == sig) {
1954 wp->homing_object = objp;
1955 wp->target_sig = objp->signature;
1962 // if the old homing object is different that the new one, send a packet to clients
1963 if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
1964 send_homing_weapon_info( weapon_objp->instance );
1969 // Make weapon num home. It's also object *obj.
1970 void weapon_home(object *obj, int num, float frame_time)
1976 SDL_assert(obj->type == OBJ_WEAPON);
1977 SDL_assert(obj->instance == num);
1979 wip = &Weapon_info[wp->weapon_info_index];
1980 hobjp = Weapons[num].homing_object;
1982 // If not 1/2 second gone by, don't home yet.
1983 if ((hobjp == &obj_used_list) || ( f2fl(Missiontime - wp->creation_time) < 0.25f )) {
1984 // If this is a heat seeking homing missile and 1/2 second has elapsed since firing
1985 // and we don't have a target (else we wouldn't be inside the IF), find a new target.
1986 if (wip->wi_flags & WIF_HOMING_HEAT)
1987 if ( f2fl(Missiontime - wp->creation_time) > 0.5f )
1988 find_homing_object(obj, num);
1990 if (obj->phys_info.speed > wip->max_speed) {
1991 obj->phys_info.speed -= frame_time * 4;
1992 vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.v.fvec, obj->phys_info.speed);
1993 } else if ((obj->phys_info.speed < wip->max_speed/4) && (wip->wi_flags & WIF_HOMING_HEAT)) {
1994 obj->phys_info.speed = wip->max_speed/4;
1995 vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.v.fvec, obj->phys_info.speed);
1998 /* Removed code that makes bombs drop for a bit. They looked odd and it was confusing. People wondered where their weapons went.
1999 // Make bombs drop down for first second of life.
2000 if (wip->wi_flags & WIF_BOMB) {
2001 if (wip->lifetime - wp->lifeleft < 0.5f) {
2002 float time_scale = wip->lifetime - wp->lifeleft;
2003 vm_vec_scale_add2(&obj->phys_info.desired_vel, &obj->orient.uvec, (time_scale - 0.5f) * SDL_max(10.0f, obj->phys_info.speed/2.0f));
2010 // AL 4-8-98: If orgiginal target for aspect lock missile is lost, stop homing
2011 if (wip->wi_flags & WIF_HOMING_ASPECT) {
2012 if ( wp->target_sig > 0 ) {
2013 if ( wp->homing_object->signature != wp->target_sig ) {
2014 wp->homing_object = &obj_used_list;
2020 // AL 4-13-98: Stop homing on a subsystem if parent ship has changed
2021 if (wip->wi_flags & WIF_HOMING_HEAT) {
2022 if ( wp->target_sig > 0 ) {
2023 if ( wp->homing_object->signature != wp->target_sig ) {
2024 wp->homing_subsys = NULL;
2030 if (hobjp->type == OBJ_NONE) {
2031 find_homing_object(obj, num);
2036 switch (hobjp->type) {
2038 if (wip->wi_flags & WIF_HOMING_ASPECT)
2039 find_homing_object_by_sig(obj, wp->target_sig);
2041 find_homing_object(obj, num);
2045 if (hobjp->signature != wp->target_sig) {
2046 if (wip->wi_flags & WIF_HOMING_ASPECT)
2047 find_homing_object_by_sig(obj, wp->target_sig);
2049 find_homing_object(obj, num);
2054 // only allowed to home on bombs
2055 SDL_assert(Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags & WIF_BOMB);
2056 if (wip->wi_flags & WIF_HOMING_ASPECT)
2057 find_homing_object_by_sig(obj, wp->target_sig);
2059 find_homing_object(obj, num);
2067 // See if this weapon is the nearest homing object to the object it is homing on.
2068 // If so, update some fields in the target object's ai_info.
2069 if (hobjp != &obj_used_list) {
2072 dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
2074 if (hobjp->type == OBJ_SHIP) {
2077 aip = &Ai_info[Ships[hobjp->instance].ai_index];
2079 if ((aip->nearest_locked_object == -1) || (dist < aip->nearest_locked_distance)) {
2080 aip->nearest_locked_object = obj-Objects;
2081 aip->nearest_locked_distance = dist;
2086 // If the object it is homing on is still valid, home some more!
2087 if (hobjp != &obj_used_list) {
2090 vector target_pos; // position of what the homing missile is seeking
2092 vm_vec_zero(&target_pos);
2094 // the homing missile may be seeking a subsystem on a ship. If so, we need to calculate the
2095 // world coordinates of that subsystem so the homing missile can seek it out.
2096 // For now, March 7, 1997, MK, heat seeking homing missiles will be able to home on
2097 // any subsystem. Probably makes sense for them to only home on certain kinds of subsystems.
2098 if ( wp->homing_subsys != NULL ) {
2099 get_subsystem_world_pos(hobjp, Weapons[num].homing_subsys, &target_pos);
2100 wp->homing_pos = target_pos; // store the homing position in weapon data
2101 SDL_assert( !vm_is_vec_nan(&wp->homing_pos) );
2106 dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
2107 if (hobjp->type == OBJ_CMEASURE) {
2108 if (dist < CMEASURE_DETONATE_DISTANCE) {
2111 cmp = &Cmeasures[hobjp->instance];
2113 // Make this missile detonate soon. Not right away, not sure why. Seems better.
2114 if (cmp->team != wp->team) {
2115 detonate_nearby_missiles(cmp);
2116 //nprintf(("AI", "Frame %i: Weapon %i hit cmeasure, will die!\n", Framecount, wp-Weapons));
2123 if (wip->fov > 0.8f)
2126 int pick_homing_point = 0;
2127 if ( IS_VEC_NULL(&wp->homing_pos) ) {
2128 pick_homing_point = 1;
2131 // Update homing position if it hasn't been set, you're within 500 meters, or every half second, approximately.
2132 // For large objects, don't lead them.
2133 if (hobjp->radius < 40.0f) {
2134 target_pos = hobjp->pos;
2135 wp->homing_pos = target_pos;
2136 } else if ( pick_homing_point || (dist < 500.0f) || (rand_chance(flFrametime, 2.0f)) ) {
2138 if (hobjp->type == OBJ_SHIP) {
2139 if ( !pick_homing_point ) {
2140 // ensure that current attack point is only updated in world coords (ie not pick a different vertex)
2141 wp->pick_big_attack_point_timestamp = 0;
2144 if ( pick_homing_point ) {
2145 // If *any* player is parent of homing missile, then use position where lock indicator is
2146 if ( Objects[obj->parent].flags & OF_PLAYER_SHIP ) {
2149 // determine the player
2151 if ( Game_mode & GM_MULTIPLAYER ) {
2154 pnum = multi_find_player_by_object( &Objects[obj->parent] );
2156 pp = Net_players[pnum].player;
2160 // If player has apect lock, we don't want to find a homing point on the closest
2161 // octant... setting the timestamp to 0 ensures this.
2162 if (wip->wi_flags & WIF_HOMING_ASPECT) {
2163 wp->pick_big_attack_point_timestamp = 0;
2165 wp->pick_big_attack_point_timestamp = 1;
2168 if ( pp && pp->locking_subsys ) {
2169 wp->big_attack_point = pp->locking_subsys->system_info->pnt;
2171 vm_vec_zero(&wp->big_attack_point);
2176 ai_big_pick_attack_point(hobjp, obj, &target_pos, fov);
2179 target_pos = hobjp->pos;
2182 wp->homing_pos = target_pos;
2183 SDL_assert( !vm_is_vec_nan(&wp->homing_pos) );
2184 // nprintf(("AI", "Attack point = %7.3f %7.3f %7.3f\n", target_pos.xyz.x, target_pos.xyz.y, target_pos.xyz.z));
2186 target_pos = wp->homing_pos;
2189 // Couldn't find a lock.
2190 if (IS_VEC_NULL(&target_pos))
2193 // Cause aspect seeking weapon to home at target's predicted position.
2194 // But don't use predicted position if dot product small or negative.
2195 // If do this, with a ship headed towards missile, could choose a point behind missile.``1
2196 float dist_to_target, time_to_target;
2198 dist_to_target = vm_vec_normalized_dir(&vec_to_goal, &target_pos, &obj->pos);
2199 time_to_target = dist_to_target/wip->max_speed;
2202 tvec = obj->phys_info.vel;
2203 vm_vec_normalize(&tvec);
2205 old_dot = vm_vec_dot(&tvec, &vec_to_goal);
2207 // If a weapon has missed its target, detonate it.
2208 // This solves the problem of a weapon circling the center of a subsystem that has been blown away.
2209 // Problem: It does not do impact damage, just proximity damage.
2210 if ((dist_to_target < flFrametime * obj->phys_info.speed * 4.0f + 10.0f) && (old_dot < 0.0f)) {
2211 int kill_missile = TRUE;
2212 if (wp->homing_object) {
2213 if (wp->homing_object->type == OBJ_SHIP) {
2214 ship *shipp = &Ships[wp->homing_object->instance];
2215 if (Ship_info[shipp->ship_info_index].flags & SIF_DONT_COLLIDE_INVIS) {
2216 kill_missile = FALSE;
2221 if (kill_missile && (wp->lifeleft > 0.01f)) {
2222 wp->lifeleft = 0.01f;
2226 // Only lead target if more than one second away. Otherwise can miss target. I think this
2227 // is what's causing Harbingers to miss the super destroyer. -- MK, 4/15/98
2228 if ((wip->wi_flags & WIF_HOMING_ASPECT) && (old_dot > 0.1f) && (time_to_target > 0.1f))
2229 vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, SDL_min(time_to_target, 2.0f));
2231 //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));
2233 // 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));
2235 // If a HEAT seeking (rather than ASPECT seeking) homing missile, verify that target is in viewcone.
2236 if (wip->wi_flags & WIF_HOMING_HEAT) {
2237 if ((old_dot < wip->fov) && (dist_to_target > wip->inner_radius*1.1f)) { // Delay finding new target one frame to allow detonation.
2238 find_homing_object(obj, num);
2239 return; // Maybe found a new homing object. Return, process more next frame.
2240 } else // Subtract out life based on how far from target this missile points.
2241 if (wip->fov < 0.95f) {
2242 wp->lifeleft -= flFrametime * (0.95f - old_dot);
2243 //Should only happen when time is compressed.
2244 //if (flFrametime * (1.0f - old_dot) > 1.0f)
2247 } else if (wip->wi_flags & WIF_HOMING_ASPECT) { // subtract life as if max turn is 90 degrees.
2248 if (wip->fov < 0.95f)
2249 wp->lifeleft -= flFrametime * (0.95f - old_dot);
2251 SDL_assert(0); // Hmm, a homing missile, but not aspect or heat?
2254 // Control speed based on dot product to goal. If close to straight ahead, move
2255 // at max speed, else move slower based on how far from ahead.
2256 if (old_dot < 0.90f) {
2257 obj->phys_info.speed = SDL_max(0.2f, old_dot* (float) fabs(old_dot));
2258 if (obj->phys_info.speed < wip->max_speed*0.75f)
2259 obj->phys_info.speed = wip->max_speed*0.75f;
2261 obj->phys_info.speed = wip->max_speed;
2263 // For first second of weapon's life, it doesn't fly at top speed. It ramps up.
2264 if (Missiontime - wp->creation_time < i2f(1)) {
2267 t = f2fl(Missiontime - wp->creation_time);
2268 obj->phys_info.speed *= t*t;
2271 SDL_assert( obj->phys_info.speed > 0.0f );
2273 vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.v.fvec, obj->phys_info.speed);
2275 // turn the missile towards the target only if non-swarm. Homing swarm missiles choose
2276 // a different vector to turn towards, this is done in swarm_update_direction().
2277 // if ( !(wip->wi_flags & WIF_SWARM) ) {
2278 if ( wp->swarm_index < 0 ) {
2279 // 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));
2280 ai_turn_towards_vector(&target_pos, obj, frame_time, wip->turn_time, NULL, NULL, 0.0f, 0, NULL);
2281 vel = vm_vec_mag(&obj->phys_info.desired_vel);
2283 vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.v.fvec, vel);
2287 /* // If this weapon shot past its target, make it detonate.
2288 if ((old_dot < 0.0f) && (dist_to_target < 50.0f)) {
2289 if (wp->lifeleft > 0.01f)
2290 wp->lifeleft = 0.01f;
2295 // as Mike K did with ships -- break weapon into process_pre and process_post for code to execute
2296 // before and after physics movement
2298 void weapon_process_pre( object *obj, float frame_time)
2300 // if the object is a corkscrew style weapon, process it now
2301 if((obj->type == OBJ_WEAPON) && (Weapons[obj->instance].cscrew_index >= 0)){
2302 cscrew_process_pre(obj);
2305 // if the weapon is a flak weapon, maybe detonate it early
2306 if((obj->type == OBJ_WEAPON) && (Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags & WIF_FLAK) && (Weapons[obj->instance].flak_index >= 0)){
2307 flak_maybe_detonate(obj);
2311 int Homing_hits = 0, Homing_misses = 0;
2314 MONITOR( NumWeapons );
2316 // maybe play a "whizz sound" if close enough to view position
2317 void weapon_maybe_play_flyby_sound(object *weapon_objp, weapon *wp)
2319 // do a quick out if not a laser
2320 if ( Weapon_info[wp->weapon_info_index].subtype != WP_LASER ) {
2324 // don't play flyby sounds too close together
2325 if ( !timestamp_elapsed(Weapon_flyby_sound_timer) ) {
2329 if ( !(wp->weapon_flags & WF_PLAYED_FLYBY_SOUND) && (wp->weapon_flags & WF_CONSIDER_FOR_FLYBY_SOUND) ) {
2330 float dist, dot, radius;
2332 dist = vm_vec_dist_quick(&weapon_objp->pos, &Eye_position);
2335 radius = Viewer_obj->radius;
2340 if ( (dist > radius) && (dist < 55) ) {
2341 vector vec_to_weapon;
2343 vm_vec_sub(&vec_to_weapon, &weapon_objp->pos, &Eye_position);
2344 vm_vec_normalize(&vec_to_weapon);
2346 // ensure laser is in front of eye
2347 dot = vm_vec_dot(&vec_to_weapon, &Eye_matrix.v.fvec);
2352 // ensure that laser is moving in similar direction to fvec
2353 dot = vm_vec_dot(&vec_to_weapon, &weapon_objp->orient.v.fvec);
2355 // nprintf(("Alan", "Weapon dot: %.2f\n", dot));
2356 if ( (dot < -0.80) && (dot > -0.98) ) {
2357 snd_play_3d( &Snds[SND_WEAPON_FLYBY], &weapon_objp->pos, &Eye_position );
2358 Weapon_flyby_sound_timer = timestamp(200);
2359 wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
2365 // process a weapon after physics movement. MWA reorders some of the code on 8/13 for multiplayer. When
2366 // adding something to this function, decide whether or not a client in a multiplayer game needs to do
2367 // what is normally done in a single player game. Things like plotting an object on a radar, effect
2368 // for exhaust are things that are done on all machines. Things which calculate weapon targets, new
2369 // velocities, etc, are server only functions and should go after the if ( !MULTIPLAYER_MASTER ) statement
2370 // See Allender if you cannot decide what to do.
2371 void weapon_process_post(object * obj, float frame_time)
2377 MONITOR_INC( NumWeapons, 1 );
2379 SDL_assert(obj->type == OBJ_WEAPON);
2381 num = obj->instance;
2384 int objnum = OBJ_INDEX(obj);
2385 SDL_assert( Weapons[num].objnum == objnum );
2390 wp->lifeleft -= frame_time;
2391 wip = &Weapon_info[wp->weapon_info_index];
2393 // check life left. Multiplayer client code will go through here as well. We must be careful in weapon_hit
2394 // when killing a missile that spawn child weapons!!!!
2395 if ( wp->lifeleft < 0.0f ) {
2396 if ( wip->subtype & WP_MISSILE ) {
2397 if(Game_mode & GM_MULTIPLAYER){
2398 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
2399 // nprintf(("AI", "Frame %i: Weapon %i detonated, dist = %7.3f!\n", Framecount, obj-Objects));
2400 weapon_detonate(obj);
2403 // nprintf(("AI", "Frame %i: Weapon %i detonated, dist = %7.3f!\n", Framecount, obj-Objects));
2404 weapon_detonate(obj);
2406 if (wip->wi_flags & WIF_HOMING) {
2408 // nprintf(("AI", "Miss! Hits = %i/%i\n", Homing_hits, (Homing_hits + Homing_misses)));
2411 obj->flags |= OF_SHOULD_BE_DEAD;
2412 // demo_do_flag_dead(OBJ_INDEX(obj));
2418 // plot homing missiles on the radar
2419 if (wip->wi_flags & WIF_HOMING) {
2420 if ( hud_gauge_active(HUD_RADAR) ) {
2421 radar_plot_object( obj );
2426 if ((wip->wi_flags & WIF_TRAIL) && !(wip->wi_flags & WIF_CORKSCREW)) {
2427 if ( wp->trail_num > -1 ) {
2428 if (trail_stamp_elapsed(wp->trail_num)) {
2430 trail_add_segment( wp->trail_num, &obj->pos );
2432 trail_set_stamp(wp->trail_num);
2434 trail_set_segment( wp->trail_num, &obj->pos );
2440 if ( wip->wi_flags & WIF_THRUSTER ) {
2441 ship_do_weapon_thruster_frame( wp, obj, flFrametime );
2444 // maybe play a "whizz sound" if close enough to view position
2446 if ( Weapon_flyby_sound_enabled ) {
2447 weapon_maybe_play_flyby_sound(obj, wp);
2450 weapon_maybe_play_flyby_sound(obj, wp);
2453 // If our target is still valid, then update some info.
2454 if (wp->target_num != -1) {
2455 if (Objects[wp->target_num].signature == wp->target_sig) {
2459 vm_vec_avg(&v0, &obj->pos, &obj->last_pos);
2461 cur_dist = vm_vec_dist_quick(&v0, &Objects[wp->target_num].pos);
2463 if (cur_dist < wp->nearest_dist) {
2464 wp->nearest_dist = cur_dist;
2465 } else if (cur_dist > wp->nearest_dist + 1.0f) {
2468 ai_info *parent_aip;
2469 // float lead_scale = 0.0f;
2472 if (obj->parent != Player_obj-Objects) {
2473 parent_aip = &Ai_info[Ships[Objects[obj->parent].instance].ai_index];
2474 // lead_scale = parent_aip->lead_scale;
2477 vm_vec_normalized_dir(&tvec, &v0, &Objects[wp->target_num].pos);
2478 dot = vm_vec_dot(&tvec, &Objects[wp->target_num].orient.v.fvec);
2479 // nprintf(("AI", "Miss dot = %7.3f, dist = %7.3f, lead_scale = %7.3f\n", dot, cur_dist, lead_scale));
2480 wp->target_num = -1;
2482 // Learn! If over-shooting or under-shooting, compensate.
2483 // Really need to compensate for left/right errors. This does no good against someone circling
2484 // in a plane perpendicular to the attacker's forward vector.
2485 if (parent_aip != NULL) {
2486 if (cur_dist > 100.0f)
2487 parent_aip->lead_scale = 0.0f;
2490 parent_aip->lead_scale += cur_dist/2000.0f;
2491 } else if (dot > 0.1f) {
2492 parent_aip->lead_scale -= cur_dist/2000.0f;
2495 if (fl_abs(parent_aip->lead_scale) > 1.0f){
2496 parent_aip->lead_scale *= 0.9f;
2503 if(wip->wi_flags & WIF_PARTICLE_SPEW){
2504 weapon_maybe_spew_particle(obj);
2507 // a single player or multiplayer server function -- it affects actual weapon movement.
2508 if (wip->wi_flags & WIF_HOMING) {
2509 weapon_home(obj, num, frame_time);
2511 /* if (wip->wi_flags & WIF_BOMB) {
2512 if (wip->lifetime - obj->lifeleft < 1.0f) {
2517 // If this is a swarm type missile,
2518 // if ( wip->wi_flags & WIF_SWARM )
2519 if ( wp->swarm_index >= 0 ) {
2520 swarm_update_direction(obj, frame_time);
2523 if( wp->cscrew_index >= 0) {
2524 cscrew_process_post(obj);
2529 // Update weapon tracking information.
2530 void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_objnum, int target_is_locked, ship_subsys *target_subsys)
2533 object *parent_objp;
2536 int targeting_same = 0;
2538 if ( weapon_objnum < 0 ) {
2542 SDL_assert(Objects[weapon_objnum].type == OBJ_WEAPON);
2544 wp = &Weapons[Objects[weapon_objnum].instance];
2545 wip = &Weapon_info[wp->weapon_info_index];
2546 parent_objp = &Objects[parent_objnum];
2548 SDL_assert(parent_objp->type == OBJ_SHIP);
2549 ai_index = Ships[parent_objp->instance].ai_index;
2551 if ( ai_index >= 0 ) {
2552 int target_team = -1;
2553 if ( target_objnum >= 0 ) {
2554 int obj_type = Objects[target_objnum].type;
2555 if ( (obj_type == OBJ_SHIP) || (obj_type == OBJ_WEAPON) ) {
2556 target_team = obj_team(&Objects[target_objnum]);
2560 // determining if we're targeting the same team
2561 if(Ships[parent_objp->instance].team == target_team){
2567 if ((target_objnum != -1) && (!targeting_same || ((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_DOGFIGHT) && (target_team == TEAM_TRAITOR))) ) {
2568 wp->target_num = target_objnum;
2569 wp->target_sig = Objects[target_objnum].signature;
2570 wp->nearest_dist = 99999.0f;
2571 if ( (wip->wi_flags & WIF_HOMING_ASPECT) && target_is_locked) {
2572 wp->homing_object = &Objects[target_objnum];
2573 wp->homing_subsys = target_subsys;
2574 weapon_maybe_play_warning(wp);
2575 } else if ( wip->wi_flags & WIF_HOMING_HEAT ) {
2576 // Make a heat seeking missile try to home. If the target is outside the view cone, it will
2577 // immediately drop it and try to find one in its view cone.
2578 if (target_objnum != -1) {
2579 wp->homing_object = &Objects[target_objnum];
2580 weapon_maybe_play_warning(wp);
2582 wp->homing_object = &obj_used_list;
2584 wp->homing_subsys = target_subsys;
2587 wp->target_num = -1;
2588 wp->target_sig = -1;
2591 // If missile is locked on target, increase its lifetime by 20% since missiles can be fired at limit of range
2592 // as defined by velocity*lifeleft, but missiles often slow down a bit, plus can be fired at a moving away target.
2593 // Confusing to many players when their missiles run out of gas before getting to target.
2594 // DB - removed 7:14 pm 9/6/99. was totally messing up lifetimes for all weapons.
2595 // 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
2596 // put a sanity check in the color changing laser code that was broken by this code.
2597 if (target_is_locked && (wp->target_num != -1) && (wip->wi_flags & WIF_HOMING_ASPECT) ) {
2598 wp->lifeleft *= 1.2f;
2601 ai_update_danger_weapon(target_objnum, weapon_objnum);
2606 // weapon_create() will create a weapon object
2608 // Returns: index of weapon in the Objects[] array, -1 if the weapon object was not created
2609 int Weapons_created = 0;
2610 int weapon_create( vector * pos, matrix * orient, int weapon_id, int parent_objnum, int secondary_flag, int group_id, int is_locked )
2614 object *objp, *parent_objp;
2618 SDL_assert(weapon_id >= 0 && weapon_id < Num_weapon_types);
2620 // beam weapons should never come through here!
2621 SDL_assert(!(Weapon_info[weapon_id].wi_flags & WIF_BEAM));
2624 if (Num_weapons >= MAX_WEAPONS-5) {
2626 //No, do remove for AI ships -- MK, 3/12/98 // don't need to try and delete weapons for ai ships
2627 //if ( !(Objects[parent_objnum].flags & OF_PLAYER_SHIP) )
2630 num_deleted = collide_remove_weapons();
2631 nprintf(("WARNING", "Deleted %d weapons because of lack of slots\n", num_deleted));
2632 if (num_deleted == 0){
2637 for (n=0; n<MAX_WEAPONS; n++ ){
2638 if (Weapons[n].weapon_info_index < 0){
2643 if (n == MAX_WEAPONS) {
2644 // if we supposedly deleted weapons above, what happened here!!!!
2646 Int3(); // get allender -- something funny is going on!!!
2653 objnum = obj_create( OBJ_WEAPON, parent_objnum, n, orient, pos, 2.0f, OF_RENDERS | OF_COLLIDES | OF_PHYSICS );
2654 SDL_assert(objnum >= 0);
2655 SDL_assert(First_secondary_index != -1);
2656 objp = &Objects[objnum];
2659 if(parent_objnum >= 0){
2660 parent_objp = &Objects[parent_objnum];
2665 wip = &Weapon_info[weapon_id];
2667 // check if laser or dumbfire missile
2668 // set physics flag to allow optimization
2669 if ((wip->subtype == WP_LASER) || ((wip->subtype == WP_MISSILE) && !(wip->wi_flags & WIF_HOMING))) {
2671 objp->phys_info.flags |= PF_CONST_VEL;
2674 wp->weapon_info_index = weapon_id;
2675 wp->lifeleft = wip->lifetime;
2677 wp->objnum = objnum;
2678 wp->homing_object = &obj_used_list; // Assume not homing on anything.
2679 wp->homing_subsys = NULL;
2680 wp->creation_time = Missiontime;
2681 wp->group_id = group_id;
2683 // we don't necessarily need a parent
2684 if(parent_objp != NULL){
2685 SDL_assert(parent_objp->type == OBJ_SHIP); // Get Mike, a non-ship has fired a weapon!
2686 SDL_assert((parent_objp->instance >= 0) && (parent_objp->instance < MAX_SHIPS));
2687 wp->team = Ships[parent_objp->instance].team;
2688 wp->species = Ship_info[Ships[parent_objp->instance].ship_info_index].species;
2693 wp->turret_subsys = NULL;
2694 vm_vec_zero(&wp->homing_pos);
2695 wp->weapon_flags = 0;
2696 wp->target_sig = -1;
2697 wp->cmeasure_ignore_objnum = -1;
2698 wp->cmeasure_chase_objnum = -1;
2700 // Init the thruster info
2701 wp->thruster_bitmap = -1;
2702 wp->thruster_frame = 0.0f;
2703 wp->thruster_glow_bitmap = -1;
2704 wp->thruster_glow_noise = 1.0f;
2705 wp->thruster_glow_frame = 0.0f;
2707 if ( wip->wi_flags & WIF_SWARM ) {
2708 wp->swarm_index = (short)swarm_create();
2710 wp->swarm_index = -1;
2713 // if this is a particle spewing weapon, setup some stuff
2714 if(wip->wi_flags & WIF_PARTICLE_SPEW){
2715 wp->particle_spew_time = -1;
2718 // assign the network signature. The starting sig is sent to all clients, so this call should
2719 // result in the same net signature numbers getting assigned to every player in the game
2720 if ( Game_mode & GM_MULTIPLAYER ) {
2721 if(wip->subtype == WP_MISSILE){
2722 Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
2724 // for weapons that respawn, add the number of respawnable weapons to the net signature pool
2725 // to reserve N signatures for the spawned weapons
2726 if ( wip->wi_flags & WIF_SPAWN ){
2727 multi_set_network_signature( (ushort)(Objects[objnum].net_signature + wip->spawn_count), MULTI_SIG_NON_PERMANENT );
2730 Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
2732 // for multiplayer clients, when creating lasers, add some more life to the lasers. This helps
2733 // to overcome some problems associated with lasers dying on client machine before they get message
2734 // from server saying it hit something.
2735 // removed 1/13/98 -- MWA if ( MULTIPLAYER_CLIENT && (wip->subtype == WP_LASER) )
2736 // removed 1/13/98 -- MWA wp->lifeleft += 1.5f;
2739 // Make remote detonate missiles look like they're getting detonated by firer simply by giving them variable lifetimes.
2740 if (!(Objects[parent_objnum].flags & OF_PLAYER_SHIP) && (wip->wi_flags & WIF_REMOTE)) {
2743 if ( Game_mode & GM_NORMAL ){
2746 rand_val = static_randf(Objects[objnum].net_signature);
2749 wp->lifeleft = wp->lifeleft/2.0f + rand_val * wp->lifeleft/2.0f;
2752 objp->phys_info.mass = wip->mass;
2753 objp->phys_info.side_slip_time_const = 0.0f;
2754 objp->phys_info.rotdamp = 0.0f;
2755 vm_vec_zero(&objp->phys_info.max_vel);
2756 objp->phys_info.max_vel.xyz.z = wip->max_speed;
2757 vm_vec_zero(&objp->phys_info.max_rotvel);
2758 objp->shields[0] = wip->damage;
2759 if (wip->wi_flags & WIF_BOMB){
2760 objp->hull_strength = 50.0f;
2762 objp->hull_strength = 0.0f;
2765 if ( wip->subtype == WP_MISSILE ){
2766 objp->radius = model_get_radius(wip->model_num);
2767 } else if ( wip->subtype == WP_LASER ) {
2768 objp->radius = wip->laser_head_radius;
2771 // Set desired velocity and initial velocity.
2772 // For lasers, velocity is always the same.
2773 // For missiles, it is a small amount plus the firing ship's velocity.
2774 // For missiles, the velocity trends towards some goal.
2775 // Note: If you change how speed works here, such as adding in speed of parent ship, you'll need to change the AI code
2776 // that predicts collision points. See Mike Kulas or Dave Andsager. (Or see ai_get_weapon_speed().)
2777 if (!(wip->wi_flags & WIF_HOMING)) {
2778 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.v.fvec, objp->phys_info.max_vel.xyz.z );
2779 objp->phys_info.vel = objp->phys_info.desired_vel;
2780 objp->phys_info.speed = vm_vec_mag(&objp->phys_info.desired_vel);
2782 // For weapons that home, set velocity to sum of forward component of parent's velocity and 1/4 weapon's max speed.
2783 // Note that it is important to extract the forward component of the parent's velocity to factor out sliding, else
2784 // the missile will not be moving forward.
2785 if(parent_objp != NULL){
2786 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 );
2788 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.v.fvec, objp->phys_info.max_vel.xyz.z/4 );
2790 objp->phys_info.vel = objp->phys_info.desired_vel;
2791 objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
2794 // create the corkscrew
2795 if ( wip->wi_flags & WIF_CORKSCREW ) {
2796 wp->cscrew_index = (short)cscrew_create(objp);
2798 wp->cscrew_index = -1;
2801 // if this is a flak weapon shell, make it so
2802 // NOTE : this function will change some fundamental things about the weapon object
2803 if ( wip->wi_flags & WIF_FLAK ){
2806 wp->flak_index = -1;
2809 wp->missile_list_index = -1;
2810 // If this is a missile, then add it to the Missile_obj_list
2811 if ( wip->subtype == WP_MISSILE ) {
2812 wp->missile_list_index = missile_obj_list_add(objnum);
2815 if (wip->wi_flags & WIF_TRAIL /*&& !(wip->wi_flags & WIF_CORKSCREW) */) {
2816 wp->trail_num = trail_create(wip->tr_info);
2818 if ( wp->trail_num > -1 ) {
2819 // Add two segments. One to stay at launch pos, one to move.
2820 trail_add_segment( wp->trail_num, &objp->pos );
2821 trail_add_segment( wp->trail_num, &objp->pos );
2825 // Ensure weapon flyby sound doesn't get played for player lasers
2826 if ( parent_objp == Player_obj ) {
2827 wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
2830 wp->pick_big_attack_point_timestamp = timestamp(1);
2832 // Set detail levels for POF-type weapons.
2833 if (Weapon_info[wp->weapon_info_index].model_num != -1) {
2836 pm = model_get(Weapon_info[wp->weapon_info_index].model_num);
2838 for (i=0; i<pm->n_detail_levels; i++){
2839 pm->detail_depth[i] = (objp->radius*20.0f + 20.0f) * i;
2843 // if the weapon was fired locked
2845 wp->weapon_flags |= WF_LOCKED_WHEN_FIRED;
2852 // Spawn child weapons from object *objp.
2853 void spawn_child_weapons(object *objp)
2858 ushort starting_sig;
2862 SDL_assert(objp->type == OBJ_WEAPON);
2863 SDL_assert((objp->instance >= 0) && (objp->instance < MAX_WEAPONS));
2865 wp = &Weapons[objp->instance];
2866 SDL_assert((wp->weapon_info_index >= 0) && (wp->weapon_info_index < MAX_WEAPON_TYPES));
2867 wip = &Weapon_info[wp->weapon_info_index];
2869 child_id = wip->spawn_type;
2871 parent_num = objp->parent;
2873 if ((Objects[parent_num].type != objp->parent_type) || (Objects[parent_num].signature != objp->parent_sig)) {
2874 mprintf(("Warning: Parent of spawn weapon does not exist. Not spawning.\n"));
2879 if ( Game_mode & GM_MULTIPLAYER ) {
2880 // get the next network signature and save it. Set the next usable network signature to be
2881 // the passed in objects signature + 1. We "reserved" N of these slots when we created objp
2882 // for it's spawned children.
2883 starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
2884 multi_set_network_signature( objp->net_signature, MULTI_SIG_NON_PERMANENT );
2887 for (i=0; i<wip->spawn_count; i++) {
2892 // for multiplayer, use the static randvec functions based on the network signatures to provide
2893 // the randomness so that it is the same on all machines.
2894 if ( Game_mode & GM_MULTIPLAYER ){
2895 static_randvec(objp->net_signature + i, &tvec);
2897 vm_vec_rand_vec_quick(&tvec);
2899 vm_vec_scale_add(&pos, &objp->pos, &tvec, objp->radius);
2901 vm_vector_2_matrix(&orient, &tvec, NULL, NULL);
2902 weapon_objnum = weapon_create(&pos, &orient, child_id, parent_num, 1, -1, wp->weapon_flags & WF_LOCKED_WHEN_FIRED);
2904 // Assign a little randomness to lifeleft so they don't all disappear at the same time.
2905 if (weapon_objnum != -1) {
2908 if ( Game_mode & GM_NORMAL ){
2911 rand_val = static_randf(objp->net_signature + i);
2914 Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f;
2919 // in multiplayer, reset the next network signature to the one that was saved.
2920 if ( Game_mode & GM_MULTIPLAYER ){
2921 multi_set_network_signature( starting_sig, MULTI_SIG_NON_PERMANENT );
2925 // -----------------------------------------------------------------------
2926 // weapon_hit_do_sound()
2928 // Play a sound effect when a weapon hits a ship
2930 // To elimate the "stereo" effect of two lasers hitting at nearly
2931 // the same time, and to reduce the number of sound channels used,
2932 // only play one impact sound if IMPACT_SOUND_DELTA has elapsed
2934 // Note: Uses Weapon_impact_timer global for timer variable
2936 void weapon_hit_do_sound(object *hit_obj, weapon_info *wip, vector *hitpos)
2941 // If non-missiles (namely lasers) expire without hitting a ship, don't play impact sound
2942 if ( wip->subtype != WP_MISSILE ) {
2944 // flak weapons make sounds
2945 if(wip->wi_flags & WIF_FLAK){
2946 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
2951 switch(hit_obj->type) {
2957 if ( timestamp_elapsed(Weapon_impact_timer) ) {
2958 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
2959 Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
2969 if ( hit_obj == NULL ) {
2970 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
2974 if ( timestamp_elapsed(Weapon_impact_timer) ) {
2977 if ( hit_obj->type == OBJ_SHIP ) {
2978 shield_str = ship_quadrant_shield_strength(hit_obj, hitpos);
2983 // play a shield hit if shields are above 10% max in this quadrant
2984 if ( shield_str > 0.1f ) {
2988 if ( !is_hull_hit ) {
2989 // Play a shield impact sound effect
2990 if ( hit_obj == Player_obj ) {
2991 snd_play_3d( &Snds[SND_SHIELD_HIT_YOU], hitpos, &Eye_position );
2992 // AL 12-15-97: Add missile impact sound even when shield is hit
2993 if ( wip->subtype == WP_MISSILE ) {
2994 snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
2997 snd_play_3d( &Snds[SND_SHIELD_HIT], hitpos, &Eye_position );
3000 // Play a hull impact sound effect
3001 switch ( wip->subtype ) {
3003 if ( hit_obj == Player_obj )
3004 snd_play_3d( &Snds[SND_PLAYER_HIT_LASER], hitpos, &Eye_position );
3006 if ( wip->impact_snd != -1 ) {
3007 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
3012 if ( hit_obj == Player_obj )
3013 snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
3015 if ( wip->impact_snd != -1 ) {
3016 snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
3021 nprintf(("Warning","WARNING ==> Cannot determine sound to play for weapon impact\n"));
3026 Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
3030 // distrupt any subsystems that fall into damage sphere of this Electronics missile
3032 // input: ship_obj => pointer to ship that holds subsystem
3033 // blast_pos => world pos of weapon blast
3034 // wi_index => weapon info index of weapon causing blast
3035 void weapon_do_electronics_affect(object *ship_objp, vector *blast_pos, int wi_index)
3040 model_subsystem *psub;
3041 vector subsys_world_pos;
3044 shipp = &Ships[ship_objp->instance];
3045 wip = &Weapon_info[wi_index];
3047 for ( ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss) ) {
3048 psub = ss->system_info;
3050 // convert subsys point to world coords
3051 vm_vec_unrotate(&subsys_world_pos, &psub->pnt, &ship_objp->orient);
3052 vm_vec_add2(&subsys_world_pos, &ship_objp->pos);
3054 // see if subsys point is within damage sphere
3055 dist = vm_vec_dist_quick(blast_pos, &subsys_world_pos);
3056 if ( dist < wip->outer_radius ) {
3057 ship_subsys_set_disrupted(ss, fl2i(6000.0f + frand()*4000.0f));
3062 // ----------------------------------------------------------------------
3063 // weapon_area_calc_damage()
3065 // Calculate teh damage for an object based on the location of an area-effect
3068 // input: objp => object pointer ship receiving blast effect
3069 // pos => world pos of blast center
3070 // inner_rad => smallest radius at which full damage is done
3071 // outer_rad => radius at which no damage is done
3072 // max_blast => maximum blast possible from explosion
3073 // max_damage => maximum damage possible from explosion
3074 // blast => OUTPUT PARAMETER: receives blast value from explosion
3075 // damage => OUTPUT PARAMETER: receives damage value from explosion
3076 // limit => a limit on the area, needed for shockwave damage
3078 // returns: no damage occurred => -1
3079 // damage occured => 0
3081 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)
3083 float dist, max_dist, min_dist;
3085 // only blast ships and asteroids
3086 if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID)) {
3090 max_dist = objp->radius + outer_rad;
3091 dist = vm_vec_dist_quick(&objp->pos, pos);
3092 if ( (dist > max_dist) || (dist > (limit+objp->radius)) ) {
3093 return -1; // spheres don't intersect at all
3096 if ( dist < (inner_rad+objp->radius) ) {
3097 // damage is maximum within inner radius
3098 *damage = max_damage;
3101 float dist_to_outer_rad_squared, total_dist_squared;
3102 min_dist = dist - objp->radius;
3103 SDL_assert(min_dist < outer_rad);
3104 dist_to_outer_rad_squared = (outer_rad-min_dist)*(outer_rad-min_dist);
3105 total_dist_squared = (inner_rad-outer_rad)*(inner_rad-outer_rad);
3106 // AL 2-24-98: drop off damage relative to square of distance
3107 SDL_assert(dist_to_outer_rad_squared <= total_dist_squared);
3108 *damage = max_damage * dist_to_outer_rad_squared/total_dist_squared;
3111 // *damage = (min_dist - outer_rad) * max_damage/(inner_rad - outer_rad);
3112 *blast = (min_dist - outer_rad) * max_blast /(inner_rad - outer_rad);
3115 // nprintf(("AI", "Frame %i: Damage = %7.3f, %7.3f meters away.\n", Framecount, *damage, dist));
3120 // ----------------------------------------------------------------------
3121 // weapon_area_apply_blast()
3123 // Apply the blast effects of an explosion to a ship
3125 // input: force_apply_pos => world pos of where force is applied to object
3126 // ship_obj => object pointer of ship receiving the blast
3127 // blast_pos => world pos of blast center
3128 // blast => force of blast
3129 // make_shockwave => boolean, whether to create a shockwave or not
3131 void weapon_area_apply_blast(vector *force_apply_pos, object *ship_obj, vector *blast_pos, float blast, int make_shockwave)
3133 #define SHAKE_CONST 3000
3134 vector force, vec_blast_to_ship, vec_ship_to_impact;
3137 // apply blast force based on distance from center of explosion
3138 vm_vec_sub(&vec_blast_to_ship, &ship_obj->pos, blast_pos);
3139 vm_vec_normalize_safe(&vec_blast_to_ship);
3140 vm_vec_copy_scale(&force, &vec_blast_to_ship, blast );
3143 vm_vec_sub(&vec_ship_to_impact, blast_pos, &ship_obj->pos);
3145 pm = model_get(Ships[ship_obj->instance].modelnum);
3146 SDL_assert ( pm != NULL );
3148 if (make_shockwave) {
3149 physics_apply_shock (&force, blast, &ship_obj->phys_info, &ship_obj->orient, &pm->mins, &pm->maxs, pm->rad);
3150 if (ship_obj == Player_obj) {
3151 joy_ff_play_vector_effect(&vec_blast_to_ship, blast * 2.0f);
3154 ship_apply_whack( &force, &vec_ship_to_impact, ship_obj);
3158 // ----------------------------------------------------------------------
3159 // weapon_do_area_effect()
3161 // Do the area effect for a weapon
3163 // input: wobjp => object pointer to weapon causing explosion
3164 // pos => world pos of explosion center
3165 // other_obj => object pointer to ship that weapon impacted on (can be NULL)
3167 void weapon_do_area_effect(object *wobjp, vector *pos, object *other_obj)
3171 float damage, blast;
3173 wip = &Weapon_info[Weapons[wobjp->instance].weapon_info_index];
3175 SDL_assert(wip->inner_radius != 0);
3177 // only blast ships and asteroids
3178 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
3179 if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) ) {
3183 if ( objp->type == OBJ_SHIP ) {
3184 // don't blast navbuoys
3185 if ( ship_get_SIF(objp->instance) & SIF_NAVBUOY ) {
3190 if ( weapon_area_calc_damage(objp, pos, wip->inner_radius, wip->outer_radius, wip->blast_force, wip->damage, &blast, &damage, wip->outer_radius) == -1 ){
3195 damage *= weapon_get_damage_scale(wip, wobjp, other_obj);
3197 switch ( objp->type ) {
3199 ship_apply_global_damage(objp, wobjp, pos, damage);
3200 weapon_area_apply_blast(NULL, objp, pos, blast, 0);
3203 asteroid_hit(objp, NULL, NULL, damage);
3212 // if this weapon has the "Electronics" flag set, then disrupt subsystems in sphere
3213 if ( (other_obj != NULL) && (wip->wi_flags & WIF_ELECTRONICS) ) {
3214 if ( other_obj->type == OBJ_SHIP ) {
3215 weapon_do_electronics_affect(other_obj, pos, Weapons[wobjp->instance].weapon_info_index);
3221 // ----------------------------------------------------------------------
3224 // This function is called when a weapon hits something (or, in the case of
3225 // missiles explodes for any particular reason)
3227 void weapon_hit( object * weapon_obj, object * other_obj, vector * hitpos )
3229 SDL_assert(weapon_obj != NULL);
3230 if(weapon_obj == NULL){
3233 SDL_assert((weapon_obj->type == OBJ_WEAPON) && (weapon_obj->instance >= 0) && (weapon_obj->instance < MAX_WEAPONS));
3234 if((weapon_obj->type != OBJ_WEAPON) || (weapon_obj->instance < 0) || (weapon_obj->instance >= MAX_WEAPONS)){
3238 int num = weapon_obj->instance;
3239 int weapon_type = Weapons[num].weapon_info_index;
3243 SDL_assert((weapon_type >= 0) && (weapon_type < MAX_WEAPONS));
3244 if((weapon_type < 0) || (weapon_type >= MAX_WEAPONS)){
3247 wip = &Weapon_info[weapon_type];
3249 // if this is the player ship, and is a laser hit, skip it. wait for player "pain" to take care of it
3250 // if( ((wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT) && (Player_obj != NULL) && (other_obj == Player_obj) ){
3251 if((other_obj != Player_obj) || (wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT){
3252 weapon_hit_do_sound(other_obj, wip, hitpos);
3255 if ( wip->impact_weapon_expl_index > -1 ) {
3256 int expl_ani_handle = weapon_get_expl_handle(wip->impact_weapon_expl_index, hitpos, wip->impact_explosion_radius);
3257 particle_create( hitpos, &vmd_zero_vector, 0.0f, wip->impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, expl_ani_handle );
3260 weapon_obj->flags |= OF_SHOULD_BE_DEAD;
3262 int sw_flag = SW_WEAPON;
3264 // check if this is an area effect weapon
3265 if ( wip->wi_flags & WIF_AREA_EFFECT ) {
3266 // if ( wip->subtype & WP_MISSILE && wip->wi_flags & WIF_AREA_EFFECT ) {
3267 if ( wip->wi_flags & WIF_SHOCKWAVE ) {
3268 float actual_damage = wip->damage;
3269 // Shockwaves caused by weapons hitting weapons are 1/4 as powerful
3270 if ( ((other_obj) && (other_obj->type == OBJ_WEAPON)) || (Weapons[num].weapon_flags & WF_DESTROYED_BY_WEAPON)) {
3271 actual_damage /= 4.0f;
3272 sw_flag |= SW_WEAPON_KILL;
3274 shockwave_create_info sci;
3275 sci.blast = wip->blast_force;
3276 sci.damage = actual_damage;
3277 sci.inner_rad = wip->inner_radius;
3278 sci.outer_rad = wip->outer_radius;
3279 sci.speed = wip->shockwave_speed;
3280 sci.rot_angle = 0.0f;
3282 shockwave_create(OBJ_INDEX(weapon_obj), hitpos, &sci, sw_flag);
3283 // snd_play_3d( &Snds[SND_SHOCKWAVE_IMPACT], hitpos, &Eye_position );
3286 weapon_do_area_effect(weapon_obj, hitpos, other_obj);
3290 // check if this is an EMP weapon
3291 if(wip->wi_flags & WIF_EMP){
3292 emp_apply(&weapon_obj->pos, wip->inner_radius, wip->outer_radius, wip->emp_intensity, wip->emp_time);
3295 // spawn weapons - note the change from FS 1 multiplayer.
3296 if (wip->wi_flags & WIF_SPAWN){
3297 spawn_child_weapons(weapon_obj);
3301 void weapon_detonate(object *objp)
3303 SDL_assert(objp != NULL);
3307 SDL_assert((objp->type == OBJ_WEAPON) && (objp->instance >= 0));
3308 if((objp->type != OBJ_WEAPON) || (objp->instance < 0)){
3312 // send a detonate packet in multiplayer
3313 if(MULTIPLAYER_MASTER){
3314 send_weapon_detonate_packet(objp);
3318 weapon_hit(objp, NULL, &objp->pos);
3321 // Return the Weapon_info[] index of the weapon with name *name.
3322 int weapon_name_lookup(const char *name)
3326 for ( i=0; i < Num_weapon_types; i++) {
3327 if (!SDL_strcasecmp(name, Weapon_info[i].name)) {
3335 // Group_id: If you should quad lasers, they should all have the same group id.
3336 // This will be used to optimize lighting, since each group only needs to cast one light.
3337 // Call this to get a new group id, then pass it to each weapon_create call for all the
3338 // weapons in the group. Number will be between 0 and WEAPON_MAX_GROUP_IDS and will
3340 int weapon_create_group_id()
3342 static int current_id = 0;
3347 if ( current_id >= WEAPON_MAX_GROUP_IDS ) {
3354 void weapons_page_in()
3358 // Page in bitmaps for all weapons
3359 for (i=0; i<Num_weapon_types; i++ ) {
3360 weapon_info *wip = &Weapon_info[i];
3362 wip->wi_flags &= (~WIF_THRUSTER); // Assume no thrusters
3364 switch( wip->render_type ) {
3367 wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
3369 polymodel *pm = model_get( wip->model_num );
3371 // If it has a model, and the model pof has thrusters, then set
3373 if ( pm->n_thrusters > 0 ) {
3374 //mprintf(( "Weapon %s has thrusters!\n", wip->name ));
3375 wip->wi_flags |= WIF_THRUSTER;
3378 for (j=0; j<pm->n_textures; j++ ) {
3379 int bitmap_num = pm->original_textures[j];
3381 if ( bitmap_num > -1 ) {
3382 bm_page_in_texture( bitmap_num );
3390 bm_page_in_texture( wip->laser_bitmap );
3392 if(wip->laser_glow_bitmap >= 0){
3393 bm_page_in_texture(wip->laser_glow_bitmap);
3399 Int3(); // Invalid weapon rendering type.
3402 // If this has an impact vclip page it in.
3403 // if ( wip->impact_explosion_ani > -1 ) {
3404 // int nframes, fps;
3405 // bm_get_info( wip->impact_explosion_ani, NULL, NULL, NULL, &nframes, &fps );
3406 // bm_page_in_xparent_texture( wip->impact_explosion_ani, nframes );
3410 if ( (wip->wi_flags & WIF_TRAIL) && (wip->tr_info.bitmap > -1) ) {
3411 bm_page_in_texture( wip->tr_info.bitmap );
3414 // if this is a beam weapon, page in its stuff
3415 if(wip->wi_flags & WIF_BEAM){
3416 // all beam sections
3417 for(idx=0; idx<wip->b_info.beam_num_sections; idx++){
3418 if((idx < MAX_BEAM_SECTIONS) && (wip->b_info.sections[idx].texture >= 0)){
3419 bm_page_in_texture(wip->b_info.sections[idx].texture);
3424 if(wip->b_info.beam_glow_bitmap >= 0){
3425 bm_page_in_texture(wip->b_info.beam_glow_bitmap);
3429 if(wip->b_info.beam_particle_ani >= 0){
3431 bm_get_info( wip->b_info.beam_particle_ani, NULL, NULL, NULL, &nframes, &fps );
3432 bm_page_in_texture( wip->b_info.beam_particle_ani, nframes );
3438 for (i=0; i<Num_weapon_expl; i++) {
3439 int bitmap_handle, nframes, fps;
3441 for (j=0; j<Weapon_expl_info[i].lod_count; j++) {
3443 bitmap_handle = bm_load_animation(Weapon_expl_info[i].lod[j].filename, &nframes, &fps, 1);
3444 Weapon_expl_info[i].lod[j].bitmap_id = bitmap_handle;
3445 Weapon_expl_info[i].lod[j].fps = fps;
3446 Weapon_expl_info[i].lod[j].num_frames = nframes;
3449 bm_page_in_xparent_texture(bitmap_handle, nframes);
3454 for (i=0; i<Num_cmeasure_types; i++ ) {
3455 cmeasure_info *cmeasurep;
3457 cmeasurep = &Cmeasure_info[i];
3459 cmeasurep->model_num = model_load( cmeasurep->pof_name, 0, NULL );
3461 polymodel *pm = model_get( cmeasurep->model_num );
3463 for (j=0; j<pm->n_textures; j++ ) {
3464 int bitmap_num = pm->original_textures[j];
3466 if ( bitmap_num > -1 ) {
3467 bm_page_in_texture( bitmap_num );
3470 SDL_assert( cmeasurep->model_num > -1 );
3475 // call to get the "color" of the laser at the given moment (since glowing lasers can cycle colors)
3476 void weapon_get_laser_color(color *c, object *objp)
3488 SDL_assert(objp->type == OBJ_WEAPON);
3489 SDL_assert(objp->instance >= 0);
3490 SDL_assert(Weapons[objp->instance].weapon_info_index >= 0);
3491 if((objp->type != OBJ_WEAPON) || (objp->instance < 0) || (Weapons[objp->instance].weapon_info_index < 0)){
3494 wep = &Weapons[objp->instance];
3495 winfo = &Weapon_info[wep->weapon_info_index];
3497 // if we're a one-color laser
3498 if((winfo->laser_color_2.red == 0) && (winfo->laser_color_2.green == 0) && (winfo->laser_color_2.blue == 0)){
3499 *c = winfo->laser_color_1;
3503 pct = 1.0f - (wep->lifeleft / winfo->lifetime);
3506 } else if (pct < 0.0f)
3511 // otherwise interpolate between the colors
3512 gr_init_color( c, (int)((float)winfo->laser_color_1.red + (((float)winfo->laser_color_2.red - (float)winfo->laser_color_1.red) * pct)),
3513 (int)((float)winfo->laser_color_1.green + (((float)winfo->laser_color_2.green - (float)winfo->laser_color_1.green) * pct)),
3514 (int)((float)winfo->laser_color_1.blue + (((float)winfo->laser_color_2.blue - (float)winfo->laser_color_1.blue) * pct)) );
3517 // default weapon particle spew data
3518 int Weapon_particle_spew_count = 1;
3519 int Weapon_particle_spew_time = 25;
3520 float Weapon_particle_spew_vel = 0.4f;
3521 float Weapon_particle_spew_radius = 2.0f;
3522 float Weapon_particle_spew_lifetime = 0.15f;
3523 float Weapon_particle_spew_scale = 0.8f;
3525 // for weapons flagged as particle spewers, spew particles. wheee
3526 void weapon_maybe_spew_particle(object *obj)
3530 vector direct, direct_temp, particle_pos;
3531 vector null_vec = ZERO_VECTOR;
3536 SDL_assert(obj->type == OBJ_WEAPON);
3537 SDL_assert(obj->instance >= 0);
3538 SDL_assert(Weapons[obj->instance].weapon_info_index >= 0);
3539 SDL_assert(Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags & WIF_PARTICLE_SPEW);
3541 wp = &Weapons[obj->instance];
3543 // if the weapon's particle timestamp has elapse`d
3544 if((wp->particle_spew_time == -1) || timestamp_elapsed(wp->particle_spew_time)){
3545 // reset the timestamp
3546 wp->particle_spew_time = timestamp(Weapon_particle_spew_time);
3548 // spew some particles
3549 for(idx=0; idx<Weapon_particle_spew_count; idx++){
3550 // get the backward vector of the weapon
3551 direct = obj->orient.v.fvec;
3552 vm_vec_negate(&direct);
3554 // randomly perturb x, y and z
3557 ang = fl_radian(frand_range(-90.0f, 90.0f));
3558 vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.v.fvec);
3559 direct = direct_temp;
3560 vm_vec_scale(&direct, Weapon_particle_spew_scale);
3563 ang = fl_radian(frand_range(-90.0f, 90.0f));
3564 vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.v.rvec);
3565 direct = direct_temp;
3566 vm_vec_scale(&direct, Weapon_particle_spew_scale);
3569 ang = fl_radian(frand_range(-90.0f, 90.0f));
3570 vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.v.uvec);
3571 direct = direct_temp;
3572 vm_vec_scale(&direct, Weapon_particle_spew_scale);
3574 // get a velovity vector of some percentage of the weapon's velocity
3575 vel = obj->phys_info.vel;
3576 vm_vec_scale(&vel, Weapon_particle_spew_vel);
3578 // emit the particle
3579 vm_vec_add(&particle_pos, &obj->pos, &direct);
3580 particle_create(&particle_pos, &vel, Weapon_particle_spew_lifetime, Weapon_particle_spew_radius, PARTICLE_SMOKE);
3585 // debug console functionality
3586 void pspew_display_dcf()
3588 dc_printf("Particle spew settings\n\n");
3589 dc_printf("Particle spew count (pspew_count) : %d\n", Weapon_particle_spew_count);
3590 dc_printf("Particle spew time (pspew_time) : %d\n", Weapon_particle_spew_time);
3591 dc_printf("Particle spew velocity (pspew_vel) : %f\n", Weapon_particle_spew_vel);
3592 dc_printf("Particle spew size (pspew_size) : %f\n", Weapon_particle_spew_radius);
3593 dc_printf("Particle spew lifetime (pspew_life) : %f\n", Weapon_particle_spew_lifetime);
3594 dc_printf("Particle spew scale (psnew_scale) : %f\n", Weapon_particle_spew_scale);
3597 DCF(pspew_count, "Number of particles spewed at a time")
3599 dc_get_arg(ARG_INT);
3600 if(Dc_arg_type & ARG_INT){
3601 Weapon_particle_spew_count = Dc_arg_int;
3604 pspew_display_dcf();
3607 DCF(pspew_time, "Time between particle spews")
3609 dc_get_arg(ARG_INT);
3610 if(Dc_arg_type & ARG_INT){
3611 Weapon_particle_spew_time = Dc_arg_int;
3614 pspew_display_dcf();
3617 DCF(pspew_vel, "Relative velocity of particles (0.0 - 1.0)")
3619 dc_get_arg(ARG_FLOAT);
3620 if(Dc_arg_type & ARG_FLOAT){
3621 Weapon_particle_spew_vel = Dc_arg_float;
3624 pspew_display_dcf();
3627 DCF(pspew_size, "Size of spewed particles")
3629 dc_get_arg(ARG_FLOAT);
3630 if(Dc_arg_type & ARG_FLOAT){
3631 Weapon_particle_spew_radius = Dc_arg_float;
3634 pspew_display_dcf();
3637 DCF(pspew_life, "Lifetime of spewed particles")
3639 dc_get_arg(ARG_FLOAT);
3640 if(Dc_arg_type & ARG_FLOAT){
3641 Weapon_particle_spew_lifetime = Dc_arg_float;
3644 pspew_display_dcf();
3647 DCF(pspew_scale, "How far away particles are from the weapon path")
3649 dc_get_arg(ARG_FLOAT);
3650 if(Dc_arg_type & ARG_FLOAT){
3651 Weapon_particle_spew_scale = Dc_arg_float;
3654 pspew_display_dcf();
3657 // return a scale factor for damage which should be applied for 2 collisions
3658 float weapon_get_damage_scale(weapon_info *wip, object *wep, object *target)
3661 // don't do special damage scaling for capships in FS1
3667 int from_player = 0;
3668 float total_scale = 1.0f;
3670 int is_big_damage_ship = 0;
3673 if((wip == NULL) || (wep == NULL) || (target == NULL)){
3677 // don't scale any damage if its not a weapon
3678 if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){
3681 wp = &Weapons[wep->instance];
3683 // was the weapon fired by the player
3685 if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags & OF_PLAYER_SHIP)){
3689 // if this is a lockarm weapon, and it was fired unlocked
3690 if((wip->wi_flags & WIF_LOCKARM) && !(wp->weapon_flags & WF_LOCKED_WHEN_FIRED)){
3691 total_scale *= 0.1f;
3694 // if the hit object was a ship
3695 if(target->type == OBJ_SHIP){
3698 // get some info on the ship
3699 SDL_assert((target->instance >= 0) && (target->instance < MAX_SHIPS));
3700 if((target->instance < 0) || (target->instance >= MAX_SHIPS)){
3704 sip = &Ship_info[Ships[target->instance].ship_info_index];
3706 // get hull pct of the ship currently
3707 hull_pct = target->hull_strength / sip->initial_hull_strength;
3709 // if it has hit a supercap ship and is not a supercap class weapon
3710 if((sip->flags & SIF_SUPERCAP) && !(wip->wi_flags & WIF_SUPERCAP)){
3711 // if the supercap is around 3/4 damage, apply nothing
3712 if(hull_pct <= 0.75f){
3715 total_scale *= SUPERCAP_DAMAGE_SCALE;
3719 // determine if this is a big damage ship
3720 is_big_damage_ship = (sip->flags & SIF_BIG_DAMAGE);
3722 // if this is a large ship, and is being hit by flak
3723 if(is_big_damage_ship && (wip->wi_flags & WIF_FLAK)){
3724 total_scale *= FLAK_DAMAGE_SCALE;
3727 // if the player is firing small weapons at a big ship
3728 if( from_player && is_big_damage_ship && !(wip->wi_flags & (WIF_HURTS_BIG_SHIPS)) ){
3730 // if its a laser weapon
3731 if(wip->subtype == WP_LASER){
3732 total_scale *= 0.01f;
3734 total_scale *= 0.05f;
3738 // if the weapon is a small weapon being fired at a big ship
3739 if( is_big_damage_ship && !(wip->wi_flags & (WIF_HURTS_BIG_SHIPS)) ){
3740 if(hull_pct > 0.1f){
3741 total_scale *= hull_pct;
3752 int weapon_get_expl_handle(int weapon_expl_index, vector *pos, float size)
3754 weapon_expl_info *wei = &Weapon_expl_info[weapon_expl_index];
3756 if (wei->lod_count == 1) {
3757 return wei->lod[0].bitmap_id;
3760 // now we have to do some work
3762 int x, y, w, h, bm_size;
3768 extern float Viewer_zoom;
3769 extern int G3_count;
3775 g3_set_view_matrix(&Eye_position, &Eye_matrix, Viewer_zoom);
3777 // get extents of the rotated bitmap
3778 g3_rotate_vertex(&v, pos);
3780 // if vertex is behind, find size if in front, then drop down 1 LOD
3781 if (v.codes & CC_BEHIND) {
3782 float dist = vm_vec_dist_quick(&Eye_position, pos);
3786 vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.v.fvec, dist);
3787 g3_rotate_vertex(&v, &temp);
3789 // if still behind, bail and go with default
3790 if (v.codes & CC_BEHIND) {
3795 if (!g3_get_bitmap_dims(wei->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
3796 if (Detail.hardware_textures == 4) {
3800 } else if(w <= bm_size/2){
3802 } else if(w <= 1.3f*bm_size){
3808 // less aggressive LOD for lower detail settings
3811 } else if(w <= bm_size/3){
3813 } else if(w <= (1.15f*bm_size)){
3821 // if it's behind, bump up LOD by 1
3831 best_lod = SDL_min(best_lod, wei->lod_count - 1);
3832 return wei->lod[best_lod].bitmap_id;
3835 // -------------------------------------------------------------------------------------------------
3838 // called in game_shutdown() to free malloced memory
3840 // NOTE: do not call this function. It is only called from game_shutdown()
3845 // free info from parsed table data
3846 for (i=0; i<MAX_WEAPON_TYPES; i++) {
3847 if ( Weapon_info[i].desc != NULL ) {
3848 free(Weapon_info[i].desc);
3849 Weapon_info[i].desc = NULL;
3851 if ( Weapon_info[i].tech_desc != NULL ) {
3852 free(Weapon_info[i].tech_desc);
3853 Weapon_info[i].tech_desc = NULL;