mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-11 04:31:35 -10:00
9581 lines
285 KiB
C++
9581 lines
285 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* $Logfile: /Freespace2/code/Ship/Ship.cpp $
|
|
* $Revision: 2.1 $
|
|
* $Date: 2002-07-20 23:49:46 $
|
|
* $Author: DTP $
|
|
*
|
|
* Ship (and other object) handling functions
|
|
*
|
|
* $Log: not supported by cvs2svn $
|
|
* Revision 2.0 2002/06/03 04:02:28 penguin
|
|
* Warpcore CVS sync
|
|
*
|
|
* Revision 1.6 2002/05/16 00:43:32 mharris
|
|
* More ifndef NO_NETWORK
|
|
*
|
|
* Revision 1.5 2002/05/13 21:43:38 mharris
|
|
* A little more network and sound cleanup
|
|
*
|
|
* Revision 1.4 2002/05/13 21:09:29 mharris
|
|
* I think the last of the networking code has ifndef NO_NETWORK...
|
|
*
|
|
* Revision 1.3 2002/05/10 20:42:45 mharris
|
|
* use "ifndef NO_NETWORK" all over the place
|
|
*
|
|
* Revision 1.2 2002/05/03 22:07:10 mharris
|
|
* got some stuff to compile
|
|
*
|
|
* Revision 1.1 2002/05/02 18:03:12 mharris
|
|
* Initial checkin - converted filenames and includes to lower case
|
|
*
|
|
*
|
|
* 144 10/13/99 3:43p Jefff
|
|
* fixed unnumbered XSTRs
|
|
*
|
|
* 143 9/14/99 3:26a Dave
|
|
* Fixed laser fogging problem in nebula (D3D)> Fixed multiplayer
|
|
* respawn-too-early problem. Made a few crash points safe.
|
|
*
|
|
* 142 9/11/99 4:02p Dave
|
|
* Don't page in model textures when doing ship_model_change() in fred.
|
|
*
|
|
* 141 9/10/99 9:44p Dave
|
|
* Bumped version # up. Make server reliable connects not have such a huge
|
|
* timeout.
|
|
*
|
|
* 140 9/06/99 3:30p Mikek
|
|
* Added system to restrict weapon choices for dogfight missions.
|
|
*
|
|
* 139 9/01/99 10:15a Dave
|
|
*
|
|
* 138 9/01/99 8:43a Andsager
|
|
* supress WARNING from no_fred ships flag
|
|
*
|
|
* 137 8/26/99 8:52p Dave
|
|
* Gave multiplayer TvT messaging a heavy dose of sanity. Cheat codes.
|
|
*
|
|
* 136 8/26/99 6:08p Andsager
|
|
* Add debug code for lethality and number of turrets targeting player.
|
|
*
|
|
* 135 8/26/99 5:14p Andsager
|
|
*
|
|
* 134 8/26/99 9:45a Dave
|
|
* First pass at easter eggs and cheats.
|
|
*
|
|
* 133 8/24/99 4:25p Andsager
|
|
* Add ship-vanish sexp
|
|
*
|
|
* 132 8/23/99 11:59a Andsager
|
|
* Force choice of big fireball when Knossos destroyed. Allow logging of
|
|
* ship destroyed when no killer_name (ie, from debug).
|
|
*
|
|
* 131 8/23/99 11:09a Andsager
|
|
* Round 2 of Knossos explosion
|
|
*
|
|
* 130 8/20/99 5:09p Andsager
|
|
* Second pass on Knossos device explosion
|
|
*
|
|
* 129 8/18/99 10:59p Andsager
|
|
* Enable "b" key to target bombers.
|
|
*
|
|
* 128 8/18/99 12:09p Andsager
|
|
* Add debug if message has no anim for message. Make messages come from
|
|
* wing leader.
|
|
*
|
|
* 127 8/16/99 10:04p Andsager
|
|
* Add special-warp-dist and special-warpout-name sexp for Knossos device
|
|
* warpout.
|
|
*
|
|
* 126 8/16/99 4:06p Dave
|
|
* Big honking checkin.
|
|
*
|
|
* 125 8/16/99 2:01p Andsager
|
|
* Knossos warp-in warp-out.
|
|
*
|
|
* 124 8/13/99 10:49a Andsager
|
|
* Knossos and HUGE ship warp out. HUGE ship warp in. Stealth search
|
|
* modes dont collide big ships.
|
|
*
|
|
* 123 8/05/99 6:19p Dave
|
|
* New demo checksums.
|
|
*
|
|
* 122 8/05/99 12:57a Andsager
|
|
* the insanity of it all!
|
|
*
|
|
* 121 8/03/99 11:13a Andsager
|
|
* Bump up number of ship_subsystems for demo
|
|
*
|
|
* 120 8/02/99 10:39p Dave
|
|
* Added colored shields. OoOoOoooOoo
|
|
*
|
|
* 119 7/29/99 10:47p Dave
|
|
* Standardized D3D fogging using vertex fog. Shook out Savage 4 bugs.
|
|
*
|
|
* 118 7/29/99 12:05a Dave
|
|
* Nebula speed optimizations.
|
|
*
|
|
* 117 7/28/99 1:36p Andsager
|
|
* Modify cargo1 to include flag CARGO_NO_DEPLETE. Add sexp
|
|
* cargo-no-deplete (only for BIG / HUGE). Modify ship struct to pack
|
|
* better.
|
|
*
|
|
* 116 7/26/99 5:50p Dave
|
|
* Revised ingame join. Better? We'll see....
|
|
*
|
|
* 115 7/26/99 8:06a Andsager
|
|
* Consistent personas
|
|
*
|
|
* 114 7/24/99 5:22p Jefff
|
|
* Removed debug rendering of interpolated ships in multiplayer.
|
|
*
|
|
* 113 7/19/99 8:57p Andsager
|
|
* Added Ship_exited red_alert_carry flag and hull strength for RED ALERT
|
|
* carry over of Exited_ships
|
|
*
|
|
* 112 7/19/99 7:20p Dave
|
|
* Beam tooling. Specialized player-killed-self messages. Fixed d3d nebula
|
|
* pre-rendering.
|
|
*
|
|
* 111 7/19/99 12:02p Andsager
|
|
* Allow AWACS on any ship subsystem. Fix sexp_set_subsystem_strength to
|
|
* only blow up subsystem if its strength is > 0
|
|
*
|
|
* 110 7/18/99 9:56p Andsager
|
|
* Make max_ship_subsys 300 again!
|
|
*
|
|
* 109 7/18/99 5:20p Dave
|
|
* Jump node icon. Fixed debris fogging. Framerate warning stuff.
|
|
*
|
|
* 108 7/18/99 12:32p Dave
|
|
* Randomly oriented shockwaves.
|
|
*
|
|
* 107 7/15/99 6:36p Jamesa
|
|
* Moved default ship name into the ships.tbl
|
|
*
|
|
* 104 7/15/99 9:20a Andsager
|
|
* FS2_DEMO initial checkin
|
|
*
|
|
* 103 7/13/99 5:03p Alanl
|
|
* make sure object sounds get assigned to ships
|
|
*
|
|
* 102 7/09/99 5:54p Dave
|
|
* Seperated cruiser types into individual types. Added tons of new
|
|
* briefing icons. Campaign screen.
|
|
*
|
|
* 101 7/08/99 5:49p Andsager
|
|
* Fixed bug colliding with just warped in Cap ship
|
|
*
|
|
* 100 7/08/99 10:53a Dave
|
|
* New multiplayer interpolation scheme. Not 100% done yet, but still
|
|
* better than the old way.
|
|
*
|
|
* 99 7/06/99 4:24p Dave
|
|
* Mid-level checkin. Starting on some potentially cool multiplayer
|
|
* smoothness crap.
|
|
*
|
|
* 98 7/06/99 10:45a Andsager
|
|
* Modify engine wash to work on any ship that is not small. Add AWACS
|
|
* ask for help.
|
|
*
|
|
* 97 7/01/99 4:23p Dave
|
|
* Full support for multiple linked ambient engine sounds. Added "big
|
|
* damage" flag.
|
|
*
|
|
* 96 7/01/99 11:44a Dave
|
|
* Updated object sound system to allow multiple obj sounds per ship.
|
|
* Added hit-by-beam sound. Added killed by beam sound.
|
|
*
|
|
* 95 6/30/99 5:53p Dave
|
|
* Put in new anti-camper code.
|
|
*
|
|
* 94 6/20/99 12:06a Alanl
|
|
* new event music changes
|
|
*
|
|
* 93 6/16/99 4:06p Dave
|
|
* New pilot info popup. Added new draw-bitmap-as-poly function.
|
|
*
|
|
* 92 6/16/99 10:21a Dave
|
|
* Added send-message-list sexpression.
|
|
*
|
|
* 91 6/14/99 3:21p Andsager
|
|
* Allow collisions between ship and its debris. Fix up collision pairs
|
|
* when large ship is warping out.
|
|
*
|
|
* 90 6/10/99 3:43p Dave
|
|
* Do a better job of syncing text colors to HUD gauges.
|
|
*
|
|
* 89 6/07/99 4:21p Andsager
|
|
* Add HUD color for tagged object. Apply to target and radar.
|
|
*
|
|
* 88 6/04/99 5:08p Andsager
|
|
* Sort of hack to allow minicap and supercap tat the same time.
|
|
*
|
|
* 87 6/03/99 9:29p Andsager
|
|
* Remove special case for Asteroid ship LOD warning
|
|
*
|
|
* 86 6/03/99 11:43a Dave
|
|
* Added the ability to use a different model when rendering to the HUD
|
|
* target box.
|
|
*
|
|
* 85 6/01/99 8:35p Dave
|
|
* Finished lockarm weapons. Added proper supercap weapons/damage. Added
|
|
* awacs-set-radius sexpression.
|
|
*
|
|
* 84 5/28/99 9:26a Andsager
|
|
* Added check_world_pt_in_expanded_ship_bbox() function
|
|
*
|
|
* 83 5/26/99 4:00p Dave
|
|
* Fixed small lighting bug,
|
|
*
|
|
* 82 5/26/99 11:46a Dave
|
|
* Added ship-blasting lighting and made the randomization of lighting
|
|
* much more customizable.
|
|
*
|
|
* 81 5/24/99 5:45p Dave
|
|
* Added detail levels to the nebula, with a decent speedup. Split nebula
|
|
* lightning into its own section.
|
|
*
|
|
* 80 5/21/99 5:03p Andsager
|
|
* Add code to display engine wash death. Modify ship_kill_packet
|
|
*
|
|
* 79 5/20/99 7:00p Dave
|
|
* Added alternate type names for ships. Changed swarm missile table
|
|
* entries.
|
|
*
|
|
* 78 5/19/99 11:09a Andsager
|
|
* Turn on engine wash. Check every 1/4 sec.
|
|
*
|
|
* 77 5/18/99 1:30p Dave
|
|
* Added muzzle flash table stuff.
|
|
*
|
|
* 76 5/18/99 12:08p Andsager
|
|
* Added observer_process_post to handle observer too far away
|
|
*
|
|
* 75 5/18/99 11:15a Andsager
|
|
* Fix bug in mulitplayer max rangel
|
|
*
|
|
* 74 5/18/99 10:08a Andsager
|
|
* Modified single maximum range before blown up to also be multi
|
|
* friendly.
|
|
*
|
|
* 73 5/14/99 3:01p Andsager
|
|
* Fix bug in ship_do_cap_subsys_cargo_revealed
|
|
*
|
|
* 72 5/14/99 1:59p Andsager
|
|
* Multiplayer message for subsystem cargo revealed.
|
|
*
|
|
* 71 5/14/99 11:50a Andsager
|
|
* Added vaporize for SMALL ships hit by HUGE beams. Modified dying
|
|
* frame. Enlarged debris shards and range at which visible.
|
|
*
|
|
* 70 5/12/99 2:55p Andsager
|
|
* Implemented level 2 tag as priority in turret object selection
|
|
*
|
|
* 69 5/11/99 10:16p Andsager
|
|
* First pass on engine wash effect. Rotation (control input), damage,
|
|
* shake.
|
|
*
|
|
* 68 5/10/99 4:54p Dave
|
|
* Fixed particularly hideous subsystem bug related to multiple ship types
|
|
* using the same model.
|
|
*
|
|
* 67 4/30/99 12:18p Dave
|
|
* Several minor bug fixes.
|
|
*
|
|
* 66 4/29/99 2:29p Dave
|
|
* Made flak work much better in multiplayer.
|
|
*
|
|
* 65 4/28/99 11:13p Dave
|
|
* Temporary checkin of artillery code.
|
|
*
|
|
* 64 4/28/99 3:11p Andsager
|
|
* Stagger turret weapon fire times. Make turrets smarter when target is
|
|
* protected or beam protected. Add weaopn range to weapon info struct.
|
|
*
|
|
* 63 4/27/99 12:16a Dave
|
|
* Fixed beam weapon muzzle glow problem. Fixed premature timeout on the
|
|
* pxo server list screen. Fixed secondary firing for hosts on a
|
|
* standalone. Fixed wacky multiplayer weapon "shuddering" problem.
|
|
*
|
|
* 62 4/23/99 12:30p Andsager
|
|
* Add debug code for showing attack point against big ships.
|
|
*
|
|
* 61 4/23/99 12:01p Johnson
|
|
* Added SIF_HUGE_SHIP
|
|
*
|
|
* 60 4/20/99 6:39p Dave
|
|
* Almost done with artillery targeting. Added support for downloading
|
|
* images on the PXO screen.
|
|
*
|
|
* 59 4/19/99 11:01p Dave
|
|
* More sophisticated targeting laser support. Temporary checkin.
|
|
*
|
|
* 58 4/19/99 12:21p Johnson
|
|
* Allow ships with invisible polygons which do not collide
|
|
*
|
|
* 57 4/16/99 5:54p Dave
|
|
* Support for on/off style "stream" weapons. Real early support for
|
|
* target-painting lasers.
|
|
*
|
|
* 56 4/12/99 10:07p Dave
|
|
* Made network startup more forgiving. Added checkmarks to dogfight
|
|
* screen for players who hit commit.
|
|
*
|
|
* 55 4/02/99 9:55a Dave
|
|
* Added a few more options in the weapons.tbl for beam weapons. Attempt
|
|
* at putting "pain" packets into multiplayer.
|
|
*
|
|
* 54 3/31/99 8:24p Dave
|
|
* Beefed up all kinds of stuff, incluging beam weapons, nebula effects
|
|
* and background nebulae. Added per-ship non-dimming pixel colors.
|
|
*
|
|
* 53 3/30/99 5:40p Dave
|
|
* Fixed reinforcements for TvT in multiplayer.
|
|
*
|
|
* 52 3/29/99 6:17p Dave
|
|
* More work on demo system. Got just about everything in except for
|
|
* blowing ships up, secondary weapons and player death/warpout.
|
|
*
|
|
* 51 3/28/99 5:58p Dave
|
|
* Added early demo code. Make objects move. Nice and framerate
|
|
* independant, but not much else. Don't use yet unless you're me :)
|
|
*
|
|
* 50 3/26/99 5:23p Andsager
|
|
* Fix bug with special explostions sometimes not generating shockwaves.
|
|
*
|
|
* 49 3/26/99 4:49p Dave
|
|
* Made cruisers able to dock with stuff. Made docking points and paths
|
|
* visible in fred.
|
|
*
|
|
* 48 3/25/99 4:47p Johnson
|
|
* HACK allow Mycernus to dock with Enif
|
|
*
|
|
* 47 3/25/99 2:38p Johnson
|
|
* Give brad special mission docking stuff
|
|
*
|
|
* 46 3/25/99 1:30p Johnson
|
|
* Allow Arcadia/Mentu docking
|
|
*
|
|
* 45 3/24/99 4:05p Dave
|
|
* Put in support for assigning the player to a specific squadron with a
|
|
* specific logo. Preliminary work for doing pos/orient checksumming in
|
|
* multiplayer to reduce bandwidth.
|
|
*
|
|
* 44 3/23/99 2:29p Andsager
|
|
* Fix shockwaves for kamikazi and Fred defined. Collect together
|
|
* shockwave_create_info struct.
|
|
*
|
|
* 43 3/20/99 3:46p Dave
|
|
* Added support for model-based background nebulae. Added 3 new
|
|
* sexpressions.
|
|
*
|
|
* 42 3/19/99 9:51a Dave
|
|
* Checkin to repair massive source safe crash. Also added support for
|
|
* pof-style nebulae, and some new weapons code.
|
|
*
|
|
* 43 3/12/99 4:30p Anoop
|
|
* Check for OBJ_NONE as well as OBJ_GHOST when firing secondary weapons
|
|
*
|
|
* 42 3/11/99 2:22p Dave
|
|
* Fixed a countermeasure firing assert for multiplayer.
|
|
*
|
|
* 41 3/10/99 6:51p Dave
|
|
* Changed the way we buffer packets for all clients. Optimized turret
|
|
* fired packets. Did some weapon firing optimizations.
|
|
*
|
|
* 40 3/10/99 2:29p Dan
|
|
* disable lod warning for asteroid ships
|
|
*
|
|
* 39 3/09/99 6:24p Dave
|
|
* More work on object update revamping. Identified several sources of
|
|
* unnecessary bandwidth.
|
|
*
|
|
* 38 3/08/99 7:03p Dave
|
|
* First run of new object update system. Looks very promising.
|
|
*
|
|
* 37 3/05/99 1:33p Dave
|
|
* Upped subsystem max to 700
|
|
*
|
|
* 36 3/04/99 6:09p Dave
|
|
* Added in sexpressions for firing beams and checking for if a ship is
|
|
* tagged.
|
|
*
|
|
* 35 3/02/99 9:25p Dave
|
|
* Added a bunch of model rendering debug code. Started work on fixing
|
|
* beam weapon wacky firing.
|
|
*
|
|
* 34 3/01/99 7:39p Dave
|
|
* Added prioritizing ship respawns. Also fixed respawns in TvT so teams
|
|
* don't mix respawn points.
|
|
*
|
|
* 33 2/26/99 6:01p Andsager
|
|
* Add sexp has-been-tagged-delay and cap-subsys-cargo-known-delay
|
|
*
|
|
* 32 2/26/99 4:14p Dave
|
|
* Put in the ability to have multiple shockwaves for ships.
|
|
*
|
|
* 31 2/21/99 1:48p Dave
|
|
* Some code for monitoring datarate for multiplayer in detail.
|
|
*
|
|
* 30 2/19/99 3:52p Neilk
|
|
* Put in some proper handling code for undocking dying objects (handle
|
|
* wacky object types like OBJ_GHOST).
|
|
*
|
|
* 29 2/11/99 5:22p Andsager
|
|
* Fixed bugs, generalized block Sexp_variables
|
|
*
|
|
* 28 2/11/99 2:15p Andsager
|
|
* Add ship explosion modification to FRED
|
|
*
|
|
* 27 2/05/99 12:52p Dave
|
|
* Fixed Glide nondarkening textures.
|
|
*
|
|
* 26 2/03/99 12:42p Andsager
|
|
* Add escort priority. Modify ship_flags_dlg to include field. Save and
|
|
* Load. Add escort priority field to ship.
|
|
*
|
|
* 25 2/02/99 9:36a Andsager
|
|
* Bash hull strength to zero when ship is killed (by sexp)
|
|
*
|
|
* 24 1/29/99 2:25p Andsager
|
|
* Added turret_swarm_missiles
|
|
*
|
|
* 23 1/29/99 12:47a Dave
|
|
* Put in sounds for beam weapon. A bunch of interface screens (tech
|
|
* database stuff).
|
|
*
|
|
* 22 1/27/99 9:56a Dave
|
|
* Temporary checkin of beam weapons for Dan to make cool sounds.
|
|
*
|
|
* 21 1/25/99 5:03a Dave
|
|
* First run of stealth, AWACS and TAG missile support. New mission type
|
|
* :)
|
|
*
|
|
* 20 1/24/99 11:37p Dave
|
|
* First full rev of beam weapons. Very customizable. Removed some bogus
|
|
* Int3()'s in low level net code.
|
|
*
|
|
* 19 1/14/99 6:06p Dave
|
|
* 100% full squad logo support for single player and multiplayer.
|
|
*
|
|
* 18 1/14/99 12:48a Dave
|
|
* Todo list bug fixes. Made a pass at putting briefing icons back into
|
|
* FRED. Sort of works :(
|
|
*
|
|
* 17 1/12/99 5:45p Dave
|
|
* Moved weapon pipeline in multiplayer to almost exclusively client side.
|
|
* Very good results. Bandwidth goes down, playability goes up for crappy
|
|
* connections. Fixed object update problem for ship subsystems.
|
|
*
|
|
* 16 1/08/99 2:08p Dave
|
|
* Fixed software rendering for pofview. Super early support for AWACS and
|
|
* beam weapons.
|
|
*
|
|
* 15 1/06/99 2:24p Dave
|
|
* Stubs and release build fixes.
|
|
*
|
|
* 14 12/23/98 2:53p Andsager
|
|
* Added ship activation and gas collection subsystems, removed bridge
|
|
*
|
|
* 13 12/09/98 7:34p Dave
|
|
* Cleanup up nebula effect. Tweaked many values.
|
|
*
|
|
* 12 12/08/98 9:36a Dave
|
|
* Almost done nebula effect for D3D. Looks 85% as good as Glide.
|
|
*
|
|
* 11 12/06/98 2:36p Dave
|
|
* Drastically improved nebula fogging.
|
|
*
|
|
* 10 11/19/98 4:19p Dave
|
|
* Put IPX sockets back in psnet. Consolidated all multiplayer config
|
|
* files into one.
|
|
*
|
|
* 9 11/14/98 5:33p Dave
|
|
* Lots of nebula work. Put in ship contrails.
|
|
*
|
|
* 8 11/11/98 5:37p Dave
|
|
* Checkin for multiplayer testing.
|
|
*
|
|
* 7 10/26/98 9:42a Dave
|
|
* Early flak gun support.
|
|
*
|
|
* 6 10/23/98 3:51p Dave
|
|
* Full support for tstrings.tbl and foreign languages. All that remains
|
|
* is to make it active in Fred.
|
|
*
|
|
* 5 10/23/98 3:03p Andsager
|
|
* Initial support for changing rotation rate.
|
|
*
|
|
* 4 10/20/98 1:39p Andsager
|
|
* Make so sparks follow animated ship submodels. Modify
|
|
* ship_weapon_do_hit_stuff() and ship_apply_local_damage() to add
|
|
* submodel_num. Add submodel_num to multiplayer hit packet.
|
|
*
|
|
* 3 10/13/98 9:29a Dave
|
|
* Started neatening up freespace.h. Many variables renamed and
|
|
* reorganized. Added AlphaColors.[h,cpp]
|
|
*
|
|
* 2 10/07/98 10:53a Dave
|
|
* Initial checkin.
|
|
*
|
|
* 1 10/07/98 10:51a Dave
|
|
*
|
|
* 915 8/28/98 3:29p Dave
|
|
* EMP effect done. AI effects may need some tweaking as required.
|
|
*
|
|
* 914 8/25/98 1:48p Dave
|
|
* First rev of EMP effect. Player side stuff basically done. Next comes
|
|
* AI code.
|
|
*
|
|
* 913 8/17/98 5:07p Dave
|
|
* First rev of corkscrewing missiles.
|
|
*
|
|
* 912 7/15/98 11:29a Allender
|
|
* quick error dialog to prevent the > 50 ships per mission problem
|
|
*
|
|
* 911 7/06/98 6:11p Dave
|
|
* More object update stuff.
|
|
*
|
|
* 910 6/30/98 2:23p Dave
|
|
* Revised object update system. Removed updates for all weapons. Put
|
|
* button info back into control info packet.
|
|
*
|
|
* 909 6/12/98 2:49p Dave
|
|
* Patch 1.02 changes.
|
|
*
|
|
* 908 6/10/98 6:46p Lawrance
|
|
* increase SHIP_MULTITEXT_LENGTH to 1500
|
|
*
|
|
* 906 6/09/98 10:31a Hoffoss
|
|
* Created index numbers for all xstr() references. Any new xstr() stuff
|
|
* added from here on out should be added to the end if the list. The
|
|
* current list count can be found in FreeSpace.cpp (search for
|
|
* XSTR_SIZE).
|
|
*
|
|
* 905 6/01/98 11:43a John
|
|
* JAS & MK: Classified all strings for localization.
|
|
*
|
|
* 904 5/24/98 10:50p Mike
|
|
* Fix problem with ships with propagating explosions not being able to
|
|
* kamikaze.
|
|
*
|
|
* 903 5/23/98 4:14p John
|
|
* Added code to preload textures to video card for AGP. Added in code
|
|
* to page in some bitmaps that weren't getting paged in at level start.
|
|
*
|
|
* 902 5/23/98 12:05a Adam
|
|
* change ship_is_getting_locked() to take weapon range into account
|
|
*
|
|
* 901 5/22/98 5:32p Andsager
|
|
* Make big ship explosion sounds play all the way through. remove
|
|
* cur_snd from ship struct.
|
|
*
|
|
* 900 5/21/98 7:11p Sandeep
|
|
* Increased the buffer size a bit during parse ships.tbl to account for
|
|
* slightly lengthy descriptions
|
|
*
|
|
* 899 5/21/98 3:31p Allender
|
|
* fix bug where Ship_obj_list was getting overwritten by the exited ships
|
|
* list
|
|
*
|
|
* 898 5/21/98 1:44p Lawrance
|
|
* add ship_obj list validation
|
|
*
|
|
* 897 5/21/98 11:33a Lawrance
|
|
* Check aspect_locked_time when determining if another ship is seeking
|
|
* lock
|
|
*
|
|
* 896 5/19/98 8:42p Andsager
|
|
* Add cur_snd (used for sound management of big ship explosions)
|
|
*
|
|
* 895 5/18/98 2:50p Peter
|
|
* AL: Check to make sure we don't overflow support_ships[] array in
|
|
* ship_find_repair_ship
|
|
*
|
|
* 894 5/15/98 11:11p Mike
|
|
* Make game a bit easier based on skill level, mainly at Easy and, to a
|
|
* lesser extent, Medium.
|
|
*
|
|
* 893 5/15/98 6:45p Hoffoss
|
|
* Made some things not appear in the release version of Fred.
|
|
*
|
|
* 892 5/15/98 5:37p Hoffoss
|
|
* Added new 'tech description' fields to ship and weapon tables, and
|
|
* added usage of it in tech room.
|
|
*
|
|
* 891 5/15/98 12:07p Allender
|
|
* make messaging come from proper ships when in team vs. team games.
|
|
*
|
|
* 890 5/14/98 9:38p Andsager
|
|
* Fixed bug in dying_undock_physics when both ships have ovelapping dying
|
|
* times.
|
|
*
|
|
* 889 5/13/98 6:54p Dave
|
|
* More sophistication to PXO interface. Changed respawn checking so
|
|
* there's no window for desynchronization between the server and the
|
|
* clients.
|
|
*
|
|
* 888 5/12/98 10:54p Andsager
|
|
* Add new sound manager for big ship sub-explosion sounds
|
|
*
|
|
* 887 5/12/98 2:34p Adam
|
|
* re-instated the old nicer particle ANI's for ship explosions.
|
|
*
|
|
* 886 5/12/98 9:15a Andsager
|
|
* Clean up big ship sub-explosion sound. Make single instance of sounds.
|
|
* Choose random sounds. Add timestamp to post split sounds. Make
|
|
* explosion flash depend on wheth ship was visible if within ~1.5 radii.
|
|
*
|
|
* 885 5/11/98 4:33p Allender
|
|
* fixed ingame join problems -- started to work on new object updating
|
|
* code (currently ifdef'ed out)
|
|
*
|
|
* 884 5/11/98 4:05p Lawrance
|
|
* Increase sanity timestamp for next_fire_time to 60 seconds
|
|
*
|
|
* 883 5/10/98 11:30p Mike
|
|
* Better firing of bombs, less likely to go into strafe mode.
|
|
*
|
|
* 882 5/09/98 4:52p Lawrance
|
|
* Implement padlock view (up/rear/left/right)
|
|
*
|
|
* 881 5/08/98 5:31p Hoffoss
|
|
* Isolated the joystick force feedback code more from dependence on other
|
|
* libraries.
|
|
*
|
|
* 880 5/08/98 4:39p Mike
|
|
* Comment out two Asserts that trap a condition that is actually legal.
|
|
*
|
|
* $NoKeywords: $
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <setjmp.h>
|
|
|
|
#include "pstypes.h"
|
|
#include "object.h"
|
|
#include "physics.h"
|
|
#include "vecmat.h"
|
|
#include "ship.h"
|
|
#include "model.h"
|
|
#include "key.h"
|
|
#include "weapon.h"
|
|
#include "radar.h"
|
|
#include "2d.h"
|
|
#include "3d.h"
|
|
#include "floating.h"
|
|
#include "ai.h"
|
|
#include "ailocal.h"
|
|
#include "fireballs.h"
|
|
#include "debris.h"
|
|
#include "hud.h"
|
|
#include "timer.h"
|
|
#include "cfile.h"
|
|
#include "missionlog.h"
|
|
#include "missionparse.h"
|
|
#include "bmpman.h"
|
|
#include "joy.h"
|
|
#include "joy_ff.h"
|
|
#include "player.h"
|
|
#include "parselo.h"
|
|
#include "freespace.h"
|
|
#include "sound.h"
|
|
#include "model.h"
|
|
#include "linklist.h"
|
|
#include "hudets.h"
|
|
#include "hudtarget.h"
|
|
#include "hudshield.h"
|
|
#include "aigoals.h"
|
|
#include "gamesnd.h"
|
|
#include "eventmusic.h"
|
|
#include "shipfx.h"
|
|
#include "sexp.h"
|
|
#include "gamesequence.h"
|
|
#include "objectsnd.h"
|
|
#include "cmeasure.h"
|
|
#include "animplay.h"
|
|
#include "controlsconfig.h"
|
|
#include "afterburner.h"
|
|
#include "shockwave.h"
|
|
#include "hudsquadmsg.h"
|
|
#include "swarm.h"
|
|
#include "fvi.h"
|
|
#include "subsysdamage.h"
|
|
#include "missionmessage.h"
|
|
#include "lighting.h"
|
|
#include "particle.h"
|
|
#include "shiphit.h"
|
|
#include "asteroid.h"
|
|
#include "hudtargetbox.h"
|
|
#include "hudwingmanstatus.h"
|
|
#include "jumpnode.h"
|
|
#include "redalert.h"
|
|
#include "corkscrew.h"
|
|
#include "emp.h"
|
|
#include "localize.h"
|
|
#include "neb.h"
|
|
#include "shipcontrails.h"
|
|
#include "alphacolors.h"
|
|
#include "demo.h"
|
|
#include "beam.h"
|
|
#include "staticrand.h"
|
|
#include "missionshipchoice.h"
|
|
#include "hudartillery.h"
|
|
|
|
#ifndef NO_NETWORK
|
|
#include "multi.h"
|
|
#include "multiutil.h"
|
|
#include "multimsgs.h"
|
|
#include "multi_respawn.h"
|
|
#endif
|
|
|
|
#ifdef FS2_DEMO
|
|
#define MAX_SHIP_SUBOBJECTS 360
|
|
#else
|
|
#define MAX_SHIP_SUBOBJECTS 700 // Reduced from 1000 to 400 by MK on 4/1/98.
|
|
// Highest I saw was 164 in sm2-03a which Sandeep says has a lot of ships.
|
|
// JAS: sm3-01 needs 460. You cannot know this number until *all* ships
|
|
// have warped in. So I put code in the paging code which knows all ships
|
|
// that will warp in.
|
|
#endif
|
|
|
|
//#define MIN_COLLISION_MOVE_DIST 5.0
|
|
//#define COLLISION_VEL_CONST 0.1
|
|
#define COLLISION_FRICTION_FACTOR 0.0 // ratio of maximum friction impulse to repulsion impulse
|
|
#define COLLISION_ROTATION_FACTOR 1.0 // increase in rotation from collision
|
|
|
|
int Ai_render_debug_flag=0;
|
|
#ifndef NDEBUG
|
|
int Ship_sphere_check = 0;
|
|
int Ship_auto_repair = 1; // flag to indicate auto-repair of subsystem should occur
|
|
extern void render_path_points(object *objp);
|
|
#endif
|
|
|
|
// mwa -- removed 11/24/97 int num_ships = 0;
|
|
int num_wings = 0;
|
|
int Num_reinforcements = 0;
|
|
ship Ships[MAX_SHIPS];
|
|
ship *Player_ship;
|
|
wing Wings[MAX_WINGS];
|
|
int Starting_wings[MAX_PLAYER_WINGS]; // wings player starts a mission with (-1 = none)
|
|
int ships_inited = 0;
|
|
|
|
engine_wash_info Engine_wash_info[MAX_ENGINE_WASH_TYPES];
|
|
char get_engine_wash_index(char *engine_wash_name);
|
|
|
|
// information for ships which have exited the game
|
|
exited_ship Ships_exited[MAX_EXITED_SHIPS];
|
|
int Num_exited_ships;
|
|
|
|
int Num_engine_wash_types;
|
|
int Num_ship_types;
|
|
int Num_ship_subobj_types;
|
|
int Num_ship_subobjects;
|
|
int Player_ship_class; // needs to be player specific, move to player structure
|
|
|
|
#define SHIP_OBJ_USED (1<<0) // flag used in ship_obj struct
|
|
#define MAX_SHIP_OBJS MAX_SHIPS // max number of ships tracked in ship list
|
|
ship_obj Ship_objs[MAX_SHIP_OBJS]; // array used to store ship object indexes
|
|
ship_obj Ship_obj_list; // head of linked list of ship_obj structs
|
|
|
|
ship_info Ship_info[MAX_SHIP_TYPES];
|
|
ship_subsys Ship_subsystems[MAX_SHIP_SUBOBJECTS];
|
|
ship_subsys ship_subsys_free_list;
|
|
reinforcements Reinforcements[MAX_REINFORCEMENTS];
|
|
|
|
int Num_player_ship_precedence; // Number of ship types in Player_ship_precedence
|
|
int Player_ship_precedence[MAX_PLAYER_SHIP_CHOICES]; // Array of ship types, precedence list for player ship/wing selection
|
|
|
|
static int Laser_energy_out_snd_timer; // timer so we play out of laser sound effect periodically
|
|
static int Missile_out_snd_timer; // timer so we play out of laser sound effect periodically
|
|
|
|
// structure used to hold ship counts of particular types. The order in which these appear is crucial
|
|
// since the goal code relies on this placement to find the array index in the Ship_counts array
|
|
char *Ship_type_names[MAX_SHIP_TYPE_COUNTS] = {
|
|
//XSTR:OFF
|
|
"no type",
|
|
"cargo",
|
|
"fighter/bomber",
|
|
"cruiser",
|
|
"freighter",
|
|
"capital",
|
|
"transport",
|
|
"support",
|
|
"navbuoy",
|
|
"sentry gun",
|
|
"escape pod",
|
|
"super cap",
|
|
"stealth",
|
|
"fighter",
|
|
"bomber",
|
|
"drydock",
|
|
"awacs",
|
|
"gas miner",
|
|
"corvette",
|
|
"knossos device"
|
|
//XSTR:ON
|
|
};
|
|
|
|
int Ship_type_flags[MAX_SHIP_TYPE_COUNTS] = {
|
|
0,
|
|
SIF_CARGO,
|
|
SIF_FIGHTER | SIF_BOMBER,
|
|
SIF_CRUISER,
|
|
SIF_FREIGHTER,
|
|
SIF_CAPITAL,
|
|
SIF_TRANSPORT,
|
|
SIF_SUPPORT,
|
|
SIF_NAVBUOY,
|
|
SIF_SENTRYGUN,
|
|
SIF_ESCAPEPOD,
|
|
SIF_SUPERCAP,
|
|
SIF_STEALTH,
|
|
SIF_FIGHTER,
|
|
SIF_BOMBER,
|
|
SIF_DRYDOCK,
|
|
SIF_AWACS,
|
|
SIF_GAS_MINER,
|
|
SIF_CORVETTE,
|
|
SIF_KNOSSOS_DEVICE,
|
|
};
|
|
|
|
ship_counts Ship_counts[MAX_SHIP_TYPE_COUNTS];
|
|
|
|
// I don't want to do an AI cargo check every frame, so I made a global timer to limit check to
|
|
// every SHIP_CARGO_CHECK_INTERVAL ms. Didn't want to make a timer in each ship struct. Ensure
|
|
// inited to 1 at mission start.
|
|
static int Ship_cargo_check_timer;
|
|
|
|
// a global definition of the IFF colors
|
|
color IFF_colors[MAX_IFF_COLORS][2]; // AL 1-2-97: Create two IFF colors, regular and bright
|
|
|
|
void ship_iff_init_colors()
|
|
{
|
|
int i, alpha;
|
|
int iff_bright_delta=4;
|
|
|
|
// init IFF colors
|
|
for ( i=0; i<2; i++ ) {
|
|
|
|
if ( i == 0 )
|
|
alpha = (HUD_COLOR_ALPHA_MAX - iff_bright_delta) * 16;
|
|
else
|
|
alpha = HUD_COLOR_ALPHA_MAX * 16;
|
|
|
|
gr_init_alphacolor( &IFF_colors[IFF_COLOR_HOSTILE][i], 0xff, 0x00, 0x00, alpha );
|
|
gr_init_alphacolor( &IFF_colors[IFF_COLOR_FRIENDLY][i], 0x00, 0xff, 0x00, alpha );
|
|
gr_init_alphacolor( &IFF_colors[IFF_COLOR_NEUTRAL][i], 0xff, 0x00, 0x00, alpha );
|
|
gr_init_alphacolor( &IFF_colors[IFF_COLOR_UNKNOWN][i], 0xff, 0x00, 0xff, alpha );
|
|
gr_init_alphacolor( &IFF_colors[IFF_COLOR_SELECTION][i], 0xff, 0xff, 0xff, alpha );
|
|
gr_init_alphacolor( &IFF_colors[IFF_COLOR_MESSAGE][i], 0x7f, 0x7f, 0x7f, alpha );
|
|
gr_init_alphacolor( &IFF_colors[IFF_COLOR_TAGGED][i], 0xff, 0xff, 0x00, alpha );
|
|
}
|
|
}
|
|
|
|
// set the ship_obj struct fields to default values
|
|
void ship_obj_list_reset_slot(int index)
|
|
{
|
|
Ship_objs[index].flags = 0;
|
|
Ship_objs[index].next = NULL;
|
|
Ship_objs[index].prev = (ship_obj*)-1;
|
|
}
|
|
|
|
// if the given ship is in alpha/beta/gamma/zeta wings
|
|
int ship_in_abgz(ship *shipp)
|
|
{
|
|
if(!strcmp(shipp->ship_name, "Alpha 1")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Alpha 2")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Alpha 3")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Alpha 4")) return 1;
|
|
|
|
if(!strcmp(shipp->ship_name, "Beta 1")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Beta 2")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Beta 3")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Beta 4")) return 1;
|
|
|
|
if(!strcmp(shipp->ship_name, "Gamma 1")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Gamma 2")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Gamma 3")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Gamma 4")) return 1;
|
|
|
|
if(!strcmp(shipp->ship_name, "Zeta 1")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Zeta 2")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Zeta 3")) return 1;
|
|
if(!strcmp(shipp->ship_name, "Zeta 4")) return 1;
|
|
|
|
// not in
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------
|
|
// ship_obj_list_init()
|
|
//
|
|
void ship_obj_list_init()
|
|
{
|
|
int i;
|
|
|
|
list_init(&Ship_obj_list);
|
|
for ( i = 0; i < MAX_SHIP_OBJS; i++ ) {
|
|
ship_obj_list_reset_slot(i);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------
|
|
// ship_obj_list_add()
|
|
//
|
|
// Function to add a node to the Ship_obj_list. Only
|
|
// called from ship_create()
|
|
int ship_obj_list_add(int objnum)
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_SHIP_OBJS; i++ ) {
|
|
if ( !(Ship_objs[i].flags & SHIP_OBJ_USED) )
|
|
break;
|
|
}
|
|
if ( i == MAX_SHIP_OBJS ) {
|
|
Error(LOCATION, "Fatal Error: Ran out of ship object nodes\n");
|
|
return -1;
|
|
}
|
|
|
|
Ship_objs[i].flags = 0;
|
|
Ship_objs[i].objnum = objnum;
|
|
list_append(&Ship_obj_list, &Ship_objs[i]);
|
|
Ship_objs[i].flags |= SHIP_OBJ_USED;
|
|
|
|
return i;
|
|
}
|
|
|
|
// ---------------------------------------------------
|
|
// ship_obj_list_remove()
|
|
//
|
|
// Function to remove a node from the Ship_obj_list. Only
|
|
// called from ship_delete()
|
|
void ship_obj_list_remove(int index)
|
|
{
|
|
Assert(index >= 0 && index < MAX_SHIP_OBJS);
|
|
list_remove(&Ship_obj_list, &Ship_objs[index]);
|
|
ship_obj_list_reset_slot(index);
|
|
}
|
|
|
|
// ---------------------------------------------------
|
|
// ship_obj_list_rebuild()
|
|
//
|
|
// Called from the save/restore code to re-create the Ship_obj_list
|
|
//
|
|
void ship_obj_list_rebuild()
|
|
{
|
|
object *objp;
|
|
|
|
ship_obj_list_init();
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ( objp->type == OBJ_SHIP ) {
|
|
Ships[objp->instance].ship_list_index = ship_obj_list_add(OBJ_INDEX(objp));
|
|
}
|
|
}
|
|
}
|
|
|
|
ship_obj *get_ship_obj_ptr_from_index(int index)
|
|
{
|
|
Assert(index >= 0 && index < MAX_SHIP_OBJS);
|
|
return &Ship_objs[index];
|
|
}
|
|
|
|
|
|
// return number of ships in the game.
|
|
int ship_get_num_ships()
|
|
{
|
|
int count;
|
|
ship_obj *so;
|
|
|
|
count = 0;
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) )
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
// parse an engine wash info record
|
|
void parse_engine_wash()
|
|
{
|
|
engine_wash_info *ewp;
|
|
ewp = &Engine_wash_info[Num_engine_wash_types];
|
|
|
|
// name of engine wash info
|
|
required_string("$Name:");
|
|
stuff_string(ewp->name, F_NAME, NULL);
|
|
|
|
// half angle of cone of wash from thruster
|
|
required_string("$Angle:");
|
|
stuff_float(&ewp->angle);
|
|
ewp->angle *= (PI / 180.0f);
|
|
|
|
// radius multiplier for hemisphere around thruster pt
|
|
required_string("$Radius Mult:");
|
|
stuff_float(&ewp->radius_mult);
|
|
|
|
// length of cone
|
|
required_string("$Length:");
|
|
stuff_float(&ewp->length);
|
|
|
|
// intensity inside hemisphere (or at 0 distance from frustated cone)
|
|
required_string("$Intensity:");
|
|
stuff_float(&ewp->intensity);
|
|
}
|
|
|
|
|
|
#define SHIP_MULTITEXT_LENGTH 1500
|
|
// function to parse the information for a specific ship type.
|
|
int parse_ship()
|
|
{
|
|
char buf[SHIP_MULTITEXT_LENGTH + 1];
|
|
ship_info *sip;
|
|
int cont_flag = 1;
|
|
float hull_percentage_of_hits = 100.0f;
|
|
int n_subsystems = 0;
|
|
model_subsystem subsystems[MAX_MODEL_SUBSYSTEMS]; // see model.h for max_model_subsystems
|
|
for (int idx=0; idx<MAX_MODEL_SUBSYSTEMS; idx++) {
|
|
subsystems[idx].stepped_rotation = NULL;
|
|
subsystems[idx].ai_rotation = NULL;
|
|
}
|
|
int i, num_allowed, rtn = 0;
|
|
int allowed_weapons[MAX_WEAPON_TYPES];
|
|
|
|
sip = &Ship_info[Num_ship_types];
|
|
|
|
// Defaults!
|
|
// These should be specified in ships.tbl eventually!
|
|
// End of defaults.
|
|
|
|
required_string("$Name:");
|
|
stuff_string(sip->name, F_NAME, NULL);
|
|
|
|
// AL 28-3-98: If this is a demo build, we only want to parse weapons that are preceded with
|
|
// the '@' symbol
|
|
#ifdef DEMO // not needed FS2_DEMO (using separate table file)
|
|
if ( sip->name[0] != '@' ) {
|
|
// advance to next weapon, and return -1
|
|
if ( skip_to_start_of_strings("$Name:", "#End") != 1 ) {
|
|
Int3();
|
|
}
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef NDEBUG
|
|
if (strchr(sip->name, '#') && Fred_running)
|
|
rtn = 1;
|
|
#endif
|
|
|
|
if ( sip->name[0] == '@' ) {
|
|
char old_name[NAME_LENGTH];
|
|
strcpy(old_name, sip->name);
|
|
strcpy(sip->name, old_name+1);
|
|
}
|
|
|
|
diag_printf ("Ship name -- %s\n", sip->name);
|
|
if ( ship_info_lookup( sip->name ) != -1 ){
|
|
Error(LOCATION, "Error: Ship name %s already exists in ships.tbl. All ship class names must be unique.", sip->name);
|
|
}
|
|
|
|
required_string("$Short name:");
|
|
stuff_string(sip->short_name, F_NAME, NULL);
|
|
diag_printf ("Ship short name -- %s\n", sip->short_name);
|
|
|
|
find_and_stuff("$Species:", &sip->species, F_NAME, Species_names, MAX_SPECIES_NAMES, "species names");
|
|
diag_printf ("Ship species -- %s\n", Species_names[sip->species]);
|
|
|
|
sip->type_str = sip->maneuverability_str = sip->armor_str = sip->manufacturer_str = NULL;
|
|
if (optional_string("+Type:")) {
|
|
stuff_string(buf, F_MESSAGE, NULL);
|
|
sip->type_str = strdup(buf);
|
|
}
|
|
|
|
if (optional_string("+Maneuverability:")) {
|
|
stuff_string(buf, F_MESSAGE, NULL);
|
|
sip->maneuverability_str = strdup(buf);
|
|
}
|
|
|
|
if (optional_string("+Armor:")) {
|
|
stuff_string(buf, F_MESSAGE, NULL);
|
|
sip->armor_str = strdup(buf);
|
|
}
|
|
|
|
if (optional_string("+Manufacturer:")) {
|
|
stuff_string(buf, F_MESSAGE, NULL);
|
|
sip->manufacturer_str = strdup(buf);
|
|
}
|
|
|
|
sip->desc = NULL;
|
|
if (optional_string("+Description:")) {
|
|
stuff_string(buf, F_MULTITEXT, NULL);
|
|
sip->desc = strdup(buf);
|
|
}
|
|
|
|
sip->tech_desc = NULL;
|
|
if (optional_string("+Tech Description:")) {
|
|
stuff_string(buf, F_MULTITEXT, NULL, SHIP_MULTITEXT_LENGTH);
|
|
sip->tech_desc = strdup(buf);
|
|
}
|
|
|
|
// Code added here by SS to parse the optional strings for length, gun_mounts, missile_banks
|
|
|
|
sip->ship_length = NULL;
|
|
if (optional_string("+Length:")) {
|
|
stuff_string(buf, F_MESSAGE, NULL);
|
|
sip->ship_length = strdup(buf);
|
|
}
|
|
|
|
sip->gun_mounts = NULL;
|
|
if (optional_string("+Gun Mounts:")) {
|
|
stuff_string(buf, F_MESSAGE, NULL);
|
|
sip->gun_mounts = strdup(buf);
|
|
}
|
|
|
|
sip->missile_banks = NULL;
|
|
if (optional_string("+Missile Banks:")) {
|
|
stuff_string(buf, F_MESSAGE, NULL);
|
|
sip->missile_banks = strdup(buf);
|
|
}
|
|
|
|
|
|
// End code by SS
|
|
|
|
sip->num_detail_levels = 0;
|
|
|
|
required_string( "$POF file:" );
|
|
stuff_string( sip->pof_file, F_NAME, NULL );
|
|
|
|
// optional hud targeting model
|
|
strcpy(sip->pof_file_hud, "");
|
|
if(optional_string( "$POF target file:")){
|
|
stuff_string(sip->pof_file_hud, F_NAME, NULL);
|
|
}
|
|
|
|
required_string("$Detail distance:");
|
|
sip->num_detail_levels = stuff_int_list(sip->detail_distance, MAX_SHIP_DETAIL_LEVELS, RAW_INTEGER_TYPE);
|
|
|
|
// check for optional pixel colors
|
|
sip->num_nondark_colors = 0;
|
|
while(optional_string("$ND:")){
|
|
ubyte nr, ng, nb;
|
|
stuff_byte(&nr);
|
|
stuff_byte(&ng);
|
|
stuff_byte(&nb);
|
|
|
|
if(sip->num_nondark_colors < MAX_NONDARK_COLORS){
|
|
sip->nondark_colors[sip->num_nondark_colors][0] = nr;
|
|
sip->nondark_colors[sip->num_nondark_colors][1] = ng;
|
|
sip->nondark_colors[sip->num_nondark_colors++][2] = nb;
|
|
}
|
|
}
|
|
|
|
required_string("$Show damage:");
|
|
int bogus_bool;
|
|
stuff_boolean(&bogus_bool);
|
|
|
|
required_string("$Density:");
|
|
stuff_float( &(sip->density) );
|
|
diag_printf ("Ship density -- %7.3f\n", sip->density);
|
|
|
|
required_string("$Damp:");
|
|
stuff_float( &(sip->damp) );
|
|
diag_printf ("Ship damp -- %7.3f\n", sip->damp);
|
|
|
|
required_string("$Rotdamp:");
|
|
stuff_float( &(sip->rotdamp) );
|
|
diag_printf ("Ship rotdamp -- %7.3f\n", sip->rotdamp);
|
|
|
|
required_string("$Max Velocity:");
|
|
stuff_vector(&sip->max_vel);
|
|
|
|
// calculate the max speed from max_velocity
|
|
sip->max_speed = vm_vec_mag(&sip->max_vel);
|
|
|
|
required_string("$Rotation Time:");
|
|
stuff_vector(&sip->rotation_time);
|
|
|
|
sip->srotation_time = (sip->rotation_time.xyz.x + sip->rotation_time.xyz.y)/2.0f;
|
|
|
|
sip->max_rotvel.xyz.x = (2 * PI) / sip->rotation_time.xyz.x;
|
|
sip->max_rotvel.xyz.y = (2 * PI) / sip->rotation_time.xyz.y;
|
|
sip->max_rotvel.xyz.z = (2 * PI) / sip->rotation_time.xyz.z;
|
|
|
|
// get the backwards velocity;
|
|
required_string("$Rear Velocity:");
|
|
stuff_float(&sip->max_rear_vel);
|
|
|
|
// get the accelerations
|
|
required_string("$Forward accel:");
|
|
stuff_float(&sip->forward_accel );
|
|
|
|
required_string("$Forward decel:");
|
|
stuff_float(&sip->forward_decel );
|
|
|
|
required_string("$Slide accel:");
|
|
stuff_float(&sip->slide_accel );
|
|
|
|
required_string("$Slide decel:");
|
|
stuff_float(&sip->slide_decel );
|
|
|
|
// get ship explosion info
|
|
required_string("$Expl inner rad:");
|
|
stuff_float(&sip->inner_rad);
|
|
|
|
required_string("$Expl outer rad:");
|
|
stuff_float(&sip->outer_rad);
|
|
|
|
required_string("$Expl damage:");
|
|
stuff_float(&sip->damage);
|
|
|
|
required_string("$Expl blast:");
|
|
stuff_float(&sip->blast);
|
|
|
|
required_string("$Expl Propagates:");
|
|
stuff_boolean(&sip->explosion_propagates);
|
|
|
|
required_string("$Shockwave Speed:");
|
|
stuff_float( &sip->shockwave_speed );
|
|
|
|
sip->shockwave_count = 1;
|
|
if(optional_string("$Shockwave Count:")){
|
|
stuff_int(&sip->shockwave_count);
|
|
}
|
|
|
|
for ( i = 0; i < MAX_WEAPON_TYPES; i++ ){
|
|
sip->allowed_weapons[i] = 0;
|
|
}
|
|
|
|
// Set the weapons filter used in weapons loadout (for primary weapons)
|
|
if (optional_string("$Allowed PBanks:")) {
|
|
num_allowed = stuff_int_list(allowed_weapons, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
|
|
|
|
// actually say which weapons are allowed
|
|
for ( i = 0; i < num_allowed; i++ ) {
|
|
if ( allowed_weapons[i] >= 0 ) { // MK, Bug fix, 9/6/99. Used to be "allowed_weapons" not "allowed_weapons[i]".
|
|
sip->allowed_weapons[allowed_weapons[i]] |= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the weapons filter used in weapons loadout (for primary weapons)
|
|
if (optional_string("$Allowed Dogfight PBanks:")) {
|
|
num_allowed = stuff_int_list(allowed_weapons, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
|
|
|
|
// actually say which weapons are allowed
|
|
for ( i = 0; i < num_allowed; i++ ) {
|
|
if ( allowed_weapons[i] >= 0 ) {
|
|
sip->allowed_weapons[allowed_weapons[i]] |= 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get default primary bank weapons
|
|
for ( i = 0; i < MAX_PRIMARY_BANKS; i++ ){
|
|
sip->primary_bank_weapons[i] = -1;
|
|
}
|
|
required_string("$Default PBanks:");
|
|
sip->num_primary_banks = stuff_int_list(sip->primary_bank_weapons, MAX_PRIMARY_BANKS, WEAPON_LIST_TYPE);
|
|
|
|
// error checking
|
|
for ( i = 0; i < sip->num_primary_banks; i++ ) {
|
|
Assert(sip->primary_bank_weapons[i] >= 0);
|
|
}
|
|
|
|
// Set the weapons filter used in weapons loadout (for secondary weapons)
|
|
if (optional_string("$Allowed SBanks:")) {
|
|
num_allowed = stuff_int_list(allowed_weapons, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
|
|
|
|
// actually say which weapons are allowed
|
|
for ( i = 0; i < num_allowed; i++ ) {
|
|
if ( allowed_weapons[i] >= 0 ) {
|
|
sip->allowed_weapons[allowed_weapons[i]] |= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the weapons filter used in weapons loadout (for secondary weapons)
|
|
if (optional_string("$Allowed Dogfight SBanks:")) {
|
|
num_allowed = stuff_int_list(allowed_weapons, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
|
|
|
|
// actually say which weapons are allowed
|
|
for ( i = 0; i < num_allowed; i++ ) {
|
|
if ( allowed_weapons[i] >= 0 ) {
|
|
sip->allowed_weapons[allowed_weapons[i]] |= 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get default secondary bank weapons
|
|
for ( i = 0; i < MAX_SECONDARY_BANKS; i++ ){
|
|
sip->secondary_bank_weapons[i] = -1;
|
|
}
|
|
required_string("$Default SBanks:");
|
|
sip->num_secondary_banks = stuff_int_list(sip->secondary_bank_weapons, MAX_SECONDARY_BANKS, WEAPON_LIST_TYPE);
|
|
|
|
// error checking
|
|
for ( i = 0; i < sip->num_secondary_banks; i++ ) {
|
|
Assert(sip->secondary_bank_weapons[i] >= 0);
|
|
}
|
|
|
|
// Get the capacity of each secondary bank
|
|
required_string("$Sbank Capacity:");
|
|
int capacity_count;
|
|
capacity_count = stuff_int_list(sip->secondary_bank_ammo_capacity, MAX_SECONDARY_BANKS, RAW_INTEGER_TYPE);
|
|
if ( capacity_count != sip->num_secondary_banks ) {
|
|
Warning(LOCATION, "Secondary bank capacities have not been specified for ship class %s... fix this!!", sip->name);
|
|
}
|
|
|
|
required_string("$Shields:");
|
|
stuff_float(&sip->shields);
|
|
|
|
// optional shield color
|
|
sip->shield_color[0] = 255;
|
|
sip->shield_color[1] = 255;
|
|
sip->shield_color[2] = 255;
|
|
if(optional_string("$Shield Color:")){
|
|
stuff_byte(&sip->shield_color[0]);
|
|
stuff_byte(&sip->shield_color[1]);
|
|
stuff_byte(&sip->shield_color[2]);
|
|
}
|
|
|
|
// The next three fields are used for the ETS
|
|
required_string("$Power Output:");
|
|
stuff_float(&sip->power_output);
|
|
|
|
required_string("$Max Oclk Speed:");
|
|
stuff_float(&sip->max_overclocked_speed);
|
|
|
|
required_string("$Max Weapon Eng:");
|
|
stuff_float(&sip->max_weapon_reserve);
|
|
|
|
required_string("$Hitpoints:");
|
|
stuff_float(&sip->initial_hull_strength);
|
|
|
|
required_string("$Flags:");
|
|
char ship_strings[MAX_SHIP_FLAGS][NAME_LENGTH];
|
|
int num_strings = stuff_string_list(ship_strings, MAX_SHIP_FLAGS);
|
|
sip->flags = SIF_DEFAULT_VALUE;
|
|
for ( i=0; i<num_strings; i++ ) {
|
|
if (!stricmp(NOX("no_collide"), ship_strings[i]))
|
|
sip->flags &= ~SIF_DO_COLLISION_CHECK;
|
|
else if (!stricmp(NOX("player_ship"), ship_strings[i]))
|
|
sip->flags |= SIF_PLAYER_SHIP;
|
|
else if (!stricmp(NOX("default_player_ship"), ship_strings[i]))
|
|
sip->flags |= SIF_DEFAULT_PLAYER_SHIP;
|
|
else if ( !stricmp(NOX("repair_rearm"), ship_strings[i]))
|
|
sip->flags |= SIF_SUPPORT;
|
|
else if ( !stricmp(NOX("cargo"), ship_strings[i]))
|
|
sip->flags |= SIF_CARGO;
|
|
else if ( !stricmp( NOX("fighter"), ship_strings[i]))
|
|
sip->flags |= SIF_FIGHTER;
|
|
else if ( !stricmp( NOX("bomber"), ship_strings[i]))
|
|
sip->flags |= SIF_BOMBER;
|
|
else if ( !stricmp( NOX("transport"), ship_strings[i]))
|
|
sip->flags |= SIF_TRANSPORT;
|
|
else if ( !stricmp( NOX("freighter"), ship_strings[i]))
|
|
sip->flags |= SIF_FREIGHTER;
|
|
else if ( !stricmp( NOX("capital"), ship_strings[i]))
|
|
sip->flags |= SIF_CAPITAL;
|
|
else if (!stricmp( NOX("supercap"), ship_strings[i]))
|
|
sip->flags |= SIF_SUPERCAP;
|
|
else if (!stricmp( NOX("drydock"), ship_strings[i]))
|
|
sip->flags |= SIF_DRYDOCK;
|
|
else if ( !stricmp( NOX("cruiser"), ship_strings[i]))
|
|
sip->flags |= SIF_CRUISER;
|
|
else if ( !stricmp( NOX("navbuoy"), ship_strings[i]))
|
|
sip->flags |= SIF_NAVBUOY;
|
|
else if ( !stricmp( NOX("sentrygun"), ship_strings[i]))
|
|
sip->flags |= SIF_SENTRYGUN;
|
|
else if ( !stricmp( NOX("escapepod"), ship_strings[i]))
|
|
sip->flags |= SIF_ESCAPEPOD;
|
|
else if ( !stricmp( NOX("no type"), ship_strings[i]))
|
|
sip->flags |= SIF_NO_SHIP_TYPE;
|
|
else if ( !stricmp( NOX("ship copy"), ship_strings[i]))
|
|
sip->flags |= SIF_SHIP_COPY;
|
|
else if ( !stricmp( NOX("in tech database"), ship_strings[i]))
|
|
sip->flags |= SIF_IN_TECH_DATABASE | SIF_IN_TECH_DATABASE_M;
|
|
else if ( !stricmp( NOX("in tech database multi"), ship_strings[i]))
|
|
sip->flags |= SIF_IN_TECH_DATABASE_M;
|
|
else if ( !stricmp( NOX("dont collide invisible"), ship_strings[i]))
|
|
sip->flags |= SIF_DONT_COLLIDE_INVIS;
|
|
else if ( !stricmp( NOX("big damage"), ship_strings[i]))
|
|
sip->flags |= SIF_BIG_DAMAGE;
|
|
else if ( !stricmp( NOX("corvette"), ship_strings[i]))
|
|
sip->flags |= SIF_CORVETTE;
|
|
else if ( !stricmp( NOX("gas miner"), ship_strings[i]))
|
|
sip->flags |= SIF_GAS_MINER;
|
|
else if ( !stricmp( NOX("awacs"), ship_strings[i]))
|
|
sip->flags |= SIF_AWACS;
|
|
else if ( !stricmp( NOX("knossos"), ship_strings[i]))
|
|
sip->flags |= SIF_KNOSSOS_DEVICE;
|
|
else if ( !stricmp( NOX("no_fred"), ship_strings[i]))
|
|
sip->flags |= SIF_NO_FRED;
|
|
else
|
|
Warning(LOCATION, "Bogus string in ship flags: %s\n", ship_strings[i]);
|
|
}
|
|
|
|
find_and_stuff("$AI Class:", &sip->ai_class, F_NAME, Ai_class_names, Num_ai_classes, "AI class names");
|
|
|
|
// Get Afterburner information
|
|
// Be aware that if $Afterburner is not 1, the other Afterburner fields are not read in
|
|
required_string("$Afterburner:");
|
|
int has_afterburner;
|
|
stuff_boolean(&has_afterburner);
|
|
if ( has_afterburner == 1 ) {
|
|
sip->flags |= SIF_AFTERBURNER;
|
|
|
|
required_string("+Aburn Max Vel:");
|
|
stuff_vector(&sip->afterburner_max_vel);
|
|
|
|
required_string("+Aburn For accel:");
|
|
stuff_float(&sip->afterburner_forward_accel);
|
|
|
|
required_string("+Aburn Fuel:");
|
|
stuff_float(&sip->afterburner_fuel_capacity);
|
|
|
|
required_string("+Aburn Burn Rate:");
|
|
stuff_float(&sip->afterburner_burn_rate);
|
|
|
|
|
|
required_string("+Aburn Rec Rate:");
|
|
stuff_float(&sip->afterburner_recover_rate);
|
|
} else {
|
|
sip->afterburner_max_vel.xyz.x = 0.0f;
|
|
sip->afterburner_max_vel.xyz.y = 0.0f;
|
|
sip->afterburner_max_vel.xyz.z = 0.0f;
|
|
}
|
|
|
|
required_string("$Countermeasures:");
|
|
stuff_int(&sip->cmeasure_max);
|
|
|
|
required_string("$Scan time:");
|
|
stuff_int(&sip->scan_time);
|
|
|
|
required_string("$EngineSnd:");
|
|
stuff_int(&sip->engine_snd);
|
|
|
|
required_string("$Closeup_pos:");
|
|
stuff_vector(&sip->closeup_pos);
|
|
|
|
required_string("$Closeup_zoom:");
|
|
stuff_float(&sip->closeup_zoom);
|
|
|
|
sip->shield_icon_index = 255; // stored as ubyte
|
|
if (optional_string("$Shield_icon:")) {
|
|
char tmpbuf[NAME_LENGTH];
|
|
stuff_string(tmpbuf, F_NAME, NULL);
|
|
hud_shield_assign_info(sip, tmpbuf);
|
|
}
|
|
|
|
// read in filename for icon that is used in ship selection
|
|
sip->icon_filename[0] = 0;
|
|
if ( optional_string("$Ship_icon:") ) {
|
|
stuff_string(sip->icon_filename, F_NAME, NULL);
|
|
}
|
|
|
|
// read in filename for animation that is used in ship selection
|
|
sip->anim_filename[0] = 0;
|
|
if ( optional_string("$Ship_anim:") ) {
|
|
stuff_string(sip->anim_filename, F_NAME, NULL);
|
|
}
|
|
|
|
// read in filename for animation that is used in ship selection
|
|
sip->overhead_filename[0] = 0;
|
|
if ( optional_string("$Ship_overhead:") ) {
|
|
stuff_string(sip->overhead_filename, F_NAME, NULL);
|
|
}
|
|
|
|
sip->score = 0;
|
|
if ( optional_string("$Score:") ){
|
|
stuff_int( &sip->score );
|
|
}
|
|
|
|
// if the ship is a stealth ship
|
|
if ( optional_string("$Stealth:") ){
|
|
sip->flags |= SIF_STEALTH;
|
|
}
|
|
|
|
// parse contrail info
|
|
char trail_name[MAX_FILENAME_LEN] = "";
|
|
trail_info *ci;
|
|
memset(&sip->ct_info, 0, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
|
|
sip->ct_count = 0;
|
|
while(optional_string("$Trail:")){
|
|
// this means you've reached the max # of contrails for a ship
|
|
Assert(sip->ct_count <= MAX_SHIP_CONTRAILS);
|
|
|
|
ci = &sip->ct_info[sip->ct_count++];
|
|
|
|
required_string("+Offset:");
|
|
stuff_vector(&ci->pt);
|
|
|
|
required_string("+Start Width:");
|
|
stuff_float(&ci->w_start);
|
|
|
|
required_string("+End Width:");
|
|
stuff_float(&ci->w_end);
|
|
|
|
required_string("+Start Alpha:");
|
|
stuff_float(&ci->a_start);
|
|
|
|
required_string("+End Alpha:");
|
|
stuff_float(&ci->a_end);
|
|
|
|
required_string("+Max Life:");
|
|
stuff_float(&ci->max_life);
|
|
|
|
required_string("+Spew Time:");
|
|
stuff_int(&ci->stamp);
|
|
|
|
required_string("+Bitmap:");
|
|
stuff_string(trail_name, F_NAME, NULL);
|
|
ci->bitmap = bm_load(trail_name);
|
|
}
|
|
|
|
while (cont_flag) {
|
|
int r = required_string_3("#End", "$Subsystem:", "$Name" );
|
|
switch (r) {
|
|
case 0:
|
|
cont_flag = 0;
|
|
break;
|
|
case 1:
|
|
{
|
|
float turning_rate;
|
|
float percentage_of_hits;
|
|
model_subsystem *sp; // to append on the ships list of subsystems
|
|
|
|
Assert ( n_subsystems < MAX_MODEL_SUBSYSTEMS );
|
|
sp = &subsystems[n_subsystems++]; // subsystems a local -- when done, we will malloc and copy
|
|
required_string("$Subsystem:");
|
|
stuff_string(sp->subobj_name, F_NAME, ",");
|
|
Mp++;
|
|
stuff_float(&percentage_of_hits);
|
|
stuff_float(&turning_rate);
|
|
hull_percentage_of_hits -= percentage_of_hits;
|
|
sp->max_hits = sip->initial_hull_strength * (percentage_of_hits / 100.0f);
|
|
sp->type = SUBSYSTEM_UNKNOWN;
|
|
// specified as how long to turn 360 degrees in ships.tbl
|
|
if ( turning_rate > 0.0f ){
|
|
sp->turret_turning_rate = PI2 / turning_rate;
|
|
} else {
|
|
sp->turret_turning_rate = 0.0f;
|
|
}
|
|
|
|
for (i=0; i<MAX_PRIMARY_BANKS; i++)
|
|
sp->primary_banks[i] = -1;
|
|
for (i=0; i<MAX_SECONDARY_BANKS; i++) {
|
|
sp->secondary_banks[i] = -1;
|
|
sp->secondary_bank_capacity[i] = 0;
|
|
}
|
|
|
|
// Get default primary bank weapons
|
|
if (optional_string("$Default PBanks:")){
|
|
stuff_int_list(sp->primary_banks, MAX_PRIMARY_BANKS, WEAPON_LIST_TYPE);
|
|
}
|
|
|
|
// Get default secondary bank weapons
|
|
if (optional_string("$Default SBanks:")){
|
|
stuff_int_list(sp->secondary_banks, MAX_SECONDARY_BANKS, WEAPON_LIST_TYPE);
|
|
}
|
|
|
|
// Get the capacity of each secondary bank
|
|
if (optional_string("$Sbank Capacity:")){
|
|
stuff_int_list(sp->secondary_bank_capacity, MAX_SECONDARY_BANKS, RAW_INTEGER_TYPE);
|
|
}
|
|
|
|
// Get optional engine wake info
|
|
if (optional_string("$Engine Wash:")) {
|
|
char engine_wash_name[32];
|
|
stuff_string(engine_wash_name, F_NAME, NULL);
|
|
// get and set index
|
|
sp->engine_wash_index = get_engine_wash_index(engine_wash_name);
|
|
} else {
|
|
sp->engine_wash_index = -1;
|
|
}
|
|
|
|
// Get any AWACS info
|
|
sp->awacs_intensity = 0.0f;
|
|
if(optional_string("$AWACS:")){
|
|
stuff_float(&sp->awacs_intensity);
|
|
stuff_float(&sp->awacs_radius);
|
|
sip->flags |= SIF_HAS_AWACS;
|
|
}
|
|
|
|
sp->turret_weapon_type = sp->primary_banks[0]; // temporary, will be obsolete later.
|
|
if ( sp->turret_weapon_type < 0 ) {
|
|
sp->turret_weapon_type = sp->secondary_banks[0];
|
|
}
|
|
sp->model_num = -1; // init value for later sanity checking!!
|
|
}
|
|
break;
|
|
case 2:
|
|
cont_flag = 0;
|
|
break;
|
|
default:
|
|
Int3(); // Impossible return value from required_string_3.
|
|
}
|
|
}
|
|
Assert( hull_percentage_of_hits > 0.0f ); // must be > 0
|
|
|
|
// when done reading subsystems, malloc and copy the subsystem data to the ship info structure
|
|
sip->n_subsystems = n_subsystems;
|
|
if ( n_subsystems > 0 ) {
|
|
sip->subsystems = (model_subsystem *)malloc(sizeof(model_subsystem) * n_subsystems );
|
|
Assert( sip->subsystems != NULL );
|
|
}
|
|
else {
|
|
sip->subsystems = NULL;
|
|
}
|
|
|
|
for ( i = 0; i < n_subsystems; i++ ){
|
|
sip->subsystems[i] = subsystems[i];
|
|
}
|
|
|
|
// if we have a ship copy, then check to be sure that our base ship exists
|
|
if ( sip->flags & SIF_SHIP_COPY ) {
|
|
int index;
|
|
|
|
index = ship_info_base_lookup( Num_ship_types ); // Num_ship_types is our current entry into the array
|
|
if ( index == -1 ) {
|
|
char *p, name[NAME_LENGTH];;
|
|
|
|
strcpy( name, sip->name );
|
|
p = strchr(name, '#');
|
|
if ( p )
|
|
*p = '\0';
|
|
Error(LOCATION, "Ship %s is a copy, but base ship %s couldn't be found.", sip->name, name);
|
|
}
|
|
}
|
|
|
|
return rtn;
|
|
}
|
|
|
|
char get_engine_wash_index(char *engine_wash_name)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<Num_engine_wash_types; i++) {
|
|
if ( 0 == stricmp(engine_wash_name, Engine_wash_info[i].name) ) {
|
|
return (char)i;
|
|
}
|
|
}
|
|
|
|
// not found, so return -1
|
|
return -1;
|
|
}
|
|
|
|
void parse_shiptbl()
|
|
{
|
|
// open localization
|
|
lcl_ext_open();
|
|
|
|
read_file_text("ships.tbl");
|
|
reset_parse();
|
|
|
|
// parse default ship
|
|
required_string("#Default Player Ship");
|
|
required_string("$Name:");
|
|
stuff_string(default_player_ship, F_NAME, NULL, 254);
|
|
required_string("#End");
|
|
|
|
Num_engine_wash_types = 0;
|
|
Num_ship_types = 0;
|
|
|
|
required_string("#Engine Wash Info");
|
|
while (required_string_either("#End", "$Name:")) {
|
|
Assert( Num_engine_wash_types < MAX_ENGINE_WASH_TYPES );
|
|
|
|
parse_engine_wash();
|
|
Num_engine_wash_types++;
|
|
}
|
|
|
|
required_string("#End");
|
|
required_string("#Ship Classes");
|
|
|
|
while (required_string_either("#End","$Name:")) {
|
|
Assert( Num_ship_types < MAX_SHIP_TYPES );
|
|
|
|
if ( parse_ship() ) {
|
|
continue;
|
|
}
|
|
|
|
Num_ship_types++;
|
|
}
|
|
|
|
required_string("#End");
|
|
|
|
// Read in a list of ship_info indicies that are an ordering of the player ship precedence.
|
|
// This list is used to select an alternate ship when a particular ship is not available
|
|
// during ship selection.
|
|
required_string("$Player Ship Precedence:");
|
|
Num_player_ship_precedence = stuff_int_list(Player_ship_precedence, MAX_PLAYER_SHIP_CHOICES, SHIP_INFO_TYPE);
|
|
|
|
// close localization
|
|
lcl_ext_close();
|
|
}
|
|
|
|
int ship_show_velocity_dot = 0;
|
|
|
|
|
|
DCF_BOOL( show_velocity_dot, ship_show_velocity_dot )
|
|
|
|
|
|
// Called once at the beginning of the game to parse ships.tbl and stuff the Ship_info[]
|
|
// structure
|
|
void ship_init()
|
|
{
|
|
int rval;
|
|
|
|
if ( !ships_inited ) {
|
|
|
|
if ((rval = setjmp(parse_abort)) != 0) {
|
|
Error(LOCATION, "Error parsing 'ships.tbl'\r\nError code = %i.\r\n", rval);
|
|
} else {
|
|
parse_shiptbl();
|
|
ships_inited = 1;
|
|
}
|
|
|
|
ship_iff_init_colors();
|
|
}
|
|
|
|
ship_level_init(); // needed for FRED
|
|
}
|
|
|
|
|
|
// This will get called at the start of each level.
|
|
void ship_level_init()
|
|
{
|
|
int i;
|
|
|
|
// Reset everything between levels
|
|
|
|
|
|
// Mark all the models as invalid, since all the models get paged out
|
|
// between levels.
|
|
for (i=0; i<MAX_SHIP_TYPES; i++ ) {
|
|
Ship_info[i].modelnum = -1;
|
|
Ship_info[i].modelnum_hud = -1;
|
|
}
|
|
|
|
// mwa removed 11/24/97 num_ships = 0;
|
|
Num_exited_ships = 0;
|
|
for (i=0; i<MAX_SHIPS; i++ ) {
|
|
Ships[i].objnum = -1;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_EXITED_SHIPS; i++ ) {
|
|
memset ( &Ships_exited[i], 0, sizeof(exited_ship) );
|
|
Ships_exited[i].obj_signature = -1;
|
|
}
|
|
|
|
num_wings = 0;
|
|
for (i = 0; i < MAX_WINGS; i++ )
|
|
Wings[i].num_waves = -1;
|
|
|
|
for (i=0; i<MAX_PLAYER_WINGS; i++)
|
|
Starting_wings[i] = -1;
|
|
|
|
// Empty the subsys list
|
|
memset( Ship_subsystems, 0, sizeof(ship_subsys)*MAX_SHIP_SUBOBJECTS );
|
|
|
|
list_init( &ship_subsys_free_list );
|
|
for ( i = 0; i < MAX_SHIP_SUBOBJECTS; i++ )
|
|
list_append( &ship_subsys_free_list, &Ship_subsystems[i] );
|
|
|
|
Laser_energy_out_snd_timer = 1;
|
|
Missile_out_snd_timer = 1;
|
|
|
|
for (i = 0; i < MAX_SHIP_TYPE_COUNTS; i++ ) {
|
|
Ship_counts[i].total = 0;
|
|
Ship_counts[i].killed = 0;
|
|
}
|
|
|
|
ship_obj_list_init();
|
|
|
|
Ship_cargo_check_timer = 1;
|
|
|
|
shipfx_large_blowup_level_init();
|
|
}
|
|
|
|
// function to add a ship onto the exited ships list. The reason parameter
|
|
// tells us why the ship left the mission (i.e. departed or destroyed)
|
|
void ship_add_exited_ship( ship *sp, int reason )
|
|
{
|
|
int i, entry;
|
|
|
|
// reuse oldest slots if none left
|
|
if ( Num_exited_ships == MAX_EXITED_SHIPS ) {
|
|
int oldest_entry;
|
|
|
|
// find the oldest entry
|
|
oldest_entry = 0;
|
|
for ( i = 1; i < MAX_SHIPS; i++ ) {
|
|
if ( Ships_exited[i].time < Ships_exited[oldest_entry].time ) {
|
|
oldest_entry = i;
|
|
}
|
|
}
|
|
entry = oldest_entry;
|
|
} else {
|
|
entry = Num_exited_ships;
|
|
Num_exited_ships++;
|
|
}
|
|
|
|
strcpy( Ships_exited[entry].ship_name, sp->ship_name );
|
|
Ships_exited[entry].obj_signature = Objects[sp->objnum].signature;
|
|
Ships_exited[entry].team = sp->team;
|
|
Ships_exited[entry].flags = reason;
|
|
// if ship is red alert, flag as such
|
|
if (sp->flags & SF_RED_ALERT_STORE_STATUS) {
|
|
Ships_exited[entry].flags |= SEF_RED_ALERT_CARRY;
|
|
}
|
|
Ships_exited[entry].time = Missiontime;
|
|
Ships_exited[entry].hull_strength = int(Objects[sp->objnum].hull_strength);
|
|
|
|
if ( sp->flags & SF_CARGO_REVEALED )
|
|
Ships_exited[entry].flags |= SEF_CARGO_KNOWN;
|
|
if ( sp->time_first_tagged > 0 )
|
|
Ships_exited[entry].flags |= SEF_BEEN_TAGGED;
|
|
}
|
|
|
|
// function which attempts to find information about an exited ship based on shipname
|
|
int ship_find_exited_ship_by_name( char *name )
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < Num_exited_ships; i++) {
|
|
if ( !stricmp(name, Ships_exited[i].ship_name) )
|
|
break;
|
|
}
|
|
|
|
if ( i == Num_exited_ships )
|
|
return -1;
|
|
else
|
|
return i;
|
|
}
|
|
|
|
// function which attempts to find information about an exited ship based on shipname
|
|
int ship_find_exited_ship_by_signature( int signature )
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < Num_exited_ships; i++) {
|
|
if ( signature == Ships_exited[i].obj_signature )
|
|
break;
|
|
}
|
|
|
|
if ( i == Num_exited_ships )
|
|
return -1;
|
|
else
|
|
return i;
|
|
}
|
|
|
|
|
|
void physics_ship_init(object *objp)
|
|
{
|
|
ship_info *sinfo = &Ship_info[Ships[objp->instance].ship_info_index];
|
|
physics_info *pi = &objp->phys_info;
|
|
polymodel *pm = model_get( Ships[objp->instance].modelnum );
|
|
|
|
// use mass and I_body_inv from POF read into polymodel
|
|
physics_init(pi);
|
|
pi->mass = pm->mass * sinfo->density;
|
|
pi->I_body_inv = pm->moment_of_inertia;
|
|
// scale pm->I_body_inv value by density
|
|
vm_vec_scale( &pi->I_body_inv.vec.rvec, sinfo->density );
|
|
vm_vec_scale( &pi->I_body_inv.vec.uvec, sinfo->density );
|
|
vm_vec_scale( &pi->I_body_inv.vec.fvec, sinfo->density );
|
|
|
|
pi->side_slip_time_const = sinfo->damp;
|
|
pi->rotdamp = sinfo->rotdamp;
|
|
pi->max_vel = sinfo->max_vel;
|
|
pi->afterburner_max_vel = sinfo->afterburner_max_vel;
|
|
pi->max_rotvel = sinfo->max_rotvel;
|
|
pi->max_rear_vel = sinfo->max_rear_vel;
|
|
pi->flags |= PF_ACCELERATES;
|
|
|
|
|
|
|
|
|
|
pi->forward_accel_time_const=sinfo->forward_accel;
|
|
pi->afterburner_forward_accel_time_const=sinfo->afterburner_forward_accel;
|
|
pi->forward_decel_time_const=sinfo->forward_decel;
|
|
pi->slide_accel_time_const=sinfo->slide_accel;
|
|
pi->slide_decel_time_const=sinfo->slide_decel;
|
|
|
|
if ( (pi->max_vel.xyz.x > 0.000001f) || (pi->max_vel.xyz.y > 0.000001f) )
|
|
pi->flags |= PF_SLIDE_ENABLED;
|
|
|
|
vm_vec_zero(&pi->vel);
|
|
vm_vec_zero(&pi->rotvel);
|
|
pi->speed = 0.0f;
|
|
pi->heading = 0.0f;
|
|
// pi->accel = 0.0f;
|
|
vm_set_identity(&pi->last_rotmat);
|
|
}
|
|
|
|
// function to set the orders allowed for a ship -- based on ship type. This value might get overridden
|
|
// by a value in the mission file.
|
|
int ship_get_default_orders_accepted( ship_info *sip )
|
|
{
|
|
int ship_info_flag;
|
|
|
|
ship_info_flag = sip->flags;
|
|
|
|
if ( ship_info_flag & SIF_FIGHTER )
|
|
return FIGHTER_MESSAGES;
|
|
else if ( ship_info_flag & SIF_BOMBER )
|
|
return BOMBER_MESSAGES;
|
|
else if ( ship_info_flag & (SIF_CRUISER | SIF_GAS_MINER | SIF_AWACS | SIF_CORVETTE) )
|
|
return CRUISER_MESSAGES;
|
|
else if ( ship_info_flag & SIF_FREIGHTER )
|
|
return FREIGHTER_MESSAGES;
|
|
else if ( ship_info_flag & SIF_CAPITAL )
|
|
return CAPITAL_MESSAGES;
|
|
else if ( ship_info_flag & SIF_TRANSPORT )
|
|
return TRANSPORT_MESSAGES;
|
|
else if ( ship_info_flag & SIF_SUPPORT )
|
|
return SUPPORT_MESSAGES;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void ship_set(int ship_index, int objnum, int ship_type)
|
|
{
|
|
int i;
|
|
|
|
object *objp = &Objects[objnum];
|
|
ship *shipp = &Ships[ship_index];
|
|
ship_weapon *swp = &shipp->weapons;
|
|
ship_info *sip = &(Ship_info[ship_type]);
|
|
|
|
// Create n!
|
|
// sprintf(shipp->ship_name, "%s %d", Ship_info[ship_type].name, ship_index); // moved to ship_create()
|
|
Assert(strlen(shipp->ship_name) < NAME_LENGTH - 1);
|
|
shipp->ship_info_index = ship_type;
|
|
shipp->objnum = objnum;
|
|
shipp->group = 0;
|
|
shipp->reinforcement_index = -1;
|
|
shipp->cargo1 = 0;
|
|
shipp->hotkey = -1;
|
|
shipp->score = 0;
|
|
shipp->escort_priority = 0;
|
|
shipp->special_exp_index = -1;
|
|
shipp->num_hits = 0;
|
|
shipp->flags = 0;
|
|
shipp->wash_killed = 0;
|
|
shipp->time_cargo_revealed = 0;
|
|
shipp->time_first_tagged = 0;
|
|
shipp->wash_timestamp = timestamp(0);
|
|
shipp->dock_objnum_when_dead = -1;
|
|
shipp->large_ship_blowup_index = -1;
|
|
shipp->respawn_priority = 0;
|
|
for (i=0; i<NUM_SUB_EXPL_HANDLES; i++) {
|
|
shipp->sub_expl_sound_handle[i] = -1;
|
|
}
|
|
|
|
if ( !Fred_running ) {
|
|
shipp->final_warp_time = timestamp(-1);
|
|
shipp->final_death_time = timestamp(-1); // There death sequence ain't start et.
|
|
shipp->really_final_death_time = timestamp(-1); // There death sequence ain't start et.
|
|
shipp->next_fireball = timestamp(-1); // When a random fireball will pop up
|
|
shipp->next_hit_spark = timestamp(-1); // when a hit spot will spark
|
|
for (i=0; i<MAX_SHIP_ARCS; i++ ) {
|
|
shipp->arc_timestamp[i] = timestamp(-1); // Mark this arc as unused
|
|
}
|
|
shipp->arc_next_time = timestamp(-1); // No electrical arcs yet.
|
|
} else { // the values should be different for Fred
|
|
shipp->final_warp_time = -1;
|
|
shipp->final_death_time = 0;
|
|
shipp->really_final_death_time = -1;
|
|
shipp->next_fireball = -1;
|
|
shipp->next_hit_spark = -1;
|
|
for (i=0; i<MAX_SHIP_ARCS; i++ ) {
|
|
shipp->arc_timestamp[i] = -1;
|
|
}
|
|
shipp->arc_next_time = -1;
|
|
}
|
|
shipp->team = TEAM_FRIENDLY; // Default friendly, probably get overridden.
|
|
shipp->arrival_location = 0;
|
|
shipp->arrival_distance = 0;
|
|
shipp->arrival_anchor = -1;
|
|
shipp->arrival_delay = 0;
|
|
shipp->arrival_cue = -1;
|
|
shipp->departure_location = 0;
|
|
shipp->departure_delay = 0;
|
|
shipp->departure_cue = -1;
|
|
shipp->shield_hits = 0; // No shield hits yet on this baby.
|
|
shipp->current_max_speed = Ship_info[ship_type].max_speed;
|
|
|
|
shipp->alt_type_index = -1;
|
|
|
|
shipp->lightning_stamp = -1;
|
|
|
|
shipp->emp_intensity = -1.0f;
|
|
shipp->emp_decr = 0.0f;
|
|
|
|
shipp->targeting_laser_bank = -1;
|
|
shipp->targeting_laser_objnum = -1;
|
|
|
|
shipp->determination = 10;
|
|
shipp->wingnum = -1;
|
|
for (i = 0; i < MAX_PLAYERS; i++)
|
|
shipp->last_targeted_subobject[i] = NULL;
|
|
|
|
if (Fred_running){
|
|
objp->hull_strength = 100.0f;
|
|
} else {
|
|
objp->hull_strength = sip->initial_hull_strength;
|
|
}
|
|
|
|
shipp->afterburner_fuel = sip->afterburner_fuel_capacity;
|
|
|
|
shipp->cmeasure_count = sip->cmeasure_max;
|
|
|
|
for ( i = 0; i < MAX_PRIMARY_BANKS; i++ ){
|
|
swp->next_primary_fire_stamp[i] = timestamp(0);
|
|
}
|
|
|
|
shipp->cmeasure_fire_stamp = timestamp(0);
|
|
|
|
for ( i = 0; i < MAX_SECONDARY_BANKS; i++ ) {
|
|
if (Fred_running){
|
|
shipp->weapons.secondary_bank_ammo[i] = 100;
|
|
} else {
|
|
shipp->weapons.secondary_bank_ammo[i] = 0;
|
|
}
|
|
|
|
swp->secondary_bank_ammo[i] = 0;
|
|
swp->secondary_next_slot[i] = 0;
|
|
swp->next_secondary_fire_stamp[i] = timestamp(0);
|
|
swp->secondary_bank_rearm_time[i] = timestamp(0); // will be able to rearm this bank immediately
|
|
}
|
|
|
|
for ( i = 0; i < sip->num_secondary_banks; i++ ) {
|
|
float weapon_size;
|
|
weapon_size = Weapon_info[sip->secondary_bank_weapons[i]].cargo_size;
|
|
Assert( weapon_size > 0.0f );
|
|
if (Fred_running){
|
|
swp->secondary_bank_ammo[i] = 100;
|
|
} else {
|
|
swp->secondary_bank_ammo[i] = fl2i(sip->secondary_bank_ammo_capacity[i] / weapon_size + 0.5f );
|
|
}
|
|
}
|
|
|
|
swp->current_primary_bank = -1;
|
|
swp->current_secondary_bank = -1;
|
|
|
|
|
|
if ( sip->num_primary_banks > 0 ) {
|
|
if ( swp->primary_bank_weapons[BANK_1] >= 0 ) {
|
|
swp->current_primary_bank = BANK_1;
|
|
} else {
|
|
swp->current_primary_bank = -1;
|
|
}
|
|
}
|
|
else {
|
|
swp->current_primary_bank = -1;
|
|
}
|
|
|
|
if ( sip->num_secondary_banks > 0 ) {
|
|
if ( swp->secondary_bank_weapons[BANK_1] >= 0 ) {
|
|
swp->current_secondary_bank = BANK_1;
|
|
} else {
|
|
swp->current_secondary_bank = -1;
|
|
}
|
|
}
|
|
else {
|
|
swp->current_secondary_bank = -1;
|
|
}
|
|
|
|
shipp->current_cmeasure = 0;
|
|
|
|
ets_init_ship(objp); // init ship fields that are used for the ETS
|
|
|
|
physics_ship_init(objp);
|
|
if (Fred_running)
|
|
objp->shields[0] = 100.0f;
|
|
else
|
|
set_shield_strength(objp, sip->shields);
|
|
|
|
shipp->target_shields_delta = 0.0f;
|
|
shipp->target_weapon_energy_delta = 0.0f;
|
|
|
|
ai_object_init(objp, shipp->ai_index);
|
|
shipp->weapons.ai_class = Ai_info[shipp->ai_index].ai_class;
|
|
shipp->shield_integrity = NULL;
|
|
// shipp->sw.blast_duration = -1; // init shockwave struct
|
|
subsys_set(objnum);
|
|
shipp->orders_accepted = ship_get_default_orders_accepted( sip );
|
|
shipp->num_swarm_missiles_to_fire = 0;
|
|
shipp->num_turret_swarm_info = 0;
|
|
shipp->death_roll_snd = -1;
|
|
shipp->thruster_bitmap = -1;
|
|
shipp->thruster_frame = 0.0f;
|
|
shipp->thruster_glow_bitmap = -1;
|
|
shipp->thruster_glow_noise = 1.0f;
|
|
shipp->thruster_glow_frame = 0.0f;
|
|
shipp->next_engine_stutter = 1;
|
|
shipp->persona_index = -1;
|
|
shipp->flags |= SF_ENGINES_ON;
|
|
shipp->subsys_disrupted_flags=0;
|
|
shipp->subsys_disrupted_check_timestamp=timestamp(0);
|
|
|
|
// swarm missile stuff
|
|
shipp->next_swarm_fire = 1;
|
|
|
|
// corkscrew missile stuff
|
|
shipp->next_corkscrew_fire = 1;
|
|
|
|
// field for score
|
|
shipp->score = sip->score;
|
|
|
|
// tag
|
|
shipp->tag_left = -1.0f;
|
|
shipp->level2_tag_left = -1.0f;
|
|
|
|
#ifndef NO_NETWORK
|
|
// multiplayer field initializations
|
|
for (i = 0; i < MAX_PLAYERS; i++ ) {
|
|
shipp->np_updates[i].update_stamp = -1;
|
|
shipp->np_updates[i].status_update_stamp = -1;
|
|
shipp->np_updates[i].subsys_update_stamp = -1;
|
|
shipp->np_updates[i].seq = 0;
|
|
}
|
|
extern int oo_arrive_time_count[MAX_SHIPS];
|
|
extern int oo_interp_count[MAX_SHIPS];
|
|
oo_arrive_time_count[shipp - Ships] = 0;
|
|
oo_interp_count[shipp - Ships] = 0;
|
|
#endif
|
|
|
|
shipp->special_warp_objnum = -1;
|
|
|
|
// set awacs warning flags so awacs ship only asks for help once at each level
|
|
shipp->awacs_warning_flag = AWACS_WARN_NONE;
|
|
}
|
|
|
|
// function which recalculates the overall strength of subsystems. Needed because
|
|
// several places in FreeSpace change subsystem strength and all this data needs to
|
|
// be kept up to date.
|
|
void ship_recalc_subsys_strength( ship *shipp )
|
|
{
|
|
int i;
|
|
ship_subsys *ship_system;
|
|
|
|
// fill in the subsys_info fields for all particular types of subsystems
|
|
// make the current strength be 1.0. If there are initial conditions on the ship, then
|
|
// the mission parse code should take care of setting that.
|
|
for (i = 0; i < SUBSYSTEM_MAX; i++) {
|
|
shipp->subsys_info[i].num = 0;
|
|
shipp->subsys_info[i].total_hits = 0.0f;
|
|
shipp->subsys_info[i].current_hits = 0.0f;
|
|
}
|
|
|
|
// count all of the subsystems of a particular type. For each generic type of subsystem, we store the
|
|
// total count of hits. (i.e. for 3 engines, we store the sum of the max_hits for each engine)
|
|
for ( ship_system = GET_FIRST(&shipp->subsys_list); ship_system != END_OF_LIST(&shipp->subsys_list); ship_system = GET_NEXT(ship_system) ) {
|
|
int type;
|
|
|
|
type = ship_system->system_info->type;
|
|
Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
|
|
shipp->subsys_info[type].num++;
|
|
shipp->subsys_info[type].total_hits += ship_system->system_info->max_hits;
|
|
shipp->subsys_info[type].current_hits += ship_system->current_hits;
|
|
}
|
|
|
|
// set any ship flags which should be set. unset the flags since we might be repairing a subsystem
|
|
// through sexpressions.
|
|
shipp->flags &= ~SF_DISABLED;
|
|
if ( (shipp->subsys_info[SUBSYSTEM_ENGINE].num > 0) && (shipp->subsys_info[SUBSYSTEM_ENGINE].current_hits == 0.0f) ){
|
|
shipp->flags |= SF_DISABLED;
|
|
}
|
|
|
|
/*
|
|
shipp->flags &= ~SF_DISARMED;
|
|
if ( (shipp->subsys_info[SUBSYSTEM_TURRET].num > 0) && (shipp->subsys_info[SUBSYSTEM_TURRET].current_hits == 0.0f) ){
|
|
shipp->flags |= SF_DISARMED;
|
|
}
|
|
*/
|
|
}
|
|
|
|
// routine to possibly fixup the model subsystem information for this ship pointer. Needed when
|
|
// ships share the same model.
|
|
void ship_copy_subsystem_fixup(ship_info *sip)
|
|
{
|
|
int i, model_num;
|
|
|
|
model_num = sip->modelnum;
|
|
|
|
// since we allow a model file to be shared between several ships, we must check to be sure that our
|
|
// subsystems have been loaded properly
|
|
/*
|
|
subsystems_needed = 0;
|
|
for (i = 0; i < sip->n_subsystems; i++ ) {
|
|
if ( sip->subsystems[i].model_num == -1 ){
|
|
subsystems_needed++;
|
|
}
|
|
}
|
|
*/
|
|
|
|
// if we need to get information for all our subsystems, we need to find another ship with the same model
|
|
// number as our own and that has the model information
|
|
// if ( subsystems_needed == sip->n_subsystems ) {
|
|
for ( i = 0; i < Num_ship_types; i++ ) {
|
|
model_subsystem *msp;
|
|
|
|
if ( (Ship_info[i].modelnum != model_num) || (&Ship_info[i] == sip) ){
|
|
continue;
|
|
}
|
|
|
|
// see if this ship has subsystems and a model for the subsystems. We only need check the first
|
|
// subsystem since previous error checking would have trapped it's loading as an error.
|
|
Assert( Ship_info[i].n_subsystems == sip->n_subsystems );
|
|
|
|
msp = &Ship_info[i].subsystems[0];
|
|
model_copy_subsystems( sip->n_subsystems, &(sip->subsystems[0]), msp );
|
|
sip->flags |= SIF_PATH_FIXUP;
|
|
break;
|
|
}
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
// ignore_subsys_info => default parameter with value of 0. This is
|
|
// only set to 1 by the save/restore code
|
|
void subsys_set(int objnum, int ignore_subsys_info)
|
|
{
|
|
ship *shipp = &Ships[Objects[objnum].instance];
|
|
ship_info *sinfo = &Ship_info[Ships[Objects[objnum].instance].ship_info_index];
|
|
model_subsystem *sp;
|
|
ship_subsys *ship_system;
|
|
int i, j, k;
|
|
|
|
// set up the subsystems for this ship. walk through list of subsystems in the ship-info array.
|
|
// for each subsystem, get a new ship_subsys instance and set up the pointers and other values
|
|
list_init ( &shipp->subsys_list ); // initialize the ship's list of subsystems
|
|
for ( i = 0; i < sinfo->n_subsystems; i++ ) {
|
|
|
|
sp = &(sinfo->subsystems[i]);
|
|
if ( sp->model_num == -1 ) {
|
|
Warning (LOCATION, "Invalid subobj_num or model_num in subsystem %s on ship type %s.\nNot linking into ship!\n\n(This warning means that a subsystem was present in ships.tbl and not present in the model\nit should probably be removed from the model.)\n", sp->subobj_name, sinfo->name );
|
|
continue;
|
|
}
|
|
|
|
// set up the linked list
|
|
ship_system = GET_FIRST( &ship_subsys_free_list ); // get a new element from the ship_subsystem array
|
|
Assert ( ship_system != &ship_subsys_free_list ); // shouldn't have the dummy element
|
|
list_remove( &ship_subsys_free_list, ship_system ); // remove the element from the array
|
|
list_append( &shipp->subsys_list, ship_system ); // link the element into the ship
|
|
|
|
ship_system->system_info = sp; // set the system_info pointer to point to the data read in from the model
|
|
if ( !Fred_running ){
|
|
ship_system->current_hits = sp->max_hits; // set the max hits
|
|
} else {
|
|
ship_system->current_hits = 0.0f; // Jason wants this to be 0 in Fred.
|
|
}
|
|
ship_system->turret_next_fire_stamp = timestamp(0);
|
|
ship_system->turret_next_enemy_check_stamp = timestamp(0);
|
|
ship_system->turret_enemy_objnum = -1;
|
|
ship_system->turret_next_fire_stamp = timestamp((int) frand_range(1.0f, 500.0f)); // next time this turret can fire
|
|
ship_system->turret_last_fire_direction = sp->turret_norm;
|
|
ship_system->turret_next_fire_pos = 0;
|
|
ship_system->turret_time_enemy_in_range = 0.0f;
|
|
ship_system->disruption_timestamp=timestamp(0);
|
|
ship_system->turret_pick_big_attack_point_timestamp = timestamp(0);
|
|
vm_vec_zero(&ship_system->turret_big_attack_point);
|
|
ship_system->subsys_cargo_name = -1;
|
|
ship_system->subsys_cargo_revealed = 0;
|
|
|
|
// zero flags
|
|
ship_system->weapons.flags = 0;
|
|
|
|
j = 0;
|
|
for (k=0; k<MAX_PRIMARY_BANKS; k++){
|
|
if (sp->primary_banks[k] != -1) {
|
|
ship_system->weapons.primary_bank_weapons[j] = sp->primary_banks[k];
|
|
ship_system->weapons.next_primary_fire_stamp[j++] = 0;
|
|
}
|
|
}
|
|
|
|
ship_system->weapons.num_primary_banks = j;
|
|
|
|
j = 0;
|
|
for (k=0; k<MAX_SECONDARY_BANKS; k++){
|
|
if (sp->secondary_banks[k] != -1) {
|
|
ship_system->weapons.secondary_bank_weapons[j] = sp->secondary_banks[k];
|
|
ship_system->weapons.secondary_bank_capacity[j] = sp->secondary_bank_capacity[k];
|
|
ship_system->weapons.next_secondary_fire_stamp[j++] = timestamp(0);
|
|
}
|
|
}
|
|
|
|
ship_system->weapons.num_secondary_banks = j;
|
|
ship_system->weapons.current_primary_bank = -1;
|
|
ship_system->weapons.current_secondary_bank = -1;
|
|
|
|
for (k=0; k<MAX_SECONDARY_BANKS; k++) {
|
|
ship_system->weapons.secondary_bank_ammo[k] = (Fred_running ? 100 : ship_system->weapons.secondary_bank_capacity[k]);
|
|
|
|
ship_system->weapons.secondary_next_slot[k] = 0;
|
|
}
|
|
|
|
ship_system->weapons.last_fired_weapon_index = -1;
|
|
ship_system->weapons.last_fired_weapon_signature = -1;
|
|
ship_system->weapons.detonate_weapon_time = -1;
|
|
ship_system->weapons.ai_class = sinfo->ai_class; // assume ai class of ship for turret
|
|
|
|
// rapid fire (swarm) stuff
|
|
ship_system->turret_swarm_info_index = -1;
|
|
|
|
// AWACS stuff
|
|
ship_system->awacs_intensity = sp->awacs_intensity;
|
|
ship_system->awacs_radius = sp->awacs_radius;
|
|
if (ship_system->awacs_intensity > 0) {
|
|
ship_system->system_info->flags |= MSS_FLAG_AWACS;
|
|
}
|
|
|
|
// turn_rate, turn_accel
|
|
// model_set_instance_info
|
|
float turn_accel = 0.5f;
|
|
model_set_instance_info(&ship_system->submodel_info_1, sp->turn_rate, turn_accel);
|
|
|
|
// model_clear_instance_info( &ship_system->submodel_info_1 );
|
|
model_clear_instance_info( &ship_system->submodel_info_2 );
|
|
}
|
|
|
|
if ( !ignore_subsys_info ) {
|
|
ship_recalc_subsys_strength( shipp );
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef NDEBUG
|
|
|
|
// Render docking information, NOT while in object's reference frame.
|
|
void render_dock_bays(object *objp)
|
|
{
|
|
ship_info *sip;
|
|
polymodel *pm;
|
|
dock_bay *db;
|
|
|
|
sip = &Ship_info[Ships[objp->instance].ship_info_index];
|
|
pm = model_get( sip->modelnum );
|
|
|
|
if (pm->docking_bays == NULL)
|
|
return;
|
|
|
|
if (pm->docking_bays[0].num_slots != 2)
|
|
return;
|
|
|
|
db = &pm->docking_bays[0];
|
|
|
|
vertex v0, v1;
|
|
vector p0, p1, p2, p3, nr;
|
|
|
|
vm_vec_unrotate(&p0, &db->pnt[0], &objp->orient);
|
|
vm_vec_add2(&p0, &objp->pos);
|
|
g3_rotate_vertex(&v0, &p0);
|
|
|
|
vm_vec_unrotate(&p1, &db->pnt[1], &objp->orient);
|
|
vm_vec_add2(&p1, &objp->pos);
|
|
g3_rotate_vertex(&v1, &p1);
|
|
|
|
gr_set_color(255, 0, 0);
|
|
g3_draw_line(&v0, &v1);
|
|
|
|
vm_vec_avg(&p2, &p0, &p1);
|
|
|
|
vm_vec_unrotate(&nr, &db->norm[0], &objp->orient);
|
|
vm_vec_scale_add(&p3, &p2, &nr, 10.0f);
|
|
|
|
g3_rotate_vertex(&v0, &p2);
|
|
g3_rotate_vertex(&v1, &p3);
|
|
gr_set_color(255, 255, 0);
|
|
g3_draw_line(&v0, &v1);
|
|
g3_draw_sphere(&v1, 1.25f);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
int Ship_shadows = 0;
|
|
|
|
DCF_BOOL( ship_shadows, Ship_shadows );
|
|
|
|
MONITOR( NumShipsRend );
|
|
|
|
int Show_shield_hits = 0;
|
|
DCF_BOOL( show_shield_hits, Show_shield_hits );
|
|
|
|
int Show_tnorms = 0;
|
|
DCF_BOOL( show_tnorms, Show_tnorms );
|
|
|
|
int Show_paths = 0;
|
|
DCF_BOOL( show_paths, Show_paths );
|
|
|
|
int Show_fpaths = 0;
|
|
DCF_BOOL( show_fpaths, Show_fpaths );
|
|
|
|
void ship_render(object * obj)
|
|
{
|
|
int num;
|
|
ship_info * si;
|
|
ship * shipp;
|
|
|
|
num = obj->instance;
|
|
|
|
Assert( num >= 0);
|
|
|
|
#if 0
|
|
// show target when attacking big ship
|
|
vector temp, target;
|
|
ai_info *aip = &Ai_info[Ships[obj->instance].ai_index];
|
|
if ( (aip->target_objnum >= 0) && (Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index].flags & (SIF_SUPERCAP|SIF_CAPITAL|SIF_CRUISER)) ) {
|
|
vm_vec_unrotate(&temp, &aip->big_attack_point, &Objects[aip->target_objnum].orient);
|
|
vm_vec_add(&target, &temp, &Objects[aip->target_objnum].pos);
|
|
|
|
vertex v0, v1;
|
|
gr_set_color(128,0,0);
|
|
g3_rotate_vertex( &v0, &obj->pos );
|
|
g3_rotate_vertex( &v1, &target );
|
|
|
|
g3_draw_line(&v0, &v1);
|
|
|
|
g3_draw_sphere(&v1, 5.0f);
|
|
}
|
|
#endif
|
|
|
|
|
|
// if (Ships[num].subtype == SHIP_PLAYER ) return;
|
|
if ( obj == Viewer_obj ) {
|
|
if (ship_show_velocity_dot && (obj==Player_obj) ) {
|
|
vector p0,v;
|
|
vertex v0;
|
|
|
|
vm_vec_scale_add( &v, &obj->phys_info.vel, &obj->orient.vec.fvec, 3.0f );
|
|
vm_vec_normalize( &v );
|
|
|
|
|
|
vm_vec_scale_add( &p0, &obj->pos, &v, 20.0f);
|
|
|
|
g3_rotate_vertex( &v0, &p0 );
|
|
|
|
gr_set_color(0,128,0);
|
|
g3_draw_sphere( &v0, 0.1f );
|
|
}
|
|
|
|
// Show the shield hit effect for the viewer.
|
|
if ( Show_shield_hits ) {
|
|
shipp = &Ships[num];
|
|
if (shipp->shield_hits) {
|
|
create_shield_explosion_all(obj);
|
|
shipp->shield_hits = 0;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
MONITOR_INC( NumShipsRend, 1 );
|
|
|
|
shipp = &Ships[num];
|
|
si = &Ship_info[Ships[num].ship_info_index];
|
|
|
|
// Make ships that are warping in not render during stage 1
|
|
if ( shipp->flags & SF_ARRIVING_STAGE_1 ){
|
|
return;
|
|
}
|
|
|
|
if ( Ship_shadows && shipfx_in_shadow( obj ) ) {
|
|
light_set_shadow(1);
|
|
} else {
|
|
light_set_shadow(0);
|
|
}
|
|
|
|
ship_model_start(obj);
|
|
|
|
uint render_flags = MR_NORMAL;
|
|
|
|
// Turn off model caching for the player ship in external view.
|
|
if (obj == Player_obj) {
|
|
render_flags |= MR_ALWAYS_REDRAW;
|
|
}
|
|
|
|
// Turn off model caching if this is the player's target.
|
|
if ( Player_ai->target_objnum == OBJ_INDEX(obj)) {
|
|
render_flags |= MR_ALWAYS_REDRAW;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
if(Show_paths || Show_fpaths){
|
|
render_flags |= MR_BAY_PATHS;
|
|
}
|
|
#endif
|
|
|
|
// Only render electrical arcs if within 500m of the eye (for a 10m piece)
|
|
if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) {
|
|
int i;
|
|
for (i=0; i<MAX_SHIP_ARCS; i++ ) {
|
|
if ( timestamp_valid( shipp->arc_timestamp[i] ) ) {
|
|
render_flags |= MR_ALWAYS_REDRAW; // Turn off model caching if arcing.
|
|
model_add_arc( shipp->modelnum, -1, &shipp->arc_pts[i][0], &shipp->arc_pts[i][1], shipp->arc_type[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( shipp->large_ship_blowup_index > -1 ) {
|
|
shipfx_large_blowup_render(shipp);
|
|
} else {
|
|
|
|
// ship_get_subsystem_strength( shipp, SUBSYSTEM_ENGINE)>ENGINE_MIN_STR
|
|
|
|
if ( (shipp->thruster_bitmap > -1) && (!(shipp->flags & SF_DISABLED)) && (!ship_subsys_disrupted(shipp, SUBSYSTEM_ENGINE)) ) {
|
|
float ft;
|
|
|
|
// Add noise to thruster geometry.
|
|
ft = obj->phys_info.forward_thrust;
|
|
ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
|
|
if (ft > 1.0f)
|
|
ft = 1.0f;
|
|
|
|
model_set_thrust( shipp->modelnum, ft, shipp->thruster_bitmap, shipp->thruster_glow_bitmap, shipp->thruster_glow_noise );
|
|
render_flags |= MR_SHOW_THRUSTERS;
|
|
}
|
|
|
|
// fill the model flash lighting values in
|
|
shipfx_flash_light_model( obj, shipp );
|
|
|
|
object *docked_objp = NULL;
|
|
ship * docked_shipp = NULL;
|
|
ship * warp_shipp = shipp;
|
|
|
|
// check to see if departing ship is docked with anything.
|
|
docked_objp = ai_find_docked_object( obj );
|
|
if ( docked_objp ) {
|
|
docked_shipp = &Ships[docked_objp->instance];
|
|
|
|
if ( docked_shipp->flags & (SF_DEPART_WARP|SF_ARRIVING) ) {
|
|
warp_shipp = docked_shipp;
|
|
}
|
|
}
|
|
|
|
// Warp_shipp points to the ship that is going through a
|
|
// warp... either this ship or the ship it is docked with.
|
|
|
|
// If the ship is going "through" the warp effect, then
|
|
// set up the model renderer to only draw the polygons in front
|
|
// of the warp in effect
|
|
int clip_started = 0;
|
|
|
|
if ( warp_shipp->flags & (SF_ARRIVING|SF_DEPART_WARP) ) {
|
|
|
|
clip_started = 1;
|
|
g3_start_user_clip_plane( &warp_shipp->warp_effect_pos, &warp_shipp->warp_effect_fvec );
|
|
|
|
// Turn off model caching while going thru warp effect.
|
|
render_flags |= MR_ALWAYS_REDRAW;
|
|
}
|
|
|
|
// maybe set squad logo bitmap
|
|
model_set_insignia_bitmap(-1);
|
|
#ifndef NO_NETWORK
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
// if its any player's object
|
|
int np_index = multi_find_player_by_object( obj );
|
|
if((np_index >= 0) && (np_index < MAX_PLAYERS) && MULTI_CONNECTED(Net_players[np_index]) && (Net_players[np_index].player != NULL)){
|
|
model_set_insignia_bitmap(Net_players[np_index].player->insignia_texture);
|
|
}
|
|
}
|
|
// in single player, we want to render model insignias on all ships in alpha beta and gamma
|
|
else
|
|
#endif
|
|
{
|
|
// if its an object in my squadron
|
|
if(ship_in_abgz(shipp)){
|
|
model_set_insignia_bitmap(Player->insignia_texture);
|
|
}
|
|
}
|
|
|
|
// maybe disable lighting
|
|
// if((The_mission.flags & MISSION_FLAG_FULLNEB) && (neb2_get_fog_intensity(obj) > 0.33f) && (si->flags & SIF_SMALL_SHIP)){
|
|
// render_flags |= MR_NO_LIGHTING;
|
|
// }
|
|
|
|
// nebula
|
|
if(The_mission.flags & MISSION_FLAG_FULLNEB){
|
|
extern void model_set_fog_level(float l);
|
|
model_set_fog_level(neb2_get_fog_intensity(obj));
|
|
}
|
|
|
|
// small ships
|
|
if((The_mission.flags & MISSION_FLAG_FULLNEB) && (si->flags & SIF_SMALL_SHIP)){
|
|
// force detail levels
|
|
float fog_val = neb2_get_fog_intensity(obj);
|
|
if(fog_val >= 0.6f){
|
|
model_set_detail_level(2);
|
|
model_render( shipp->modelnum, &obj->orient, &obj->pos, render_flags | MR_LOCK_DETAIL, OBJ_INDEX(obj) );
|
|
} else {
|
|
model_render( shipp->modelnum, &obj->orient, &obj->pos, render_flags, OBJ_INDEX(obj) );
|
|
}
|
|
} else {
|
|
model_render( shipp->modelnum, &obj->orient, &obj->pos, render_flags, OBJ_INDEX(obj) );
|
|
}
|
|
|
|
// always turn off fog after rendering a ship
|
|
gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
|
|
|
|
light_set_shadow(0);
|
|
|
|
#ifndef NDEBUG
|
|
if (Show_shield_mesh)
|
|
ship_draw_shield( obj); // Render the shield.
|
|
#endif
|
|
|
|
if ( clip_started ) {
|
|
g3_stop_user_clip_plane();
|
|
}
|
|
}
|
|
|
|
/* if (Mc.shield_hit_tri != -1) {
|
|
//render_shield_explosion(model_num, orient, pos, &Hit_point, Hit_tri);
|
|
Mc.shield_hit_tri = -1;
|
|
}
|
|
*/
|
|
|
|
ship_model_stop(obj);
|
|
|
|
if (shipp->shield_hits) {
|
|
create_shield_explosion_all(obj);
|
|
shipp->shield_hits = 0;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
if (Ai_render_debug_flag || Show_paths) {
|
|
if ( shipp->ai_index != -1 ){
|
|
render_path_points(obj);
|
|
}
|
|
|
|
render_dock_bays(obj);
|
|
}
|
|
#endif
|
|
|
|
#ifndef NDEBUG
|
|
if(Show_tnorms){
|
|
ship_subsys *systemp;
|
|
vector tpos, tnorm, temp;
|
|
vector v1, v2;
|
|
vertex l1, l2;
|
|
|
|
gr_set_color(0, 0, 255);
|
|
systemp = GET_FIRST( &shipp->subsys_list );
|
|
while ( systemp != END_OF_LIST(&shipp->subsys_list) ) {
|
|
ship_get_global_turret_gun_info(obj, systemp, &tpos, &tnorm, 1, &temp);
|
|
|
|
v1 = tpos;
|
|
vm_vec_scale_add(&v2, &v1, &tnorm, 20.0f);
|
|
|
|
g3_rotate_vertex(&l1, &v1);
|
|
g3_rotate_vertex(&l2, &v2);
|
|
|
|
g3_draw_sphere(&l1, 2.0f);
|
|
g3_draw_line(&l1, &l2);
|
|
|
|
systemp = GET_NEXT(systemp);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ship_subsystem_delete(ship *shipp)
|
|
{
|
|
ship_subsys *systemp, *temp;
|
|
|
|
systemp = GET_FIRST( &shipp->subsys_list );
|
|
while ( systemp != END_OF_LIST(&shipp->subsys_list) ) {
|
|
temp = GET_NEXT( systemp ); // use temporary since pointers will get screwed with next operation
|
|
list_remove( &shipp->subsys_list, systemp ); // remove the element
|
|
list_append( &ship_subsys_free_list, systemp ); // and place back onto free list
|
|
systemp = temp; // use the temp variable to move right along
|
|
}
|
|
}
|
|
|
|
void ship_delete( object * obj )
|
|
{
|
|
ship *shipp;
|
|
int num, objnum;
|
|
|
|
num = obj->instance;
|
|
Assert( num >= 0);
|
|
|
|
objnum = OBJ_INDEX(obj);
|
|
Assert( Ships[num].objnum == objnum );
|
|
|
|
shipp = &Ships[num];
|
|
|
|
if (shipp->ai_index != -1){
|
|
ai_free_slot(shipp->ai_index);
|
|
}
|
|
|
|
// free up the list of subsystems of this ship. walk through list and move remaining subsystems
|
|
// on ship back to the free list for other ships to use.
|
|
ship_subsystem_delete(&Ships[num]);
|
|
|
|
shipp->objnum = -1;
|
|
// mwa 11/24/97 num_ships--;
|
|
|
|
if (model_get(shipp->modelnum)->shield.ntris) {
|
|
free(shipp->shield_integrity);
|
|
shipp->shield_integrity = NULL;
|
|
}
|
|
|
|
if ( shipp->ship_list_index != -1 ) {
|
|
ship_obj_list_remove(shipp->ship_list_index);
|
|
shipp->ship_list_index = -1;
|
|
}
|
|
|
|
free_sexp2(shipp->arrival_cue);
|
|
free_sexp2(shipp->departure_cue);
|
|
|
|
// call the contrail system
|
|
ct_ship_delete(shipp);
|
|
}
|
|
|
|
// function used by ship_destroyed and ship_departed which is called if the ship
|
|
// is in a wing. This function updates the ship_index list (i.e. removes it's
|
|
// entry in the list), and packs the array accordingly.
|
|
void ship_wing_cleanup( int shipnum, wing *wingp )
|
|
{
|
|
int i, index = -1, team;
|
|
|
|
team = Ships[shipnum].team;
|
|
// compress the ship_index array and mark the last entry with a -1
|
|
for (i = 0; i < wingp->current_count; i++ ) {
|
|
if ( wingp->ship_index[i] == shipnum ) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Assert(index != -1);
|
|
|
|
// this can happen in multiplayer (dogfight, ingame join specifically)
|
|
if(index == -1){
|
|
return;
|
|
}
|
|
|
|
for ( i = index; i < wingp->current_count - 1; i++ ){
|
|
wingp->ship_index[i] = wingp->ship_index[i+1];
|
|
}
|
|
|
|
wingp->current_count--;
|
|
Assert ( wingp->current_count >= 0 );
|
|
wingp->ship_index[wingp->current_count] = -1;
|
|
|
|
// if the current count is 0, check to see if the wing departed or was destroyed.
|
|
if ( wingp->current_count == 0 ) {
|
|
|
|
// if this wing was ordered to depart by the player, set the current_wave equal to the total
|
|
// waves so we can mark the wing as gone and no other ships arrive
|
|
if ( wingp->flags & WF_DEPARTURE_ORDERED )
|
|
wingp->current_wave = wingp->num_waves;
|
|
|
|
// first, be sure to mark a wing destroyed event if all members of wing were destroyed and on
|
|
// the last wave. This circumvents a problem where the wing could be marked as departed and
|
|
// destroyed if the last ships were destroyed after the wing's departure cue became true.
|
|
|
|
// if the wing wasn't destroyed, and it is departing, then mark it as departed -- in this
|
|
// case, there had better be ships in this wing with departure entries in the log file. The
|
|
// logfile code checks for this case.
|
|
if ( (wingp->current_wave == wingp->num_waves) && (wingp->total_destroyed == wingp->total_arrived_count) ) {
|
|
mission_log_add_entry(LOG_WING_DESTROYED, wingp->name, NULL, team);
|
|
wingp->flags |= WF_WING_GONE;
|
|
wingp->time_gone = Missiontime;
|
|
} else if ( (wingp->flags & WF_WING_DEPARTING) || (wingp->current_wave == wingp->num_waves) ) {
|
|
#ifndef NDEBUG
|
|
ship_obj *so;
|
|
|
|
|
|
// apparently, there have been reports of ships still present in the mission when this log
|
|
// entry if written. Do a sanity check here to find out for sure.
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
|
|
// skip the player -- stupid special case.
|
|
if ( &Objects[so->objnum] == Player_obj )
|
|
continue;
|
|
|
|
#ifndef NO_NETWORK
|
|
if ( (Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_INGAME_JOIN) )
|
|
continue;
|
|
#endif
|
|
if ( (Ships[Objects[so->objnum].instance].wingnum == WING_INDEX(wingp)) && !(Ships[Objects[so->objnum].instance].flags & (SF_DEPARTING|SF_DYING)) )
|
|
Int3();
|
|
}
|
|
#endif
|
|
|
|
if ( wingp->flags & (WF_WING_DEPARTING|WF_DEPARTURE_ORDERED) )
|
|
mission_log_add_entry(LOG_WING_DEPART, wingp->name, NULL, team);
|
|
|
|
wingp->flags |= WF_WING_GONE;
|
|
wingp->time_gone = Missiontime;
|
|
}
|
|
}
|
|
}
|
|
|
|
// function to do management, like log entries and wing cleanup after a ship has been destroyed
|
|
|
|
void ship_destroyed( int num )
|
|
{
|
|
ship *shipp;
|
|
object *objp;
|
|
|
|
shipp = &Ships[num];
|
|
objp = &Objects[shipp->objnum];
|
|
|
|
// add the information to the exited ship list
|
|
ship_add_exited_ship( shipp, SEF_DESTROYED );
|
|
|
|
// determine if we need to count this ship as a klll in counting number of kills per ship type
|
|
// look at the ignore flag for the ship (if not in a wing), or the ignore flag for the wing
|
|
// (if the ship is in a wing), and add to the kill count if the flags are not set
|
|
if ( !(shipp->flags & SF_IGNORE_COUNT) || ((shipp->wingnum != -1) && !(Wings[shipp->wingnum].flags & WF_IGNORE_COUNT)) )
|
|
ship_add_ship_type_kill_count( Ship_info[shipp->ship_info_index].flags );
|
|
|
|
// if ship belongs to a wing -- increment the total number of ships in the wing destroyed
|
|
if ( shipp->wingnum != -1 ) {
|
|
wing *wingp;
|
|
|
|
wingp = &Wings[shipp->wingnum];
|
|
wingp->total_destroyed++;
|
|
ship_wing_cleanup( num, wingp );
|
|
}
|
|
|
|
// Note, this call to ai_ship_destroy must come after ship_wing_cleanup for guarded wings to
|
|
// properly note the destruction of a ship in their wing.
|
|
if ( shipp->ai_index != -1 ) {
|
|
ai_ship_destroy(num, SEF_DESTROYED); // Do AI stuff for destruction of ship.
|
|
}
|
|
|
|
nprintf(("Alan","SHIP DESTROYED: %s\n", shipp->ship_name));
|
|
|
|
if ( (shipp->wing_status_wing_index >= 0) && (shipp->wing_status_wing_pos >= 0) ) {
|
|
nprintf(("Alan","STATUS UPDATED: %s\n", shipp->ship_name));
|
|
hud_set_wingman_status_dead(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
|
|
}
|
|
|
|
// let the event music system know a hostile was destoyed (important for deciding when to transition from battle to normal music)
|
|
if (Player_ship != NULL) {
|
|
if (shipp->team != Player_ship->team) {
|
|
event_music_hostile_ship_destroyed();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ship_vanished(int num)
|
|
{
|
|
ship *sp;
|
|
object *objp;
|
|
|
|
sp = &Ships[num];
|
|
objp = &Objects[sp->objnum];
|
|
|
|
// demo recording
|
|
if(Game_mode & GM_DEMO_RECORD){
|
|
demo_POST_departed(Objects[Ships[num].objnum].signature, Ships[num].flags);
|
|
}
|
|
|
|
// add the information to the exited ship list
|
|
ship_add_exited_ship( sp, SEF_DEPARTED );
|
|
|
|
// update wingman status gauge
|
|
if ( (sp->wing_status_wing_index >= 0) && (sp->wing_status_wing_pos >= 0) ) {
|
|
hud_set_wingman_status_departed(sp->wing_status_wing_index, sp->wing_status_wing_pos);
|
|
}
|
|
|
|
ai_ship_destroy(num, SEF_DEPARTED); // should still do AI cleanup after ship has departed
|
|
}
|
|
|
|
void ship_departed( int num )
|
|
{
|
|
ship *sp;
|
|
int i;
|
|
|
|
sp = &Ships[num];
|
|
|
|
// demo recording
|
|
if(Game_mode & GM_DEMO_RECORD){
|
|
demo_POST_departed(Objects[Ships[num].objnum].signature, Ships[num].flags);
|
|
}
|
|
|
|
// add the information to the exited ship list
|
|
ship_add_exited_ship( sp, SEF_DEPARTED );
|
|
|
|
// update wingman status gauge
|
|
if ( (sp->wing_status_wing_index >= 0) && (sp->wing_status_wing_pos >= 0) ) {
|
|
hud_set_wingman_status_departed(sp->wing_status_wing_index, sp->wing_status_wing_pos);
|
|
}
|
|
|
|
// see if this ship departed within the radius of a jump node -- if so, put the node name into
|
|
// the secondary mission log field
|
|
for ( i = 0; i < Num_jump_nodes; i++ ) {
|
|
float radius, dist;
|
|
vector ship_pos, node_pos;
|
|
|
|
ship_pos = Objects[sp->objnum].pos;
|
|
node_pos = Objects[Jump_nodes[i].objnum].pos;
|
|
radius = model_get_radius( Jump_nodes[i].modelnum );
|
|
dist = vm_vec_dist( &ship_pos, &node_pos );
|
|
if ( dist <= radius ) {
|
|
mission_log_add_entry(LOG_SHIP_DEPART, sp->ship_name, Jump_nodes[i].name, sp->wingnum);
|
|
break;
|
|
}
|
|
dist = 1.0f;
|
|
}
|
|
|
|
if ( i == Num_jump_nodes ){
|
|
mission_log_add_entry(LOG_SHIP_DEPART, sp->ship_name, NULL, sp->wingnum);
|
|
}
|
|
|
|
ai_ship_destroy(num, SEF_DEPARTED); // should still do AI cleanup after ship has departed
|
|
|
|
// don't bother doing this for demo playback - we don't keep track of wing info
|
|
if(!(Game_mode & GM_DEMO_PLAYBACK)){
|
|
if ( sp->wingnum != -1 ) {
|
|
wing *wingp;
|
|
|
|
wingp = &Wings[sp->wingnum];
|
|
wingp->total_departed++;
|
|
ship_wing_cleanup( num, wingp );
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
// ship_explode_area_calc_damage
|
|
//
|
|
// input pos1 => ship explosion position
|
|
// pos2 => other ship position
|
|
// inner_rad => distance from ship center for which full damage is applied
|
|
// outer_rad => distance from ship center for which no damage is applied
|
|
// max_damage => maximum damage applied
|
|
// max_blast => maximum impulse applied from blast
|
|
//
|
|
// calculates the blast and damage applied to a ship from another ship blowing up.
|
|
//
|
|
int ship_explode_area_calc_damage( vector *pos1, vector *pos2, float inner_rad, float outer_rad, float max_damage, float max_blast, float *damage, float *blast )
|
|
{
|
|
float dist;
|
|
|
|
dist = vm_vec_dist_quick( pos1, pos2 );
|
|
|
|
// check outside outer radius
|
|
if ( dist > outer_rad )
|
|
return -1;
|
|
|
|
if ( dist < inner_rad ) {
|
|
// check insider inner radius
|
|
*damage = max_damage;
|
|
*blast = max_blast;
|
|
} else {
|
|
// between inner and outer
|
|
float fraction = 1.0f - (dist - inner_rad) / (outer_rad - inner_rad);
|
|
*damage = fraction * max_damage;
|
|
*blast = fraction * max_blast;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
// ship_blow_up_area_apply_blast
|
|
// this function applies damage to ship close to others when a ship dies and blows up
|
|
//
|
|
// inputs: objp => ship object pointers
|
|
// pos => position of the ship when it finally blows up
|
|
// inner_rad => distance from ship center for which full damage is applied
|
|
// outer_rad => distance from ship center for which no damage is applied
|
|
// damage => maximum damage applied
|
|
// blast => maximum impulse applied from blast
|
|
|
|
void ship_blow_up_area_apply_blast( object *exp_objp)
|
|
{
|
|
ship_info *sip;
|
|
Assert( exp_objp->type == OBJ_SHIP );
|
|
float inner_rad, outer_rad, max_damage, max_blast, shockwave_speed;
|
|
shockwave_create_info sci;
|
|
|
|
// No area explosion in training missions.
|
|
if (The_mission.game_type & MISSION_TYPE_TRAINING){
|
|
return;
|
|
}
|
|
|
|
if ((exp_objp->hull_strength <= KAMIKAZE_HULL_ON_DEATH) && (Ai_info[Ships[exp_objp->instance].ai_index].ai_flags & AIF_KAMIKAZE) && (Ships[exp_objp->instance].special_exp_index < 0)) {
|
|
float override = Ai_info[Ships[exp_objp->instance].ai_index].kamikaze_damage;
|
|
|
|
inner_rad = exp_objp->radius*2.0f;
|
|
outer_rad = exp_objp->radius*4.0f; // + (override * 0.3f);
|
|
max_damage = override;
|
|
max_blast = override * 5.0f;
|
|
shockwave_speed = 100.0f;
|
|
} else {
|
|
sip = &Ship_info[Ships[exp_objp->instance].ship_info_index];
|
|
|
|
if (Ships[exp_objp->instance].special_exp_index != -1) {
|
|
int start = Ships[exp_objp->instance].special_exp_index;
|
|
int propagates;
|
|
inner_rad = (float) atoi(Sexp_variables[start+INNER_RAD].text);
|
|
outer_rad = (float) atoi(Sexp_variables[start+OUTER_RAD].text);
|
|
max_damage = (float) atoi(Sexp_variables[start+DAMAGE].text);
|
|
max_blast = (float) atoi(Sexp_variables[start+BLAST].text);
|
|
propagates = atoi(Sexp_variables[start+PROPAGATE].text);
|
|
if (propagates) {
|
|
shockwave_speed = (float) atoi(Sexp_variables[start+SHOCK_SPEED].text);
|
|
} else {
|
|
shockwave_speed = 0.0f;
|
|
}
|
|
} else {
|
|
inner_rad = sip->inner_rad;
|
|
outer_rad = sip->outer_rad;
|
|
max_damage = sip->damage;
|
|
max_blast = sip->blast;
|
|
shockwave_speed = sip->shockwave_speed;
|
|
}
|
|
}
|
|
|
|
// nprintf(("AI", "Frame %i: Area effect blast from ship %s\n", Framecount, Ships[exp_objp->instance].ship_name));
|
|
|
|
// account for ships that give no damage when they blow up.
|
|
if ( (max_damage < 0.1f) && (max_blast < 0.1f) ){
|
|
return;
|
|
}
|
|
|
|
if ( shockwave_speed > 0 ) {
|
|
sci.inner_rad = inner_rad;
|
|
sci.outer_rad = outer_rad;
|
|
sci.blast = max_blast;
|
|
sci.damage = max_damage;
|
|
sci.speed = shockwave_speed;
|
|
sci.rot_angle = frand_range(0.0f, 359.0f);
|
|
shipfx_do_shockwave_stuff(&Ships[exp_objp->instance], &sci);
|
|
// shockwave_create(Ships[exp_objp->instance].objnum, &exp_objp->pos, shockwave_speed, inner_rad, outer_rad, max_damage, max_blast, SW_SHIP_DEATH);
|
|
} else {
|
|
object *objp;
|
|
float blast = 0.0f;
|
|
float damage = 0.0f;
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( objp == exp_objp ){
|
|
continue;
|
|
}
|
|
|
|
// don't blast navbuoys
|
|
if ( objp->type == OBJ_SHIP ) {
|
|
if ( ship_get_SIF(objp->instance) & SIF_NAVBUOY ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( ship_explode_area_calc_damage( &exp_objp->pos, &objp->pos, inner_rad, outer_rad, max_damage, max_blast, &damage, &blast ) == -1 ){
|
|
continue;
|
|
}
|
|
|
|
switch ( objp->type ) {
|
|
case OBJ_SHIP:
|
|
ship_apply_global_damage( objp, exp_objp, &exp_objp->pos, damage );
|
|
vector force, vec_ship_to_impact;
|
|
vm_vec_sub( &vec_ship_to_impact, &objp->pos, &exp_objp->pos );
|
|
vm_vec_copy_normalize( &force, &vec_ship_to_impact );
|
|
vm_vec_scale( &force, blast );
|
|
ship_apply_whack( &force, &vec_ship_to_impact, objp );
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
asteroid_hit(objp, NULL, NULL, damage);
|
|
break;
|
|
default:
|
|
Int3();
|
|
break;
|
|
}
|
|
} // end for
|
|
}
|
|
}
|
|
|
|
void do_dying_undock_physics(object* objp, ship* sp)
|
|
{
|
|
Assert(sp->dock_objnum_when_dead >= 0);
|
|
if(sp->dock_objnum_when_dead < 0){
|
|
return;
|
|
}
|
|
object* dock_obj = &Objects[sp->dock_objnum_when_dead];
|
|
|
|
// sanity checks
|
|
Assert(objp->type == OBJ_SHIP);
|
|
Assert(dock_obj->type == OBJ_SHIP);
|
|
if((objp->type != OBJ_SHIP) || (dock_obj->type != OBJ_SHIP)){
|
|
return;
|
|
}
|
|
|
|
float damage = 0.2f*Ship_info[sp->ship_info_index].initial_hull_strength;
|
|
ship_apply_global_damage(dock_obj, objp, &objp->pos, damage);
|
|
|
|
// do physics
|
|
vector impulse_norm, impulse_vec, pos;
|
|
vm_vec_sub(&impulse_norm, &dock_obj->pos, &objp->pos);
|
|
vm_vec_normalize(&impulse_norm);
|
|
// set for relative separation velocity of ~30
|
|
float impulse_mag = 50.f*dock_obj->phys_info.mass*objp->phys_info.mass/(dock_obj->phys_info.mass + objp->phys_info.mass);
|
|
vm_vec_copy_scale(&impulse_vec, &impulse_norm, impulse_mag);
|
|
vm_vec_rand_vec_quick(&pos);
|
|
vm_vec_scale(&pos, dock_obj->radius);
|
|
// apply whack to dock obj
|
|
physics_apply_whack(&impulse_vec, &pos, &dock_obj->phys_info, &dock_obj->orient, dock_obj->phys_info.mass);
|
|
// enhance rotation of the docked ship
|
|
vm_vec_scale(&dock_obj->phys_info.rotvel, 2.0f);
|
|
|
|
// apply whack to ship
|
|
vm_vec_negate(&impulse_vec);
|
|
vm_vec_rand_vec_quick(&pos);
|
|
vm_vec_scale(&pos, objp->radius);
|
|
physics_apply_whack(&impulse_vec, &pos, &objp->phys_info, &objp->orient, objp->phys_info.mass);
|
|
|
|
// reset dock_objnum_when_dead to -1 for dockee, since docker has blown up.
|
|
if (Ships[dock_obj->instance].dock_objnum_when_dead == sp->objnum) {
|
|
Ships[dock_obj->instance].dock_objnum_when_dead = -1;
|
|
}
|
|
}
|
|
|
|
// Do the stuff we do in a frame for a ship that's in its death throes.
|
|
void ship_dying_frame(object *objp, int ship_num)
|
|
{
|
|
ship *sp;
|
|
sp = &Ships[ship_num];
|
|
int knossos_ship = false;
|
|
|
|
if ( sp->flags & SF_DYING ) {
|
|
knossos_ship = (Ship_info[sp->ship_info_index].flags & SIF_KNOSSOS_DEVICE);
|
|
|
|
// bash hull value toward 0 (from self destruct)
|
|
if (objp->hull_strength > 0) {
|
|
int time_left = timestamp_until(sp->final_death_time);
|
|
float hits_left = objp->hull_strength;
|
|
|
|
objp->hull_strength -= hits_left * (1000.0f * flFrametime) / time_left;
|
|
}
|
|
|
|
// special case of VAPORIZE
|
|
if (sp->flags & SF_VAPORIZE) {
|
|
// Assert(Ship_info[sp->ship_info_index].flags & SIF_SMALL_SHIP);
|
|
if (timestamp_elapsed(sp->final_death_time)) {
|
|
|
|
// play death sound
|
|
snd_play_3d( &Snds[SND_VAPORIZED], &objp->pos, &View_position, objp->radius, NULL, 0, 1.0f, SND_PRIORITY_MUST_PLAY );
|
|
|
|
// do joystick effect
|
|
if (objp == Player_obj) {
|
|
joy_ff_explode();
|
|
}
|
|
|
|
// if dying ship is docked, do damage to docked and physics
|
|
if (sp->dock_objnum_when_dead != -1) {
|
|
do_dying_undock_physics(objp, sp);
|
|
}
|
|
|
|
// do all accounting for respawning client and server side here.
|
|
if (objp == Player_obj) {
|
|
gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
|
|
}
|
|
|
|
// mark object as dead
|
|
objp->flags |= OF_SHOULD_BE_DEAD;
|
|
|
|
// Don't blow up model. Only use debris shards.
|
|
// call ship function to clean up after the ship is destroyed.
|
|
ship_destroyed(ship_num);
|
|
return;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// bash the desired rotvel
|
|
objp->phys_info.desired_rotvel = sp->deathroll_rotvel;
|
|
|
|
// Do fireballs for Big ship with propagating explostion, but not Kamikaze
|
|
if (!(Ai_info[sp->ai_index].ai_flags & AIF_KAMIKAZE) && ship_get_exp_propagates(sp)) {
|
|
if ( timestamp_elapsed(Ships[ship_num].next_fireball)) {
|
|
vector outpnt, pnt1, pnt2;
|
|
polymodel *pm = model_get(sp->modelnum);
|
|
|
|
// Gets two random points on the surface of a submodel
|
|
submodel_get_two_random_points(sp->modelnum, pm->detail[0], &pnt1, &pnt2 );
|
|
|
|
// vm_vec_avg( &tmp, &pnt1, &pnt2 ); [KNOSSOS get random in plane 1/1.414 in rad
|
|
model_find_world_point(&outpnt, &pnt1, sp->modelnum, pm->detail[0], &objp->orient, &objp->pos );
|
|
|
|
float rad = objp->radius*0.1f;
|
|
int fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
|
|
fireball_create( &outpnt, fireball_type, OBJ_INDEX(objp), rad, 0, &objp->phys_info.vel );
|
|
// start the next fireball up in the next 50 - 200 ms (2-3 per frame)
|
|
sp->next_fireball = timestamp_rand(333,500);
|
|
|
|
// do sound - maybe start a random sound, if it has played far enough.
|
|
do_sub_expl_sound(objp->radius, &outpnt, sp->sub_expl_sound_handle);
|
|
}
|
|
}
|
|
|
|
// create little fireballs for knossos as it dies
|
|
if (knossos_ship) {
|
|
if ( timestamp_elapsed(Ships[ship_num].next_fireball)) {
|
|
vector rand_vec, outpnt; // [0-.7 rad] in plane
|
|
vm_vec_rand_vec_quick(&rand_vec);
|
|
float scale = -vm_vec_dotprod(&objp->orient.vec.fvec, &rand_vec) * (0.9f + 0.2f * frand());
|
|
vm_vec_scale_add2(&rand_vec, &objp->orient.vec.fvec, scale);
|
|
vm_vec_normalize_quick(&rand_vec);
|
|
scale = objp->radius * frand() * 0.717f;
|
|
vm_vec_scale(&rand_vec, scale);
|
|
vm_vec_add(&outpnt, &objp->pos, &rand_vec);
|
|
|
|
float rad = objp->radius*0.2f;
|
|
int fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
|
|
fireball_create( &outpnt, fireball_type, OBJ_INDEX(objp), rad, 0, &objp->phys_info.vel );
|
|
// start the next fireball up in the next 50 - 200 ms (2-3 per frame)
|
|
sp->next_fireball = timestamp_rand(333,500);
|
|
|
|
// emit particles
|
|
particle_emitter pe;
|
|
|
|
pe.num_low = 15; // Lowest number of particles to create
|
|
pe.num_high = 30; // Highest number of particles to create
|
|
pe.pos = outpnt; // Where the particles emit from
|
|
pe.vel = objp->phys_info.vel; // Initial velocity of all the particles
|
|
pe.min_life = 2.0f; // How long the particles live
|
|
pe.max_life = 12.0f; // How long the particles live
|
|
pe.normal = objp->orient.vec.uvec; // What normal the particle emit around
|
|
pe.normal_variance = 2.0f; // How close they stick to that normal 0=on normal, 1=180, 2=360 degree
|
|
pe.min_vel = 50.0f;
|
|
pe.max_vel = 350.0f;
|
|
pe.min_rad = 30.0f; // * objp->radius;
|
|
pe.max_rad = 100.0f; // * objp->radius;
|
|
particle_emit( &pe, PARTICLE_SMOKE2, 0, 50 );
|
|
|
|
// do sound - maybe start a random sound, if it has played far enough.
|
|
do_sub_expl_sound(objp->radius, &outpnt, sp->sub_expl_sound_handle);
|
|
}
|
|
}
|
|
|
|
|
|
//nprintf(("AI", "Ship.cpp: Frame=%i, Time = %7.3f, Ship %s will die in %7.3f seconds.\n", Framecount, f2fl(Missiontime), Ships[ship_num].ship_name, (float) timestamp_until(sp->final_death_time)/1000.0f));
|
|
int time_until_minor_explosions = timestamp_until(sp->final_death_time);
|
|
|
|
// Wait until just before death and set off some explosions
|
|
// If it is less than 1/2 second until large explosion, but there is
|
|
// at least 1/10th of a second left, then create 5 small explosions
|
|
if ( (time_until_minor_explosions < 500) && (time_until_minor_explosions > 100) && (!sp->pre_death_explosion_happened) ) {
|
|
//mprintf(( "Ship almost dying!!\n" ));
|
|
sp->next_fireball = timestamp(-1); // never time out again
|
|
sp->pre_death_explosion_happened=1; // Mark this event as having occurred
|
|
|
|
polymodel *pm = model_get(sp->modelnum);
|
|
|
|
// Start shockwave for ship with propagating explosion, do now for timing
|
|
if ( ship_get_exp_propagates(sp) ) {
|
|
ship_blow_up_area_apply_blast( objp );
|
|
}
|
|
|
|
for (int zz=0; zz<6; zz++ ) {
|
|
// dont make sequence of fireballs for knossos
|
|
if (knossos_ship) {
|
|
break;
|
|
}
|
|
// Find two random vertices on the model, then average them
|
|
// and make the piece start there.
|
|
vector tmp, outpnt, pnt1, pnt2;
|
|
|
|
// Gets two random points on the surface of a submodel [KNOSSOS]
|
|
submodel_get_two_random_points(sp->modelnum, pm->detail[0], &pnt1, &pnt2 );
|
|
|
|
vm_vec_avg( &tmp, &pnt1, &pnt2 );
|
|
model_find_world_point(&outpnt, &tmp, sp->modelnum, pm->detail[0], &objp->orient, &objp->pos );
|
|
|
|
float rad = frand()*0.30f;
|
|
rad += objp->radius*0.40f;
|
|
fireball_create( &outpnt, FIREBALL_EXPLOSION_MEDIUM, OBJ_INDEX(objp), rad, 0, &objp->phys_info.vel );
|
|
}
|
|
|
|
// if ship is docked, undock now.
|
|
if (sp->dock_objnum_when_dead != -1) {
|
|
// other ship undocks
|
|
// These asserts should no longer be needed and they cause a problem that is not obvious how to fix.
|
|
//Assert( !(Ai_info[Ships[dock_obj->instance].ai_index].ai_flags & AIF_DOCKED) );
|
|
//Assert( Ai_info[Ships[dock_obj->instance].ai_index].dock_objnum == -1 );
|
|
// MWA Ai_info[Ships[dock_obj->instance].ai_index].ai_flags &= ~AIF_DOCKED;
|
|
// MWA Ai_info[Ships[dock_obj->instance].ai_index].dock_objnum = -1;
|
|
// MWA Ai_info[Ships[dock_obj->instance].ai_index].mode = AIM_NONE;
|
|
}
|
|
}
|
|
|
|
if ( timestamp_elapsed(sp->final_death_time)) {
|
|
|
|
sp->final_death_time = timestamp(-1); // never time out again
|
|
//mprintf(( "Ship dying!!\n" ));
|
|
|
|
// play ship explosion sound effect, pick appropriate explosion sound
|
|
int sound_index;
|
|
if ( Ship_info[sp->ship_info_index].flags & (SIF_CAPITAL | SIF_KNOSSOS_DEVICE) ) {
|
|
sound_index=SND_CAPSHIP_EXPLODE;
|
|
} else {
|
|
if ( OBJ_INDEX(objp) & 1 ) {
|
|
sound_index=SND_SHIP_EXPLODE_1;
|
|
} else {
|
|
sound_index=SND_SHIP_EXPLODE_2;
|
|
}
|
|
}
|
|
|
|
snd_play_3d( &Snds[sound_index], &objp->pos, &View_position, objp->radius, NULL, 0, 1.0f, SND_PRIORITY_MUST_PLAY );
|
|
if (objp == Player_obj)
|
|
joy_ff_explode();
|
|
|
|
if ( sp->death_roll_snd != -1 ) {
|
|
snd_stop(sp->death_roll_snd);
|
|
sp->death_roll_snd = -1;
|
|
}
|
|
|
|
// if dying ship is docked, do damage to docked and physics
|
|
if (sp->dock_objnum_when_dead != -1) {
|
|
do_dying_undock_physics(objp, sp);
|
|
}
|
|
|
|
// play a random explosion
|
|
particle_emitter pe;
|
|
|
|
pe.num_low = 50; // Lowest number of particles to create
|
|
pe.num_high = 100; // Highest number of particles to create
|
|
pe.pos = objp->pos; // Where the particles emit from
|
|
pe.vel = objp->phys_info.vel; // Initial velocity of all the particles
|
|
pe.min_life = 0.5f; // How long the particles live
|
|
pe.max_life = 4.0f; // How long the particles live
|
|
pe.normal = objp->orient.vec.uvec; // What normal the particle emit around
|
|
pe.normal_variance = 2.0f; // How close they stick to that normal 0=on normal, 1=180, 2=360 degree
|
|
pe.min_vel = 0.0f; // How fast the slowest particle can move
|
|
pe.max_vel = 20.0f; // How fast the fastest particle can move
|
|
pe.min_rad = 0.1f; // Min radius
|
|
pe.max_rad = 1.5f; // Max radius
|
|
|
|
if (!knossos_ship) {
|
|
particle_emit( &pe, PARTICLE_SMOKE2, 0 );
|
|
}
|
|
|
|
// If this is a large ship with a propagating explosion, set it to blow up.
|
|
if ( ship_get_exp_propagates(sp) ) {
|
|
if (Ai_info[sp->ai_index].ai_flags & AIF_KAMIKAZE) {
|
|
ship_blow_up_area_apply_blast( objp );
|
|
}
|
|
shipfx_large_blowup_init(sp);
|
|
// need to timeout immediately to keep physics in sync
|
|
sp->really_final_death_time = timestamp(0);
|
|
} else {
|
|
// only do big fireball if not big ship
|
|
float big_rad;
|
|
int fireball_objnum, fireball_type;
|
|
float explosion_life;
|
|
big_rad = objp->radius*1.75f;
|
|
fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
|
|
if (knossos_ship) {
|
|
big_rad = objp->radius * 1.2f;
|
|
fireball_type = FIREBALL_EXPLOSION_LARGE1;
|
|
}
|
|
fireball_objnum = fireball_create( &objp->pos, fireball_type, OBJ_INDEX(objp), big_rad, 0, &objp->phys_info.vel );
|
|
if ( fireball_objnum > -1 ) {
|
|
explosion_life = fireball_lifeleft(&Objects[fireball_objnum]);
|
|
} else {
|
|
explosion_life = 0.0f;
|
|
}
|
|
|
|
// JAS: I put in all this code because of an item on my todo list that
|
|
// said that the ship destroyed debris shouldn't pop in until the
|
|
// big explosion is 30% done. I did this on Oct24 and me & Adam
|
|
// thought it looked dumb since the explosion didn't move with the
|
|
// ship, so instead of just taking this code out, since we might need
|
|
// it in the future, I disabled it. You can reenable it by changing
|
|
// the commenting on the following two lines.
|
|
sp->really_final_death_time = timestamp( fl2i(explosion_life*1000.0f)/5 ); // Wait till 30% of vclip time before breaking the ship up.
|
|
//sp->really_final_death_time = timestamp(0); // Make ship break apart the instant the explosion starts
|
|
}
|
|
|
|
sp->flags |= SF_EXPLODED;
|
|
|
|
if ( !(ship_get_exp_propagates(sp)) ) {
|
|
// apply area of effect blast damage from ship explosion
|
|
ship_blow_up_area_apply_blast( objp );
|
|
}
|
|
}
|
|
|
|
if ( timestamp_elapsed(sp->really_final_death_time)) {
|
|
|
|
//mprintf(( "Ship really dying!!\n" ));
|
|
// do large_ship_split and explosion
|
|
if ( sp->large_ship_blowup_index > -1 ) {
|
|
if ( shipfx_large_blowup_do_frame(sp, flFrametime) ) {
|
|
// do all accounting for respawning client and server side here.
|
|
if(objp == Player_obj) {
|
|
gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
|
|
}
|
|
|
|
objp->flags |= OF_SHOULD_BE_DEAD;
|
|
|
|
ship_destroyed(ship_num); // call ship function to clean up after the ship is destroyed.
|
|
}
|
|
return;
|
|
}
|
|
|
|
//fireball_create( &objp->pos, FIREBALL_SHIP_EXPLODE1, OBJ_INDEX(objp), objp->radius/2.0f );
|
|
//mprintf(("Frame %i: Died!\n", Framecount));
|
|
|
|
shipfx_blow_up_model(objp, Ships[ship_num].modelnum, 0, 20, &objp->pos );
|
|
|
|
// do all accounting for respawning client and server side here.
|
|
if(objp == Player_obj) {
|
|
gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
|
|
}
|
|
|
|
objp->flags |= OF_SHOULD_BE_DEAD;
|
|
|
|
ship_destroyed(ship_num); // call ship function to clean up after the ship is destroyed.
|
|
sp->really_final_death_time = timestamp( -1 ); // Never time out again!
|
|
}
|
|
|
|
// If a ship is dying (and not a capital or big ship) then stutter the engine sound
|
|
if ( timestamp_elapsed(sp->next_engine_stutter) ) {
|
|
if ( !(Ship_info[sp->ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) ) {
|
|
sp->flags ^= SF_ENGINES_ON; // toggle state of engines
|
|
sp->next_engine_stutter = timestamp_rand(50, 250);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ship_chase_shield_energy_targets(ship *shipp, object *obj, float frametime)
|
|
{
|
|
float delta;
|
|
ship_info *sip;
|
|
|
|
if (shipp->flags & SF_DYING)
|
|
return;
|
|
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
delta = frametime * ETS_RECHARGE_RATE * sip->shields / 100.0f;
|
|
|
|
// Chase target_shields and target_weapon_energy
|
|
if (shipp->target_shields_delta > 0.0f) {
|
|
if (delta > shipp->target_shields_delta)
|
|
delta = shipp->target_shields_delta;
|
|
|
|
add_shield_strength(obj, delta);
|
|
shipp->target_shields_delta -= delta;
|
|
} else if (shipp->target_shields_delta < 0.0f) {
|
|
if (delta < -shipp->target_shields_delta)
|
|
delta = -shipp->target_shields_delta;
|
|
|
|
add_shield_strength(obj, -delta);
|
|
shipp->target_shields_delta += delta;
|
|
}
|
|
|
|
delta = frametime * ETS_RECHARGE_RATE * sip->max_weapon_reserve / 100.0f;
|
|
|
|
if (shipp->target_weapon_energy_delta > 0.0f) {
|
|
if (delta > shipp->target_weapon_energy_delta)
|
|
delta = shipp->target_weapon_energy_delta;
|
|
|
|
shipp->weapon_energy += delta;
|
|
shipp->target_weapon_energy_delta -= delta;
|
|
} else if (shipp->target_weapon_energy_delta < 0.0f) {
|
|
if (delta < -shipp->target_weapon_energy_delta)
|
|
delta = -shipp->target_weapon_energy_delta;
|
|
|
|
shipp->weapon_energy -= delta;
|
|
shipp->target_weapon_energy_delta += delta;
|
|
}
|
|
|
|
}
|
|
|
|
// Stuff for showing ship thrusters.
|
|
typedef struct thrust_anim {
|
|
int num_frames;
|
|
int first_frame;
|
|
float time; // in seconds
|
|
} thrust_anim;
|
|
|
|
#define NUM_THRUST_ANIMS 6
|
|
#define NUM_THRUST_GLOW_ANIMS 6
|
|
|
|
// These are indexed by: Species*2 + (After_burner_on?1:0)
|
|
static thrust_anim Thrust_anims[NUM_THRUST_ANIMS];
|
|
char Thrust_anim_names[NUM_THRUST_ANIMS][MAX_FILENAME_LEN] = {
|
|
//XSTR:OFF
|
|
"thruster01", "thruster01a",
|
|
"thruster02", "thruster02a",
|
|
"thruster03", "thruster03a"
|
|
//XSTR:ON
|
|
};
|
|
|
|
// These are indexed by: Species*2 + (After_burner_on?1:0)
|
|
static thrust_anim Thrust_glow_anims[NUM_THRUST_GLOW_ANIMS];
|
|
char Thrust_glow_anim_names[NUM_THRUST_GLOW_ANIMS][MAX_FILENAME_LEN] = {
|
|
//XSTR:OFF
|
|
"thrusterglow01", "thrusterglow01a",
|
|
"thrusterglow02", "thrusterglow02a",
|
|
"thrusterglow03", "thrusterglow03a"
|
|
//XSTR:ON
|
|
};
|
|
|
|
static int Thrust_anim_inited = 0;
|
|
|
|
// loads the animations for ship's afterburners
|
|
void ship_init_thrusters()
|
|
{
|
|
int fps, i;
|
|
thrust_anim *ta;
|
|
|
|
if ( Thrust_anim_inited == 1 )
|
|
return;
|
|
|
|
// AL 29-3-98: Don't want to include Shivan thrusters in the demo build
|
|
int num_thrust_anims = NUM_THRUST_ANIMS;
|
|
#ifdef DEMO // N/A FS2_DEMO
|
|
num_thrust_anims = NUM_THRUST_ANIMS - 2;
|
|
#endif
|
|
|
|
for ( i = 0; i < num_thrust_anims; i++ ) {
|
|
ta = &Thrust_anims[i];
|
|
ta->first_frame = bm_load_animation(Thrust_anim_names[i], &ta->num_frames, &fps, 1);
|
|
if ( ta->first_frame == -1 ) {
|
|
Error(LOCATION,"Error loading animation file: %s\n",Thrust_anim_names[i]);
|
|
return;
|
|
}
|
|
Assert(fps != 0);
|
|
ta->time = i2fl(ta->num_frames)/fps;
|
|
}
|
|
|
|
// AL 29-3-98: Don't want to include Shivan thrusters in the demo build
|
|
int num_thrust_glow_anims = NUM_THRUST_GLOW_ANIMS;
|
|
#ifdef DEMO // N/A FS2_DEMO
|
|
num_thrust_glow_anims = NUM_THRUST_GLOW_ANIMS - 2;
|
|
#endif
|
|
|
|
for ( i = 0; i < num_thrust_glow_anims; i++ ) {
|
|
ta = &Thrust_glow_anims[i];
|
|
ta->num_frames = NOISE_NUM_FRAMES;
|
|
fps = 15;
|
|
ta->first_frame = bm_load( Thrust_glow_anim_names[i] );
|
|
if ( ta->first_frame == -1 ) {
|
|
Error(LOCATION,"Error loading bitmap file: %s\n",Thrust_glow_anim_names[i]);
|
|
return;
|
|
}
|
|
Assert(fps != 0);
|
|
ta->time = i2fl(ta->num_frames)/fps;
|
|
}
|
|
|
|
Thrust_anim_inited = 1;
|
|
}
|
|
|
|
|
|
// JAS - figure out which thruster bitmap will get rendered next
|
|
// time around. ship_render needs to have shipp->thruster_bitmap set to
|
|
// a valid bitmap number, or -1 if we shouldn't render thrusters.
|
|
void ship_do_thruster_frame( ship *shipp, object *objp, float frametime )
|
|
{
|
|
float rate;
|
|
int framenum;
|
|
int anim_index;
|
|
thrust_anim *the_anim;
|
|
ship_info *sinfo = &Ship_info[shipp->ship_info_index];
|
|
|
|
if ( !Thrust_anim_inited ) ship_init_thrusters();
|
|
|
|
// The animations are organized by:
|
|
// Species*2 + (After_burner_on?1:0)
|
|
anim_index = sinfo->species*2;
|
|
|
|
if ( objp->phys_info.flags & PF_AFTERBURNER_ON ) {
|
|
anim_index++; // select afterburner anim.
|
|
rate = 1.5f; // go at 1.5x faster when afterburners on
|
|
} else {
|
|
// If thrust at 0, go at half as fast, full thrust; full framerate
|
|
// so set rate from 0.5 to 1.0, depending on thrust from 0 to 1
|
|
// rate = 0.5f + objp->phys_info.forward_thrust / 2.0f;
|
|
rate = 0.67f * (1.0f + objp->phys_info.forward_thrust);
|
|
}
|
|
|
|
// rate = 0.1f;
|
|
|
|
Assert( anim_index > -1 );
|
|
Assert( anim_index < NUM_THRUST_ANIMS );
|
|
|
|
the_anim = &Thrust_anims[anim_index];
|
|
|
|
Assert( frametime > 0.0f );
|
|
shipp->thruster_frame += frametime * rate;
|
|
|
|
// Sanity checks
|
|
if ( shipp->thruster_frame < 0.0f ) shipp->thruster_frame = 0.0f;
|
|
if ( shipp->thruster_frame > 100.0f ) shipp->thruster_frame = 0.0f;
|
|
|
|
while ( shipp->thruster_frame > the_anim->time ) {
|
|
shipp->thruster_frame -= the_anim->time;
|
|
}
|
|
framenum = fl2i( (shipp->thruster_frame*the_anim->num_frames) / the_anim->time );
|
|
if ( framenum < 0 ) framenum = 0;
|
|
if ( framenum >= the_anim->num_frames ) framenum = the_anim->num_frames-1;
|
|
|
|
// if ( anim_index == 0 )
|
|
// mprintf(( "Frame = %d/%d, anim=%d\n", framenum+1, the_anim->num_frames, anim_index ));
|
|
|
|
// Get the bitmap for this frame
|
|
shipp->thruster_bitmap = the_anim->first_frame + framenum;
|
|
|
|
// mprintf(( "TF: %.2f\n", shipp->thruster_frame ));
|
|
|
|
// Do it for glow bitmaps
|
|
the_anim = &Thrust_glow_anims[anim_index];
|
|
|
|
Assert( frametime > 0.0f );
|
|
shipp->thruster_glow_frame += frametime * rate;
|
|
|
|
// Sanity checks
|
|
if ( shipp->thruster_glow_frame < 0.0f ) shipp->thruster_glow_frame = 0.0f;
|
|
if ( shipp->thruster_glow_frame > 100.0f ) shipp->thruster_glow_frame = 0.0f;
|
|
|
|
while ( shipp->thruster_glow_frame > the_anim->time ) {
|
|
shipp->thruster_glow_frame -= the_anim->time;
|
|
}
|
|
framenum = fl2i( (shipp->thruster_glow_frame*the_anim->num_frames) / the_anim->time );
|
|
if ( framenum < 0 ) framenum = 0;
|
|
if ( framenum >= the_anim->num_frames ) framenum = the_anim->num_frames-1;
|
|
|
|
// if ( anim_index == 0 )
|
|
// mprintf(( "Frame = %d/%d, anim=%d\n", framenum+1, the_anim->num_frames, anim_index ));
|
|
|
|
// Get the bitmap for this frame
|
|
shipp->thruster_glow_bitmap = the_anim->first_frame; // + framenum;
|
|
shipp->thruster_glow_noise = Noise[framenum];
|
|
|
|
}
|
|
|
|
|
|
// JAS - figure out which thruster bitmap will get rendered next
|
|
// time around. ship_render needs to have shipp->thruster_bitmap set to
|
|
// a valid bitmap number, or -1 if we shouldn't render thrusters.
|
|
// This does basically the same thing as ship_do_thruster_frame, except it
|
|
// operates on a weapon. This is in the ship code because it needs
|
|
// the same thruster animation info as the ship stuff, and I would
|
|
// rather extern this one function than all the thruster animation stuff.
|
|
void ship_do_weapon_thruster_frame( weapon *weaponp, object *objp, float frametime )
|
|
{
|
|
float rate;
|
|
int framenum;
|
|
int anim_index;
|
|
thrust_anim *the_anim;
|
|
|
|
if ( !Thrust_anim_inited ) ship_init_thrusters();
|
|
|
|
// The animations are organized by:
|
|
// Species*2 + (After_burner_on?1:0)
|
|
anim_index = weaponp->species*2;
|
|
|
|
// If thrust at 0, go at half as fast, full thrust; full framerate
|
|
// so set rate from 0.5 to 1.0, depending on thrust from 0 to 1
|
|
// rate = 0.5f + objp->phys_info.forward_thrust / 2.0f;
|
|
rate = 0.67f * (1.0f + objp->phys_info.forward_thrust);
|
|
|
|
Assert( anim_index > -1 );
|
|
Assert( anim_index < NUM_THRUST_ANIMS );
|
|
|
|
the_anim = &Thrust_anims[anim_index];
|
|
|
|
Assert( frametime > 0.0f );
|
|
weaponp->thruster_frame += frametime * rate;
|
|
|
|
// Sanity checks
|
|
if ( weaponp->thruster_frame < 0.0f ) weaponp->thruster_frame = 0.0f;
|
|
if ( weaponp->thruster_frame > 100.0f ) weaponp->thruster_frame = 0.0f;
|
|
|
|
while ( weaponp->thruster_frame > the_anim->time ) {
|
|
weaponp->thruster_frame -= the_anim->time;
|
|
}
|
|
framenum = fl2i( (weaponp->thruster_frame*the_anim->num_frames) / the_anim->time );
|
|
if ( framenum < 0 ) framenum = 0;
|
|
if ( framenum >= the_anim->num_frames ) framenum = the_anim->num_frames-1;
|
|
|
|
// if ( anim_index == 0 )
|
|
// mprintf(( "Frame = %d/%d, anim=%d\n", framenum+1, the_anim->num_frames, anim_index ));
|
|
|
|
// Get the bitmap for this frame
|
|
weaponp->thruster_bitmap = the_anim->first_frame + framenum;
|
|
|
|
// mprintf(( "TF: %.2f\n", weaponp->thruster_frame ));
|
|
|
|
// Do it for glow bitmaps
|
|
the_anim = &Thrust_glow_anims[anim_index];
|
|
|
|
Assert( frametime > 0.0f );
|
|
weaponp->thruster_glow_frame += frametime * rate;
|
|
|
|
// Sanity checks
|
|
if ( weaponp->thruster_glow_frame < 0.0f ) weaponp->thruster_glow_frame = 0.0f;
|
|
if ( weaponp->thruster_glow_frame > 100.0f ) weaponp->thruster_glow_frame = 0.0f;
|
|
|
|
while ( weaponp->thruster_glow_frame > the_anim->time ) {
|
|
weaponp->thruster_glow_frame -= the_anim->time;
|
|
}
|
|
framenum = fl2i( (weaponp->thruster_glow_frame*the_anim->num_frames) / the_anim->time );
|
|
if ( framenum < 0 ) framenum = 0;
|
|
if ( framenum >= the_anim->num_frames ) framenum = the_anim->num_frames-1;
|
|
|
|
// if ( anim_index == 0 )
|
|
// mprintf(( "Frame = %d/%d, anim=%d\n", framenum+1, the_anim->num_frames, anim_index ));
|
|
|
|
// Get the bitmap for this frame
|
|
weaponp->thruster_glow_bitmap = the_anim->first_frame; // + framenum;
|
|
weaponp->thruster_glow_noise = Noise[framenum];
|
|
}
|
|
|
|
|
|
|
|
// Repair damaged subsystems for a ship, called for each ship once per frame.
|
|
// TODO: optimize by only calling ever N seconds and keeping track of elapsed time
|
|
//
|
|
// NOTE: need to update current_hits in the sp->subsys_list element, and the sp->subsys_info[]
|
|
// element.
|
|
#define SHIP_REPAIR_SUBSYSTEM_RATE 0.01f // percent repair per second for a subsystem
|
|
#define SUBSYS_REPAIR_THRESHOLD 0.1 // only repair subsystems that have > 10% strength
|
|
void ship_auto_repair_frame(int shipnum, float frametime)
|
|
{
|
|
ship_subsys *ssp;
|
|
ship_subsys_info *ssip;
|
|
ship *sp;
|
|
ship_info *sip;
|
|
|
|
#ifndef NDEBUG
|
|
if ( !Ship_auto_repair ) // only repair subsystems if Ship_auto_repair flag is set
|
|
return;
|
|
#endif
|
|
|
|
Assert( shipnum >= 0 && shipnum < MAX_SHIPS);
|
|
sp = &Ships[shipnum];
|
|
sip = &Ship_info[sp->ship_info_index];
|
|
|
|
// only allow for the auto-repair of subsystems on small ships
|
|
if ( !(sip->flags & SIF_SMALL_SHIP) )
|
|
return;
|
|
|
|
// AL 3-14-98: only allow auto-repair if power output not zero
|
|
if ( sip->power_output <= 0 )
|
|
return;
|
|
|
|
// iterate through subsystems, repair as needed based on elapsed frametime
|
|
for ( ssp = GET_FIRST(&sp->subsys_list); ssp != END_OF_LIST(&sp->subsys_list); ssp = GET_NEXT(ssp) ) {
|
|
Assert(ssp->system_info->type >= 0 && ssp->system_info->type < SUBSYSTEM_MAX);
|
|
ssip = &sp->subsys_info[ssp->system_info->type];
|
|
|
|
if ( ssp->current_hits != ssp->system_info->max_hits ) {
|
|
|
|
// only repair those subsystems which are not destroyed
|
|
if ( ssp->system_info->max_hits <= 0 || ssp->current_hits <= 0 )
|
|
continue;
|
|
|
|
// do incremental repair on the subsystem
|
|
ssp->current_hits += ssp->system_info->max_hits * SHIP_REPAIR_SUBSYSTEM_RATE * frametime;
|
|
ssip->current_hits += ssip->total_hits * SHIP_REPAIR_SUBSYSTEM_RATE * frametime;
|
|
|
|
// check for overflow of current_hits
|
|
if ( ssp->current_hits >= ssp->system_info->max_hits ) {
|
|
// TODO: here is hook for when a subsystem is fully repaired (eg add voice)
|
|
ssp->current_hits = ssp->system_info->max_hits;
|
|
}
|
|
if ( ssip->current_hits >= ssip->total_hits ) {
|
|
ssip->current_hits = ssip->total_hits;
|
|
}
|
|
}
|
|
} // end for
|
|
}
|
|
|
|
// this function checks to see how far the player has strayed from his starting location (should be
|
|
// single player only). Issues a warning at some distance. Makes mission end if he keeps flying away
|
|
// 3 strikes and you're out or too far away
|
|
#define PLAYER_MAX_DIST_WARNING 70000 // distance in KM at which player gets warning to return to battle
|
|
#define PLAYER_DISTANCE_MAX_WARNINGS 3 // maximum number of warnings player can receive before mission ends
|
|
#define PLAYER_MAX_DIST_END 75000 // distance from starting loc at which we end mission
|
|
#define PLAYER_WARN_DELTA_TIME 10000
|
|
#define PLAYER_DEATH_DELTA_TIME 5000
|
|
|
|
void ship_check_player_distance_sub(player *p, int multi_target=-1)
|
|
{
|
|
// only check distance for ships
|
|
if ( p->control_mode != PCM_NORMAL ) {
|
|
// already warping out... don't bother checking anymore
|
|
return;
|
|
}
|
|
|
|
float dist = vm_vec_dist_quick(&Objects[p->objnum].pos, &vmd_zero_vector);
|
|
|
|
int give_warning_to_player = 0;
|
|
if ( dist > PLAYER_MAX_DIST_WARNING ) {
|
|
if (p->distance_warning_count == 0) {
|
|
give_warning_to_player = 1;
|
|
} else {
|
|
if (timestamp_until(p->distance_warning_time) < 0) {
|
|
give_warning_to_player = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( give_warning_to_player ) {
|
|
// increase warning count
|
|
p->distance_warning_count++;
|
|
// set timestamp unless player PLAYER_FLAGS_DIST_TO_BE_KILLED flag is set
|
|
if ( !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
|
|
p->distance_warning_time = timestamp(PLAYER_WARN_DELTA_TIME);
|
|
}
|
|
// issue up to max warnings
|
|
if (p->distance_warning_count <= PLAYER_DISTANCE_MAX_WARNINGS) {
|
|
message_send_builtin_to_player( MESSAGE_STRAY_WARNING, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_SOON, 0, 0, multi_target, -1 );
|
|
}
|
|
|
|
// HUD_sourced_printf(HUD_SOURCE_TERRAN_CMD, XSTR("Terran Command: You're straying too far from battle pilot, return immediately or be taken from the battlefield.", -1));
|
|
if (p->distance_warning_count > PLAYER_DISTANCE_MAX_WARNINGS) {
|
|
p->flags |= PLAYER_FLAGS_DIST_WARNING;
|
|
}
|
|
}
|
|
|
|
if ( !(p->flags & PLAYER_FLAGS_FORCE_MISSION_OVER) && ((p->distance_warning_count > PLAYER_DISTANCE_MAX_WARNINGS) || (dist > PLAYER_MAX_DIST_END)) ) {
|
|
// DKA 5/17/99 - DONT force warpout. Won't work multiplayer. Blow up ship.
|
|
if ( !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
|
|
message_send_builtin_to_player( MESSAGE_STRAY_WARNING_FINAL, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, multi_target, -1 );
|
|
p->flags |= PLAYER_FLAGS_DIST_TO_BE_KILLED;
|
|
p->distance_warning_time = timestamp(PLAYER_DEATH_DELTA_TIME);
|
|
}
|
|
// HUD_sourced_printf(HUD_SOURCE_TERRAN_CMD, XSTR("Terran Command: Sorry pilot, removing you from battle because of your insubordination!!!", -1));
|
|
// gameseq_post_event(GS_EVENT_PLAYER_WARPOUT_START_FORCED);
|
|
|
|
// get hull strength and blow up
|
|
if ( (p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) && (timestamp_until(p->distance_warning_time) < 0) ) {
|
|
p->flags |= PLAYER_FLAGS_FORCE_MISSION_OVER;
|
|
float damage = 10.0f * Objects[p->objnum].hull_strength;
|
|
ship_apply_global_damage(&Objects[p->objnum], &Objects[p->objnum], NULL, damage);
|
|
}
|
|
}
|
|
|
|
// see if player has moved back into "bounds"
|
|
if ( (dist < PLAYER_MAX_DIST_WARNING) && (p->flags & PLAYER_FLAGS_DIST_WARNING) && !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
|
|
p->flags &= ~PLAYER_FLAGS_DIST_WARNING;
|
|
p->distance_warning_count = 1;
|
|
}
|
|
}
|
|
|
|
void ship_check_player_distance()
|
|
{
|
|
int idx;
|
|
|
|
#ifndef NO_NETWORK
|
|
// multiplayer
|
|
if (Game_mode & GM_MULTIPLAYER) {
|
|
// if I'm the server, check all non-observer players including myself
|
|
if (MULTIPLAYER_MASTER) {
|
|
// warn all players
|
|
for (idx=0; idx<MAX_PLAYERS; idx++) {
|
|
if (MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx]) && (Objects[Net_players[idx].player->objnum].type != OBJ_GHOST) ) {
|
|
// if bad, blow him up
|
|
ship_check_player_distance_sub(Net_players[idx].player, idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// single player
|
|
else
|
|
#endif
|
|
{
|
|
// maybe blow him up
|
|
ship_check_player_distance_sub(Player);
|
|
}
|
|
}
|
|
|
|
void observer_process_post(object *objp)
|
|
{
|
|
Assert(objp->type == OBJ_OBSERVER);
|
|
|
|
#ifndef NO_NETWORK
|
|
if (Game_mode & GM_MULTIPLAYER) {
|
|
// if I'm just an observer
|
|
if (MULTI_OBSERVER(Net_players[MY_NET_PLAYER_NUM])) {
|
|
float dist = vm_vec_dist_quick(&Player_obj->pos, &vmd_zero_vector);
|
|
// if beyond max dist, reset to 0
|
|
if (dist > PLAYER_MAX_DIST_END) {
|
|
// set me to zero
|
|
if ((Player_obj != NULL) && (Player_obj->type != OBJ_GHOST)) {
|
|
Player_obj->pos = vmd_zero_vector;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// reset some physics info when ship's engines goes from disabled->enabled
|
|
void ship_reset_disabled_physics(object *objp, int ship_class)
|
|
{
|
|
objp->phys_info.flags &= ~(PF_REDUCED_DAMP | PF_DEAD_DAMP);
|
|
objp->phys_info.side_slip_time_const = Ship_info[ship_class].damp;
|
|
}
|
|
|
|
// Clear/set the subsystem disrupted flags
|
|
void ship_subsys_disrupted_check(ship *sp)
|
|
{
|
|
ship_subsys *ss;
|
|
int engines_disabled=0;
|
|
|
|
if ( sp->subsys_disrupted_flags & (1<<SUBSYSTEM_ENGINE) ) {
|
|
engines_disabled=1;
|
|
}
|
|
|
|
sp->subsys_disrupted_flags=0;
|
|
|
|
ss = GET_FIRST(&sp->subsys_list);
|
|
while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
|
|
if ( !timestamp_elapsed(ss->disruption_timestamp) ) {
|
|
sp->subsys_disrupted_flags |= (1<<ss->system_info->type);
|
|
}
|
|
ss = GET_NEXT( ss );
|
|
}
|
|
|
|
if ( engines_disabled ) {
|
|
if ( !(sp->subsys_disrupted_flags & (1<<SUBSYSTEM_ENGINE)) ) {
|
|
if ( !(sp->flags & SF_DISABLED) ) {
|
|
ship_reset_disabled_physics(&Objects[sp->objnum], sp->ship_info_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Maybe check ship subsystems for disruption, and set/clear flags
|
|
void ship_subsys_disrupted_maybe_check(ship *shipp)
|
|
{
|
|
if ( timestamp_elapsed(shipp->subsys_disrupted_check_timestamp) ) {
|
|
ship_subsys_disrupted_check(shipp);
|
|
shipp->subsys_disrupted_check_timestamp=timestamp(250);
|
|
}
|
|
}
|
|
|
|
// Determine if a given subsystem is disrupted (ie inoperable)
|
|
// input: ss => pointer to ship subsystem
|
|
// exit: 1 => subsystem is disrupted
|
|
// 0 => subsystem is not disrupted
|
|
int ship_subsys_disrupted(ship_subsys *ss)
|
|
{
|
|
if ( !ss ) {
|
|
Int3(); // should never happen, get Alan if it does.
|
|
return 0;
|
|
}
|
|
|
|
if ( timestamp_elapsed(ss->disruption_timestamp) ) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Disrupt a subsystem (ie make it inoperable for a time)
|
|
// input: ss => ship subsystem to be disrupted
|
|
// time => time in ms that subsystem should be disrupted
|
|
void ship_subsys_set_disrupted(ship_subsys *ss, int time)
|
|
{
|
|
int time_left=0;
|
|
|
|
if ( !ss ) {
|
|
Int3(); // should never happen, get Alan if it does.
|
|
return;
|
|
}
|
|
|
|
time_left=timestamp_until(ss->disruption_timestamp);
|
|
if ( time_left < 0 ) {
|
|
time_left=0;
|
|
}
|
|
|
|
ss->disruption_timestamp = timestamp(time+time_left);
|
|
}
|
|
|
|
// Determine if a given subsystem is disrupted (ie inoperable)
|
|
// input: sp => pointer to ship containing subsystem
|
|
// type => type of subsystem (SUBSYSTEM_*)
|
|
// exit: 1 => subsystem is disrupted
|
|
// 0 => subsystem is not disrupted
|
|
//
|
|
int ship_subsys_disrupted(ship *sp, int type)
|
|
{
|
|
if ( sp->subsys_disrupted_flags & (1<<type) ) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
float Decay_rate = 1.0f / 120.0f;
|
|
DCF(lethality_decay, "time in sec to return from 100 to 0")
|
|
{
|
|
dc_get_arg(ARG_FLOAT);
|
|
Decay_rate = Dc_arg_float;
|
|
}
|
|
|
|
float min_lethality = 0.0f;
|
|
|
|
void lethality_decay(ai_info *aip)
|
|
{
|
|
float decay_rate = Decay_rate;
|
|
aip->lethality -= 100.0f * decay_rate * flFrametime;
|
|
aip->lethality = max(-10.0f, aip->lethality);
|
|
|
|
// if (aip->lethality < min_lethality) {
|
|
// min_lethality = aip->lethality;
|
|
// mprintf(("new lethality low: %.1f\n", min_lethality));
|
|
// }
|
|
|
|
#ifndef NDEBUG
|
|
if (Objects[Ships[aip->shipnum].objnum].flags & OF_PLAYER_SHIP) {
|
|
if (Framecount % 10 == 0) {
|
|
int num_turrets = 0;
|
|
if ((aip->target_objnum != -1) && (Objects[aip->target_objnum].type == OBJ_SHIP)) {
|
|
int num_turrets_attacking(object *turret_parent, int target_objnum);
|
|
num_turrets = num_turrets_attacking(&Objects[aip->target_objnum], Ships[aip->shipnum].objnum);
|
|
}
|
|
nprintf(("lethality", "Player lethality: %.1f, num turrets targeting player: %d\n", aip->lethality, num_turrets));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ship_process_pre(object *objp, float frametime)
|
|
{
|
|
}
|
|
|
|
MONITOR( NumShips );
|
|
|
|
// Player ship uses this code, but does a quick out after doing a few things.
|
|
// when adding code to this function, decide whether or not a client in a multiplayer game
|
|
// needs to execute the code you are adding. Code which moves things, creates things, etc
|
|
// probably doesn't need to be called. If you don't know -- find Allender!!!
|
|
void ship_process_post(object * obj, float frametime)
|
|
{
|
|
int num;
|
|
ship *shipp;
|
|
|
|
if(obj->type != OBJ_SHIP){
|
|
nprintf(("Network","Ignoring non-ship object in ship_process_post()\n"));
|
|
return;
|
|
}
|
|
|
|
MONITOR_INC( NumShips, 1 );
|
|
|
|
num = obj->instance;
|
|
Assert( num >= 0 && num < MAX_SHIPS);
|
|
Assert( obj->type == OBJ_SHIP );
|
|
Assert( Ships[num].objnum == OBJ_INDEX(obj));
|
|
|
|
shipp = &Ships[num];
|
|
|
|
shipp->shield_hits = 0;
|
|
|
|
update_ets(obj, frametime);
|
|
|
|
afterburners_update(obj, frametime);
|
|
|
|
ship_subsys_disrupted_maybe_check(shipp);
|
|
|
|
ship_dying_frame(obj, num);
|
|
|
|
ship_chase_shield_energy_targets(shipp, obj, frametime);
|
|
|
|
// AL 1-6-98: record the initial ammo counts for ships, which is used as the max limit for rearming
|
|
if ( !(shipp->flags & SF_AMMO_COUNT_RECORDED) ) {
|
|
for ( int i=0; i<MAX_SECONDARY_BANKS; i++ ) {
|
|
if ( red_alert_mission() ) {
|
|
int max_missiles = get_max_ammo_count_for_bank(shipp->ship_info_index, i, shipp->weapons.secondary_bank_weapons[i]);
|
|
shipp->weapons.secondary_bank_start_ammo[i] = max_missiles;
|
|
} else {
|
|
shipp->weapons.secondary_bank_start_ammo[i] = shipp->weapons.secondary_bank_ammo[i];
|
|
}
|
|
}
|
|
shipp->flags |= SF_AMMO_COUNT_RECORDED;
|
|
}
|
|
|
|
#ifndef NO_NETWORK
|
|
if(!(Game_mode & GM_STANDALONE_SERVER))
|
|
#endif
|
|
{
|
|
// Plot ship on the radar. What about multiplayer ships?
|
|
if ( obj != Player_obj ) // don't plot myself.
|
|
radar_plot_object( obj );
|
|
|
|
// MWA -- move the spark code to before the check for multiplayer master
|
|
// Do ship sparks. Don't do sparks on my ship (since I cannot see it). This
|
|
// code will do sparks on other ships in multiplayer though.
|
|
// JAS: Actually in external view, you can see sparks, so I don't do sparks
|
|
// on the Viewer_obj, not Player_obj.
|
|
if ( (obj != Viewer_obj) && timestamp_elapsed(Ships[num].next_hit_spark) ) {
|
|
shipfx_emit_spark(num,-1); // -1 means choose random spark location
|
|
}
|
|
|
|
if ( obj != Viewer_obj ) {
|
|
shipfx_do_damaged_arcs_frame( shipp );
|
|
}
|
|
|
|
// JAS - flicker the thruster bitmaps
|
|
ship_do_thruster_frame(shipp,obj,frametime);
|
|
}
|
|
|
|
ship_auto_repair_frame(num, frametime);
|
|
|
|
// MWA -- move the spark code to before the check for multiplayer master
|
|
// Do ship sparks.
|
|
// if (timestamp_elapsed(Ships[num].next_hit_spark)) {
|
|
// ship_spark(num);
|
|
// Ships[num].next_hit_spark = timestamp_rand(100,500);
|
|
// }
|
|
|
|
shipfx_do_lightning_frame(shipp);
|
|
|
|
// if the ship has an EMP effect active, process it
|
|
emp_process_ship(shipp);
|
|
|
|
// call the contrail system
|
|
ct_ship_process(shipp);
|
|
|
|
// process engine wash
|
|
void engine_wash_ship_process(ship *shipp);
|
|
engine_wash_ship_process(shipp);
|
|
|
|
// update TAG info
|
|
if(shipp->tag_left > 0.0f){
|
|
shipp->tag_left -= flFrametime;
|
|
if(shipp->tag_left <= 0.000001f){
|
|
shipp->tag_left = -1.0f;
|
|
|
|
mprintf(("Killing TAG for %s\n", shipp->ship_name));
|
|
}
|
|
}
|
|
|
|
// update level 2 TAG info
|
|
if(shipp->level2_tag_left > 0.0f){
|
|
shipp->level2_tag_left -= flFrametime;
|
|
if(shipp->level2_tag_left <= 0.000001f){
|
|
shipp->level2_tag_left = -1.0f;
|
|
|
|
mprintf(("Killing level 2 TAG for %s\n", shipp->ship_name));
|
|
}
|
|
}
|
|
|
|
if ( shipp->flags & SF_ARRIVING && Ai_info[shipp->ai_index].mode != AIM_BAY_EMERGE ) {
|
|
// JAS -- if the ship is warping in, just move it forward at a speed
|
|
// fast enough to move 2x it's radius in SHIP_WARP_TIME seconds.
|
|
shipfx_warpin_frame( obj, frametime );
|
|
} else if ( shipp->flags & SF_DEPART_WARP ) {
|
|
// JAS -- if the ship is warping out, just move it forward at a speed
|
|
// fast enough to move 2x it's radius in SHIP_WARP_TIME seconds.
|
|
shipfx_warpout_frame( obj, frametime );
|
|
} else {
|
|
// Do AI.
|
|
|
|
#ifndef NO_NETWORK
|
|
// for multiplayer people. return here if in multiplay and not the host
|
|
if ( (Game_mode & GM_MULTIPLAYER) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER) )
|
|
return;
|
|
#endif
|
|
|
|
// MWA -- moved the code to maybe fire swarm missiles to after the check for
|
|
// multiplayer master. Only single player and multi server needs to do this code
|
|
// this code might call ship_fire_secondary which will send the fire packets
|
|
swarm_maybe_fire_missile(num);
|
|
|
|
// maybe fire turret swarm missiles
|
|
void turret_swarm_maybe_fire_missile(int num);
|
|
turret_swarm_maybe_fire_missile(num);
|
|
|
|
// maybe fire a corkscrew missile (just like swarmers)
|
|
cscrew_maybe_fire_missile(num);
|
|
|
|
// AL 2-19-98: Fire turret for player if it exists
|
|
if ( obj->flags & OF_PLAYER_SHIP ) {
|
|
player_maybe_fire_turret(obj);
|
|
}
|
|
|
|
// if single player, check player object is not too far from starting location
|
|
// DKA 5/17/99 check SINGLE and MULTI
|
|
// if ( !(Game_mode & GM_MULTIPLAYER) && (obj == Player_obj) )
|
|
if (obj == Player_obj) {
|
|
ship_check_player_distance();
|
|
}
|
|
|
|
// update ship lethality
|
|
if ( Ships[num].ai_index >= 0 ){
|
|
if (!physics_paused && !ai_paused){
|
|
lethality_decay(&Ai_info[Ships[num].ai_index]);
|
|
}
|
|
}
|
|
|
|
// if the ship is a player ship or an observer ship don't need to do AI
|
|
if ( (obj->flags & OF_PLAYER_SHIP) || (obj->type == OBJ_OBSERVER) ) {
|
|
return;
|
|
}
|
|
|
|
if ( Ships[num].ai_index >= 0 ){
|
|
if (!physics_paused && !ai_paused){
|
|
ai_process( obj, Ships[num].ai_index, frametime );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
// ship_set_default_weapons()
|
|
//
|
|
// Set the ship level weapons based on the information contained in the ship
|
|
// info. Weapon assignments are checked against the model to ensure the models
|
|
// and the ship info weapon data are in synch.
|
|
//
|
|
//
|
|
|
|
void ship_set_default_weapons(ship *shipp, ship_info *sip)
|
|
{
|
|
int i;
|
|
polymodel *po;
|
|
ship_weapon *swp = &shipp->weapons;
|
|
|
|
// Copy primary and secondary weapons from ship_info to ship.
|
|
// Later, this will happen in the weapon loadout screen.
|
|
for (i=0; i < MAX_PRIMARY_BANKS; i++){
|
|
swp->primary_bank_weapons[i] = sip->primary_bank_weapons[i];
|
|
}
|
|
|
|
for (i=0; i < MAX_SECONDARY_BANKS; i++){
|
|
swp->secondary_bank_weapons[i] = sip->secondary_bank_weapons[i];
|
|
}
|
|
|
|
// Copy the number of primary and secondary banks to ship, and verify that
|
|
// model is in synch
|
|
po = model_get( sip->modelnum );
|
|
|
|
// Primary banks
|
|
if ( po->n_guns > sip->num_primary_banks ) {
|
|
Assert(po->n_guns <= MAX_PRIMARY_BANKS);
|
|
Warning(LOCATION, "There are %d primary banks in the model file,\nbut only %d primary banks in ships.tbl for %s\n", po->n_guns, sip->num_primary_banks, sip->name);
|
|
for ( i = sip->num_primary_banks; i < po->n_guns; i++ ) {
|
|
// Make unspecified weapon for bank be a Light Laser
|
|
swp->primary_bank_weapons[i] = weapon_info_lookup(NOX("Light Laser"));
|
|
Assert(swp->primary_bank_weapons[i] >= 0);
|
|
}
|
|
sip->num_primary_banks = po->n_guns;
|
|
}
|
|
else if ( po->n_guns < sip->num_primary_banks ) {
|
|
Warning(LOCATION, "There are %d primary banks in ships.tbl for %s\nbut only %d primary banks in the model\n", sip->num_primary_banks, sip->name, po->n_guns);
|
|
sip->num_primary_banks = po->n_guns;
|
|
}
|
|
|
|
// Secondary banks
|
|
if ( po->n_missiles > sip->num_secondary_banks ) {
|
|
Assert(po->n_missiles <= MAX_SECONDARY_BANKS);
|
|
Warning(LOCATION, "There are %d secondary banks in model,\nbut only %d secondary banks in ships.tbl for %s\n", po->n_missiles, sip->num_secondary_banks, sip->name);
|
|
for ( i = sip->num_secondary_banks; i < po->n_missiles; i++ ) {
|
|
// Make unspecified weapon for bank be a Rockeye Missile
|
|
swp->secondary_bank_weapons[i] = weapon_info_lookup(NOX("Rockeye Missile"));
|
|
Assert(swp->secondary_bank_weapons[i] >= 0);
|
|
}
|
|
sip->num_secondary_banks = po->n_missiles;
|
|
}
|
|
else if ( po->n_missiles < sip->num_secondary_banks ) {
|
|
Warning(LOCATION, "There are %d secondary banks in ships.tbl for %s,\n but only %d secondary banks in the model.\n", sip->num_secondary_banks, sip->name, po->n_missiles);
|
|
sip->num_secondary_banks = po->n_missiles;
|
|
}
|
|
|
|
swp->num_primary_banks = sip->num_primary_banks;
|
|
swp->num_secondary_banks = sip->num_secondary_banks;
|
|
for ( i = 0; i < swp->num_secondary_banks; i++ ) {
|
|
if (Fred_running){
|
|
swp->secondary_bank_ammo[i] = 100;
|
|
} else {
|
|
swp->secondary_bank_ammo[i] = sip->secondary_bank_ammo_capacity[i];
|
|
}
|
|
|
|
swp->secondary_bank_capacity[i] = sip->secondary_bank_ammo_capacity[i];
|
|
}
|
|
|
|
for ( i = 0; i < MAX_PRIMARY_BANKS; i++ ){
|
|
swp->next_primary_fire_stamp[i] = timestamp(0);
|
|
}
|
|
|
|
for ( i = 0; i < MAX_SECONDARY_BANKS; i++ ){
|
|
swp->next_secondary_fire_stamp[i] = timestamp(0);
|
|
}
|
|
}
|
|
|
|
|
|
// A faster version of ship_check_collision that does not do checking at the polygon
|
|
// level. Just checks to see if a vector will intersect a sphere.
|
|
int ship_check_collision_fast( object * obj, object * other_obj, vector * hitpos)
|
|
{
|
|
int num;
|
|
mc_info mc;
|
|
|
|
Assert( obj->type == OBJ_SHIP );
|
|
Assert( obj->instance >= 0 );
|
|
|
|
num = obj->instance;
|
|
|
|
ship_model_start(obj); // are these needed in this fast case? probably not.
|
|
|
|
mc.model_num = Ships[num].modelnum; // Fill in the model to check
|
|
mc.orient = &obj->orient; // The object's orient
|
|
mc.pos = &obj->pos; // The object's position
|
|
mc.p0 = &other_obj->last_pos; // Point 1 of ray to check
|
|
mc.p1 = &other_obj->pos; // Point 2 of ray to check
|
|
mc.flags = MC_ONLY_SPHERE; // flags
|
|
|
|
model_collide(&mc);
|
|
if (mc.num_hits)
|
|
*hitpos = mc.hit_point_world;
|
|
|
|
ship_model_stop(obj); // are these needed in this fast case? probably not.
|
|
|
|
return mc.num_hits;
|
|
}
|
|
|
|
// ensure that the subsys path is at least SUBSYS_PATH_DIST from the
|
|
// second last to last point.
|
|
void ship_maybe_fixup_subsys_path(polymodel *pm, int path_num)
|
|
{
|
|
vector *v1, *v2, dir;
|
|
float dist;
|
|
int index_1, index_2;
|
|
|
|
model_path *mp;
|
|
mp = &pm->paths[path_num];
|
|
|
|
Assert(mp != NULL);
|
|
Assert(mp->nverts > 1);
|
|
|
|
index_1 = 1;
|
|
index_2 = 0;
|
|
|
|
v1 = &mp->verts[index_1].pos;
|
|
v2 = &mp->verts[index_2].pos;
|
|
|
|
dist = vm_vec_dist(v1, v2);
|
|
if ( dist < SUBSYS_PATH_DIST-10 ) {
|
|
vm_vec_normalized_dir(&dir, v2, v1);
|
|
vm_vec_scale_add(v2, v1, &dir, SUBSYS_PATH_DIST);
|
|
}
|
|
}
|
|
|
|
// fill in the path_num field inside the model_subsystem struct. This is an index into
|
|
// the pm->paths[] array, which is a path that provides a frontal approach to a subsystem
|
|
// (used for attacking purposes)
|
|
//
|
|
// NOTE: path_num in model_subsystem has the follows the following convention:
|
|
// > 0 => index into pm->paths[] for model that subsystem sits on
|
|
// -1 => path is not yet determined (may or may not exist)
|
|
// -2 => path doesn't yet exist for this subsystem
|
|
void ship_set_subsys_path_nums(ship_info *sip, polymodel *pm)
|
|
{
|
|
int i,j,found_path;
|
|
|
|
for ( i = 0; i < sip->n_subsystems; i++ ) {
|
|
sip->subsystems[i].path_num = -1;
|
|
}
|
|
|
|
for ( i = 0; i < sip->n_subsystems; i++ ) {
|
|
found_path = 0;
|
|
for ( j = 0; j < pm->n_paths; j++ ) {
|
|
if ( (sip->subsystems[i].subobj_num != -1) && (sip->subsystems[i].subobj_num == pm->paths[j].parent_submodel) ) {
|
|
found_path = 1;
|
|
} else if ( !stricmp(sip->subsystems[i].subobj_name, pm->paths[j].parent_name) ) {
|
|
found_path = 1;
|
|
}
|
|
|
|
if ( found_path ) {
|
|
if ( pm->n_paths > j ) {
|
|
sip->subsystems[i].path_num = j;
|
|
ship_maybe_fixup_subsys_path(pm, j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If a path num wasn't located, then set value to -2
|
|
if ( sip->subsystems[i].path_num == -1 )
|
|
sip->subsystems[i].path_num = -2;
|
|
}
|
|
}
|
|
|
|
// Determine the path indices (indicies into pm->paths[]) for the paths used for approaching/departing
|
|
// a fighter bay on a capital ship.
|
|
void ship_set_bay_path_nums(ship_info *sip, polymodel *pm)
|
|
{
|
|
int bay_num, i;
|
|
char bay_num_str[3];
|
|
|
|
if ( pm->ship_bay != NULL ) {
|
|
free(pm->ship_bay);
|
|
pm->ship_bay = NULL;
|
|
}
|
|
|
|
// currently only capital ships have fighter bays
|
|
if ( !(sip->flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) ) {
|
|
return;
|
|
}
|
|
|
|
// malloc out storage for the path information
|
|
pm->ship_bay = (ship_bay*)malloc(sizeof(ship_bay));
|
|
Assert(pm->ship_bay != NULL);
|
|
|
|
pm->ship_bay->num_paths = 0;
|
|
// TODO: determine if zeroing out here is affecting any earlier initializations
|
|
pm->ship_bay->arrive_flags = 0; // bitfield, set to 1 when that path number is reserved for an arrival
|
|
pm->ship_bay->depart_flags = 0; // bitfield, set to 1 when that path number is reserved for a departure
|
|
|
|
|
|
// iterate through the paths that exist in the polymodel, searching for $bayN pathnames
|
|
for ( i = 0; i < pm->n_paths; i++ ) {
|
|
if ( !strnicmp(pm->paths[i].name, NOX("$bay"), 4) ) {
|
|
strncpy(bay_num_str, pm->paths[i].name+4, 2);
|
|
bay_num_str[2] = 0;
|
|
bay_num = atoi(bay_num_str);
|
|
Assert(bay_num >= 1 && bay_num <= MAX_SHIP_BAY_PATHS);
|
|
pm->ship_bay->paths[bay_num-1] = i;
|
|
pm->ship_bay->num_paths++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure create time for ship is unqiue
|
|
void ship_make_create_time_unique(ship *shipp)
|
|
{
|
|
int sanity_counter = 0, collision;
|
|
ship *compare_shipp;
|
|
ship_obj *so;
|
|
uint new_create_time;
|
|
|
|
new_create_time = shipp->create_time;
|
|
|
|
while (1) {
|
|
|
|
if ( sanity_counter++ > 50 ) {
|
|
Int3();
|
|
break;
|
|
}
|
|
|
|
collision = 0;
|
|
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
compare_shipp = &Ships[Objects[so->objnum].instance];
|
|
|
|
if ( compare_shipp == shipp ) {
|
|
continue;
|
|
}
|
|
|
|
if ( compare_shipp->create_time == new_create_time ) {
|
|
new_create_time++;
|
|
collision = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !collision ) {
|
|
shipp->create_time = new_create_time;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int Ship_subsys_hwm = 0;
|
|
|
|
void show_ship_subsys_count()
|
|
{
|
|
object *objp;
|
|
int count = 0;
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if (objp->type == OBJ_SHIP) {
|
|
count += Ship_info[Ships[objp->type].ship_info_index].n_subsystems;
|
|
}
|
|
}
|
|
|
|
//nprintf(("AI", "Num subsystems, high water mark = %i, %i\n", count, Ship_subsys_hwm));
|
|
|
|
if (count > Ship_subsys_hwm) {
|
|
Ship_subsys_hwm = count;
|
|
}
|
|
}
|
|
|
|
// Returns object index of ship.
|
|
// -1 means failed.
|
|
int ship_create(matrix *orient, vector *pos, int ship_type)
|
|
{
|
|
int i, n, objnum, j, k, t;
|
|
ship_info *sip;
|
|
ship *shipp;
|
|
|
|
t = ship_get_num_ships();
|
|
|
|
// The following check caps the number of ships that can be created. Because Fred needs
|
|
// to create all the ships, regardless of when they arrive/depart, it needs a higher
|
|
// limit than FreeSpace. On release, however, we will reduce it, thus FreeSpace needs
|
|
// to check against what this limit will be, otherwise testing the missions before
|
|
// release could work fine, yet not work anymore once a release build is made.
|
|
if (Fred_running) {
|
|
if (t >= MAX_SHIPS)
|
|
return -1;
|
|
|
|
} else {
|
|
if (t >= SHIPS_LIMIT) {
|
|
Error(LOCATION, XSTR("There is a limit of %d ships in the mission at once. Please be sure that you do not have more than %d ships present in the mission at the same time.", 1495), SHIPS_LIMIT, SHIPS_LIMIT );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//nprintf(("AI", "Number of ships = %i\n", t));
|
|
|
|
for (n=0; n<MAX_SHIPS; n++){
|
|
if (Ships[n].objnum == -1){
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n == MAX_SHIPS){
|
|
return -1;
|
|
}
|
|
|
|
Assert((ship_type >= 0) && (ship_type < Num_ship_types));
|
|
sip = &(Ship_info[ship_type]);
|
|
shipp = &Ships[n];
|
|
|
|
// check to be sure that this ship falls into a ship size category!!!
|
|
// get Allender or Mike if you hit this Assert
|
|
Assert( sip->flags & (SIF_SMALL_SHIP | SIF_BIG_SHIP | SIF_CAPITAL | SIF_NO_SHIP_TYPE | SIF_NOT_FLYABLE | SIF_ESCAPEPOD | SIF_SUPERCAP | SIF_DRYDOCK | SIF_KNOSSOS_DEVICE) );
|
|
|
|
sip->modelnum = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]); // use the highest detail level
|
|
shipp->modelnum = sip->modelnum;
|
|
|
|
// maybe load an optional hud target model
|
|
if(strlen(sip->pof_file_hud)){
|
|
// check to see if a "real" ship uses this model. if so, load it up for him so that subsystems are setup properly
|
|
int idx;
|
|
for(idx=0; idx<Num_ship_types; idx++){
|
|
if(!stricmp(Ship_info[idx].pof_file, sip->pof_file_hud)){
|
|
Ship_info[idx].modelnum = model_load(Ship_info[idx].pof_file, Ship_info[idx].n_subsystems, &Ship_info[idx].subsystems[0]);
|
|
}
|
|
}
|
|
|
|
// mow load it for me with no subsystems
|
|
sip->modelnum_hud = model_load(sip->pof_file_hud, 0, NULL);
|
|
}
|
|
|
|
polymodel * pm;
|
|
pm = model_get(shipp->modelnum);
|
|
|
|
ship_copy_subsystem_fixup(sip);
|
|
|
|
show_ship_subsys_count();
|
|
|
|
if ( sip->num_detail_levels < pm->n_detail_levels ) {
|
|
Warning(LOCATION, "For ship '%s', detail level\nmismatch (POF needs %d)", sip->name, pm->n_detail_levels );
|
|
|
|
for (i=0; i<pm->n_detail_levels; i++ ) {
|
|
sip->detail_distance[i] = 0;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<sip->num_detail_levels; i++ ) {
|
|
pm->detail_depth[i] = i2fl(sip->detail_distance[i]);
|
|
}
|
|
|
|
if ( sip->flags & SIF_NAVBUOY ) {
|
|
// JAS: Nav buoys don't need to do collisions!
|
|
objnum = obj_create(OBJ_SHIP, -1, n, orient, pos, model_get_radius(shipp->modelnum), OF_RENDERS | OF_PHYSICS );
|
|
} else {
|
|
objnum = obj_create(OBJ_SHIP, -1, n, orient, pos, model_get_radius(shipp->modelnum), OF_RENDERS | OF_COLLIDES | OF_PHYSICS );
|
|
}
|
|
Assert( objnum >= 0 );
|
|
|
|
shipp->ai_index = ai_get_slot(n);
|
|
Assert( shipp->ai_index >= 0 );
|
|
|
|
sprintf(shipp->ship_name, NOX("%s %d"), Ship_info[ship_type].name, n);
|
|
ship_set_default_weapons(shipp, sip); // Moved up here because ship_set requires that weapon info be valid. MK, 4/28/98
|
|
ship_set(n, objnum, ship_type);
|
|
|
|
// fill in the path_num field inside the model_subsystem struct. This is an index into
|
|
// the pm->paths[] array, which is a path that provides a frontal approach to a subsystem
|
|
// (used for attacking purposes)
|
|
//
|
|
// NOTE: path_num in model_subsystem has the follows the following convention:
|
|
// > 0 => index into pm->paths[] for model that subsystem sits on
|
|
// -1 => path is not yet determined (may or may not exist)
|
|
// -2 => path doesn't yet exist for this subsystem
|
|
ship_set_subsys_path_nums(sip, pm);
|
|
|
|
// set the path indicies for fighter bays on the ship (currently, only capital ships have fighter bays)
|
|
ship_set_bay_path_nums(sip, pm);
|
|
|
|
init_ai_object(objnum);
|
|
ai_clear_ship_goals( &Ai_info[Ships[n].ai_index] ); // only do this one here. Can't do it in init_ai because it might wipe out goals in mission file
|
|
|
|
//ship_set_default_weapons(shipp, sip);
|
|
|
|
// Allocate shield and initialize it.
|
|
if (pm->shield.ntris) {
|
|
shipp->shield_integrity = (float *)malloc(sizeof(float)*pm->shield.ntris);
|
|
for (i=0; i<pm->shield.ntris; i++)
|
|
shipp->shield_integrity[i] = 1.0f;
|
|
|
|
} else
|
|
shipp->shield_integrity = NULL;
|
|
|
|
// fix up references into paths for this ship's model to point to a ship_subsys entry instead
|
|
// of a submodel index. The ship_subsys entry should be the same for *all* instances of the
|
|
// same ship.
|
|
|
|
if ( !(sip->flags & SIF_PATH_FIXUP )) {
|
|
for ( i = 0; i < pm->n_paths; i++ ) {
|
|
for ( j = 0; j < pm->paths[i].nverts; j++ ) {
|
|
for ( k = 0; k < pm->paths[i].verts[j].nturrets; k++ ) {
|
|
int ptindex = pm->paths[i].verts[j].turret_ids[k]; // this index is a submodel number (ala bspgen)
|
|
int index;
|
|
ship_subsys *ss;
|
|
|
|
// iterate through the ship_subsystems looking for an id that matches
|
|
index = 0;
|
|
ss = GET_FIRST(&Ships[n].subsys_list);
|
|
while ( ss != END_OF_LIST( &Ships[n].subsys_list ) ) {
|
|
if ( ss->system_info->subobj_num == ptindex ) { // when these are equal, fix up the ref
|
|
pm->paths[i].verts[j].turret_ids[k] = index; // in path structure to index a ship_subsys
|
|
break;
|
|
}
|
|
index++;
|
|
ss = GET_NEXT( ss );
|
|
}
|
|
|
|
if ( ss == END_OF_LIST(&Ships[n].subsys_list) )
|
|
Warning(LOCATION, "Couldn't fix up turret indices in spline path\n\nModel: %s\nPath: %s\nVertex: %d\nTurret model id:%d\n\nThis probably means the turret was not specified in ships.tbl", sip->pof_file, pm->paths[i].name, j, ptindex );
|
|
}
|
|
}
|
|
}
|
|
sip->flags |= SIF_PATH_FIXUP;
|
|
}
|
|
|
|
// reset the damage record fields (for scoring purposes)
|
|
shipp->total_damage_received = 0.0f;
|
|
for(i=0;i<MAX_DAMAGE_SLOTS;i++){
|
|
shipp->damage_ship[i] = 0.0f;
|
|
shipp->damage_ship_id[i] = -1;
|
|
}
|
|
|
|
// Add this ship to Ship_obj_list
|
|
shipp->ship_list_index = ship_obj_list_add(objnum);
|
|
|
|
// Set time when ship is created
|
|
shipp->create_time = timer_get_milliseconds();
|
|
|
|
ship_make_create_time_unique(shipp);
|
|
|
|
// set the team select index to be -1
|
|
shipp->ts_index = -1;
|
|
|
|
shipp->wing_status_wing_index = -1; // wing index (0-4) in wingman status gauge
|
|
shipp->wing_status_wing_pos = -1; // wing position (0-5) in wingman status gauge
|
|
|
|
// call the contrail system
|
|
ct_ship_create(shipp);
|
|
|
|
return objnum;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// ship_model_change()
|
|
//
|
|
// Change the ship model for a ship to that for ship class 'ship_type'
|
|
//
|
|
// input: n => index of ship in Ships[] array
|
|
// ship_type => ship class (index into Ship_info[])
|
|
//
|
|
void ship_model_change(int n, int ship_type)
|
|
{
|
|
int model_num, i;
|
|
ship_info *sip;
|
|
ship *sp;
|
|
|
|
|
|
Assert( n >= 0 && n < MAX_SHIPS );
|
|
sp = &Ships[n];
|
|
sip = &(Ship_info[ship_type]);
|
|
|
|
model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]); // use the highest detail level
|
|
|
|
// page in nondims
|
|
if(!Fred_running){
|
|
model_page_in_textures(model_num, ship_type);
|
|
}
|
|
|
|
Objects[sp->objnum].radius = model_get_radius(model_num);
|
|
sip->modelnum = model_num;
|
|
sp->modelnum = model_num;
|
|
|
|
polymodel * pm;
|
|
pm = model_get(sp->modelnum);
|
|
|
|
ship_copy_subsystem_fixup(sip);
|
|
|
|
if ( sip->num_detail_levels < pm->n_detail_levels ) {
|
|
Warning(LOCATION, "For ship '%s', detail level\nmismatch (POF needs %d)", sip->name, pm->n_detail_levels );
|
|
|
|
for (i=0; i<pm->n_detail_levels; i++ ) {
|
|
sip->detail_distance[i] = 0;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<sip->num_detail_levels; i++ ) {
|
|
pm->detail_depth[i] = i2fl(sip->detail_distance[i]);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// change_ship_type()
|
|
//
|
|
// Change the ship class on a ship, and changing all required information
|
|
// for consistency (ie textures, subsystems, weapons, physics)
|
|
//
|
|
// input: n => index of ship in Ships[] array
|
|
// ship_type => ship class (index into Ship_info[])
|
|
//
|
|
void change_ship_type(int n, int ship_type)
|
|
{
|
|
ship_info *sip;
|
|
ship *sp;
|
|
object *objp;
|
|
|
|
|
|
Assert( n >= 0 && n < MAX_SHIPS );
|
|
sp = &Ships[n];
|
|
sip = &(Ship_info[ship_type]);
|
|
objp = &Objects[sp->objnum];
|
|
|
|
// point to new ship data
|
|
sp->ship_info_index = ship_type;
|
|
|
|
ship_model_change(n, ship_type);
|
|
|
|
// if the subsystem list is not currently empty, then we need to clear it out first.
|
|
if ( NOT_EMPTY(&sp->subsys_list) ) {
|
|
ship_subsys *ship_system, *tmp;
|
|
|
|
for ( ship_system = GET_FIRST(&sp->subsys_list); ship_system != END_OF_LIST(&sp->subsys_list); ) {
|
|
tmp = GET_NEXT(ship_system);
|
|
list_remove( &sp->subsys_list, ship_system );
|
|
list_append( &ship_subsys_free_list, ship_system );
|
|
ship_system = tmp;
|
|
}
|
|
}
|
|
// fix up the subsystems
|
|
subsys_set( sp->objnum );
|
|
|
|
// set the correct hull strength
|
|
if (Fred_running) {
|
|
objp->hull_strength = 100.0f;
|
|
} else {
|
|
objp->hull_strength = sip->initial_hull_strength;
|
|
}
|
|
|
|
// set the correct shields strength
|
|
if (Fred_running) {
|
|
objp->shields[0] = 100.0f;
|
|
} else {
|
|
set_shield_strength(objp, sip->shields);
|
|
}
|
|
|
|
sp->afterburner_fuel = sip->afterburner_fuel_capacity;
|
|
|
|
ship_set_default_weapons(sp, sip);
|
|
physics_ship_init(&Objects[sp->objnum]);
|
|
ets_init_ship(&Objects[sp->objnum]);
|
|
// mwa removed the next line in favor of simply setting the ai_class in AI_info. ai_object_init
|
|
// was trashing mode in ai_info when it was valid due to goals.
|
|
//ai_object_init(&Objects[sp->objnum], sp->ai_index);
|
|
Ai_info[sp->ai_index].ai_class = sip->ai_class;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
// Fire the debug laser
|
|
int ship_fire_primary_debug(object *objp)
|
|
{
|
|
int i;
|
|
ship *shipp = &Ships[objp->instance];
|
|
vector wpos;
|
|
|
|
if ( !timestamp_elapsed(shipp->weapons.next_primary_fire_stamp[0]) )
|
|
return 0;
|
|
|
|
// do timestamp stuff for next firing time
|
|
shipp->weapons.next_primary_fire_stamp[0] = timestamp(250);
|
|
|
|
// Debug code! Make the single laser fire only one bolt and from the object center!
|
|
for (i=0; i<MAX_WEAPONS; i++)
|
|
if (!stricmp(Weapon_info[i].name, NOX("Debug Laser")))
|
|
break;
|
|
|
|
vm_vec_add(&wpos, &objp->pos, &(objp->orient.vec.fvec) );
|
|
if (i != MAX_WEAPONS) {
|
|
int weapon_objnum;
|
|
weapon_objnum = weapon_create( &wpos, &objp->orient, i, OBJ_INDEX(objp), 0 );
|
|
weapon_set_tracking_info(weapon_objnum, OBJ_INDEX(objp), Ai_info[shipp->ai_index].target_objnum);
|
|
return 1;
|
|
} else
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
// Launch countermeasures from object *objp. rand_val is used in multiplayer to ensure that all
|
|
// clients in the game fire countermeasure the same way
|
|
int ship_launch_countermeasure(object *objp, int rand_val)
|
|
{
|
|
int fired, check_count, cmeasure_count;
|
|
vector pos;
|
|
ship *shipp;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
|
|
// in the case where the server is an observer, he can launch countermeasures unless we do this.
|
|
if( objp->type == OBJ_OBSERVER){
|
|
return 0;
|
|
}
|
|
|
|
if ( !timestamp_elapsed(shipp->cmeasure_fire_stamp) ){
|
|
return 0;
|
|
}
|
|
|
|
shipp->cmeasure_fire_stamp = timestamp(CMEASURE_WAIT); // Can launch every half second.
|
|
#ifndef NDEBUG
|
|
if (Weapon_energy_cheat) {
|
|
shipp->cmeasure_count++;
|
|
}
|
|
#endif
|
|
|
|
// we might check the count of countermeasures left depending on game state. Multiplayer clients
|
|
// do not need to check any objects other than themselves for the count
|
|
fired = -1;
|
|
check_count = 1;
|
|
#ifndef NO_NETWORK
|
|
if ( MULTIPLAYER_CLIENT && (objp != Player_obj) ){
|
|
check_count = 0;
|
|
}
|
|
#endif
|
|
|
|
if (check_count && (shipp->cmeasure_count <= 0) ) {
|
|
if ( objp == Player_obj ) {
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "No more countermeasure charges.", 485));
|
|
snd_play( &Snds[SND_OUT_OF_MISSLES], 0.0f );
|
|
}
|
|
|
|
// if we have a player ship, then send the fired packet anyway so that the player
|
|
// who fired will get his 'out of countermeasures' sound
|
|
cmeasure_count = 0;
|
|
if ( objp->flags & OF_PLAYER_SHIP ){
|
|
goto send_countermeasure_fired;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
cmeasure_count = shipp->cmeasure_count;
|
|
shipp->cmeasure_count--;
|
|
|
|
vm_vec_scale_add(&pos, &objp->pos, &objp->orient.vec.fvec, -objp->radius/2.0f);
|
|
|
|
// cmeasure_create fires 1 countermeasure. returns -1 if not fired, otherwise a non-negative
|
|
// value
|
|
fired = cmeasure_create( objp, &pos, shipp->current_cmeasure, rand_val );
|
|
|
|
// Play sound effect for counter measure launch
|
|
Assert(shipp->current_cmeasure < Num_cmeasure_types);
|
|
if ( Cmeasure_info[shipp->current_cmeasure].launch_sound != -1 ) {
|
|
snd_play_3d( &Snds[Cmeasure_info[shipp->current_cmeasure].launch_sound], &pos, &View_position );
|
|
}
|
|
|
|
|
|
send_countermeasure_fired:
|
|
#ifndef NO_NETWORK
|
|
// the new way of doing things
|
|
// if(Netgame.debug_flags & NETD_FLAG_CLIENT_FIRING){
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
send_NEW_countermeasure_fired_packet( objp, cmeasure_count, fired );
|
|
}
|
|
// }
|
|
// the old way of doing things
|
|
//else {
|
|
// if ( MULTIPLAYER_MASTER ){
|
|
// send_countermeasure_fired_packet( objp, cmeasure_count, fired );
|
|
//}
|
|
//}
|
|
#endif
|
|
|
|
return (fired>0); // return 0 if not fired, 1 otherwise
|
|
}
|
|
|
|
// internal function.. see if enough time has elapsed to play fail sound again
|
|
int ship_maybe_play_primary_fail_sound()
|
|
{
|
|
hud_start_flash_weapon(Player_ship->weapons.current_primary_bank);
|
|
|
|
if ( timestamp_elapsed(Laser_energy_out_snd_timer) ) {
|
|
Laser_energy_out_snd_timer = timestamp(50);
|
|
snd_play( &Snds[SND_OUT_OF_WEAPON_ENERGY]);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// internal function.. see if enough time has elapsed to play fail sound again
|
|
int ship_maybe_play_secondary_fail_sound(weapon_info *wip)
|
|
{
|
|
hud_start_flash_weapon(Player_ship->weapons.num_primary_banks + Player_ship->weapons.current_secondary_bank);
|
|
|
|
if ( timestamp_elapsed(Missile_out_snd_timer) ) {
|
|
|
|
if ( wip->wi_flags & WIF_SWARM ) {
|
|
Missile_out_snd_timer = timestamp(500);
|
|
} else {
|
|
Missile_out_snd_timer = timestamp(50);
|
|
}
|
|
snd_play( &Snds[SND_OUT_OF_MISSLES] );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// internal function.. see if weapon for ship can fire based on weapons subystem
|
|
// strength.
|
|
//
|
|
// returns: 1 => weapon failed to fire
|
|
// 0 => weapon can fire
|
|
int ship_weapon_maybe_fail(ship *sp)
|
|
{
|
|
int rval;
|
|
float weapons_subsys_str;
|
|
|
|
// If playing on lowest skill level, weapons will not fail due to subsystem damage
|
|
if ( Game_skill_level == 0 ){
|
|
return 0;
|
|
}
|
|
|
|
rval = 0;
|
|
weapons_subsys_str = ship_get_subsystem_strength( sp, SUBSYSTEM_WEAPONS );
|
|
if ( weapons_subsys_str < SUBSYS_WEAPONS_STR_FIRE_FAIL ) {
|
|
rval = 1;
|
|
}
|
|
else if ( weapons_subsys_str < SUBSYS_WEAPONS_STR_FIRE_OK ) {
|
|
// chance to fire depends on weapons subsystem strength
|
|
if ( (frand()-0.2f) > weapons_subsys_str )
|
|
rval = 1;
|
|
}
|
|
|
|
if (!rval) {
|
|
// is subsystem disrupted?
|
|
if ( ship_subsys_disrupted(sp, SUBSYSTEM_WEAPONS) ) {
|
|
rval=1;
|
|
}
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
// create a moving tracer based upon a weapon which just fired
|
|
float t_rad = 0.5f;
|
|
float t_len = 10.0f;
|
|
float t_vel = 0.2f;
|
|
float t_min = 150.0f;
|
|
float t_max = 300.0f;
|
|
DCF(t_rad, "")
|
|
{
|
|
dc_get_arg(ARG_FLOAT);
|
|
t_rad = Dc_arg_float;
|
|
}
|
|
DCF(t_len, "")
|
|
{
|
|
dc_get_arg(ARG_FLOAT);
|
|
t_len = Dc_arg_float;
|
|
}
|
|
DCF(t_vel, "")
|
|
{
|
|
dc_get_arg(ARG_FLOAT);
|
|
t_vel = Dc_arg_float;
|
|
}
|
|
DCF(t_min, "")
|
|
{
|
|
dc_get_arg(ARG_FLOAT);
|
|
t_min = Dc_arg_float;
|
|
}
|
|
DCF(t_max, "")
|
|
{
|
|
dc_get_arg(ARG_FLOAT);
|
|
t_max = Dc_arg_float;
|
|
}
|
|
void ship_fire_tracer(int weapon_objnum)
|
|
{
|
|
particle_info pinfo;
|
|
object *objp = &Objects[weapon_objnum];
|
|
weapon_info *wip = &Weapon_info[Weapons[Objects[weapon_objnum].instance].weapon_info_index];
|
|
|
|
// setup particle info
|
|
memset(&pinfo, 0, sizeof(particle_info));
|
|
pinfo.pos = objp->pos;
|
|
pinfo.vel = objp->phys_info.vel;
|
|
vm_vec_scale(&pinfo.vel, t_vel);
|
|
pinfo.lifetime = wip->lifetime;
|
|
pinfo.rad = t_rad;
|
|
pinfo.type = PARTICLE_BITMAP;
|
|
pinfo.optional_data = wip->laser_bitmap;
|
|
pinfo.tracer_length = t_len;
|
|
pinfo.reverse = 0;
|
|
pinfo.attached_objnum = -1;
|
|
pinfo.attached_sig = 0;
|
|
|
|
// create the particle
|
|
particle_create(&pinfo);
|
|
}
|
|
|
|
// Multiplicative delay factors for increasing skill levels.
|
|
float Ship_fire_delay_scale_hostile[NUM_SKILL_LEVELS] = {4.0f, 2.5f, 1.75f, 1.25f, 1.0f};
|
|
float Ship_fire_delay_scale_friendly[NUM_SKILL_LEVELS] = {2.0f, 1.4f, 1.25f, 1.1f, 1.0f};
|
|
|
|
int tracers[MAX_SHIPS][4][4];
|
|
|
|
// fires a primary weapon for the given object. It also handles multiplayer cases.
|
|
// in multiplayer, the starting network signature, and number of banks fired are sent
|
|
// to all the clients in the game. All the info is passed to send_primary at the end of
|
|
// the function. The check_energy parameter (defaults to 1) tells us whether or not
|
|
// we should check the energy. It will be 0 when a multiplayer client is firing an AI
|
|
// primary.
|
|
int ship_fire_primary(object * obj, int stream_weapons, int force)
|
|
{
|
|
vector gun_point, pnt, firing_pos;
|
|
int n = obj->instance;
|
|
ship *shipp;
|
|
ship_weapon *swp;
|
|
ship_info *sip;
|
|
ai_info *aip;
|
|
int weapon, i, j, weapon_objnum;
|
|
int bank_to_fire, num_fired = 0;
|
|
int banks_fired, have_timeout; // used for multiplayer to help determine whether or not to send packet
|
|
have_timeout = 0; // used to help tell us whether or not we need to send a packet
|
|
banks_fired = 0; // used in multiplayer -- bitfield of banks that were fired
|
|
|
|
int sound_played; // used to track what sound is played. If the player is firing two banks
|
|
// of the same laser, we only want to play one sound
|
|
Assert( obj != NULL );
|
|
|
|
if(obj == NULL){
|
|
return 0;
|
|
}
|
|
|
|
// in the case where the server is an observer, he can fire (which) would be bad - unless we do this.
|
|
if( obj->type == OBJ_OBSERVER){
|
|
return 0;
|
|
}
|
|
|
|
Assert( obj->type == OBJ_SHIP );
|
|
Assert( n >= 0 );
|
|
Assert( Ships[n].objnum == OBJ_INDEX(obj));
|
|
if((obj->type != OBJ_SHIP) || (n < 0) || (n >= MAX_SHIPS) || (Ships[n].objnum != OBJ_INDEX(obj))){
|
|
return 0;
|
|
}
|
|
|
|
shipp = &Ships[n];
|
|
swp = &shipp->weapons;
|
|
|
|
// bogus
|
|
if((shipp->ship_info_index < 0) || (shipp->ship_info_index >= Num_ship_types)){
|
|
return 0;
|
|
}
|
|
if((shipp->ai_index < 0) || (shipp->ai_index >= MAX_AI_INFO)){
|
|
return 0;
|
|
}
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
aip = &Ai_info[shipp->ai_index];
|
|
|
|
if ( swp->num_primary_banks <= 0 ) {
|
|
return 0;
|
|
}
|
|
|
|
if ( swp->current_primary_bank < 0 ){
|
|
return 0;
|
|
}
|
|
|
|
sound_played = -1;
|
|
|
|
// Fire the correct primary bank. If primaries are linked (SF_PRIMARY_LINKED set), then fire
|
|
// both primary banks.
|
|
int num_primary_banks;
|
|
|
|
if ( shipp->flags & SF_PRIMARY_LINKED ) {
|
|
num_primary_banks = swp->num_primary_banks;
|
|
} else {
|
|
num_primary_banks = min(1, swp->num_primary_banks);
|
|
}
|
|
|
|
Assert(num_primary_banks > 0);
|
|
if (num_primary_banks < 1){
|
|
return 0;
|
|
}
|
|
|
|
// if we're firing stream weapons, but the trigger is not down, do nothing
|
|
if(stream_weapons && !(shipp->flags & SF_TRIGGER_DOWN)){
|
|
return 0;
|
|
}
|
|
|
|
for ( i = 0; i < num_primary_banks; i++ ) {
|
|
bank_to_fire = (swp->current_primary_bank+i)%2; // Max supported banks is 2
|
|
|
|
weapon = swp->primary_bank_weapons[bank_to_fire];
|
|
Assert( weapon >= 0 && weapon < MAX_WEAPONS );
|
|
if ( (weapon < 0) || (weapon >= MAX_WEAPON_TYPES) ) {
|
|
Int3(); // why would a ship try to fire a weapon that doesn't exist?
|
|
continue;
|
|
}
|
|
weapon_info* winfo_p = &Weapon_info[weapon];
|
|
|
|
// if this is a targeting laser, start it up
|
|
if((winfo_p->wi_flags & WIF_BEAM) && (winfo_p->b_info.beam_type == BEAM_TYPE_C)){
|
|
ship_start_targeting_laser(shipp);
|
|
continue;
|
|
}
|
|
|
|
// if we're firing stream weapons and this is a non stream weapon, skip it
|
|
if(stream_weapons && !(winfo_p->wi_flags & WIF_STREAM)){
|
|
continue;
|
|
}
|
|
// if we're firing non stream weapons and this is a stream weapon, skip it
|
|
if(!stream_weapons && (winfo_p->wi_flags & WIF_STREAM)){
|
|
continue;
|
|
}
|
|
|
|
// only non-multiplayer clients (single, multi-host) need to do timestamp checking
|
|
if ( !timestamp_elapsed(swp->next_primary_fire_stamp[bank_to_fire]) ) {
|
|
if (timestamp_until(swp->next_primary_fire_stamp[bank_to_fire]) > 5000){
|
|
swp->next_primary_fire_stamp[bank_to_fire] = timestamp(1000);
|
|
}
|
|
|
|
have_timeout = 1;
|
|
continue;
|
|
}
|
|
|
|
//nprintf(("AI", "Time = %7.3f, firing %s\n", f2fl(Missiontime), Weapon_info[weapon].name));
|
|
|
|
// do timestamp stuff for next firing time
|
|
float next_fire_delay = (float) winfo_p->fire_wait * 1000.0f;
|
|
if (!(obj->flags & OF_PLAYER_SHIP)) {
|
|
if (shipp->team == Ships[Player_obj->instance].team){
|
|
next_fire_delay *= Ship_fire_delay_scale_friendly[Game_skill_level];
|
|
} else {
|
|
next_fire_delay *= Ship_fire_delay_scale_hostile[Game_skill_level];
|
|
}
|
|
}
|
|
|
|
next_fire_delay *= 1.0f + (num_primary_banks - 1) * 0.5f; // 50% time penalty if banks linked
|
|
|
|
// MK, 2/4/98: Since you probably were allowed to fire earlier, but couldn't fire until your frame interval
|
|
// rolled around, subtract out up to half the previous frametime.
|
|
// Note, unless we track whether the fire button has been held down, and not tapped, it's hard to
|
|
// know how much time to subtract off. It could be this fire is "late" because the user didn't want to fire.
|
|
if (next_fire_delay > 0.0f) {
|
|
if (obj->flags & OF_PLAYER_SHIP) {
|
|
int t = timestamp_until(swp->next_primary_fire_stamp[bank_to_fire]);
|
|
if (t < 0) {
|
|
float tx;
|
|
|
|
tx = (float) t/-1000.0f;
|
|
if (tx > flFrametime/2.0f){
|
|
tx = 1000.0f * flFrametime * 0.7f;
|
|
}
|
|
next_fire_delay -= tx;
|
|
}
|
|
|
|
if ((int) next_fire_delay < 1){
|
|
next_fire_delay = 1.0f;
|
|
}
|
|
}
|
|
|
|
swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
|
|
}
|
|
|
|
// Here is where we check if weapons subsystem is capable of firing the weapon.
|
|
// Note that we can have partial bank firing, if the weapons subsystem is partially
|
|
// functional, which should be cool.
|
|
if ( ship_weapon_maybe_fail(shipp) && !force) {
|
|
if ( obj == Player_obj ) {
|
|
if ( ship_maybe_play_primary_fail_sound() ) {
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
polymodel *po = model_get( Ship_info[shipp->ship_info_index].modelnum );
|
|
if ( po->n_guns > 0 ) {
|
|
int num_slots = po->gun_banks[bank_to_fire].num_slots;
|
|
|
|
// fail unless we're forcing (energy based primaries)
|
|
if ( (shipp->weapon_energy < num_slots*winfo_p->energy_consumed) && !force) {
|
|
if ( obj == Player_obj ) {
|
|
swp->next_primary_fire_stamp[bank_to_fire] = timestamp(swp->next_primary_fire_stamp[bank_to_fire]);
|
|
if ( ship_maybe_play_primary_fail_sound() ) {
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// deplete the weapon reserve energy by the amount of energy used to fire the weapon
|
|
shipp->weapon_energy -= num_slots*winfo_p->energy_consumed;
|
|
if(shipp->weapon_energy < 0.0f){
|
|
shipp->weapon_energy = 0.0f;
|
|
}
|
|
|
|
// Mark all these weapons as in the same group
|
|
int new_group_id = weapon_create_group_id();
|
|
|
|
for ( j = 0; j < num_slots; j++ ) {
|
|
pnt = po->gun_banks[bank_to_fire].pnt[j];
|
|
vm_vec_unrotate(&gun_point, &pnt, &obj->orient);
|
|
vm_vec_add(&firing_pos, &gun_point, &obj->pos);
|
|
|
|
// create the weapon -- the network signature for multiplayer is created inside
|
|
// of weapon_create
|
|
weapon_objnum = weapon_create( &firing_pos, &obj->orient, weapon, OBJ_INDEX(obj),0, new_group_id );
|
|
weapon_set_tracking_info(weapon_objnum, OBJ_INDEX(obj), aip->target_objnum, aip->current_target_is_locked, aip->targeted_subsys);
|
|
|
|
// create the muzzle flash effect
|
|
shipfx_flash_create( obj, shipp, &pnt, &obj->orient.vec.fvec, 1, weapon );
|
|
|
|
// maybe shudder the ship - if its me
|
|
if((winfo_p->wi_flags & WIF_SHUDDER) && (obj == Player_obj) && !(Game_mode & GM_STANDALONE_SERVER)){
|
|
// calculate some arbitrary value between 100
|
|
// (mass * velocity) / 10
|
|
game_shudder_apply(500, (winfo_p->mass * winfo_p->max_speed) / 10.0f);
|
|
}
|
|
|
|
num_fired++;
|
|
}
|
|
|
|
banks_fired |= (1<<bank_to_fire); // mark this bank as fired.
|
|
}
|
|
|
|
// Only play the weapon fired sound if it hasn't been played yet. This is to
|
|
// avoid playing the same sound multiple times when banks are linked with the
|
|
// same weapon.
|
|
if ( sound_played != winfo_p->launch_snd ) {
|
|
sound_played = winfo_p->launch_snd;
|
|
if ( obj == Player_obj ) {
|
|
if ( winfo_p->launch_snd != -1 ) {
|
|
weapon_info *wip;
|
|
ship_weapon *swp;
|
|
|
|
// HACK
|
|
if(winfo_p->launch_snd == SND_AUTOCANNON_SHOT){
|
|
snd_play( &Snds[winfo_p->launch_snd], 0.0f, 1.0f, SND_PRIORITY_TRIPLE_INSTANCE );
|
|
} else {
|
|
snd_play( &Snds[winfo_p->launch_snd], 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
|
|
}
|
|
// snd_play( &Snds[winfo_p->launch_snd] );
|
|
|
|
swp = &Player_ship->weapons;
|
|
if (swp->current_primary_bank >= 0) {
|
|
wip = &Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]];
|
|
joy_ff_play_primary_shoot((int) ((wip->armor_factor + wip->shield_factor * 0.2f) * (wip->damage * wip->damage - 7.5f) * 0.45f + 0.6f) * 10 + 2000);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ( winfo_p->launch_snd != -1 ) {
|
|
snd_play_3d( &Snds[winfo_p->launch_snd], &obj->pos, &View_position );
|
|
}
|
|
}
|
|
}
|
|
} // end for (go to next primary bank)
|
|
|
|
#ifndef NO_NETWORK
|
|
// if multiplayer and we're client-side firing, send the packet
|
|
// if((Game_mode & GM_MULTIPLAYER) && (Netgame.debug_flags & NETD_FLAG_CLIENT_FIRING)){
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
// if i'm a client, and this is not me, don't send
|
|
if(!(MULTIPLAYER_CLIENT && (shipp != Player_ship))){
|
|
send_NEW_primary_fired_packet( shipp, banks_fired );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// post a primary fired event
|
|
if(Game_mode & GM_DEMO_RECORD){
|
|
demo_POST_primary_fired(obj, swp->current_primary_bank, shipp->flags & SF_PRIMARY_LINKED);
|
|
}
|
|
|
|
// STATS
|
|
if (obj->flags & OF_PLAYER_SHIP) {
|
|
// in multiplayer -- only the server needs to keep track of the stats. Call the cool
|
|
// function to find the player given the object *. It had better return a valid player
|
|
// or our internal structure as messed up.
|
|
#ifndef NO_NETWORK
|
|
if( Game_mode & GM_MULTIPLAYER ) {
|
|
if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
|
|
int player_num;
|
|
|
|
player_num = multi_find_player_by_object ( obj );
|
|
Assert ( player_num != -1 );
|
|
|
|
Net_players[player_num].player->stats.mp_shots_fired += num_fired;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Player->stats.mp_shots_fired += num_fired;
|
|
}
|
|
}
|
|
|
|
return num_fired;
|
|
}
|
|
|
|
void ship_start_targeting_laser(ship *shipp)
|
|
{
|
|
int bank0_laser = 0;
|
|
int bank1_laser = 0;
|
|
|
|
// determine if either of our banks have a targeting laser
|
|
if((shipp->weapons.primary_bank_weapons[0] >= 0) && (Weapon_info[shipp->weapons.primary_bank_weapons[0]].wi_flags & WIF_BEAM) && (Weapon_info[shipp->weapons.primary_bank_weapons[0]].b_info.beam_type == BEAM_TYPE_C)){
|
|
bank0_laser = 1;
|
|
}
|
|
if((shipp->weapons.primary_bank_weapons[1] >= 0) && (Weapon_info[shipp->weapons.primary_bank_weapons[1]].wi_flags & WIF_BEAM) && (Weapon_info[shipp->weapons.primary_bank_weapons[1]].b_info.beam_type == BEAM_TYPE_C)){
|
|
bank1_laser = 1;
|
|
}
|
|
|
|
// if primary banks are linked
|
|
if(shipp->flags & SF_PRIMARY_LINKED){
|
|
if(bank0_laser){
|
|
shipp->targeting_laser_bank = 0;
|
|
return;
|
|
}
|
|
if(bank1_laser){
|
|
shipp->targeting_laser_bank = 1;
|
|
return;
|
|
}
|
|
}
|
|
// if we only have 1 bank selected
|
|
else {
|
|
if(bank0_laser && (shipp->weapons.current_primary_bank == 0)){
|
|
shipp->targeting_laser_bank = 0;
|
|
return;
|
|
}
|
|
if(bank1_laser && (shipp->weapons.current_primary_bank == 1)){
|
|
shipp->targeting_laser_bank = 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ship_stop_targeting_laser(ship *shipp)
|
|
{
|
|
shipp->targeting_laser_bank = -1;
|
|
shipp->targeting_laser_objnum = -1;
|
|
}
|
|
|
|
void ship_process_targeting_lasers()
|
|
{
|
|
beam_fire_info fire_info;
|
|
ship_obj *so;
|
|
ship *shipp;
|
|
polymodel *m;
|
|
|
|
// interate over all ships
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
// sanity checks
|
|
if(so->objnum < 0){
|
|
continue;
|
|
}
|
|
if(Objects[so->objnum].type != OBJ_SHIP){
|
|
continue;
|
|
}
|
|
if(Objects[so->objnum].instance < 0){
|
|
continue;
|
|
}
|
|
shipp = &Ships[Objects[so->objnum].instance];
|
|
|
|
// if our trigger is no longer down, switch it off
|
|
if(!(shipp->flags & SF_TRIGGER_DOWN)){
|
|
ship_stop_targeting_laser(shipp);
|
|
continue;
|
|
}
|
|
|
|
// if we have a bank to fire - fire it
|
|
if((shipp->targeting_laser_bank >= 0) && (shipp->targeting_laser_bank < 2)){
|
|
// try and get the model
|
|
m = model_get(shipp->modelnum);
|
|
if(m == NULL){
|
|
continue;
|
|
}
|
|
|
|
// fire a targeting laser
|
|
fire_info.accuracy = 0.0f;
|
|
fire_info.beam_info_index = shipp->weapons.primary_bank_weapons[shipp->targeting_laser_bank];
|
|
fire_info.beam_info_override = NULL;
|
|
fire_info.shooter = &Objects[shipp->objnum];
|
|
fire_info.target = NULL;
|
|
fire_info.target_subsys = NULL;
|
|
fire_info.turret = NULL;
|
|
fire_info.targeting_laser_offset = m->gun_banks[shipp->targeting_laser_bank].pnt[0];
|
|
shipp->targeting_laser_objnum = beam_fire_targeting(&fire_info);
|
|
|
|
// hmm, why didn't it fire?
|
|
if(shipp->targeting_laser_objnum < 0){
|
|
Int3();
|
|
ship_stop_targeting_laser(shipp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Attempt to detonate weapon last fired by *shipp.
|
|
// Only used for weapons that support remote detonation.
|
|
// Return true if detonated, else return false.
|
|
// Calls weapon_hit() to detonate weapon.
|
|
// If it's a weapon that spawns particles, those will be released.
|
|
int maybe_detonate_weapon(ship_weapon *swp, object *src)
|
|
{
|
|
int objnum = swp->last_fired_weapon_index;
|
|
object *objp;
|
|
weapon_info *wip;
|
|
|
|
objp = &Objects[objnum];
|
|
|
|
if (objp->type != OBJ_WEAPON){
|
|
return 0;
|
|
}
|
|
|
|
if ((objp->instance < 0) || (objp->instance > MAX_WEAPONS)){
|
|
return 0;
|
|
}
|
|
|
|
// check to make sure that the weapon to detonate still exists
|
|
if ( swp->last_fired_weapon_signature != objp->signature ){
|
|
return 0;
|
|
}
|
|
|
|
Assert(Weapons[objp->instance].weapon_info_index != -1);
|
|
wip = &Weapon_info[Weapons[objp->instance].weapon_info_index];
|
|
|
|
if (wip->wi_flags & WIF_REMOTE) {
|
|
|
|
if ((objnum >= 0) && (objnum < MAX_OBJECTS)) {
|
|
int weapon_sig;
|
|
|
|
weapon_sig = objp->signature;
|
|
|
|
if (swp->last_fired_weapon_signature == weapon_sig) {
|
|
weapon_detonate(objp);
|
|
swp->last_fired_weapon_index = -1;
|
|
|
|
/*
|
|
if (src == Player_obj) {
|
|
char missile_name[NAME_LENGTH];
|
|
strcpy(missile_name, wip->name);
|
|
hud_end_string_at_first_hash_symbol(missile_name);
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "Detonated %s!", 486), missile_name);
|
|
}
|
|
*/
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Maybe detonate secondary weapon that's already out.
|
|
// Return true if we detonate it, false if not.
|
|
int ship_fire_secondary_detonate(object *obj, ship_weapon *swp)
|
|
{
|
|
if (swp->last_fired_weapon_index != -1)
|
|
if (timestamp_elapsed(swp->detonate_weapon_time)) {
|
|
object *first_objp = &Objects[swp->last_fired_weapon_index];
|
|
if (maybe_detonate_weapon(swp, obj)) {
|
|
// If dual fire was set, there could be another weapon to detonate. Scan all weapons.
|
|
missile_obj *mo;
|
|
|
|
//nprintf(("AI", "Weapon %i detonated\n", first_objp-Objects));
|
|
|
|
// check for currently locked missiles (highest precedence)
|
|
for ( mo = GET_FIRST(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
|
|
object *mobjp;
|
|
Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
|
|
mobjp = &Objects[mo->objnum];
|
|
if ((mobjp != first_objp) && (mobjp->parent_sig == obj->parent_sig)) {
|
|
if (Weapon_info[Weapons[mobjp->instance].weapon_info_index].wi_flags & WIF_REMOTE) {
|
|
//nprintf(("AI", "Also detonating weapon %i whose parent is %s\n", mobjp-Objects, Ships[Objects[mobjp->parent].instance].ship_name));
|
|
weapon_detonate(mobjp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Try to switch to a secondary bank that has ammo
|
|
int ship_select_next_valid_secondary_bank(ship_weapon *swp)
|
|
{
|
|
int cycled=0;
|
|
|
|
int ns = swp->num_secondary_banks;
|
|
|
|
if ( ns > 1 ) {
|
|
int i,j=swp->current_secondary_bank+1;
|
|
for (i=0; i<ns; i++) {
|
|
if ( j >= ns ) {
|
|
j=0;
|
|
}
|
|
|
|
if ( swp->secondary_bank_ammo[j] > 0 ) {
|
|
swp->current_secondary_bank=j;
|
|
cycled = 1;
|
|
break;
|
|
}
|
|
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return cycled;
|
|
}
|
|
|
|
|
|
extern void ai_maybe_announce_shockwave_weapon(object *firing_objp, int weapon_index);
|
|
|
|
// Object *obj fires its secondary weapon, if it can.
|
|
// If its most recently fired weapon is a remotely detonatable weapon, detonate it.
|
|
// Returns number of weapons fired. Note, for swarmers, returns 1 if it is allowed
|
|
// to fire the missiles when allow_swarm is NOT set. They don't actually get fired on a call here unless allow_swarm is set.
|
|
// When you want to fire swarmers, you call this function with allow_swarm NOT set and frame interval
|
|
// code comes aruond and fires it.
|
|
// allow_swarm -> default value is 0... since swarm missiles are fired over several frames,
|
|
// need to avoid firing when normally called
|
|
int ship_fire_secondary( object *obj, int allow_swarm )
|
|
{
|
|
int n, weapon, j, bank, have_timeout, starting_bank_count = -1, num_fired;
|
|
ushort starting_sig = 0;
|
|
ship *shipp;
|
|
ship_weapon *swp;
|
|
ship_info *sip;
|
|
weapon_info *wip;
|
|
ai_info *aip;
|
|
polymodel *po;
|
|
vector missile_point, pnt, firing_pos;
|
|
|
|
Assert( obj != NULL );
|
|
|
|
// in the case where the server is an observer, he can fire (which would be bad) - unless we do this.
|
|
if( obj->type == OBJ_OBSERVER ){
|
|
return 0;
|
|
}
|
|
|
|
// in the case where the object is a ghost (a delayed fire packet from right before he died, for instance)
|
|
if( (obj->type == OBJ_GHOST) || (obj->type == OBJ_NONE) ){
|
|
return 0;
|
|
}
|
|
|
|
Assert( obj->type == OBJ_SHIP );
|
|
if(obj->type != OBJ_SHIP){
|
|
return 0;
|
|
}
|
|
n = obj->instance;
|
|
Assert( n >= 0 && n < MAX_SHIPS );
|
|
if((n < 0) || (n >= MAX_SHIPS)){
|
|
return 0;
|
|
}
|
|
Assert( Ships[n].objnum == OBJ_INDEX(obj));
|
|
if(Ships[n].objnum != OBJ_INDEX(obj)){
|
|
return 0;
|
|
}
|
|
|
|
shipp = &Ships[n];
|
|
swp = &shipp->weapons;
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
aip = &Ai_info[shipp->ai_index];
|
|
|
|
// if no secondary weapons are present on ship, return
|
|
if ( swp->num_secondary_banks <= 0 ){
|
|
return 0;
|
|
}
|
|
|
|
// If ship is being repaired/rearmed, it cannot fire missiles
|
|
if ( aip->ai_flags & AIF_BEING_REPAIRED ) {
|
|
return 0;
|
|
}
|
|
|
|
num_fired = 0; // tracks how many missiles actually fired
|
|
|
|
bank = swp->current_secondary_bank;
|
|
if ( bank < 0 ) {
|
|
return 0;
|
|
}
|
|
|
|
weapon = swp->secondary_bank_weapons[bank];
|
|
Assert( (swp->secondary_bank_weapons[bank] >= 0) && (swp->secondary_bank_weapons[bank] < MAX_WEAPON_TYPES) );
|
|
if((swp->secondary_bank_weapons[bank] < 0) || (swp->secondary_bank_weapons[bank] >= MAX_WEAPON_TYPES)){
|
|
return 0;
|
|
}
|
|
wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
|
|
|
|
have_timeout = 0; // used to help tell whether or not we have a timeout
|
|
#ifndef NO_NETWORK
|
|
if ( MULTIPLAYER_MASTER ) {
|
|
starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
|
|
starting_bank_count = swp->secondary_bank_ammo[bank];
|
|
}
|
|
#endif
|
|
|
|
if (ship_fire_secondary_detonate(obj, swp)) {
|
|
// in multiplayer, master sends a secondary fired packet with starting signature of -1 -- indicates
|
|
// to client code to set the detonate timer to 0.
|
|
#ifndef NO_NETWORK
|
|
if ( MULTIPLAYER_MASTER ) {
|
|
// MWA -- 4/6/98 Assert invalid since the bank count could have gone to 0.
|
|
//Assert(starting_bank_count != 0);
|
|
send_secondary_fired_packet( shipp, 0, starting_bank_count, 1, allow_swarm );
|
|
}
|
|
#endif
|
|
|
|
// For all banks, if ok to fire a weapon, make it wait a bit.
|
|
// Solves problem of fire button likely being down next frame and
|
|
// firing weapon despite fire causing detonation of existing weapon.
|
|
if (swp->current_secondary_bank >= 0) {
|
|
if (timestamp_elapsed(swp->next_secondary_fire_stamp[bank])){
|
|
swp->next_secondary_fire_stamp[bank] = timestamp(max((int) flFrametime*3000, 250));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if ( swp->current_secondary_bank < 0 ){
|
|
return 0;
|
|
}
|
|
|
|
if ( !timestamp_elapsed(swp->next_secondary_fire_stamp[bank]) && !allow_swarm) {
|
|
if (timestamp_until(swp->next_secondary_fire_stamp[bank]) > 60000){
|
|
swp->next_secondary_fire_stamp[bank] = timestamp(1000);
|
|
}
|
|
have_timeout = 1;
|
|
goto done_secondary;
|
|
}
|
|
|
|
// Ensure if this is a "require-lock" missile, that a lock actually exists
|
|
if ( wip->wi_flags & WIF_NO_DUMBFIRE ) {
|
|
if ( aip->current_target_is_locked <= 0 ) {
|
|
if ( obj == Player_obj ) {
|
|
if ( !Weapon_energy_cheat ) {
|
|
if ((aip->target_objnum != -1) && (vm_vec_dist_quick(&obj->pos, &Objects[aip->target_objnum].pos) > wip->lifetime * wip->max_speed)) {
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "Too far from target to acquire lock", 487));
|
|
} else {
|
|
char missile_name[NAME_LENGTH];
|
|
strcpy(missile_name, wip->name);
|
|
hud_end_string_at_first_hash_symbol(missile_name);
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "Cannot fire %s without a lock", 488), missile_name);
|
|
}
|
|
|
|
snd_play( &Snds[SND_OUT_OF_MISSLES] );
|
|
swp->next_secondary_fire_stamp[bank] = timestamp(800); // to avoid repeating messages
|
|
return 0;
|
|
}
|
|
} else {
|
|
// multiplayer clients should always fire the weapon here, so return only if not
|
|
// a multiplayer client.
|
|
#ifndef NO_NETWORK
|
|
if ( !MULTIPLAYER_CLIENT )
|
|
#endif
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if trying to fire a swarm missile, make sure being called from right place
|
|
if ( (wip->wi_flags & WIF_SWARM) && !allow_swarm ) {
|
|
Assert(wip->swarm_count > 0);
|
|
if(wip->swarm_count <= 0){
|
|
shipp->num_swarm_missiles_to_fire += SWARM_DEFAULT_NUM_MISSILES_FIRED;
|
|
} else {
|
|
shipp->num_swarm_missiles_to_fire += wip->swarm_count;
|
|
}
|
|
return 1; // Note: Missiles didn't get fired, but the frame interval code will fire them.
|
|
}
|
|
|
|
// if trying to fire a corkscrew missile, make sure being called from right place
|
|
if ( (wip->wi_flags & WIF_CORKSCREW) && !allow_swarm ) {
|
|
shipp->num_corkscrew_to_fire = (ubyte)(shipp->num_corkscrew_to_fire + (ubyte)Corkscrew_num_missiles_fired);
|
|
return 1; // Note: Missiles didn't get fired, but the frame interval code will fire them.
|
|
}
|
|
|
|
swp->next_secondary_fire_stamp[bank] = timestamp((int)(Weapon_info[weapon].fire_wait * 1000.0f)); // They can fire 5 times a second
|
|
|
|
// Here is where we check if weapons subsystem is capable of firing the weapon.
|
|
// do only in single plyaer or if I am the server of a multiplayer game
|
|
#ifndef NO_NETWORK
|
|
if ( !(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER )
|
|
#endif
|
|
{
|
|
if ( ship_weapon_maybe_fail(shipp) ) {
|
|
if ( obj == Player_obj )
|
|
if ( ship_maybe_play_secondary_fail_sound(wip) ) {
|
|
char missile_name[NAME_LENGTH];
|
|
strcpy(missile_name, Weapon_info[weapon].name);
|
|
hud_end_string_at_first_hash_symbol(missile_name);
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "Cannot fire %s due to weapons system damage", 489), missile_name);
|
|
}
|
|
goto done_secondary;
|
|
}
|
|
}
|
|
|
|
po = model_get( Ship_info[shipp->ship_info_index].modelnum );
|
|
if ( po->n_missiles > 0 ) {
|
|
int check_ammo; // used to tell if we should check ammo counts or not
|
|
int num_slots;
|
|
|
|
if ( bank > po->n_missiles ) {
|
|
nprintf(("WARNING","WARNING ==> Tried to fire bank %d, but ship has only %d banks\n", bank+1, po->n_missiles));
|
|
return 0; // we can make a quick out here!!!
|
|
}
|
|
|
|
num_slots = po->missile_banks[bank].num_slots;
|
|
|
|
// determine if there is enough ammo left to fire weapons on this bank. As with primary
|
|
// weapons, we might or might not check ammo counts depending on game mode, who is firing,
|
|
// and if I am a client in multiplayer
|
|
check_ammo = 1;
|
|
#ifndef NO_NETWORK
|
|
if ( MULTIPLAYER_CLIENT && (obj != Player_obj) ){
|
|
check_ammo = 0;
|
|
}
|
|
#endif
|
|
|
|
if ( check_ammo && ( swp->secondary_bank_ammo[bank] <= 0) ) {
|
|
if ( shipp->objnum == OBJ_INDEX(Player_obj) ) {
|
|
if ( ship_maybe_play_secondary_fail_sound(wip) ) {
|
|
// HUD_sourced_printf(HUD_SOURCE_HIDDEN, "No %s missiles left in bank", Weapon_info[swp->secondary_bank_weapons[bank]].name);
|
|
}
|
|
}
|
|
else {
|
|
// TODO: AI switch secondary weapon / re-arm?
|
|
}
|
|
goto done_secondary;
|
|
}
|
|
|
|
int start_slot, end_slot;
|
|
|
|
if ( shipp->flags & SF_SECONDARY_DUAL_FIRE ) {
|
|
start_slot = swp->secondary_next_slot[bank];
|
|
// AL 11-19-97: Ensure enough ammo remains when firing linked secondary weapons
|
|
if ( check_ammo && (swp->secondary_bank_ammo[bank] < 2) ) {
|
|
end_slot = start_slot;
|
|
} else {
|
|
end_slot = start_slot+1;
|
|
}
|
|
} else {
|
|
start_slot = swp->secondary_next_slot[bank];
|
|
end_slot = start_slot;
|
|
}
|
|
|
|
int pnt_index=start_slot;
|
|
for ( j = start_slot; j <= end_slot; j++ ) {
|
|
int weapon_num;
|
|
|
|
swp->secondary_next_slot[bank]++;
|
|
if ( swp->secondary_next_slot[bank] > (num_slots-1) ){
|
|
swp->secondary_next_slot[bank] = 0;
|
|
}
|
|
|
|
if ( pnt_index >= num_slots ){
|
|
pnt_index = 0;
|
|
}
|
|
pnt = po->missile_banks[bank].pnt[pnt_index++];
|
|
vm_vec_unrotate(&missile_point, &pnt, &obj->orient);
|
|
vm_vec_add(&firing_pos, &missile_point, &obj->pos);
|
|
|
|
#ifndef NO_NETWORK
|
|
if ( Game_mode & GM_MULTIPLAYER ) {
|
|
Assert( Weapon_info[weapon].subtype == WP_MISSILE );
|
|
}
|
|
#endif
|
|
|
|
// create the weapon -- for multiplayer, the net_signature is assigned inside
|
|
// of weapon_create
|
|
weapon_num = weapon_create( &firing_pos, &obj->orient, weapon, OBJ_INDEX(obj), 0, -1, aip->current_target_is_locked);
|
|
weapon_set_tracking_info(weapon_num, OBJ_INDEX(obj), aip->target_objnum, aip->current_target_is_locked, aip->targeted_subsys);
|
|
|
|
// create the muzzle flash effect
|
|
shipfx_flash_create( obj, shipp, &pnt, &obj->orient.vec.fvec, 0, weapon );
|
|
|
|
/*
|
|
if ( weapon_num != -1 )
|
|
Demo_fire_secondary_requests++; // testing for demo
|
|
*/
|
|
num_fired++;
|
|
swp->last_fired_weapon_index = weapon_num;
|
|
swp->detonate_weapon_time = timestamp(500); // Can detonate 1/2 second later.
|
|
if (weapon_num != -1) {
|
|
swp->last_fired_weapon_signature = Objects[weapon_num].signature;
|
|
}
|
|
|
|
// subtract the number of missiles fired
|
|
if ( Weapon_energy_cheat == 0 ){
|
|
swp->secondary_bank_ammo[bank]--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( obj == Player_obj ) {
|
|
if ( Weapon_info[weapon].launch_snd != -1 ) {
|
|
weapon_info *wip;
|
|
ship_weapon *swp;
|
|
|
|
snd_play( &Snds[Weapon_info[weapon].launch_snd], 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
|
|
swp = &Player_ship->weapons;
|
|
if (swp->current_secondary_bank >= 0) {
|
|
wip = &Weapon_info[swp->secondary_bank_weapons[swp->current_secondary_bank]];
|
|
if (Player_ship->flags & SF_SECONDARY_DUAL_FIRE){
|
|
joy_ff_play_secondary_shoot((int) (wip->cargo_size * 2.0f));
|
|
} else {
|
|
joy_ff_play_secondary_shoot((int) wip->cargo_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if ( Weapon_info[weapon].launch_snd != -1 ) {
|
|
snd_play_3d( &Snds[Weapon_info[weapon].launch_snd], &obj->pos, &View_position );
|
|
}
|
|
}
|
|
|
|
done_secondary:
|
|
|
|
if(num_fired > 0){
|
|
#ifndef NO_NETWORK
|
|
// if I am the master of a multiplayer game, send a secondary fired packet along with the
|
|
// first network signatures for the newly created weapons. if nothing got fired, send a failed
|
|
// packet if
|
|
if ( MULTIPLAYER_MASTER ) {
|
|
Assert(starting_sig != 0);
|
|
send_secondary_fired_packet( shipp, starting_sig, starting_bank_count, num_fired, allow_swarm );
|
|
}
|
|
#endif
|
|
// STATS
|
|
if (obj->flags & OF_PLAYER_SHIP) {
|
|
#ifndef NO_NETWORK
|
|
// in multiplayer -- only the server needs to keep track of the stats. Call the cool
|
|
// function to find the player given the object *. It had better return a valid player
|
|
// or our internal structure as messed up.
|
|
if( Game_mode & GM_MULTIPLAYER ) {
|
|
if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
|
|
int player_num;
|
|
|
|
player_num = multi_find_player_by_object ( obj );
|
|
Assert ( player_num != -1 );
|
|
|
|
Net_players[player_num].player->stats.ms_shots_fired += num_fired;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Player->stats.ms_shots_fired += num_fired;
|
|
}
|
|
}
|
|
|
|
// maybe announce a shockwave weapon
|
|
ai_maybe_announce_shockwave_weapon(obj, weapon);
|
|
}
|
|
|
|
// AL 3-7-98: Move to next valid secondary bank if out of ammo
|
|
//
|
|
|
|
//21-07-02 01:24 DTP; COMMENTED OUT some of the mistakes
|
|
//this bug was made by AL, when he assumed he had to take the next fire_wait time remaining and add 250 ms of delay to it,
|
|
//and put it in the next valid bank. for the player to have a 250 ms of penalty
|
|
//
|
|
//what that caused was that the next valid bank inherited the current valid banks FULL fire delay. since he used the Weapon_info struct that has
|
|
// no information / member that stores the next valids banks remaning fire_wait delay.
|
|
//
|
|
//what he should have done was to check of the next valid bank had any fire delay that had elapsed, if it had elapsed,
|
|
//then it would have no firedelay. and then add 250 ms of delay. in effect, this way there is no penalty if there is any firedelay remaning in
|
|
//the next valid bank. the delay is there to prevent things like Trible/Quad Fire Trebuchets.
|
|
//
|
|
if ( (obj->flags & OF_PLAYER_SHIP) && (swp->secondary_bank_ammo[bank] <= 0) ) {
|
|
//int fire_wait = (int)(Weapon_info[weapon].fire_wait * 1000.0f); //DTP commented out, mistake, takes our current firewait time for our current weapon, it should have been our next valid weapon, but the weapon_info contains no Var for NEXT valid bank
|
|
if ( ship_select_next_valid_secondary_bank(swp) ) { //DTP here we switch to the next valid bank, but we cant call weapon_info on next fire_wait
|
|
//swp->next_secondary_fire_stamp[swp->current_secondary_bank] = max(timestamp(250),timestamp(fire_wait)); // 1/4 second delay until can fire //DTP, Commented out mistake, here AL put the wroung firewait into the correct next_firestamp
|
|
if ( timestamp_elapsed(shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank]) ) { //DTP, this is simply a copy of the manual cycle functions
|
|
shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank] = timestamp(250); //DTP, the magical 1/4 second delay or we fire things like Quad Trebuchets, or every other weapon that do not have the "no dumbfire" flag.
|
|
}
|
|
|
|
if ( obj == Player_obj ) {
|
|
snd_play( &Snds[SND_SECONDARY_CYCLE] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return num_fired;
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// ship_select_next_primary()
|
|
//
|
|
// Return true if a new index gets selected.
|
|
//
|
|
// parameters: objp => pointer to object for ship cycling primary
|
|
// direction => forward == CYCLE_PRIMARY_NEXT, backward == CYCLE_PRIMARY_PREV
|
|
//
|
|
// NOTE: This code can be called for any arbitrary ship. HUD messages and sounds are only used
|
|
// for the player ship.
|
|
int ship_select_next_primary(object *objp, int direction)
|
|
{
|
|
ship *shipp;
|
|
ship_weapon *swp;
|
|
|
|
Assert(objp != NULL);
|
|
Assert(objp->type == OBJ_SHIP);
|
|
Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS);
|
|
|
|
shipp = &Ships[objp->instance];
|
|
swp = &shipp->weapons;
|
|
|
|
Assert(direction == CYCLE_PRIMARY_NEXT || direction == CYCLE_PRIMARY_PREV);
|
|
|
|
switch ( swp->num_primary_banks ) {
|
|
|
|
case 0:
|
|
if ( objp == Player_obj ) {
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has no primary weapons", 490));
|
|
gamesnd_play_error_beep();
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case 1:
|
|
if ( objp == Player_obj ) {
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has only one primary weapon: %s", 491),Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].name, swp->current_primary_bank + 1);
|
|
gamesnd_play_error_beep();
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case 2:
|
|
if ( shipp->flags & SF_PRIMARY_LINKED ) {
|
|
shipp->flags &= ~SF_PRIMARY_LINKED;
|
|
if ( direction == CYCLE_PRIMARY_NEXT ) {
|
|
swp->current_primary_bank = 0;
|
|
} else {
|
|
swp->current_primary_bank = 1;
|
|
}
|
|
} else {
|
|
switch ( swp->current_primary_bank ) {
|
|
case 0:
|
|
if ( direction == CYCLE_PRIMARY_NEXT ) {
|
|
swp->current_primary_bank = 1;
|
|
} else {
|
|
shipp->flags |= SF_PRIMARY_LINKED;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
if ( direction == CYCLE_PRIMARY_NEXT ) {
|
|
shipp->flags |= SF_PRIMARY_LINKED;
|
|
} else {
|
|
swp->current_primary_bank = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Int3(); // should never happen, get Alan if it does
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Int3(); // should never happen, get Alan if it does
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
if ( objp == Player_obj ) {
|
|
snd_play( &Snds[SND_PRIMARY_CYCLE], 0.0f );
|
|
}
|
|
|
|
ship_primary_changed(shipp);
|
|
return 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// ship_select_next_secondary() selects the next secondary bank with missles
|
|
//
|
|
// returns: 1 => The secondary bank was switched
|
|
// 0 => The secondary bank stayed the same
|
|
//
|
|
// If a secondary bank has no missles left, it is skipped.
|
|
//
|
|
// NOTE: This can be called for an arbitrary ship. HUD messages and sounds are only used
|
|
// for the player ship.
|
|
int ship_select_next_secondary(object *objp)
|
|
{
|
|
Assert(objp != NULL);
|
|
Assert(objp->type == OBJ_SHIP);
|
|
Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS);
|
|
|
|
int original_bank, new_bank, i;
|
|
ship *shipp;
|
|
ship_weapon *swp;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
swp = &shipp->weapons;
|
|
|
|
switch ( swp->num_secondary_banks ) {
|
|
case 0:
|
|
if ( objp == Player_obj ) {
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has no secondary weapons", 492));
|
|
gamesnd_play_error_beep();
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case 1:
|
|
if ( objp == Player_obj ) {
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has only one secondary weapon: %s", 493), Weapon_info[swp->secondary_bank_weapons[swp->current_secondary_bank]].name, swp->current_secondary_bank + 1);
|
|
gamesnd_play_error_beep();
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
Assert(swp->current_secondary_bank < swp->num_secondary_banks);
|
|
original_bank = swp->current_secondary_bank;
|
|
|
|
for ( i = 1; i < swp->num_secondary_banks; i++ ) {
|
|
new_bank = (swp->current_secondary_bank+i) % swp->num_secondary_banks;
|
|
if ( swp->secondary_bank_ammo[new_bank] <= 0 )
|
|
continue;
|
|
swp->current_secondary_bank = new_bank;
|
|
break;
|
|
}
|
|
|
|
if ( swp->current_secondary_bank != original_bank ) {
|
|
if ( objp == Player_obj ) {
|
|
snd_play( &Snds[SND_SECONDARY_CYCLE], 0.0f );
|
|
}
|
|
ship_secondary_changed(shipp);
|
|
return 1;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
Int3(); // should never happen, get Alan if it does
|
|
return 0;
|
|
break;
|
|
} // end switch
|
|
|
|
// If we've reached this point, must have failed
|
|
return 0;
|
|
}
|
|
|
|
// Stuff list of weapon indices for object *objp in list *outlist.
|
|
// Return number of weapons in list.
|
|
int get_available_secondary_weapons(object *objp, int *outlist, int *outbanklist)
|
|
{
|
|
int count = 0;
|
|
int i;
|
|
ship *shipp;
|
|
|
|
Assert(objp->type == OBJ_SHIP);
|
|
Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
|
|
shipp = &Ships[objp->instance];
|
|
|
|
for (i=0; i<shipp->weapons.num_secondary_banks; i++)
|
|
if (shipp->weapons.secondary_bank_ammo[i]) {
|
|
outbanklist[count] = i;
|
|
outlist[count++] = shipp->weapons.secondary_bank_weapons[i];
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// Return the object index of the ship with name *name.
|
|
int wing_name_lookup(char *name, int ignore_count)
|
|
{
|
|
int i, wing_limit;
|
|
|
|
if ( Fred_running )
|
|
wing_limit = MAX_WINGS;
|
|
else
|
|
wing_limit = num_wings;
|
|
|
|
if (Fred_running || ignore_count ) { // current_count not used for Fred..
|
|
for (i=0; i<wing_limit; i++)
|
|
if (Wings[i].wave_count && !stricmp(Wings[i].name, name))
|
|
return i;
|
|
|
|
} else {
|
|
for (i=0; i<wing_limit; i++)
|
|
if (Wings[i].current_count && !stricmp(Wings[i].name, name))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// this function is needed in addition to wing_name_lookup because it does a straight lookup without
|
|
// caring about how many ships are in the wing, etc.
|
|
int wing_lookup(char *name)
|
|
{
|
|
int idx;
|
|
for(idx=0;idx<num_wings;idx++)
|
|
if(strcmp(Wings[idx].name,name)==0)
|
|
return idx;
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Return the index of Ship_info[].name that is *name.
|
|
int ship_info_lookup(char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i < Num_ship_types; i++)
|
|
if (!stricmp(name, Ship_info[i].name))
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Return the index of Ship_info[].name which is the *base* ship of a ship copy
|
|
int ship_info_base_lookup(int si_index)
|
|
{
|
|
int i;
|
|
char name[NAME_LENGTH], *p;
|
|
|
|
strcpy( name, Ship_info[si_index].name );
|
|
p = strchr( name, '#' );
|
|
Assert( p ); // get allender -- something bogus with ship copy
|
|
*p = '\0';
|
|
|
|
i = ship_info_lookup( name );
|
|
Assert( i != -1 ); // get allender -- there had better be a base ship!
|
|
|
|
return i;
|
|
}
|
|
|
|
// Return the ship index of the ship with name *name.
|
|
int ship_name_lookup(char *name, int inc_players)
|
|
{
|
|
int i;
|
|
|
|
// bogus
|
|
if(name == NULL){
|
|
return -1;
|
|
}
|
|
|
|
for (i=0; i<MAX_SHIPS; i++){
|
|
if (Ships[i].objnum >= 0){
|
|
if (Objects[Ships[i].objnum].type == OBJ_SHIP || (Objects[Ships[i].objnum].type == OBJ_START && inc_players)){
|
|
if (!stricmp(name, Ships[i].ship_name)){
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// couldn't find it
|
|
return -1;
|
|
}
|
|
|
|
int ship_type_name_lookup(char *name)
|
|
{
|
|
int idx;
|
|
|
|
// bogus
|
|
if(name == NULL){
|
|
return -1;
|
|
}
|
|
|
|
// look through the Ship_type_names array
|
|
for(idx=0; idx<MAX_SHIP_TYPE_COUNTS; idx++){
|
|
if(!stricmp(name, Ship_type_names[idx])){
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
// couldn't find it
|
|
return -1;
|
|
}
|
|
|
|
// checks the (arrival & departure) state of a ship. Return values:
|
|
// -1: has yet to arrive in mission
|
|
// 0: is currently in mission
|
|
// 1: has been destroyed, departed, or never existsed
|
|
int ship_query_state(char *name)
|
|
{
|
|
int i;
|
|
p_object *objp;
|
|
|
|
// bogus
|
|
if(name == NULL){
|
|
return -1;
|
|
}
|
|
|
|
for (i=0; i<MAX_SHIPS; i++){
|
|
if (Ships[i].objnum >= 0){
|
|
if ((Objects[Ships[i].objnum].type == OBJ_SHIP) || (Objects[Ships[i].objnum].type == OBJ_START)){
|
|
if (!stricmp(name, Ships[i].ship_name)){
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
objp = GET_FIRST(&ship_arrival_list);
|
|
while (objp != END_OF_LIST(&ship_arrival_list)) {
|
|
if (!stricmp(name, objp->name)){
|
|
return -1;
|
|
}
|
|
|
|
objp = GET_NEXT(objp);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Note: This is not a general purpose routine.
|
|
// It is specifically used for targeting.
|
|
// It only returns a subsystem position if it has shields.
|
|
// Return true/false for subsystem found/not found.
|
|
// Stuff vector *pos with absolute position.
|
|
// subsysp is a pointer to the subsystem.
|
|
int get_subsystem_pos(vector *pos, object *objp, ship_subsys *subsysp)
|
|
{
|
|
matrix m;
|
|
model_subsystem *psub;
|
|
vector pnt;
|
|
ship *shipp;
|
|
|
|
Assert(objp->type == OBJ_SHIP);
|
|
shipp = &Ships[objp->instance];
|
|
|
|
Assert ( subsysp != NULL );
|
|
|
|
psub = subsysp->system_info;
|
|
vm_copy_transpose_matrix(&m, &objp->orient);
|
|
|
|
vm_vec_rotate(&pnt, &psub->pnt, &m);
|
|
vm_vec_add2(&pnt, &objp->pos);
|
|
|
|
if ( pos ){
|
|
*pos = pnt;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
//=================================================
|
|
// Takes all the angle info from the ship structure and stuffs it
|
|
// into the model data so that the model code has all the correct
|
|
// angles and stuff that it needs. This is a poorly designed
|
|
// system that should be re-engineered so that all the model functions
|
|
// accept a list of angles and everyone passes them through, but
|
|
// that would require some major code revision.
|
|
// So, anytime you are using a model that has rotating parts, you
|
|
// need to do a ship_model_start before any model_ functions are
|
|
// called and a ship_model_stop after you're done. Even for
|
|
// collision detection and stuff, not just rendering.
|
|
// See John for details.
|
|
|
|
void ship_model_start(object *objp)
|
|
{
|
|
model_subsystem *psub;
|
|
ship *shipp;
|
|
ship_subsys *pss;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
|
|
// First clear all the angles in the model to zero
|
|
model_clear_instance(shipp->modelnum);
|
|
|
|
// Go through all subsystems and bash the model angles for all
|
|
// the subsystems that need it.
|
|
for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
|
|
psub = pss->system_info;
|
|
switch (psub->type) {
|
|
case SUBSYSTEM_RADAR:
|
|
case SUBSYSTEM_NAVIGATION:
|
|
case SUBSYSTEM_COMMUNICATION:
|
|
case SUBSYSTEM_UNKNOWN:
|
|
case SUBSYSTEM_ENGINE:
|
|
case SUBSYSTEM_SENSORS:
|
|
case SUBSYSTEM_WEAPONS:
|
|
case SUBSYSTEM_SOLAR:
|
|
case SUBSYSTEM_GAS_COLLECT:
|
|
case SUBSYSTEM_ACTIVATION:
|
|
break;
|
|
case SUBSYSTEM_TURRET:
|
|
Assert( !(psub->flags & MSS_FLAG_ROTATES) ); // Turrets can't rotate!!! See John!
|
|
break;
|
|
default:
|
|
Error(LOCATION, "Illegal subsystem type.\n");
|
|
}
|
|
|
|
|
|
if ( psub->subobj_num > -1 ) {
|
|
model_set_instance(shipp->modelnum, psub->subobj_num, &pss->submodel_info_1 );
|
|
}
|
|
|
|
if ( (psub->subobj_num != psub->turret_gun_sobj) && (psub->turret_gun_sobj >-1) ) {
|
|
model_set_instance(shipp->modelnum, psub->turret_gun_sobj, &pss->submodel_info_2 );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//==========================================================
|
|
// Clears all the instance specific stuff out of the model info
|
|
void ship_model_stop(object *objp)
|
|
{
|
|
ship *shipp;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
|
|
// Then, clear all the angles in the model to zero
|
|
model_clear_instance(shipp->modelnum);
|
|
}
|
|
|
|
|
|
//==========================================================
|
|
// Finds the number of crew points in a ship
|
|
int ship_find_num_crewpoints(object *objp)
|
|
{
|
|
int n = 0;
|
|
model_subsystem *psub;
|
|
ship *shipp;
|
|
ship_subsys *pss;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
|
|
// Go through all subsystems and record the model angles for all
|
|
// the subsystems that need it.
|
|
for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
|
|
psub = pss->system_info;
|
|
switch (psub->type) {
|
|
case SUBSYSTEM_TURRET:
|
|
if ( psub->flags & MSS_FLAG_CREWPOINT )
|
|
n++; // fall through
|
|
|
|
case SUBSYSTEM_RADAR:
|
|
case SUBSYSTEM_NAVIGATION:
|
|
case SUBSYSTEM_COMMUNICATION:
|
|
case SUBSYSTEM_UNKNOWN:
|
|
case SUBSYSTEM_ENGINE:
|
|
case SUBSYSTEM_GAS_COLLECT:
|
|
case SUBSYSTEM_ACTIVATION:
|
|
break;
|
|
default:
|
|
Error(LOCATION, "Illegal subsystem type.\n");
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
//==========================================================
|
|
// Finds the number of turrets in a ship
|
|
int ship_find_num_turrets(object *objp)
|
|
{
|
|
int n = 0;
|
|
model_subsystem *psub;
|
|
ship *shipp;
|
|
ship_subsys *pss;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
|
|
// Go through all subsystems and record the model angles for all
|
|
// the subsystems that need it.
|
|
for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
|
|
psub = pss->system_info;
|
|
switch (psub->type) {
|
|
case SUBSYSTEM_TURRET:
|
|
n++; // drop through
|
|
|
|
case SUBSYSTEM_RADAR:
|
|
case SUBSYSTEM_NAVIGATION:
|
|
case SUBSYSTEM_COMMUNICATION:
|
|
case SUBSYSTEM_UNKNOWN:
|
|
case SUBSYSTEM_ENGINE:
|
|
case SUBSYSTEM_GAS_COLLECT:
|
|
case SUBSYSTEM_ACTIVATION:
|
|
break;
|
|
default:
|
|
Error(LOCATION, "Illegal subsystem type.\n");
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Modify the matrix orient by the slew angles a.
|
|
void compute_slew_matrix(matrix *orient, angles *a)
|
|
{
|
|
matrix tmp, tmp2;
|
|
angles t1, t2;
|
|
|
|
t1 = t2 = *a;
|
|
t1.h = 0.0f; t1.b = 0.0f;
|
|
t2.p = 0.0f; t2.b = 0.0f;
|
|
|
|
// put in p & b like normal
|
|
vm_angles_2_matrix(&tmp, &t1 );
|
|
vm_matrix_x_matrix( &tmp2, orient, &tmp);
|
|
|
|
// Put in heading separately
|
|
vm_angles_2_matrix(&tmp, &t2 );
|
|
vm_matrix_x_matrix( orient, &tmp2, &tmp );
|
|
|
|
vm_orthogonalize_matrix(orient);
|
|
}
|
|
|
|
// calculates the eye position for this ship in the global reference frame. Uses the
|
|
// view_positions array in the model. The 0th element is the noral viewing position.
|
|
// the vector of the eye is returned in the parameter 'eye'. The orientation of the
|
|
// eye is returned in orient. (NOTE: this is kind of bogus for now since non 0th element
|
|
// eyes have no defined up vector)
|
|
void ship_get_eye( vector *eye_pos, matrix *eye_orient, object *obj )
|
|
{
|
|
ship *shipp;
|
|
polymodel *pm;
|
|
eye *ep;
|
|
// vector vec;
|
|
|
|
shipp = &Ships[obj->instance];
|
|
pm = model_get( shipp->modelnum );
|
|
|
|
// check to be sure that we have a view eye to look at.....spit out nasty debug message
|
|
if ( pm->n_view_positions == 0 ) {
|
|
// nprintf (("Warning", "No eye position found for model %s. Find artist to get fixed.\n", pm->filename ));
|
|
*eye_pos = obj->pos;
|
|
*eye_orient = obj->orient;
|
|
return;
|
|
}
|
|
ep = &(pm->view_positions[0] );
|
|
|
|
// eye points are stored in an array -- the normal viewing position for a ship is the current_eye_index
|
|
// element.
|
|
model_find_world_point( eye_pos, &ep->pnt, shipp->modelnum, ep->parent, &obj->orient, &obj->pos );
|
|
// if ( shipp->current_eye_index == 0 ) {
|
|
*eye_orient = obj->orient;
|
|
//} else {
|
|
// model_find_world_dir( &vec, &ep->norm, shipp->modelnum, ep->parent, &obj->orient, &obj->pos );
|
|
// kind of bogus, but use the objects uvec to avoid totally stupid looking behavior.
|
|
// vm_vector_2_matrix(eye_orient,&vec,&obj->orient.uvec,NULL);
|
|
//}
|
|
|
|
// Modify the orientation based on head orientation.
|
|
if ( Viewer_obj == obj ) {
|
|
if ( Viewer_mode & VM_PADLOCK_ANY ) {
|
|
player_get_padlock_orient(eye_orient);
|
|
} else {
|
|
compute_slew_matrix(eye_orient, &Viewer_slew_angles);
|
|
}
|
|
}
|
|
}
|
|
|
|
// of attackers to make this decision.
|
|
//
|
|
// NOTE: This function takes into account how many ships are attacking a subsystem, and will
|
|
// prefer an ignored subsystem over a subsystem that is in line of sight, if the in-sight
|
|
// subsystem is attacked by more than MAX_SUBSYS_ATTACKERS
|
|
// input:
|
|
// sp => ship pointer to parent of subsystem
|
|
// subsys_type => what kind of subsystem this is
|
|
// attacker_pos => the world coords of the attacker of this subsystem
|
|
//
|
|
// returns: pointer to subsystem if one found, NULL otherwise
|
|
#define MAX_SUBSYS_ATTACKERS 3
|
|
ship_subsys *ship_get_best_subsys_to_attack(ship *sp, int subsys_type, vector *attacker_pos)
|
|
{
|
|
ship_subsys *ss;
|
|
ship_subsys *best_in_sight_subsys, *lowest_attacker_subsys, *ss_return;
|
|
int lowest_num_attackers, lowest_in_sight_attackers, num_attackers;
|
|
vector gsubpos;
|
|
ship_obj *sop;
|
|
|
|
lowest_in_sight_attackers = lowest_num_attackers = 1000;
|
|
ss_return = best_in_sight_subsys = lowest_attacker_subsys = NULL;
|
|
|
|
for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
|
|
if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) ) {
|
|
|
|
// get world pos of subsystem
|
|
vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &Objects[sp->objnum].orient);
|
|
vm_vec_add2(&gsubpos, &Objects[sp->objnum].pos);
|
|
|
|
// now find the number of ships attacking this subsystem by iterating through the ships list,
|
|
// and checking if aip->targeted_subsys matches the subsystem we're checking
|
|
num_attackers = 0;
|
|
sop = GET_FIRST(&Ship_obj_list);
|
|
while(sop != END_OF_LIST(&Ship_obj_list)){
|
|
if ( Ai_info[Ships[Objects[sop->objnum].instance].ai_index].targeted_subsys == ss ) {
|
|
num_attackers++;
|
|
}
|
|
sop = GET_NEXT(sop);
|
|
}
|
|
|
|
if ( num_attackers < lowest_num_attackers ) {
|
|
lowest_num_attackers = num_attackers;
|
|
lowest_attacker_subsys = ss;
|
|
}
|
|
|
|
if ( ship_subsystem_in_sight(&Objects[sp->objnum], ss, attacker_pos, &gsubpos) ) {
|
|
if ( num_attackers < lowest_in_sight_attackers ) {
|
|
lowest_in_sight_attackers = num_attackers;
|
|
best_in_sight_subsys = ss;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( best_in_sight_subsys == NULL ) {
|
|
// no subsystems are in sight, so return the subsystem with the lowest # of attackers
|
|
ss_return = lowest_attacker_subsys;
|
|
} else {
|
|
if ( lowest_in_sight_attackers > MAX_SUBSYS_ATTACKERS ) {
|
|
ss_return = lowest_attacker_subsys;
|
|
} else {
|
|
ss_return = best_in_sight_subsys;
|
|
}
|
|
}
|
|
|
|
return ss_return;
|
|
}
|
|
|
|
// function to return a pointer to the 'nth' ship_subsys structure in a ship's linked list
|
|
// of ship_subsys'.
|
|
// attacker_pos => world pos of attacker (default value NULL). If value is non-NULL, try
|
|
// to select the best subsystem to attack of that type (using line-of-sight)
|
|
// and based on the number of ships already attacking the subsystem
|
|
ship_subsys *ship_get_indexed_subsys( ship *sp, int index, vector *attacker_pos )
|
|
{
|
|
int count;
|
|
ship_subsys *ss;
|
|
|
|
// first, special code to see if the index < 0. If so, we are looking for one of several possible
|
|
// engines or one of several possible turrets. If we enter this if statement, we will always return
|
|
// something.
|
|
if ( index < 0 ) {
|
|
int subsys_type;
|
|
|
|
subsys_type = -index;
|
|
if ( sp->subsys_info[subsys_type].current_hits == 0.0f ) // if there are no hits, no subsystem to attack.
|
|
return NULL;
|
|
|
|
if ( attacker_pos != NULL ) {
|
|
ss = ship_get_best_subsys_to_attack(sp, subsys_type, attacker_pos);
|
|
return ss;
|
|
} else {
|
|
// next, scan the list of subsystems and search for the first subsystem of the particular
|
|
// type which has > 0 hits remaining.
|
|
for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
|
|
if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) )
|
|
return ss;
|
|
}
|
|
}
|
|
|
|
Int3(); // maybe we shouldn't get here, but with possible floating point rounding, I suppose we could
|
|
return NULL;
|
|
}
|
|
|
|
|
|
count = 0;
|
|
ss = GET_FIRST(&sp->subsys_list);
|
|
while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
|
|
if ( count == index )
|
|
return ss;
|
|
count++;
|
|
ss = GET_NEXT( ss );
|
|
}
|
|
Int3(); // get allender -- turret ref didn't fixup correctly!!!!
|
|
return NULL;
|
|
}
|
|
|
|
// Given a pointer to a subsystem and an associated object, return the index.
|
|
int ship_get_index_from_subsys(ship_subsys *ssp, int objnum, int error_bypass)
|
|
{
|
|
if (ssp == NULL)
|
|
return -1;
|
|
else {
|
|
int count;
|
|
ship *shipp;
|
|
ship_subsys *ss;
|
|
|
|
Assert(objnum >= 0);
|
|
Assert(Objects[objnum].instance >= 0);
|
|
|
|
shipp = &Ships[Objects[objnum].instance];
|
|
|
|
count = 0;
|
|
ss = GET_FIRST(&shipp->subsys_list);
|
|
while ( ss != END_OF_LIST( &shipp->subsys_list ) ) {
|
|
if ( ss == ssp)
|
|
return count;
|
|
count++;
|
|
ss = GET_NEXT( ss );
|
|
}
|
|
if ( !error_bypass )
|
|
Int3(); // get allender -- turret ref didn't fixup correctly!!!!
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// function which returns the index number of the ship_subsys parameter
|
|
int ship_get_subsys_index(ship *sp, char *ss_name, int error_bypass)
|
|
{
|
|
int count;
|
|
ship_subsys *ss;
|
|
|
|
count = 0;
|
|
ss = GET_FIRST(&sp->subsys_list);
|
|
while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
|
|
if ( !stricmp(ss->system_info->subobj_name, ss_name) )
|
|
return count;
|
|
count++;
|
|
ss = GET_NEXT( ss );
|
|
}
|
|
|
|
if (!error_bypass)
|
|
Int3();
|
|
|
|
return -1;
|
|
}
|
|
|
|
// routine to return the strength of a subsystem. We keep a total hit tally for all subsystems
|
|
// which are similar (i.e. a total for all engines). These routines will return a number between
|
|
// 0.0 and 1.0 which is the relative combined strength of the given subsystem type. The number
|
|
// calculated for the engines is slightly different. Once an engine reaches < 15% of it's hits, it's
|
|
// output drops to that %. A dead engine has no output.
|
|
float ship_get_subsystem_strength( ship *shipp, int type )
|
|
{
|
|
float strength;
|
|
ship_subsys *ssp;
|
|
|
|
Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
|
|
if ( shipp->subsys_info[type].total_hits == 0.0f )
|
|
return 1.0f;
|
|
|
|
// For a dying ship, all subsystem strengths are zero.
|
|
if (Objects[shipp->objnum].hull_strength <= 0.0f)
|
|
return 0.0f;
|
|
|
|
strength = shipp->subsys_info[type].current_hits / shipp->subsys_info[type].total_hits;
|
|
|
|
if ( strength == 0.0f ) // short circuit 0
|
|
return strength;
|
|
|
|
if ( (type == SUBSYSTEM_ENGINE) && (strength < 1.0f) ) {
|
|
float percent;
|
|
|
|
percent = 0.0f;
|
|
ssp = GET_FIRST(&shipp->subsys_list);
|
|
while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
|
|
|
|
if ( ssp->system_info->type == SUBSYSTEM_ENGINE ) {
|
|
float ratio;
|
|
|
|
ratio = ssp->current_hits / ssp->system_info->max_hits;
|
|
if ( ratio < ENGINE_MIN_STR )
|
|
ratio = ENGINE_MIN_STR;
|
|
|
|
percent += ratio;
|
|
}
|
|
ssp = GET_NEXT( ssp );
|
|
}
|
|
strength = percent / (float)shipp->subsys_info[type].num;
|
|
}
|
|
|
|
return strength;
|
|
}
|
|
|
|
// set the strength of a subsystem on a given ship. The strength passed as a
|
|
// parameter is between 0.0 and 1.0
|
|
//
|
|
// NOTE: this function was made to be called by the debug function dcf_set_subsys(). If
|
|
// you want to use this, be sure that you test it for all cases.
|
|
void ship_set_subsystem_strength( ship *shipp, int type, float strength )
|
|
{
|
|
float total_current_hits, diff;
|
|
ship_subsys *ssp;
|
|
|
|
Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
|
|
if ( shipp->subsys_info[type].total_hits == 0.0f )
|
|
return;
|
|
|
|
total_current_hits = 0.0f;
|
|
ssp = GET_FIRST(&shipp->subsys_list);
|
|
while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
|
|
|
|
if ( ssp->system_info->type == type ) {
|
|
ssp->current_hits = strength * ssp->system_info->max_hits;
|
|
total_current_hits += ssp->current_hits;
|
|
}
|
|
ssp = GET_NEXT( ssp );
|
|
}
|
|
|
|
// update the objects integrity, needed since we've bashed the strength of a subsysem
|
|
diff = total_current_hits - shipp->subsys_info[type].current_hits;
|
|
Objects[shipp->objnum].hull_strength += diff;
|
|
// fix up the shipp->subsys_info[type] current_hits value
|
|
shipp->subsys_info[type].current_hits = total_current_hits;
|
|
}
|
|
|
|
#define SHIELD_REPAIR_RATE 0.20f // Percent of shield repaired per second.
|
|
#define HULL_REPAIR_RATE 0.15f // Percent of hull repaired per second.
|
|
#define SUBSYS_REPAIR_RATE 0.10f // Percent of subsystems repaired per second.
|
|
|
|
// ==================================================================================
|
|
// ship_do_rearm_frame()
|
|
//
|
|
// function to rearm a ship. This function gets called from the ai code ai_do_rearm_frame (or
|
|
// some function of a similar name). Returns 1 when ship is fully repaired and rearmed, 0 otherwise
|
|
//
|
|
|
|
#define REARM_NUM_MISSILES_PER_BATCH 4 // how many missiles are dropped in per load sound
|
|
|
|
int ship_do_rearm_frame( object *objp, float frametime )
|
|
{
|
|
int i, banks_full, subsys_type, subsys_all_ok;
|
|
float shield_str, repair_delta, repair_allocated;
|
|
ship *shipp;
|
|
ship_weapon *swp;
|
|
ship_info *sip;
|
|
ship_subsys *ssp;
|
|
ai_info *aip;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
swp = &shipp->weapons;
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
aip = &Ai_info[shipp->ai_index];
|
|
|
|
// AL 10-31-97: Add missing primary weapons to the ship. This is required since designers
|
|
// want to have ships that start with no primaries, but can get them through
|
|
// rearm/repair
|
|
if ( swp->num_primary_banks < sip->num_primary_banks ) {
|
|
for ( i = swp->num_primary_banks; i < sip->num_primary_banks; i++ ) {
|
|
swp->primary_bank_weapons[i] = sip->primary_bank_weapons[i];
|
|
}
|
|
swp->num_primary_banks = sip->num_primary_banks;
|
|
}
|
|
|
|
// AL 12-30-97: Repair broken warp drive
|
|
if ( shipp->flags & SF_WARP_BROKEN ) {
|
|
// TODO: maybe do something here like informing player warp is fixed?
|
|
shipp->flags &= ~SF_WARP_BROKEN;
|
|
}
|
|
|
|
// AL 1-16-97: Replenish countermeasures
|
|
shipp->cmeasure_count = sip->cmeasure_max;
|
|
|
|
// Do shield repair here
|
|
if ( !(objp->flags & OF_NO_SHIELDS) ) {
|
|
shield_str = get_shield_strength(objp);
|
|
if ( shield_str < sip->shields ) {
|
|
if ( objp == Player_obj ) {
|
|
player_maybe_start_repair_sound();
|
|
}
|
|
shield_str += sip->shields * frametime * SHIELD_REPAIR_RATE;
|
|
if ( shield_str > sip->shields ) {
|
|
shield_str = sip->shields;
|
|
}
|
|
set_shield_strength(objp, shield_str);
|
|
}
|
|
}
|
|
|
|
// Repair the ship integrity (subsystems + hull). This works by applying the repair points
|
|
// to the subsystems. Ships integrity is stored is objp->hull_strength, so that always is
|
|
// incremented by repair_allocated
|
|
repair_allocated = sip->initial_hull_strength * frametime * HULL_REPAIR_RATE;
|
|
|
|
/*
|
|
AL 11-24-97: remove increase to hull integrity
|
|
|
|
objp->hull_strength += repair_allocated;
|
|
if ( objp->hull_strength > sip->initial_hull_strength ) {
|
|
repair_allocated -= ( sip->initial_hull_strength - objp->hull_strength);
|
|
objp->hull_strength = sip->initial_hull_strength;
|
|
}
|
|
*/
|
|
|
|
// check the subsystems of the ship.
|
|
subsys_all_ok = 1;
|
|
ssp = GET_FIRST(&shipp->subsys_list);
|
|
while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
|
|
|
|
if ( ssp->current_hits < ssp->system_info->max_hits && repair_allocated > 0 ) {
|
|
subsys_all_ok = 0;
|
|
subsys_type = ssp->system_info->type;
|
|
|
|
if ( objp == Player_obj ) {
|
|
player_maybe_start_repair_sound();
|
|
}
|
|
|
|
repair_delta = ssp->system_info->max_hits - ssp->current_hits;
|
|
if ( repair_delta > repair_allocated ) {
|
|
repair_delta = repair_allocated;
|
|
}
|
|
repair_allocated -= repair_delta;
|
|
Assert(repair_allocated >= 0.0f);
|
|
|
|
// add repair to current strength of single subsystem
|
|
ssp->current_hits += repair_delta;
|
|
if ( ssp->current_hits > ssp->system_info->max_hits ) {
|
|
ssp->current_hits = ssp->system_info->max_hits;
|
|
}
|
|
|
|
// add repair to aggregate strength of subsystems of that type
|
|
shipp->subsys_info[subsys_type].current_hits += repair_delta;
|
|
if ( shipp->subsys_info[subsys_type].current_hits > shipp->subsys_info[subsys_type].total_hits )
|
|
shipp->subsys_info[subsys_type].current_hits = shipp->subsys_info[subsys_type].total_hits;
|
|
|
|
if ( ssp->current_hits > ssp->system_info->max_hits )
|
|
ssp->current_hits = ssp->system_info->max_hits;
|
|
|
|
// check to see if this subsystem was totally non functional before -- if so, then
|
|
// reset the flags
|
|
if ( (ssp->system_info->type == SUBSYSTEM_ENGINE) && (shipp->flags & SF_DISABLED) ) {
|
|
shipp->flags &= ~SF_DISABLED;
|
|
ship_reset_disabled_physics(objp, shipp->ship_info_index);
|
|
}
|
|
break;
|
|
}
|
|
ssp = GET_NEXT( ssp );
|
|
}
|
|
|
|
// now deal with rearming the player. All secondary weapons have a certain rate at which
|
|
// they can be rearmed. We can rearm multiple banks at once.
|
|
banks_full = 0;
|
|
if ( subsys_all_ok ) {
|
|
for (i = 0; i < swp->num_secondary_banks; i++ ) {
|
|
if ( swp->secondary_bank_ammo[i] < swp->secondary_bank_start_ammo[i] ) {
|
|
float rearm_time;
|
|
|
|
if ( objp == Player_obj ) {
|
|
hud_gauge_popup_start(HUD_WEAPONS_GAUGE);
|
|
}
|
|
|
|
if ( timestamp_elapsed(swp->secondary_bank_rearm_time[i]) ) {
|
|
|
|
// Have to do some gymnastics to play the sound effects properly. There is a
|
|
// one time sound effect which is the missile loading start, then for each missile
|
|
// loaded there is a sound effect. These are only played for the player.
|
|
//
|
|
rearm_time = Weapon_info[swp->secondary_bank_weapons[i]].rearm_rate;
|
|
if ( aip->rearm_first_missile == TRUE ) {
|
|
rearm_time *= 3;
|
|
}
|
|
|
|
swp->secondary_bank_rearm_time[i] = timestamp( (int)(rearm_time * 1000.f) );
|
|
|
|
// Acutal loading of missiles is preceded by a sound effect which is the missile
|
|
// loading equipment moving into place
|
|
if ( aip->rearm_first_missile == TRUE ) {
|
|
snd_play_3d( &Snds[SND_MISSILE_START_LOAD], &objp->pos, &View_position );
|
|
aip->rearm_first_missile = FALSE;
|
|
|
|
} else {
|
|
snd_play_3d( &Snds[SND_MISSILE_LOAD], &objp->pos, &View_position );
|
|
if (objp == Player_obj)
|
|
joy_ff_play_reload_effect();
|
|
|
|
swp->secondary_bank_ammo[i] += REARM_NUM_MISSILES_PER_BATCH;
|
|
if ( swp->secondary_bank_ammo[i] > swp->secondary_bank_start_ammo[i] )
|
|
swp->secondary_bank_ammo[i] = swp->secondary_bank_start_ammo[i];
|
|
}
|
|
}
|
|
|
|
} else
|
|
banks_full++;
|
|
}
|
|
} // end if (subsys_all_ok)
|
|
|
|
if ( banks_full == swp->num_secondary_banks ) {
|
|
aip->rearm_first_missile = TRUE;
|
|
}
|
|
|
|
int shields_full = 0;
|
|
if ( (objp->flags & OF_NO_SHIELDS) ) {
|
|
shields_full = 1;
|
|
} else {
|
|
if ( get_shield_strength(objp) >= sip->shields )
|
|
shields_full = 1;
|
|
}
|
|
|
|
// return 1 if at end of subsystem list, hull damage at 0, and shields full and all secondary banks full.
|
|
// if ( ((ssp = END_OF_LIST(&shipp->subsys_list)) != NULL )&&(objp->hull_strength == sip->initial_hull_strength)&&(shields_full) ) {
|
|
if ( subsys_all_ok && shields_full ) {
|
|
|
|
if ( objp == Player_obj ) {
|
|
player_stop_repair_sound();
|
|
}
|
|
|
|
if (!aip->rearm_release_delay)
|
|
aip->rearm_release_delay = timestamp(1200);
|
|
|
|
if ( banks_full == swp->num_secondary_banks ) {
|
|
|
|
if ( timestamp_elapsed(aip->rearm_release_delay) )
|
|
return 1;
|
|
}
|
|
else {
|
|
aip->rearm_release_delay = timestamp(1200);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// function which is used to find a repair ship to repair requester_obj. the way repair ships will work
|
|
// is:
|
|
// if repair ship present and available, return pointer to that object.
|
|
// If repair ship present and busy, possibly return that object if he can satisfy the request soon enough.
|
|
// If repair ship present and busy and cannot satisfy request, return NULL to warp a new one in if below max number
|
|
// if no repair ship present, return NULL to force a new one to be warped in.
|
|
#define MAX_SUPPORT_SHIPS_PER_TEAM 1
|
|
|
|
object *ship_find_repair_ship( object *requester_obj )
|
|
{
|
|
object *objp;
|
|
ship *requester_ship;
|
|
int num_support_ships, num_available_support_ships;
|
|
float min_dist = 99999.0f;
|
|
object *nearest_support_ship = NULL;
|
|
int support_ships[MAX_SUPPORT_SHIPS_PER_TEAM];
|
|
|
|
Assert(requester_obj->type == OBJ_SHIP);
|
|
Assert((requester_obj->instance >= 0) && (requester_obj->instance < MAX_OBJECTS));
|
|
|
|
// if support ships are not allowed, then no support ship can repair!
|
|
if ( !is_support_allowed(requester_obj) )
|
|
return NULL;
|
|
|
|
num_support_ships = 0;
|
|
num_available_support_ships = 0;
|
|
|
|
requester_ship = &Ships[requester_obj->instance];
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ((objp->type == OBJ_SHIP) && !(objp->flags & OF_SHOULD_BE_DEAD)) {
|
|
ship *shipp;
|
|
ship_info *sip;
|
|
float dist;
|
|
|
|
Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
|
|
|
|
shipp = &Ships[objp->instance];
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
if ( shipp->team != requester_ship->team )
|
|
continue;
|
|
|
|
if ( !(sip->flags & SIF_SUPPORT) )
|
|
continue;
|
|
|
|
// don't deal with dying support ships
|
|
if ( shipp->flags & (SF_DYING | SF_DEPARTING) )
|
|
continue;
|
|
|
|
dist = vm_vec_dist_quick(&objp->pos, &requester_obj->pos);
|
|
support_ships[num_support_ships] = objp-Objects;
|
|
|
|
if (!(Ai_info[shipp->ai_index].ai_flags & AIF_REPAIRING)) {
|
|
num_available_support_ships++;
|
|
if (dist < min_dist) {
|
|
min_dist = dist;
|
|
nearest_support_ship = objp;
|
|
}
|
|
}
|
|
|
|
if ( num_support_ships >= MAX_SUPPORT_SHIPS_PER_TEAM ) {
|
|
mprintf(("Why is there more than %d support ships in this mission?\n",MAX_SUPPORT_SHIPS_PER_TEAM));
|
|
break;
|
|
} else {
|
|
support_ships[num_support_ships] = OBJ_INDEX(objp);
|
|
num_support_ships++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nearest_support_ship != NULL)
|
|
return nearest_support_ship;
|
|
else if (num_support_ships >= MAX_SUPPORT_SHIPS_PER_TEAM) {
|
|
Assert(&Objects[support_ships[0]] != NULL);
|
|
return &Objects[support_ships[0]];
|
|
} else {
|
|
Assert(num_support_ships < MAX_SUPPORT_SHIPS_PER_TEAM);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// ship_close()
|
|
//
|
|
// called in game_shutdown() to free malloced memory
|
|
//
|
|
// NOTE: do not call this function. It is only called from game_shutdown()
|
|
void ship_close()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<MAX_SHIPS; i++ ) {
|
|
if ( Ships[i].shield_integrity != NULL && Ships[i].objnum != -1 ) {
|
|
free( Ships[i].shield_integrity );
|
|
Ships[i].shield_integrity = NULL;
|
|
}
|
|
}
|
|
|
|
// free memory alloced for subsystem storage
|
|
for ( i = 0; i < Num_ship_types; i++ ) {
|
|
if ( Ship_info[i].subsystems != NULL ) {
|
|
free(Ship_info[i].subsystems);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// ship_assign_sound()
|
|
//
|
|
// Assign object-linked sound to a particular ship
|
|
//
|
|
void ship_assign_sound(ship *sp)
|
|
{
|
|
ship_info *sip;
|
|
object *objp;
|
|
vector engine_pos;
|
|
ship_subsys *moveup;
|
|
|
|
Assert( sp->objnum >= 0 );
|
|
if(sp->objnum < 0){
|
|
return;
|
|
}
|
|
objp = &Objects[sp->objnum];
|
|
sip = &Ship_info[sp->ship_info_index];
|
|
|
|
if ( sip->engine_snd != -1 ) {
|
|
vm_vec_copy_scale(&engine_pos, &objp->orient.vec.fvec, -objp->radius/2.0f);
|
|
|
|
#ifndef NO_SOUND
|
|
obj_snd_assign(sp->objnum, sip->engine_snd, &engine_pos, 1);
|
|
#endif
|
|
}
|
|
|
|
// if he's got any specific engine subsystems. go for it.
|
|
moveup = GET_FIRST(&sp->subsys_list);
|
|
while(moveup != END_OF_LIST(&sp->subsys_list)){
|
|
// check the name of the subsystem
|
|
#ifndef NO_SOUND
|
|
if(strstr(moveup->system_info->name, "enginelarge")){
|
|
obj_snd_assign(sp->objnum, SND_ENGINE_LOOP_LARGE, &moveup->system_info->pnt, 0);
|
|
} else if(strstr(moveup->system_info->name, "enginehuge")){
|
|
obj_snd_assign(sp->objnum, SND_ENGINE_LOOP_HUGE, &moveup->system_info->pnt, 0);
|
|
}
|
|
#endif
|
|
|
|
// next
|
|
moveup = GET_NEXT(moveup);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// ship_assign_sound_all()
|
|
//
|
|
// Assign object-linked sounds to all ships currently in the obj_used_list
|
|
//
|
|
void ship_assign_sound_all()
|
|
{
|
|
object *objp;
|
|
int idx, has_sounds;
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ( objp->type == OBJ_SHIP && Player_obj != objp) {
|
|
has_sounds = 0;
|
|
|
|
// check to make sure this guy hasn't got sounds already assigned to him
|
|
for(idx=0; idx<MAX_OBJECT_SOUNDS; idx++){
|
|
if(objp->objsnd_num[idx] != -1){
|
|
// skip
|
|
has_sounds = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// actually assign the sound
|
|
if(!has_sounds){
|
|
ship_assign_sound(&Ships[objp->instance]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// dcf_set_shield()
|
|
//
|
|
// Debug console function to set the shield for the player ship
|
|
//
|
|
DCF(set_shield,"Change player ship shield strength")
|
|
{
|
|
ship_info *sip;
|
|
|
|
sip = &Ship_info[Ships[Player_obj->instance].ship_info_index];
|
|
if ( Dc_command ) {
|
|
dc_get_arg(ARG_FLOAT|ARG_NONE);
|
|
|
|
if ( Dc_arg_type & ARG_FLOAT ) {
|
|
if ( Dc_arg_float < 0 )
|
|
Dc_arg_float = 0.0f;
|
|
if ( Dc_arg_float > 1.0 )
|
|
Dc_arg_float = 1.0f;
|
|
set_shield_strength(Player_obj, Dc_arg_float * sip->shields);
|
|
dc_printf("Shields set to %.2f\n", get_shield_strength(Player_obj) );
|
|
}
|
|
}
|
|
|
|
if ( Dc_help ) {
|
|
dc_printf ("Usage: set_shield [num]\n");
|
|
dc_printf ("[num] -- shield percentage 0.0 -> 1.0 of max\n");
|
|
dc_printf ("with no parameters, displays shield strength\n");
|
|
Dc_status = 0;
|
|
}
|
|
|
|
if ( Dc_status ) {
|
|
dc_printf( "Shields are currently %.2f", get_shield_strength(Player_obj) );
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// dcf_set_hull()
|
|
//
|
|
// Debug console function to set the hull for the player ship
|
|
//
|
|
DCF(set_hull, "Change player ship hull strength")
|
|
{
|
|
ship_info *sip;
|
|
|
|
sip = &Ship_info[Ships[Player_obj->instance].ship_info_index];
|
|
if ( Dc_command ) {
|
|
dc_get_arg(ARG_FLOAT|ARG_NONE);
|
|
|
|
if ( Dc_arg_type & ARG_FLOAT ) {
|
|
if ( Dc_arg_float < 0 )
|
|
Dc_arg_float = 0.0f;
|
|
if ( Dc_arg_float > 1.0 )
|
|
Dc_arg_float = 1.0f;
|
|
Player_obj->hull_strength = Dc_arg_float * sip->initial_hull_strength;
|
|
dc_printf("Hull set to %.2f\n", Player_obj->hull_strength );
|
|
}
|
|
}
|
|
|
|
if ( Dc_help ) {
|
|
dc_printf ("Usage: set_hull [num]\n");
|
|
dc_printf ("[num] -- hull percentage 0.0 -> 1.0 of max\n");
|
|
dc_printf ("with no parameters, displays hull strength\n");
|
|
Dc_status = 0;
|
|
}
|
|
|
|
if ( Dc_status ) {
|
|
dc_printf( "Hull is currently %.2f", Player_obj->hull_strength );
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// dcf_set_subsys()
|
|
//
|
|
// Debug console function to set the strength of a particular subsystem
|
|
//
|
|
//XSTR:OFF
|
|
DCF(set_subsys, "Set the strength of a particular subsystem on player ship" )
|
|
{
|
|
if ( Dc_command ) {
|
|
dc_get_arg(ARG_STRING);
|
|
if ( !stricmp( Dc_arg, "weapons" )) {
|
|
dc_get_arg(ARG_FLOAT);
|
|
if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
|
|
Dc_help = 1;
|
|
} else {
|
|
ship_set_subsystem_strength( Player_ship, SUBSYSTEM_WEAPONS, Dc_arg_float );
|
|
}
|
|
} else if ( !stricmp( Dc_arg, "engine" )) {
|
|
dc_get_arg(ARG_FLOAT);
|
|
if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
|
|
Dc_help = 1;
|
|
} else {
|
|
ship_set_subsystem_strength( Player_ship, SUBSYSTEM_ENGINE, Dc_arg_float );
|
|
if ( Dc_arg_float < ENGINE_MIN_STR ) {
|
|
Player_ship->flags |= SF_DISABLED; // add the disabled flag
|
|
} else {
|
|
Player_ship->flags &= (~SF_DISABLED); // add the disabled flag
|
|
}
|
|
}
|
|
} else if ( !stricmp( Dc_arg, "sensors" )) {
|
|
dc_get_arg(ARG_FLOAT);
|
|
if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
|
|
Dc_help = 1;
|
|
} else {
|
|
ship_set_subsystem_strength( Player_ship, SUBSYSTEM_SENSORS, Dc_arg_float );
|
|
}
|
|
} else if ( !stricmp( Dc_arg, "communication" )) {
|
|
dc_get_arg(ARG_FLOAT);
|
|
if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
|
|
Dc_help = 1;
|
|
} else {
|
|
ship_set_subsystem_strength( Player_ship, SUBSYSTEM_COMMUNICATION, Dc_arg_float );
|
|
}
|
|
} else if ( !stricmp( Dc_arg, "navigation" )) {
|
|
dc_get_arg(ARG_FLOAT);
|
|
if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
|
|
Dc_help = 1;
|
|
} else {
|
|
ship_set_subsystem_strength( Player_ship, SUBSYSTEM_NAVIGATION, Dc_arg_float );
|
|
}
|
|
} else if ( !stricmp( Dc_arg, "radar" )) {
|
|
dc_get_arg(ARG_FLOAT);
|
|
if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
|
|
Dc_help = 1;
|
|
} else {
|
|
ship_set_subsystem_strength( Player_ship, SUBSYSTEM_RADAR, Dc_arg_float );
|
|
}
|
|
} else {
|
|
// print usage
|
|
Dc_help = 1;
|
|
}
|
|
}
|
|
|
|
if ( Dc_help ) {
|
|
dc_printf( "Usage: set_subsys type X\nWhere X is value between 0 and 1.0, and type can be:\n" );
|
|
dc_printf( "weapons\n" );
|
|
dc_printf( "engine\n" );
|
|
dc_printf( "sensors\n" );
|
|
dc_printf( "communication\n" );
|
|
dc_printf( "navigation\n" );
|
|
dc_printf( "radar\n" );
|
|
Dc_status = 0; // don't print status if help is printed. Too messy.
|
|
}
|
|
}
|
|
//XSTR:ON
|
|
|
|
// console function to toggle whether auto-repair for subsystems is active
|
|
#ifndef NDEBUG
|
|
DCF_BOOL( auto_repair, Ship_auto_repair );
|
|
#endif
|
|
|
|
// two functions to keep track of counting ships of particular types. Maybe we should be rolling this
|
|
// thing into the stats section?? The first function adds a ship of a particular type to the overall
|
|
// count of ships of that type (called from MissionParse.cpp). The second function adds to the kill total
|
|
// of ships of a particular type. Note that we use the ship_info flags structure member to determine
|
|
// what is happening.
|
|
void ship_add_ship_type_count( int ship_info_flag, int num )
|
|
{
|
|
if ( ship_info_flag & SIF_CARGO )
|
|
Ship_counts[SHIP_TYPE_CARGO].total += num;
|
|
else if ( (ship_info_flag & SIF_FIGHTER) || (ship_info_flag & SIF_BOMBER) )
|
|
Ship_counts[SHIP_TYPE_FIGHTER_BOMBER].total += num;
|
|
else if ( ship_info_flag & SIF_CRUISER )
|
|
Ship_counts[SHIP_TYPE_CRUISER].total += num;
|
|
else if ( ship_info_flag & SIF_CORVETTE )
|
|
Ship_counts[SHIP_TYPE_CORVETTE].total += num;
|
|
else if ( ship_info_flag & SIF_GAS_MINER )
|
|
Ship_counts[SHIP_TYPE_GAS_MINER].total += num;
|
|
else if ( ship_info_flag & SIF_AWACS )
|
|
Ship_counts[SHIP_TYPE_AWACS].total += num;
|
|
else if ( ship_info_flag & SIF_FREIGHTER )
|
|
Ship_counts[SHIP_TYPE_FREIGHTER].total += num;
|
|
else if ( ship_info_flag & SIF_CAPITAL )
|
|
Ship_counts[SHIP_TYPE_CAPITAL].total += num;
|
|
else if ( ship_info_flag & SIF_TRANSPORT )
|
|
Ship_counts[SHIP_TYPE_TRANSPORT].total += num;
|
|
else if ( ship_info_flag & SIF_SUPPORT )
|
|
Ship_counts[SHIP_TYPE_REPAIR_REARM].total += num;
|
|
else if ( ship_info_flag & SIF_NO_SHIP_TYPE )
|
|
Ship_counts[SHIP_TYPE_NONE].total += num;
|
|
else if ( ship_info_flag & SIF_NAVBUOY ) {
|
|
Ship_counts[SHIP_TYPE_NAVBUOY].total += num;
|
|
} else if ( ship_info_flag & SIF_SENTRYGUN ) {
|
|
Ship_counts[SHIP_TYPE_SENTRYGUN].total += num;
|
|
} else if ( ship_info_flag & SIF_ESCAPEPOD ) {
|
|
Ship_counts[SHIP_TYPE_ESCAPEPOD].total += num;
|
|
} else if ( ship_info_flag & SIF_SUPERCAP ) {
|
|
Ship_counts[SHIP_TYPE_SUPERCAP].total += num;
|
|
} else if ( ship_info_flag & SIF_DRYDOCK ) {
|
|
Ship_counts[SHIP_TYPE_DRYDOCK].total += num;
|
|
} else if ( ship_info_flag & SIF_KNOSSOS_DEVICE){
|
|
Ship_counts[SHIP_TYPE_KNOSSOS_DEVICE].total += num;
|
|
}
|
|
else
|
|
Int3(); //get allender -- unknown ship type
|
|
}
|
|
|
|
void ship_add_ship_type_kill_count( int ship_info_flag )
|
|
{
|
|
if ( ship_info_flag & SIF_CARGO )
|
|
Ship_counts[SHIP_TYPE_CARGO].killed++;
|
|
else if ( (ship_info_flag & SIF_FIGHTER) || (ship_info_flag & SIF_BOMBER) )
|
|
Ship_counts[SHIP_TYPE_FIGHTER_BOMBER].killed++;
|
|
else if ( ship_info_flag & SIF_CRUISER )
|
|
Ship_counts[SHIP_TYPE_CRUISER].killed++;
|
|
else if ( ship_info_flag & SIF_CORVETTE )
|
|
Ship_counts[SHIP_TYPE_CORVETTE].killed++;
|
|
else if ( ship_info_flag & SIF_AWACS )
|
|
Ship_counts[SHIP_TYPE_AWACS].killed++;
|
|
else if ( ship_info_flag & SIF_GAS_MINER )
|
|
Ship_counts[SHIP_TYPE_GAS_MINER].killed++;
|
|
else if ( ship_info_flag & SIF_FREIGHTER )
|
|
Ship_counts[SHIP_TYPE_FREIGHTER].killed++;
|
|
else if ( ship_info_flag & SIF_CAPITAL )
|
|
Ship_counts[SHIP_TYPE_CAPITAL].killed++;
|
|
else if ( ship_info_flag & SIF_TRANSPORT )
|
|
Ship_counts[SHIP_TYPE_TRANSPORT].killed++;
|
|
else if ( ship_info_flag & SIF_SUPPORT )
|
|
Ship_counts[SHIP_TYPE_REPAIR_REARM].killed++;
|
|
else if ( ship_info_flag & SIF_SENTRYGUN )
|
|
Ship_counts[SHIP_TYPE_SENTRYGUN].killed++;
|
|
else if ( ship_info_flag & SIF_ESCAPEPOD )
|
|
Ship_counts[SHIP_TYPE_ESCAPEPOD].killed++;
|
|
else if ( ship_info_flag & SIF_NO_SHIP_TYPE )
|
|
Ship_counts[SHIP_TYPE_NONE].killed++;
|
|
else if ( ship_info_flag & SIF_SUPERCAP )
|
|
Ship_counts[SHIP_TYPE_SUPERCAP].killed++;
|
|
else if ( ship_info_flag & SIF_DRYDOCK )
|
|
Ship_counts[SHIP_TYPE_DRYDOCK].killed++;
|
|
else if ( ship_info_flag & SIF_KNOSSOS_DEVICE )
|
|
Ship_counts[SHIP_TYPE_KNOSSOS_DEVICE].killed++;
|
|
else
|
|
Int3(); //get allender -- unknown ship type
|
|
}
|
|
|
|
int ship_query_general_type(int ship)
|
|
{
|
|
return ship_query_general_type(&Ships[ship]);
|
|
}
|
|
|
|
int ship_query_general_type(ship *shipp)
|
|
{
|
|
int flags;
|
|
|
|
flags = Ship_info[shipp->ship_info_index].flags;
|
|
switch (flags & SIF_ALL_SHIP_TYPES) {
|
|
case SIF_CARGO:
|
|
return SHIP_TYPE_CARGO;
|
|
|
|
case SIF_FIGHTER:
|
|
case SIF_BOMBER:
|
|
return SHIP_TYPE_FIGHTER_BOMBER;
|
|
|
|
case SIF_CRUISER:
|
|
return SHIP_TYPE_CRUISER;
|
|
|
|
case SIF_FREIGHTER:
|
|
return SHIP_TYPE_FREIGHTER;
|
|
|
|
case SIF_CAPITAL:
|
|
return SHIP_TYPE_CAPITAL;
|
|
|
|
case SIF_TRANSPORT:
|
|
return SHIP_TYPE_TRANSPORT;
|
|
|
|
case SIF_NO_SHIP_TYPE:
|
|
return SHIP_TYPE_NONE;
|
|
|
|
case SIF_SUPPORT:
|
|
return SHIP_TYPE_REPAIR_REARM;
|
|
|
|
case SIF_NAVBUOY:
|
|
return SHIP_TYPE_NAVBUOY;
|
|
|
|
case SIF_SENTRYGUN:
|
|
return SHIP_TYPE_SENTRYGUN;
|
|
|
|
case SIF_ESCAPEPOD:
|
|
return SHIP_TYPE_ESCAPEPOD;
|
|
|
|
case SIF_SUPERCAP:
|
|
return SHIP_TYPE_SUPERCAP;
|
|
|
|
case SIF_DRYDOCK:
|
|
return SHIP_TYPE_DRYDOCK;
|
|
|
|
case SIF_CORVETTE:
|
|
return SHIP_TYPE_CORVETTE;
|
|
|
|
case SIF_AWACS:
|
|
return SHIP_TYPE_AWACS;
|
|
|
|
case SIF_GAS_MINER:
|
|
return SHIP_TYPE_GAS_MINER;
|
|
|
|
case SIF_KNOSSOS_DEVICE:
|
|
return SHIP_TYPE_KNOSSOS_DEVICE;
|
|
}
|
|
|
|
Error(LOCATION, "Ship type flag is unknown. Flags value is 0x%x", flags);
|
|
return SHIP_TYPE_NONE;
|
|
}
|
|
|
|
// returns true if the docker can (is allowed) to dock with dockee
|
|
int ship_docking_valid(int docker, int dockee)
|
|
{
|
|
int docker_type, dockee_type;
|
|
|
|
Assert(docker >= 0 && docker < MAX_SHIPS);
|
|
Assert(dockee >= 0 && dockee < MAX_SHIPS);
|
|
docker_type = ship_query_general_type(docker);
|
|
dockee_type = ship_query_general_type(dockee);
|
|
|
|
// escape pods can dock with transports, freighters, cruisers.
|
|
if ( docker_type == SHIP_TYPE_ESCAPEPOD ) {
|
|
if ( (dockee_type == SHIP_TYPE_TRANSPORT) || (dockee_type == SHIP_TYPE_CRUISER) || (dockee_type == SHIP_TYPE_FREIGHTER) || (dockee_type == SHIP_TYPE_DRYDOCK) || (dockee_type == SHIP_TYPE_CORVETTE) || (dockee_type == SHIP_TYPE_GAS_MINER) || (dockee_type == SHIP_TYPE_AWACS)){
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// docket == freighter
|
|
if (docker_type == SHIP_TYPE_FREIGHTER) {
|
|
if ( (dockee_type == SHIP_TYPE_CARGO) || (dockee_type == SHIP_TYPE_CRUISER) || (dockee_type == SHIP_TYPE_CAPITAL) || (dockee_type == SHIP_TYPE_SUPERCAP) || (dockee_type == SHIP_TYPE_DRYDOCK) || (dockee_type == SHIP_TYPE_CORVETTE) || (dockee_type == SHIP_TYPE_GAS_MINER) || (dockee_type == SHIP_TYPE_AWACS)){
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// docker == cruiser
|
|
if ( (docker_type == SHIP_TYPE_CRUISER) || (docker_type == SHIP_TYPE_CORVETTE) || (docker_type == SHIP_TYPE_GAS_MINER) || (docker_type == SHIP_TYPE_AWACS)){
|
|
if ( (dockee_type == SHIP_TYPE_CARGO) || (dockee_type == SHIP_TYPE_CRUISER) || (dockee_type == SHIP_TYPE_CAPITAL) || (dockee_type == SHIP_TYPE_SUPERCAP) || (dockee_type == SHIP_TYPE_DRYDOCK) || (dockee_type == SHIP_TYPE_CORVETTE) || (dockee_type == SHIP_TYPE_GAS_MINER) || (dockee_type == SHIP_TYPE_AWACS)){
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (docker_type == SHIP_TYPE_TRANSPORT) {
|
|
if ( (dockee_type == SHIP_TYPE_CARGO) || (dockee_type == SHIP_TYPE_CRUISER)
|
|
|| (dockee_type == SHIP_TYPE_FREIGHTER) || (dockee_type == SHIP_TYPE_TRANSPORT)
|
|
|| (dockee_type == SHIP_TYPE_CAPITAL) || (dockee_type == SHIP_TYPE_ESCAPEPOD)
|
|
|| (dockee_type == SHIP_TYPE_SUPERCAP) || (dockee_type == SHIP_TYPE_DRYDOCK) || (dockee_type == SHIP_TYPE_CORVETTE) || (dockee_type == SHIP_TYPE_GAS_MINER) || (dockee_type == SHIP_TYPE_AWACS)){
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (docker_type == SHIP_TYPE_REPAIR_REARM) {
|
|
if ((dockee_type == SHIP_TYPE_FIGHTER_BOMBER) || (dockee_type == SHIP_TYPE_STEALTH)){
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// function to return a random ship in a starting player wing. Returns -1 if a suitable
|
|
// one cannot be found
|
|
// input: max_dist => OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
|
|
// input: persona => OPTIONAL PARAMETER (default to -1) which persona to get
|
|
int ship_get_random_player_wing_ship( int flags, float max_dist, int persona_index, int get_first, int multi_team )
|
|
{
|
|
int i, j, ship_index, count;
|
|
int slist[MAX_SHIPS_PER_WING * MAX_STARTING_WINGS], which_one;
|
|
|
|
// iterate through starting wings of player. Add ship indices of ships which meet
|
|
// given criteria
|
|
count = 0;
|
|
for (i = 0; i < num_wings; i++ ) {
|
|
int wingnum;
|
|
|
|
wingnum = -1;
|
|
|
|
// multi-team?
|
|
if(multi_team >= 0){
|
|
if(!stricmp(Wings[i].name, multi_team == 0 ? "alpha" : "zeta")){
|
|
wingnum = i;
|
|
} else {
|
|
continue;
|
|
}
|
|
} else {
|
|
// first check for a player starting wing (alpha, beta, gamma)
|
|
for ( j = 0; j < MAX_PLAYER_WINGS; j++ ) {
|
|
if ( i == Starting_wings[j] ) {
|
|
wingnum = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if not found, the delta and epsilon count too
|
|
if ( wingnum == -1 ) {
|
|
if ( !stricmp(Wings[i].name, NOX("delta")) || !stricmp(Wings[i].name, NOX("epsilon")) ) {
|
|
wingnum = i;
|
|
}
|
|
}
|
|
|
|
if ( wingnum == -1 ){
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for ( j = 0; j < Wings[wingnum].current_count; j++ ) {
|
|
ship_index = Wings[wingnum].ship_index[j];
|
|
Assert( ship_index != -1 );
|
|
|
|
if ( Ships[ship_index].flags & SF_DYING ) {
|
|
continue;
|
|
}
|
|
|
|
// see if ship meets our criterea
|
|
if ( (flags == SHIP_GET_NO_PLAYERS) && (Objects[Ships[ship_index].objnum].flags & OF_PLAYER_SHIP) ){
|
|
continue;
|
|
}
|
|
|
|
// don't process ships on a different team
|
|
if(multi_team < 0){
|
|
if ( Player_ship->team != Ships[ship_index].team ){
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// see if ship is within max_dist units
|
|
if ( (max_dist > 0) && (multi_team < 0) ) {
|
|
float dist;
|
|
dist = vm_vec_dist_quick(&Objects[Ships[ship_index].objnum].pos, &Player_obj->pos);
|
|
if ( dist > max_dist ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// if we should be checking persona's, then don't add ships that don't have the proper persona
|
|
if ( persona_index != -1 ) {
|
|
if ( Ships[ship_index].persona_index != persona_index ){
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// return the first ship with correct persona
|
|
if (get_first) {
|
|
return ship_index;
|
|
}
|
|
|
|
slist[count] = ship_index;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if ( count == 0 ){
|
|
return -1;
|
|
}
|
|
|
|
// now get a random one from the list
|
|
which_one = (rand() % count);
|
|
ship_index = slist[which_one];
|
|
|
|
Assert ( Ships[ship_index].objnum != -1 );
|
|
|
|
return ship_index;
|
|
}
|
|
|
|
// like above function, but returns a random ship in the given wing -- no restrictions
|
|
// input: max_dist => OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
|
|
int ship_get_random_ship_in_wing(int wingnum, int flags, float max_dist, int get_first)
|
|
{
|
|
int i, ship_index, slist[MAX_SHIPS_PER_WING], count, which_one;
|
|
|
|
count = 0;
|
|
for ( i = 0; i < Wings[wingnum].current_count; i++ ) {
|
|
ship_index = Wings[wingnum].ship_index[i];
|
|
Assert( ship_index != -1 );
|
|
|
|
if ( Ships[ship_index].flags & SF_DYING ) {
|
|
continue;
|
|
}
|
|
|
|
// see if ship meets our criterea
|
|
if ( (flags == SHIP_GET_NO_PLAYERS) && (Objects[Ships[ship_index].objnum].flags & OF_PLAYER_SHIP) )
|
|
continue;
|
|
|
|
// see if ship is within max_dist units
|
|
if ( max_dist > 0 ) {
|
|
float dist;
|
|
dist = vm_vec_dist_quick(&Objects[Ships[ship_index].objnum].pos, &Player_obj->pos);
|
|
if ( dist > max_dist ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// return the first ship in wing
|
|
if (get_first) {
|
|
return ship_index;
|
|
}
|
|
|
|
slist[count] = ship_index;
|
|
count++;
|
|
}
|
|
|
|
if ( count == 0 ) {
|
|
return -1;
|
|
}
|
|
|
|
// now get a random one from the list
|
|
which_one = (rand() % count);
|
|
ship_index = slist[which_one];
|
|
|
|
Assert ( Ships[ship_index].objnum != -1 );
|
|
|
|
return ship_index;
|
|
}
|
|
|
|
|
|
// this function returns a random index into the Ship array of a ship of the given team
|
|
// cargo containers are not counted as ships for the purposes of this function. Why???
|
|
// because now it is only used for getting a random ship for a message and cargo containers
|
|
// can't send mesages. This function is an example of kind of bad coding :-(
|
|
// input: max_dist => OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
|
|
int ship_get_random_team_ship( int team, int flags, float max_dist )
|
|
{
|
|
int num, which_one;
|
|
object *objp, *obj_list[MAX_SHIPS];
|
|
|
|
// for any allied, go through the ships list and find all of the ships on that team
|
|
num = 0;
|
|
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ( objp->type != OBJ_SHIP )
|
|
continue;
|
|
|
|
// series of conditionals one per line for easy reading
|
|
// don't process ships on wrong team
|
|
// don't process cargo's or navbuoys
|
|
// don't process player ships if flags are set
|
|
if ( Ships[objp->instance].team != team )
|
|
continue;
|
|
else if ( Ship_info[Ships[objp->instance].ship_info_index].flags & SIF_NOT_FLYABLE )
|
|
continue;
|
|
else if ( (flags == SHIP_GET_NO_PLAYERS) && (objp->flags & OF_PLAYER_SHIP) )
|
|
continue;
|
|
else if ( (flags == SHIP_GET_ONLY_PLAYERS) && !(objp->flags & OF_PLAYER_SHIP) )
|
|
continue;
|
|
|
|
if ( Ships[objp->instance].flags & SF_DYING ) {
|
|
continue;
|
|
}
|
|
|
|
// see if ship is within max_dist units
|
|
if ( max_dist > 0 ) {
|
|
float dist;
|
|
dist = vm_vec_dist_quick(&objp->pos, &Player_obj->pos);
|
|
if ( dist > max_dist ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
obj_list[num] = objp;
|
|
num++;
|
|
}
|
|
|
|
if ( num == 0 )
|
|
return -1;
|
|
|
|
which_one = (rand() % num);
|
|
objp = obj_list[which_one];
|
|
|
|
Assert ( objp->instance != -1 );
|
|
|
|
return objp->instance;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// ship_secondary_bank_has_ammo()
|
|
//
|
|
// check if currently selected secondary bank has ammo
|
|
//
|
|
// input: shipnum => index into Ships[] array for ship to check
|
|
//
|
|
int ship_secondary_bank_has_ammo(int shipnum)
|
|
{
|
|
ship_weapon *swp;
|
|
|
|
Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
|
|
swp = &Ships[shipnum].weapons;
|
|
|
|
if ( swp->current_secondary_bank == -1 )
|
|
return 0;
|
|
|
|
Assert(swp->current_secondary_bank >= 0 && swp->current_secondary_bank < MAX_SECONDARY_BANKS );
|
|
if ( swp->secondary_bank_ammo[swp->current_secondary_bank] <= 0 )
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// see if there is enough engine power to allow the ship to warp
|
|
// returns 1 if ship is able to warp, otherwise return 0
|
|
int ship_can_warp(ship *sp)
|
|
{
|
|
float engine_str;
|
|
|
|
engine_str = ship_get_subsystem_strength( sp, SUBSYSTEM_ENGINE );
|
|
// Note that ship can always warp at lowest skill level
|
|
if ( (Game_skill_level > 0) && (engine_str >= SHIP_MIN_ENGINES_TO_WARP) ){
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
// Calculate the normal vector from a subsystem position and it's first path point
|
|
// input: sp => pointer to ship that is parent of subsystem
|
|
// ss => pointer to subsystem of interest
|
|
// norm => output parameter... vector from subsys to first path point
|
|
//
|
|
// exit: 0 => a valid vector was placed in norm
|
|
// !0 => an path normal could not be calculated
|
|
//
|
|
int ship_return_subsys_path_normal(ship *sp, ship_subsys *ss, vector *gsubpos, vector *norm)
|
|
{
|
|
if ( ss->system_info->path_num >= 0 ) {
|
|
polymodel *pm;
|
|
model_path *mp;
|
|
vector *path_point;
|
|
vector gpath_point;
|
|
pm = model_get(sp->modelnum);
|
|
mp = &pm->paths[ss->system_info->path_num];
|
|
if ( mp->nverts >= 2 ) {
|
|
// path_point = &mp->verts[mp->nverts-1].pos;
|
|
path_point = &mp->verts[0].pos;
|
|
// get path point in world coords
|
|
vm_vec_unrotate(&gpath_point, path_point, &Objects[sp->objnum].orient);
|
|
vm_vec_add2(&gpath_point, &Objects[sp->objnum].pos);
|
|
// get unit vector pointing from subsys pos to first path point
|
|
vm_vec_normalized_dir(norm, &gpath_point, gsubpos);
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Determine if the subsystem can be viewed from eye_pos. The method is to check where the
|
|
// vector from eye_pos to the subsystem hits the ship. If distance from the hit position and
|
|
// the center of the subsystem is within a range (currently the subsystem radius) it is considered
|
|
// in view (return true). If not in view, return false.
|
|
//
|
|
// input: objp => object that is the ship with the subsystem on it
|
|
// subsys => pointer to the subsystem of interest
|
|
// eye_pos => world coord for the eye looking at the subsystem
|
|
// subsys_pos => world coord for the center of the subsystem of interest
|
|
// do_facing_check => OPTIONAL PARAMETER (default value is 1), do a dot product check to see if subsystem fvec is facing
|
|
// towards the eye position
|
|
// dot_out => OPTIONAL PARAMETER, output parameter, will return dot between subsys fvec and subsys_to_eye_vec
|
|
// (only filled in if do_facing_check is true)
|
|
// vec_out => OPTIONAL PARAMETER, vector from eye_pos to absolute subsys_pos. (only filled in if do_facing_check is true)
|
|
int ship_subsystem_in_sight(object* objp, ship_subsys* subsys, vector *eye_pos, vector* subsys_pos, int do_facing_check, float *dot_out, vector *vec_out)
|
|
{
|
|
float dist, dot;
|
|
mc_info mc;
|
|
vector terminus, eye_to_pos, subsys_fvec, subsys_to_eye_vec;
|
|
|
|
if (objp->type != OBJ_SHIP)
|
|
return 0;
|
|
|
|
// See if we are at least facing the subsystem
|
|
if ( do_facing_check ) {
|
|
if ( ship_return_subsys_path_normal(&Ships[objp->instance], subsys, subsys_pos, &subsys_fvec) ) {
|
|
// non-zero return value means that we couldn't generate a normal from path info... so use inaccurate method
|
|
vm_vec_normalized_dir(&subsys_fvec, subsys_pos, &objp->pos);
|
|
}
|
|
|
|
vm_vec_normalized_dir(&subsys_to_eye_vec, eye_pos, subsys_pos);
|
|
dot = vm_vec_dot(&subsys_fvec, &subsys_to_eye_vec);
|
|
if ( dot_out ) {
|
|
*dot_out = dot;
|
|
}
|
|
|
|
if (vec_out) {
|
|
*vec_out = subsys_to_eye_vec;
|
|
vm_vec_negate(vec_out);
|
|
}
|
|
|
|
if ( dot < 0 )
|
|
return 0;
|
|
}
|
|
|
|
// See if ray from eye to subsystem actually hits close enough to the subsystem position
|
|
vm_vec_normalized_dir(&eye_to_pos, subsys_pos, eye_pos);
|
|
vm_vec_scale_add(&terminus, eye_pos, &eye_to_pos, 100000.0f);
|
|
|
|
ship_model_start(objp);
|
|
|
|
mc.model_num = Ships[objp->instance].modelnum; // Fill in the model to check
|
|
mc.orient = &objp->orient; // The object's orientation
|
|
mc.pos = &objp->pos; // The object's position
|
|
mc.p0 = eye_pos; // Point 1 of ray to check
|
|
mc.p1 = &terminus; // Point 2 of ray to check
|
|
mc.flags = MC_CHECK_MODEL;
|
|
|
|
model_collide(&mc);
|
|
|
|
ship_model_stop(objp);
|
|
|
|
if ( !mc.num_hits ) {
|
|
return 0;
|
|
}
|
|
|
|
// determine if hitpos is close enough to subsystem
|
|
dist = vm_vec_dist(&mc.hit_point_world, subsys_pos);
|
|
|
|
if ( dist <= subsys->system_info->radius ) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// try to find a subsystem matching 'type' inside the ship, and that is
|
|
// not destroyed. If cannot find one, return NULL.
|
|
ship_subsys *ship_return_next_subsys(ship *shipp, int type, vector *attacker_pos)
|
|
{
|
|
ship_subsys *ssp;
|
|
|
|
Assert ( type >= 0 && type < SUBSYSTEM_MAX );
|
|
|
|
// If aggregate total is 0, that means no subsystem is alive of that type
|
|
if ( shipp->subsys_info[type].total_hits <= 0.0f )
|
|
return NULL;
|
|
|
|
// loop through all the subsystems, if we find a match that has some strength, return it
|
|
ssp = ship_get_best_subsys_to_attack(shipp, type, attacker_pos);
|
|
|
|
return ssp;
|
|
}
|
|
|
|
// Return the shield strength in the quadrant hit on hit_objp, based on global hitpos
|
|
//
|
|
// input: hit_objp => object pointer to ship getting hit
|
|
// hitpos => global position of impact
|
|
//
|
|
// exit: strength of shields in the quadrant that was hit as a percentage, between 0 and 1.0
|
|
//
|
|
// Assumes: that hitpos is a valid global hit position
|
|
float ship_quadrant_shield_strength(object *hit_objp, vector *hitpos)
|
|
{
|
|
int quadrant_num, i;
|
|
float max_quadrant;
|
|
vector tmpv1, tmpv2;
|
|
|
|
// If ship doesn't have shield mesh, then return
|
|
if ( hit_objp->flags & OF_NO_SHIELDS ) {
|
|
return 0.0f;
|
|
}
|
|
|
|
// Check if all the shield quadrants are all already 0, if so return 0
|
|
for ( i = 0; i < 4; i++ ) {
|
|
if ( hit_objp->shields[i] > 0 )
|
|
break;
|
|
}
|
|
|
|
if ( i == 4 ) {
|
|
return 0.0f;
|
|
}
|
|
|
|
// convert hitpos to position in model coordinates
|
|
vm_vec_sub(&tmpv1, hitpos, &hit_objp->pos);
|
|
vm_vec_rotate(&tmpv2, &tmpv1, &hit_objp->orient);
|
|
quadrant_num = get_quadrant(&tmpv2);
|
|
//nprintf(("Alan","Quadrant hit: %d\n", quadrant_num));
|
|
|
|
if ( quadrant_num < 0 )
|
|
quadrant_num = 0;
|
|
|
|
max_quadrant = Ship_info[Ships[hit_objp->instance].ship_info_index].shields / 4.0f;
|
|
if ( max_quadrant <= 0 ) {
|
|
return 0.0f;
|
|
}
|
|
|
|
Assert(hit_objp->shields[quadrant_num] <= max_quadrant);
|
|
|
|
return hit_objp->shields[quadrant_num]/max_quadrant;
|
|
}
|
|
|
|
// Determine if a ship is threatened by any dumbfire projectiles (laser or missile)
|
|
// input: sp => pointer to ship that might be threatened
|
|
// exit: 0 => no dumbfire threats
|
|
// 1 => at least one dumbfire threat
|
|
//
|
|
// NOTE: Currently this function is only called periodically from the HUD code for the
|
|
// player ship.
|
|
int ship_dumbfire_threat(ship *sp)
|
|
{
|
|
#ifndef NO_NETWORK
|
|
if ( (Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_OBSERVER) ) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
if (ai_endangered_by_weapon(&Ai_info[sp->ai_index]) > 0) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Return !0 if there is a missile in the air homing on shipp
|
|
int ship_has_homing_missile_locked(ship *shipp)
|
|
{
|
|
object *locked_objp, *A;
|
|
weapon *wp;
|
|
weapon_info *wip;
|
|
missile_obj *mo;
|
|
|
|
Assert(shipp->objnum >= 0 && shipp->objnum < MAX_OBJECTS);
|
|
locked_objp = &Objects[shipp->objnum];
|
|
|
|
// check for currently locked missiles (highest precedence)
|
|
for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
|
|
Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
|
|
A = &Objects[mo->objnum];
|
|
|
|
if (A->type != OBJ_WEAPON)
|
|
continue;
|
|
|
|
Assert((A->instance >= 0) && (A->instance < MAX_WEAPONS));
|
|
wp = &Weapons[A->instance];
|
|
wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
if ( wip->subtype != WP_MISSILE )
|
|
continue;
|
|
|
|
if ( !(wip->wi_flags & (WIF_HOMING_ASPECT|WIF_HOMING_HEAT) ) )
|
|
continue;
|
|
|
|
if (wp->homing_object == locked_objp) {
|
|
return 1;
|
|
}
|
|
} // end for
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Return !0 if there is some ship attempting to lock onto shipp
|
|
int ship_is_getting_locked(ship *shipp)
|
|
{
|
|
ship_obj *so;
|
|
object *objp;
|
|
ai_info *aip;
|
|
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
objp = &Objects[so->objnum];
|
|
aip = &Ai_info[Ships[objp->instance].ai_index];
|
|
|
|
if ( aip->target_objnum == shipp->objnum ) {
|
|
if ( aip->aspect_locked_time > 0.1f ) {
|
|
float dist, wep_range;
|
|
dist = vm_vec_dist_quick(&objp->pos, &Objects[shipp->objnum].pos);
|
|
wep_range = ship_get_secondary_weapon_range(&Ships[objp->instance]);
|
|
if ( wep_range > dist ) {
|
|
nprintf(("Alan","AI ship is seeking lock\n"));
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Determine if a ship is threatened by attempted lock or actual lock
|
|
// input: sp => pointer to ship that might be threatened
|
|
// exit: 0 => no lock threats of any kind
|
|
// 1 => at least one attempting lock (no actual locks)
|
|
// 2 => at least one lock (possible other attempting locks)
|
|
//
|
|
// NOTE: Currently this function is only called periodically from the HUD code for the
|
|
// player ship.
|
|
int ship_lock_threat(ship *sp)
|
|
{
|
|
if ( ship_has_homing_missile_locked(sp) ) {
|
|
return 2;
|
|
}
|
|
|
|
if ( ship_is_getting_locked(sp) ) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// converts a bitmask, such as 0x08, into the bit number this would be (3 in this case)
|
|
// NOTE: Should move file to something like Math_utils.
|
|
int bitmask_2_bitnum(int num)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<32; i++)
|
|
if (num & (1 << i))
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Get a text description of a ships orders.
|
|
//
|
|
// input: outbuf => buffer to hold orders string
|
|
// sp => ship pointer to extract orders from
|
|
//
|
|
// exit: NULL => printable orders are not applicable
|
|
// non-NULL => pointer to string that was passed in originally
|
|
//
|
|
// This function is called from HUD code to get a text description
|
|
// of what a ship's orders are. Feel free to use this function if
|
|
// it suits your needs for something.
|
|
//
|
|
char *ship_return_orders(char *outbuf, ship *sp)
|
|
{
|
|
ai_info *aip;
|
|
ai_goal *aigp;
|
|
char *order_text;
|
|
|
|
Assert(sp->ai_index >= 0);
|
|
aip = &Ai_info[sp->ai_index];
|
|
|
|
// The active goal is always in the first element of aip->goals[]
|
|
aigp = &aip->goals[0];
|
|
|
|
if ( aigp->ai_mode < 0 )
|
|
return NULL;
|
|
|
|
order_text = Ai_goal_text(bitmask_2_bitnum(aigp->ai_mode));
|
|
if ( order_text == NULL )
|
|
return NULL;
|
|
|
|
strcpy(outbuf, order_text);
|
|
switch (aigp->ai_mode ) {
|
|
|
|
case AI_GOAL_FORM_ON_WING:
|
|
case AI_GOAL_GUARD_WING:
|
|
case AI_GOAL_CHASE_WING:
|
|
if ( aigp->ship_name ) {
|
|
strcat(outbuf, aigp->ship_name);
|
|
strcat(outbuf, XSTR( " Wing", 494));
|
|
} else {
|
|
strcpy(outbuf, XSTR( "no orders", 495));
|
|
}
|
|
break;
|
|
|
|
case AI_GOAL_CHASE:
|
|
case AI_GOAL_DOCK:
|
|
case AI_GOAL_UNDOCK:
|
|
case AI_GOAL_GUARD:
|
|
case AI_GOAL_DISABLE_SHIP:
|
|
case AI_GOAL_DISARM_SHIP:
|
|
case AI_GOAL_EVADE_SHIP:
|
|
case AI_GOAL_REARM_REPAIR:
|
|
if ( aigp->ship_name ) {
|
|
strcat(outbuf, aigp->ship_name);
|
|
} else {
|
|
strcpy(outbuf, XSTR( "no orders", 495));
|
|
}
|
|
break;
|
|
|
|
case AI_GOAL_DESTROY_SUBSYSTEM: {
|
|
char name[NAME_LENGTH];
|
|
if ( aip->targeted_subsys != NULL ) {
|
|
sprintf(outbuf, XSTR( "atk %s %s", 496), aigp->ship_name, hud_targetbox_truncate_subsys_name(aip->targeted_subsys->system_info->name));
|
|
strcat(outbuf, name);
|
|
} else {
|
|
strcpy(outbuf, XSTR( "no orders", 495) );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AI_GOAL_WAYPOINTS:
|
|
case AI_GOAL_WAYPOINTS_ONCE:
|
|
// don't do anything, all info is in order_text
|
|
break;
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
return outbuf;
|
|
}
|
|
|
|
// return the amount of time until ship reaches it's goal (in MM:SS format)
|
|
// input: outbuf => buffer to hold orders string
|
|
// sp => ship pointer to extract orders from
|
|
//
|
|
// exit: NULL => printable orders are not applicable
|
|
// non-NULL => pointer to string that was passed in originally
|
|
//
|
|
// This function is called from HUD code to get a text description
|
|
// of what a ship's orders are. Feel free to use this function if
|
|
// it suits your needs for something.
|
|
char *ship_return_time_to_goal(char *outbuf, ship *sp)
|
|
{
|
|
ai_info *aip;
|
|
int time, seconds, minutes;
|
|
float dist = 0.0f;
|
|
object *objp;
|
|
float min_speed;
|
|
|
|
objp = &Objects[sp->objnum];
|
|
aip = &Ai_info[sp->ai_index];
|
|
|
|
min_speed = objp->phys_info.speed;
|
|
|
|
if ( aip->mode == AIM_WAYPOINTS ) {
|
|
waypoint_list *wpl;
|
|
min_speed = 0.9f * sp->current_max_speed;
|
|
if (aip->wp_list >= 0) {
|
|
wpl = &Waypoint_lists[aip->wp_list];
|
|
dist += vm_vec_dist_quick(&objp->pos, &wpl->waypoints[aip->wp_index]);
|
|
for (int i=aip->wp_index; i<wpl->count-1; i++) {
|
|
dist += vm_vec_dist_quick(&wpl->waypoints[i], &wpl->waypoints[i+1]);
|
|
}
|
|
}
|
|
|
|
if ( dist < 1.0f) {
|
|
return NULL;
|
|
}
|
|
|
|
if ( (Objects[sp->objnum].phys_info.speed <= 0) || (sp->current_max_speed <= 0.0f) ) {
|
|
time = -1;
|
|
} else {
|
|
float speed;
|
|
|
|
speed = objp->phys_info.speed;
|
|
|
|
if (speed < min_speed)
|
|
speed = min_speed;
|
|
time = fl2i(dist/speed);
|
|
}
|
|
|
|
} else if ( (aip->mode == AIM_DOCK) && (aip->submode < AIS_DOCK_4) ) {
|
|
time = hud_support_get_dock_time( OBJ_INDEX(objp) );
|
|
} else {
|
|
// don't return anytime for time to except for waypoints and actual docking.
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
} else if ( aip->goal_objnum >= 0 ) {
|
|
dist = vm_vec_dist_quick(&Objects[aip->goal_objnum].pos, &objp->pos);
|
|
min_speed = sip->max_speed/4.0f;
|
|
} else if ( aip->target_objnum >= 0 ) {
|
|
if ( aip->guard_objnum < 0 ) {
|
|
dist = vm_vec_dist_quick(&Objects[aip->target_objnum].pos, &objp->pos);
|
|
min_speed = sip->max_speed/4.0f;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if ( time >= 0 ) {
|
|
minutes = time/60;
|
|
seconds = time%60;
|
|
if ( minutes > 99 ) {
|
|
minutes = 99;
|
|
seconds = 99;
|
|
}
|
|
sprintf(outbuf, NOX("%02d:%02d"), minutes, seconds);
|
|
} else {
|
|
sprintf( outbuf, XSTR( "Unknown", 497) );
|
|
}
|
|
|
|
return outbuf;
|
|
}
|
|
|
|
|
|
// Called to check if any AI ships might reveal the cargo of any cargo containers.
|
|
//
|
|
// This is called once a frame, but a global timer 'Ship_cargo_check_timer' will limit this
|
|
// function to being called every SHIP_CARGO_CHECK_INTERVAL ms. I think that should be sufficient.
|
|
//
|
|
// NOTE: This function uses CARGO_REVEAL_DISTANCE from the HUD code... which is a multiple of
|
|
// the ship radius that is used to determine when cargo is detected. AI ships do not
|
|
// have to have the ship targeted to reveal cargo. The player is ignored in this function.
|
|
#define SHIP_CARGO_CHECK_INTERVAL 1000
|
|
void ship_check_cargo_all()
|
|
{
|
|
object *cargo_objp;
|
|
ship_obj *cargo_so, *ship_so;
|
|
ship *cargo_sp, *ship_sp;
|
|
float dist_squared, limit_squared;
|
|
|
|
// I don't want to do this check every frame, so I made a global timer to limit check to
|
|
// every SHIP_CARGO_CHECK_INTERVAL ms.
|
|
if ( !timestamp_elapsed(Ship_cargo_check_timer) ) {
|
|
return;
|
|
} else {
|
|
Ship_cargo_check_timer = timestamp(SHIP_CARGO_CHECK_INTERVAL);
|
|
}
|
|
|
|
// Check all friendly fighter/bombers against all non-friendly cargo containers that don't have
|
|
// cargo revealed
|
|
|
|
// for now just locate a captial ship on the same team:
|
|
cargo_so = GET_FIRST(&Ship_obj_list);
|
|
while(cargo_so != END_OF_LIST(&Ship_obj_list)){
|
|
cargo_sp = &Ships[Objects[cargo_so->objnum].instance];
|
|
if ( (Ship_info[cargo_sp->ship_info_index].flags & SIF_CARGO) && !(cargo_sp->team & TEAM_FRIENDLY) ) {
|
|
|
|
// If the cargo is revealed, continue on to next hostile cargo
|
|
if ( cargo_sp->flags & SF_CARGO_REVEALED ) {
|
|
goto next_cargo;
|
|
}
|
|
|
|
// check against friendly fighter/bombers + cruiser/freighter/transport
|
|
// IDEA: could cull down to fighter/bomber if we want this to run a bit quicker
|
|
for ( ship_so=GET_FIRST(&Ship_obj_list); ship_so != END_OF_LIST(&Ship_obj_list); ship_so=GET_NEXT(ship_so) ) {
|
|
ship_sp = &Ships[Objects[ship_so->objnum].instance];
|
|
// only consider friendly ships
|
|
if ( !(ship_sp->team & TEAM_FRIENDLY) ) {
|
|
continue;
|
|
}
|
|
|
|
// ignore the player
|
|
if ( ship_so->objnum == OBJ_INDEX(Player_obj) ) {
|
|
continue;
|
|
}
|
|
|
|
// if this ship is a small or big ship
|
|
if ( Ship_info[ship_sp->ship_info_index].flags & (SIF_SMALL_SHIP|SIF_BIG_SHIP) ) {
|
|
cargo_objp = &Objects[cargo_sp->objnum];
|
|
// use square of distance, faster than getting real distance (which will use sqrt)
|
|
dist_squared = vm_vec_dist_squared(&cargo_objp->pos, &Objects[ship_sp->objnum].pos);
|
|
limit_squared = (cargo_objp->radius+CARGO_RADIUS_DELTA)*(cargo_objp->radius+CARGO_RADIUS_DELTA);
|
|
if ( dist_squared <= max(limit_squared, CARGO_REVEAL_MIN_DIST*CARGO_REVEAL_MIN_DIST) ) {
|
|
ship_do_cargo_revealed( cargo_sp );
|
|
break; // break out of for loop, move on to next hostile cargo
|
|
}
|
|
}
|
|
} // end for
|
|
}
|
|
next_cargo:
|
|
cargo_so = GET_NEXT(cargo_so);
|
|
} // end while
|
|
}
|
|
|
|
|
|
// Maybe warn player about this attacking ship. This is called once per frame, and the
|
|
// information about the closest attacking ship comes for free, since this function is called
|
|
// from HUD code which has already determined the closest enemy attacker and the distance.
|
|
//
|
|
// input: enemy_sp => ship pointer to the TEAM_ENEMY ship attacking the player
|
|
// dist => the distance of the enemy to the player
|
|
//
|
|
// NOTE: there are no filters on enemy_sp, so it could be any ship type
|
|
//
|
|
#define PLAYER_ALLOW_WARN_INTERVAL 60000 // minimum time between warnings
|
|
#define PLAYER_CHECK_WARN_INTERVAL 300 // how often we check for warnings
|
|
#define PLAYER_MAX_WARNINGS 2 // max number of warnings player can receive in a mission
|
|
#define PLAYER_MIN_WARN_DIST 100 // minimum distance attacking ship can be from player and still allow warning
|
|
#define PLAYER_MAX_WARN_DIST 1000 // maximum distance attacking ship can be from plyaer and still allow warning
|
|
|
|
void ship_maybe_warn_player(ship *enemy_sp, float dist)
|
|
{
|
|
float fdot; //, rdot, udot;
|
|
vector vec_to_target;
|
|
int msg_type; //, on_right;
|
|
|
|
// First check if the player has reached the maximum number of warnings for a mission
|
|
if ( Player->warn_count >= PLAYER_MAX_WARNINGS ) {
|
|
return;
|
|
}
|
|
|
|
// Check if enough time has elapsed since last warning, if not - leave
|
|
if ( !timestamp_elapsed(Player->allow_warn_timestamp) ) {
|
|
return;
|
|
}
|
|
|
|
// Check to see if check timer has elapsed. Necessary, since we don't want to check each frame
|
|
if ( !timestamp_elapsed(Player->check_warn_timestamp ) ) {
|
|
return;
|
|
}
|
|
Player->check_warn_timestamp = timestamp(PLAYER_CHECK_WARN_INTERVAL);
|
|
|
|
// only allow warnings if within a certain distance range
|
|
if ( dist < PLAYER_MIN_WARN_DIST || dist > PLAYER_MAX_WARN_DIST ) {
|
|
return;
|
|
}
|
|
|
|
// only warn if a fighter or bomber is attacking the player
|
|
if ( !(Ship_info[enemy_sp->ship_info_index].flags & SIF_SMALL_SHIP) ) {
|
|
return;
|
|
}
|
|
|
|
// get vector from player to target
|
|
vm_vec_normalized_dir(&vec_to_target, &Objects[enemy_sp->objnum].pos, &Eye_position);
|
|
|
|
// ensure that enemy fighter is oriented towards player
|
|
fdot = vm_vec_dot(&Objects[enemy_sp->objnum].orient.vec.fvec, &vec_to_target);
|
|
if ( fdot > -0.7 ) {
|
|
return;
|
|
}
|
|
|
|
fdot = vm_vec_dot(&Player_obj->orient.vec.fvec, &vec_to_target);
|
|
|
|
msg_type = -1;
|
|
|
|
// check if attacking ship is on six. return if not far enough behind player.
|
|
if ( fdot > -0.7 )
|
|
return;
|
|
|
|
msg_type = MESSAGE_CHECK_6;
|
|
/*
|
|
goto warn_player_done;
|
|
}
|
|
|
|
// see if attacking ship is in front of ship (then do nothing)
|
|
if ( fdot > 0.7 ) {
|
|
return;
|
|
}
|
|
|
|
// ok, ship is on 3 or 9. Find out which
|
|
rdot = vm_vec_dot(&Player_obj->orient.rvec, &vec_to_target);
|
|
if ( rdot > 0 ) {
|
|
on_right = 1;
|
|
} else {
|
|
on_right = 0;
|
|
}
|
|
|
|
// now determine if ship is high or low
|
|
udot = vm_vec_dot(&Player_obj->orient.uvec, &vec_to_target);
|
|
if ( udot < -0.8 ) {
|
|
return; // if ship is attacking from directly below, no warning given
|
|
}
|
|
|
|
if ( udot > 0 ) {
|
|
if ( on_right ) {
|
|
msg_type = MESSAGE_CHECK_3_HIGH;
|
|
} else {
|
|
msg_type = MESSAGE_CHECK_9_HIGH;
|
|
}
|
|
} else {
|
|
if ( on_right ) {
|
|
msg_type = MESSAGE_CHECK_3_LOW;
|
|
} else {
|
|
msg_type = MESSAGE_CHECK_9_LOW;
|
|
}
|
|
}
|
|
|
|
warn_player_done:
|
|
*/
|
|
|
|
if ( msg_type != -1 ) {
|
|
int ship_index;
|
|
|
|
// multiplayer tvt - this is client side.
|
|
#ifndef NO_NETWORK
|
|
if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM) && (Net_player != NULL)){
|
|
ship_index = ship_get_random_player_wing_ship( SHIP_GET_NO_PLAYERS, 0.0f, -1, 0, Net_player->p_info.team );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ship_index = ship_get_random_player_wing_ship( SHIP_GET_NO_PLAYERS );
|
|
}
|
|
|
|
if ( ship_index >= 0 ) {
|
|
// multiplayer - make sure I just send to myself
|
|
#ifndef NO_NETWORK
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
message_send_builtin_to_player(msg_type, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, MY_NET_PLAYER_NUM, -1);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
message_send_builtin_to_player(msg_type, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
|
|
}
|
|
Player->allow_warn_timestamp = timestamp(PLAYER_ALLOW_WARN_INTERVAL);
|
|
Player->warn_count++;
|
|
// nprintf(("Alan","Warning given for ship name: %s\n", enemy_sp->ship_name));
|
|
}
|
|
}
|
|
}
|
|
|
|
// player has just killed a ship, maybe offer send a 'good job' message
|
|
#define PLAYER_MAX_PRAISES 10 // max number of praises player can receive in a mission
|
|
void ship_maybe_praise_player(ship *deader_sp)
|
|
{
|
|
if ( myrand()&1 ) {
|
|
return;
|
|
}
|
|
|
|
// First check if the player has reached the maximum number of praises for a mission
|
|
if ( Player->praise_count >= PLAYER_MAX_PRAISES ) {
|
|
return;
|
|
}
|
|
|
|
// Check if enough time has elapsed since last praise, if not - leave
|
|
if ( !timestamp_elapsed(Player->allow_praise_timestamp) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !(Player_ship->team & TEAM_FRIENDLY) ) {
|
|
return;
|
|
}
|
|
|
|
if ( deader_sp->team == Player_ship->team ) { // only praise if killing an enemy!
|
|
return;
|
|
}
|
|
|
|
// don't praise the destruction of navbuoys, cargo or other non-flyable ship types
|
|
if ( Ship_info[deader_sp->ship_info_index].flags & SIF_NOT_FLYABLE ) {
|
|
return;
|
|
}
|
|
|
|
// There is already a praise pending
|
|
if ( Player->praise_delay_timestamp ) {
|
|
return;
|
|
}
|
|
|
|
// We don't want to praise the player right away.. it is more realistic to wait a moment
|
|
Player->praise_delay_timestamp = timestamp_rand(1000, 2000);
|
|
}
|
|
|
|
// player has just killed a ship, maybe offer send a 'good job' message
|
|
#define PLAYER_ASK_HELP_INTERVAL 60000 // minimum time between praises
|
|
#define PLAYER_MAX_ASK_HELP 10 // max number of warnings player can receive in a mission
|
|
#define ASK_HELP_SHIELD_PERCENT 0.1 // percent shields at which ship will ask for help
|
|
#define ASK_HELP_HULL_PERCENT 0.3 // percent hull at which ship will ask for help
|
|
#define AWACS_HELP_HULL_HI 0.75 // percent hull at which ship will ask for help
|
|
#define AWACS_HELP_HULL_LOW 0.25 // percent hull at which ship will ask for help
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void awacs_maybe_ask_for_help(ship *sp, int multi_team_filter)
|
|
{
|
|
object *objp;
|
|
int message = -1;
|
|
objp = &Objects[sp->objnum];
|
|
|
|
if ( objp->hull_strength < ( (AWACS_HELP_HULL_LOW + 0.01f *(static_rand(objp-Objects) & 5)) * Ship_info[sp->ship_info_index].initial_hull_strength) ) {
|
|
// awacs ship below 25 + (0-4) %
|
|
if (!(sp->awacs_warning_flag & AWACS_WARN_25)) {
|
|
message = MESSAGE_AWACS_25;
|
|
sp->awacs_warning_flag |= AWACS_WARN_25;
|
|
}
|
|
} else if ( objp->hull_strength < ( (AWACS_HELP_HULL_HI + 0.01f*(static_rand(objp-Objects) & 5)) * Ship_info[sp->ship_info_index].initial_hull_strength) ) {
|
|
// awacs ship below 75 + (0-4) %
|
|
if (!(sp->awacs_warning_flag & AWACS_WARN_75)) {
|
|
message = MESSAGE_AWACS_75;
|
|
sp->awacs_warning_flag |= AWACS_WARN_75;
|
|
}
|
|
}
|
|
|
|
if (message >= 0) {
|
|
message_send_builtin_to_player(message, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
|
|
Player->allow_ask_help_timestamp = timestamp(PLAYER_ASK_HELP_INTERVAL);
|
|
Player->ask_help_count++;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void ship_maybe_ask_for_help(ship *sp)
|
|
{
|
|
object *objp;
|
|
int multi_team_filter = -1;
|
|
|
|
// First check if the player has reached the maximum number of ask_help's for a mission
|
|
if ( Player->ask_help_count >= PLAYER_MAX_ASK_HELP ) {
|
|
return;
|
|
}
|
|
|
|
// Check if enough time has elapsed since last help request, if not - leave
|
|
if ( !timestamp_elapsed(Player->allow_ask_help_timestamp) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !(Player_ship->team & TEAM_FRIENDLY) ) {
|
|
return;
|
|
}
|
|
|
|
Assert(sp->team & TEAM_FRIENDLY );
|
|
objp = &Objects[sp->objnum];
|
|
|
|
if ( objp->flags & OF_PLAYER_SHIP ) {// don't let the player ask for help!
|
|
return;
|
|
}
|
|
|
|
#ifndef NO_NETWORK
|
|
// determine team filter if TvT
|
|
if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM)){
|
|
if(sp->team == TEAM_FRIENDLY){
|
|
multi_team_filter = 0;
|
|
} else if(sp->team == TEAM_HOSTILE){
|
|
multi_team_filter = 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// handle awacs ship as a special case
|
|
if (Ship_info[sp->ship_info_index].flags & SIF_HAS_AWACS) {
|
|
awacs_maybe_ask_for_help(sp, multi_team_filter);
|
|
return;
|
|
}
|
|
|
|
// for now, only have wingman ships request help
|
|
if ( !(sp->flags & SF_FROM_PLAYER_WING) ) {
|
|
return;
|
|
}
|
|
|
|
// first check if hull is at a critical level
|
|
if ( objp->hull_strength < ASK_HELP_HULL_PERCENT * Ship_info[sp->ship_info_index].initial_hull_strength ) {
|
|
goto play_ask_help;
|
|
}
|
|
|
|
// check if shields are near critical level
|
|
if ( objp->flags & OF_NO_SHIELDS ) {
|
|
return; // no shields on ship, no don't check shield levels
|
|
}
|
|
|
|
if ( get_shield_strength(objp) > (ASK_HELP_SHIELD_PERCENT * Ship_info[sp->ship_info_index].shields) ) {
|
|
return;
|
|
}
|
|
|
|
play_ask_help:
|
|
|
|
Assert(Ship_info[sp->ship_info_index].flags & (SIF_FIGHTER|SIF_BOMBER) ); // get Alan
|
|
message_send_builtin_to_player(MESSAGE_HELP, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
|
|
Player->allow_ask_help_timestamp = timestamp(PLAYER_ASK_HELP_INTERVAL);
|
|
|
|
if ( timestamp_until(Player->allow_scream_timestamp) < 15000 ) {
|
|
Player->allow_scream_timestamp = timestamp(15000); // prevent overlap with death message
|
|
}
|
|
|
|
Player->ask_help_count++;
|
|
}
|
|
|
|
// The player has just entered death roll, maybe have wingman mourn the loss of the player
|
|
void ship_maybe_lament()
|
|
{
|
|
int ship_index;
|
|
|
|
// no. because in multiplayer, its funny
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
return;
|
|
}
|
|
|
|
if ( rand()%4 == 0 ) {
|
|
ship_index = ship_get_random_player_wing_ship( SHIP_GET_NO_PLAYERS );
|
|
if ( ship_index >= 0 ) {
|
|
message_send_builtin_to_player(MESSAGE_PLAYED_DIED, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define PLAYER_SCREAM_INTERVAL 60000
|
|
#define PLAYER_MAX_SCREAMS 10
|
|
|
|
// play a death scream for a ship
|
|
void ship_scream(ship *sp)
|
|
{
|
|
int multi_team_filter = -1;
|
|
|
|
// bogus
|
|
if(sp == NULL){
|
|
return;
|
|
}
|
|
|
|
#ifndef NO_NETWORK
|
|
// multiplayer tvt
|
|
if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM)){
|
|
if(sp->team == TEAM_FRIENDLY){
|
|
multi_team_filter = 0;
|
|
} else if(sp->team == TEAM_HOSTILE){
|
|
multi_team_filter = 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
message_send_builtin_to_player(MESSAGE_WINGMAN_SCREAM, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
|
|
Player->allow_scream_timestamp = timestamp(PLAYER_SCREAM_INTERVAL);
|
|
Player->scream_count++;
|
|
sp->flags |= SF_SHIP_HAS_SCREAMED;
|
|
|
|
// prevent overlap with help messages
|
|
if ( timestamp_until(Player->allow_ask_help_timestamp) < 15000 ) {
|
|
Player->allow_ask_help_timestamp = timestamp(15000); // prevent overlap with death message
|
|
}
|
|
}
|
|
|
|
// ship has just died, maybe play a scream.
|
|
//
|
|
// NOTE: this is only called for ships that are in a player wing (and not player ship)
|
|
void ship_maybe_scream(ship *sp)
|
|
{
|
|
if ( rand()&1 )
|
|
return;
|
|
|
|
// First check if the player has reached the maximum number of screams for a mission
|
|
if ( Player->scream_count >= PLAYER_MAX_SCREAMS ) {
|
|
return;
|
|
}
|
|
|
|
// if on different teams (i.e. team v. team games in multiplayer), no scream
|
|
if ( sp->team != Player_ship->team ) {
|
|
return;
|
|
}
|
|
|
|
// Check if enough time has elapsed since last scream, if not - leave
|
|
if ( !timestamp_elapsed(Player->allow_scream_timestamp) ) {
|
|
return;
|
|
}
|
|
|
|
ship_scream(sp);
|
|
}
|
|
|
|
// maybe tell player that we've requested a support ship
|
|
#define PLAYER_REQUEST_REPAIR_MSG_INTERVAL 240000
|
|
void ship_maybe_tell_about_rearm(ship *sp)
|
|
{
|
|
if ( !timestamp_elapsed(Player->request_repair_timestamp) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !(Player_ship->team & TEAM_FRIENDLY) ) {
|
|
return;
|
|
}
|
|
|
|
// AL 1-4-98: If ship integrity is low, tell player you want to get repaired. Otherwise, tell
|
|
// the player you want to get re-armed.
|
|
|
|
int message_type = -1;
|
|
int heavily_damaged = 0;
|
|
if ( Objects[sp->objnum].hull_strength/Ship_info[sp->ship_info_index].initial_hull_strength < 0.4 ) {
|
|
heavily_damaged = 1;
|
|
}
|
|
|
|
if ( heavily_damaged || (sp->flags & SF_DISABLED) ) {
|
|
message_type = MESSAGE_REPAIR_REQUEST;
|
|
} else {
|
|
int i;
|
|
ship_weapon *swp;
|
|
|
|
swp = &sp->weapons;
|
|
for ( i = 0; i < swp->num_secondary_banks; i++ ) {
|
|
if (swp->secondary_bank_start_ammo[i] > 0) {
|
|
if ( swp->secondary_bank_ammo[i]/swp->secondary_bank_start_ammo[i] < 0.5f ) {
|
|
message_type = MESSAGE_REARM_REQUEST;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int multi_team_filter = -1;
|
|
|
|
#ifndef NO_NETWORK
|
|
// multiplayer tvt
|
|
if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM)){
|
|
if(sp->team == TEAM_FRIENDLY){
|
|
multi_team_filter = 0;
|
|
} else if(sp->team == TEAM_HOSTILE){
|
|
multi_team_filter = 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( message_type >= 0 ) {
|
|
if ( rand() & 1 ) {
|
|
message_send_builtin_to_player(message_type, sp, MESSAGE_PRIORITY_NORMAL, MESSAGE_TIME_SOON, 0, 0, -1, multi_team_filter);
|
|
}
|
|
Player->request_repair_timestamp = timestamp(PLAYER_REQUEST_REPAIR_MSG_INTERVAL);
|
|
}
|
|
}
|
|
|
|
// The current primary weapon or link status for a ship has changed.. notify clients if multiplayer
|
|
//
|
|
// input: sp => pointer to ship that modified primaries
|
|
void ship_primary_changed(ship *sp)
|
|
{
|
|
#if 0
|
|
ship_weapon *swp;
|
|
|
|
// we only need to deal with multiplayer issues for now, so bail it not multiplayer
|
|
if ( !(Game_mode & GM_MULTIPLAYER) )
|
|
return;
|
|
|
|
Assert(sp);
|
|
swp = &sp->weapons;
|
|
|
|
|
|
if ( MULTIPLAYER_MASTER )
|
|
send_ship_weapon_change( sp, MULTI_PRIMARY_CHANGED, swp->current_primary_bank, (sp->flags & SF_PRIMARY_LINKED)?1:0 );
|
|
#endif
|
|
}
|
|
|
|
// The current secondary weapon or dual-fire status for a ship has changed.. notify clients if multiplayer
|
|
//
|
|
// input: sp => pointer to ship that modified secondaries
|
|
void ship_secondary_changed(ship *sp)
|
|
{
|
|
#if 0
|
|
ship_weapon *swp;
|
|
|
|
// we only need to deal with multiplayer issues for now, so bail it not multiplayer
|
|
if ( !(Game_mode & GM_MULTIPLAYER) ){
|
|
return;
|
|
}
|
|
|
|
Assert(sp);
|
|
swp = &sp->weapons;
|
|
|
|
if ( MULTIPLAYER_MASTER )
|
|
send_ship_weapon_change( sp, MULTI_SECONDARY_CHANGED, swp->current_secondary_bank, (sp->flags & SF_SECONDARY_DUAL_FIRE)?1:0 );
|
|
#endif
|
|
}
|
|
|
|
int ship_get_SIF(ship *shipp)
|
|
{
|
|
return Ship_info[shipp->ship_info_index].flags;
|
|
}
|
|
|
|
int ship_get_SIF(int sh)
|
|
{
|
|
return Ship_info[Ships[sh].ship_info_index].flags;
|
|
}
|
|
|
|
int ship_get_by_signature(int signature)
|
|
{
|
|
ship_obj *so;
|
|
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
// if we found a matching ship object signature
|
|
if((Objects[so->objnum].signature == signature) && (Objects[so->objnum].type == OBJ_SHIP)){
|
|
return Objects[so->objnum].instance;
|
|
}
|
|
}
|
|
|
|
// couldn't find the ship
|
|
return -1;
|
|
}
|
|
|
|
// function which gets called when the cargo of a ship is revealed. Happens at two different locations
|
|
// (at least when this function was written), one for the player, and one for AI ships. Need to send stuff
|
|
// to clients in multiplayer game.
|
|
void ship_do_cargo_revealed( ship *shipp, int from_network )
|
|
{
|
|
// don't do anything if we already know the cargo
|
|
if ( shipp->flags & SF_CARGO_REVEALED ){
|
|
return;
|
|
}
|
|
|
|
nprintf(("Network", "Revealing cargo for %s\n", shipp->ship_name));
|
|
|
|
#ifndef NO_NETWORK
|
|
// send the packet if needed
|
|
if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
|
|
send_cargo_revealed_packet( shipp );
|
|
}
|
|
#endif
|
|
|
|
shipp->flags |= SF_CARGO_REVEALED;
|
|
shipp->time_cargo_revealed = Missiontime;
|
|
|
|
// if the cargo is something other than "nothing", then make a log entry
|
|
if ( stricmp(Cargo_names[shipp->cargo1 & CARGO_INDEX_MASK], NOX("nothing")) ){
|
|
mission_log_add_entry(LOG_CARGO_REVEALED, shipp->ship_name, NULL, (shipp->cargo1 & CARGO_INDEX_MASK) );
|
|
}
|
|
}
|
|
|
|
void ship_do_cap_subsys_cargo_revealed( ship *shipp, ship_subsys *subsys, int from_network )
|
|
{
|
|
if ( subsys->subsys_cargo_revealed ) {
|
|
return;
|
|
}
|
|
|
|
|
|
nprintf(("Network", "Revealing cap ship subsys cargo for %s\n", shipp->ship_name));
|
|
|
|
#ifndef NO_NETWORK
|
|
// send the packet if needed
|
|
if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
|
|
int subsystem_index = ship_get_index_from_subsys(subsys, shipp->objnum);
|
|
send_subsystem_cargo_revealed_packet( shipp, subsystem_index );
|
|
}
|
|
#endif
|
|
|
|
subsys->subsys_cargo_revealed = 1;
|
|
|
|
// if the cargo is something other than "nothing", then make a log entry
|
|
if ( (subsys->subsys_cargo_name > 0) && stricmp(Cargo_names[subsys->subsys_cargo_name], NOX("nothing")) ){
|
|
mission_log_add_entry(LOG_CAP_SUBSYS_CARGO_REVEALED, shipp->ship_name, subsys->system_info->name, subsys->subsys_cargo_name );
|
|
}
|
|
}
|
|
|
|
|
|
// Return the range of the currently selected secondary weapon
|
|
// NOTE: If there is no missiles left in the current bank, range returned is 0
|
|
float ship_get_secondary_weapon_range(ship *shipp)
|
|
{
|
|
float srange=0.0f;
|
|
|
|
ship_weapon *swp;
|
|
swp = &shipp->weapons;
|
|
if ( swp->current_secondary_bank >= 0 ) {
|
|
weapon_info *wip;
|
|
int bank=swp->current_secondary_bank;
|
|
wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
|
|
if ( swp->secondary_bank_ammo[bank] > 0 ) {
|
|
srange = wip->max_speed * wip->lifetime;
|
|
}
|
|
}
|
|
|
|
return srange;
|
|
}
|
|
|
|
// Determine the number of secondary ammo units (missile/bomb) allowed max for a ship
|
|
//
|
|
int get_max_ammo_count_for_bank(int ship_class, int bank, int ammo_type)
|
|
{
|
|
float capacity, size;
|
|
|
|
capacity = (float) Ship_info[ship_class].secondary_bank_ammo_capacity[bank];
|
|
size = (float) Weapon_info[ammo_type].cargo_size;
|
|
return (int) (capacity / size);
|
|
}
|
|
|
|
|
|
|
|
// Page in bitmaps for all the ships in this level
|
|
void ship_page_in()
|
|
{
|
|
int i,j;
|
|
int num_subsystems_needed = 0;
|
|
|
|
int ship_class_used[MAX_SHIP_TYPES];
|
|
|
|
// Mark all ship classes as not used
|
|
for (i=0; i<MAX_SHIP_TYPES; i++ ) {
|
|
ship_class_used[i] = 0;
|
|
}
|
|
|
|
// Mark any support ship types as used
|
|
//
|
|
for (i=0; i<Num_ship_types; i++ ) {
|
|
if ( Ship_info[i].flags & SIF_SUPPORT ) {
|
|
nprintf(( "Paging", "Found support ship '%s'\n", Ship_info[i].name ));
|
|
ship_class_used[i]++;
|
|
|
|
num_subsystems_needed += Ship_info[i].n_subsystems;
|
|
}
|
|
}
|
|
|
|
// Mark any ships in the mission as used
|
|
//
|
|
for (i=0; i<MAX_SHIPS; i++) {
|
|
if (Ships[i].objnum > -1) {
|
|
nprintf(( "Paging","Found ship '%s'\n", Ships[i].ship_name ));
|
|
ship_class_used[Ships[i].ship_info_index]++;
|
|
|
|
num_subsystems_needed += Ship_info[Ships[i].ship_info_index].n_subsystems;
|
|
}
|
|
}
|
|
|
|
// Mark any ships that might warp in in the future as used
|
|
//
|
|
p_object * p_objp;
|
|
for( p_objp = GET_FIRST(&ship_arrival_list); p_objp != END_OF_LIST(&ship_arrival_list); p_objp = GET_NEXT(p_objp) ) {
|
|
nprintf(( "Paging","Found future arrival ship '%s'\n", p_objp->name ));
|
|
ship_class_used[p_objp->ship_class]++;
|
|
|
|
num_subsystems_needed += Ship_info[p_objp->ship_class].n_subsystems;
|
|
}
|
|
|
|
|
|
// Page in all the ship classes that are used on this level
|
|
//
|
|
int num_ship_types_used = 0;
|
|
|
|
for (i=0; i<MAX_SHIP_TYPES; i++ ) {
|
|
if ( ship_class_used[i] ) {
|
|
ship_info *si = &Ship_info[i];
|
|
|
|
num_ship_types_used++;
|
|
|
|
// Page in the small hud icons for each ship
|
|
{
|
|
extern void hud_ship_icon_page_in(ship_info *sip);
|
|
|
|
hud_ship_icon_page_in(si);
|
|
|
|
}
|
|
|
|
// See if this model was previously loaded by another ship
|
|
int model_previously_loaded = -1;
|
|
int ship_previously_loaded = -1;
|
|
for (j=0; j<MAX_SHIP_TYPES; j++ ) {
|
|
if ( (Ship_info[j].modelnum > -1) && !stricmp(si->pof_file, Ship_info[j].pof_file) ) {
|
|
// Model already loaded
|
|
model_previously_loaded = Ship_info[j].modelnum;
|
|
ship_previously_loaded = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the model is previously loaded...
|
|
if ( model_previously_loaded > -1 ) {
|
|
|
|
// If previously loaded model isn't the same ship class...)
|
|
if ( ship_previously_loaded != i ) {
|
|
|
|
// update the model number.
|
|
si->modelnum = model_previously_loaded;
|
|
|
|
for ( j = 0; j < si->n_subsystems; j++ ) {
|
|
si->subsystems[j].model_num = -1;
|
|
}
|
|
|
|
ship_copy_subsystem_fixup(si);
|
|
|
|
#ifndef NDEBUG
|
|
for ( j = 0; j < si->n_subsystems; j++ ) {
|
|
Assert( si->subsystems[j].model_num == si->modelnum );
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
// Just to be safe (I mean to check that my code works...)
|
|
Assert( si->modelnum > -1 );
|
|
Assert( si->modelnum == model_previously_loaded );
|
|
|
|
#ifndef NDEBUG
|
|
for ( j = 0; j < si->n_subsystems; j++ ) {
|
|
Assert( si->subsystems[j].model_num == si->modelnum );
|
|
}
|
|
#endif
|
|
}
|
|
} else {
|
|
// Model not loaded... so load it and page in its textures
|
|
si->modelnum = model_load(si->pof_file, si->n_subsystems, &si->subsystems[0]);
|
|
|
|
Assert( si->modelnum > -1 );
|
|
|
|
// Verify that all the subsystem model numbers are updated
|
|
#ifndef NDEBUG
|
|
for ( j = 0; j < si->n_subsystems; j++ ) {
|
|
Assert( si->subsystems[j].model_num == si->modelnum ); // JAS
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
for (i=0; i<MAX_SHIP_TYPES; i++ ) {
|
|
if ( ship_class_used[i] ) {
|
|
ship_info *si = &Ship_info[i];
|
|
|
|
if ( si->modelnum > -1 ) {
|
|
polymodel *pm = model_get(si->modelnum);
|
|
|
|
nprintf(( "Paging", "Paging in textures for model '%s'\n", si->pof_file ));
|
|
|
|
for (j=0; j<pm->n_textures; j++ ) {
|
|
int bitmap_num = pm->original_textures[j];
|
|
|
|
if ( bitmap_num > -1 ) {
|
|
// if we're in Glide (and maybe later with D3D), use nondarkening textures
|
|
if(gr_screen.mode == GR_GLIDE){
|
|
bm_page_in_nondarkening_texture( bitmap_num );
|
|
} else {
|
|
bm_page_in_texture( bitmap_num );
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
nprintf(( "Paging", "Couldn't load model '%s'\n", si->pof_file ));
|
|
}
|
|
}
|
|
}
|
|
|
|
nprintf(( "Paging", "There are %d ship classes used in this mission.\n", num_ship_types_used ));
|
|
mprintf(( "This mission requires %d Ship_subsystems. See #define MAX_SHIP_SUBOBJECTS.\n", num_subsystems_needed ));
|
|
|
|
// JAS: If you hit this, then MAX_SHIP_SUBOBJECTS is set too low.
|
|
// I added this code in to detect an error that wasn't getting detected any other
|
|
// way.
|
|
Assert(num_subsystems_needed < MAX_SHIP_SUBOBJECTS );
|
|
|
|
// Page in the thruster effects
|
|
//
|
|
|
|
// Make sure thrusters are loaded
|
|
if ( !Thrust_anim_inited ) ship_init_thrusters();
|
|
|
|
for ( i = 0; i < NUM_THRUST_ANIMS; i++ ) {
|
|
thrust_anim *ta = &Thrust_anims[i];
|
|
for ( j = 0; j<ta->num_frames; j++ ) {
|
|
bm_page_in_texture( ta->first_frame + j );
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < NUM_THRUST_GLOW_ANIMS; i++ ) {
|
|
thrust_anim *ta = &Thrust_glow_anims[i];
|
|
// glows are really not anims
|
|
bm_page_in_texture( ta->first_frame );
|
|
}
|
|
|
|
// page in insignia bitmaps
|
|
#ifndef NO_NETWORK
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
for(i=0; i<MAX_PLAYERS; i++){
|
|
if(MULTI_CONNECTED(Net_players[i]) && (Net_players[i].player != NULL) && (Net_players[i].player->insignia_texture >= 0)){
|
|
bm_page_in_xparent_texture(Net_players[i].player->insignia_texture);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if((Player != NULL) && (Player->insignia_texture >= 0)){
|
|
bm_page_in_xparent_texture(Player->insignia_texture);
|
|
}
|
|
}
|
|
}
|
|
|
|
// function to return true if support ships are allowed in the mission for the given object.
|
|
// In single player, must be friendly and not Shivan.
|
|
// In multiplayer -- to be coded by Mark Allender after 5/4/98 -- MK, 5/4/98
|
|
int is_support_allowed(object *objp)
|
|
{
|
|
if (The_mission.disallow_support){
|
|
return 0;
|
|
}
|
|
|
|
if ( Game_mode & GM_NORMAL ) {
|
|
if (Ships[objp->instance].team != TEAM_FRIENDLY){
|
|
return 0;
|
|
}
|
|
|
|
switch (Ship_info[Ships[objp->instance].ship_info_index].species) {
|
|
case SPECIES_TERRAN:
|
|
break;
|
|
case SPECIES_VASUDAN:
|
|
break;
|
|
case SPECIES_SHIVAN:
|
|
return 0;
|
|
case SPECIES_NONE:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
#ifndef NO_NETWORK
|
|
} else {
|
|
// multiplayer version behaves differently. Depending on mode:
|
|
// 1) coop mode -- only available to friendly
|
|
// 2) team v team mode -- availble to either side
|
|
// 3) dogfight -- never
|
|
|
|
if(Netgame.type_flags & NG_TYPE_DOGFIGHT){
|
|
return 0;
|
|
}
|
|
|
|
if ( IS_MISSION_MULTI_COOP ) {
|
|
if ( Ships[objp->instance].team != TEAM_FRIENDLY ){
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
// return ship index
|
|
int ship_get_random_ship()
|
|
{
|
|
int num_ships;
|
|
int rand_ship;
|
|
int idx;
|
|
ship_obj *so;
|
|
|
|
// get the # of ships on the list
|
|
num_ships = ship_get_num_ships();
|
|
|
|
// get a random ship on the list
|
|
rand_ship = (int)frand_range(0.0f, (float)(num_ships - 1));
|
|
if(rand_ship < 0){
|
|
rand_ship = 0;
|
|
}
|
|
if(rand_ship > num_ships){
|
|
rand_ship = num_ships;
|
|
}
|
|
|
|
// find this guy
|
|
so = GET_FIRST(&Ship_obj_list);
|
|
for(idx=0; idx<rand_ship; idx++) {
|
|
so = GET_NEXT(so);
|
|
}
|
|
|
|
return Objects[so->objnum].instance;
|
|
}
|
|
|
|
// forcible jettison cargo from a ship
|
|
void ship_jettison_cargo(ship *shipp)
|
|
{
|
|
object *objp;
|
|
object *cargo_objp;
|
|
vector impulse, pos;
|
|
|
|
// make sure we are docked with a valid object
|
|
if(shipp->objnum < 0){
|
|
return;
|
|
}
|
|
objp = &Objects[shipp->objnum];
|
|
if(Ai_info[shipp->ai_index].dock_objnum == -1){
|
|
return;
|
|
}
|
|
if(Objects[Ai_info[shipp->ai_index].dock_objnum].type != OBJ_SHIP){
|
|
Int3();
|
|
return;
|
|
}
|
|
if(Ai_info[Ships[Objects[Ai_info[shipp->ai_index].dock_objnum].instance].ai_index].dock_objnum != OBJ_INDEX(objp)){
|
|
return;
|
|
}
|
|
cargo_objp = &Objects[Ai_info[shipp->ai_index].dock_objnum];
|
|
|
|
// undock the objects
|
|
ai_do_objects_undocked_stuff( objp, cargo_objp );
|
|
|
|
// physics stuff
|
|
vm_vec_sub(&pos, &cargo_objp->pos, &objp->pos);
|
|
impulse = pos;
|
|
vm_vec_scale(&impulse, 100.0f);
|
|
vm_vec_normalize(&pos);
|
|
|
|
// whack the ship
|
|
physics_apply_whack(&impulse, &pos, &cargo_objp->phys_info, &cargo_objp->orient, cargo_objp->phys_info.mass);
|
|
}
|
|
|
|
float ship_get_exp_damage(object* objp)
|
|
{
|
|
Assert(objp->type == OBJ_SHIP);
|
|
float damage;
|
|
|
|
ship *shipp = &Ships[objp->instance];
|
|
|
|
if (shipp->special_exp_index != -1) {
|
|
damage = (float) atoi(Sexp_variables[shipp->special_exp_index+DAMAGE].text);
|
|
} else {
|
|
damage = Ship_info[shipp->ship_info_index].damage;
|
|
}
|
|
|
|
return damage;
|
|
}
|
|
|
|
int ship_get_exp_propagates(ship *sp)
|
|
{
|
|
return Ship_info[sp->ship_info_index].explosion_propagates;
|
|
}
|
|
|
|
float ship_get_exp_outer_rad(object *ship_objp)
|
|
{
|
|
float outer_rad;
|
|
Assert(ship_objp->type == OBJ_SHIP);
|
|
|
|
if (Ships[ship_objp->instance].special_exp_index == -1) {
|
|
outer_rad = Ship_info[Ships[ship_objp->instance].ship_info_index].outer_rad;
|
|
} else {
|
|
outer_rad = (float) atoi(Sexp_variables[Ships[ship_objp->instance].special_exp_index+OUTER_RAD].text);
|
|
}
|
|
|
|
return outer_rad;
|
|
}
|
|
|
|
int valid_cap_subsys_cargo_list(char *subsys)
|
|
{
|
|
if (strstr(subsys, "nav")
|
|
|| strstr(subsys, "comm")
|
|
|| strstr(subsys, "engines")
|
|
|| strstr(subsys, "fighter") // fighter bays
|
|
|| strstr(subsys, "sensors")
|
|
|| strstr(subsys, "weapons")) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// determine turret status of a given subsystem, returns 0 for no turret, 1 for "fixed turret", 2 for "rotating" turret
|
|
int ship_get_turret_type(ship_subsys *subsys)
|
|
{
|
|
// not a turret at all
|
|
if(subsys->system_info->type != SUBSYSTEM_TURRET){
|
|
return 0;
|
|
}
|
|
|
|
// if it rotates
|
|
if(subsys->system_info->turret_turning_rate > 0.0f){
|
|
return 2;
|
|
}
|
|
|
|
// if its fixed
|
|
return 1;
|
|
}
|
|
|
|
ship_subsys *ship_get_subsys(ship *shipp, char *subsys_name)
|
|
{
|
|
ship_subsys *lookup;
|
|
|
|
// sanity checks
|
|
if((shipp == NULL) || (subsys_name == NULL)){
|
|
return NULL;
|
|
}
|
|
|
|
lookup = GET_FIRST(&shipp->subsys_list);
|
|
while(lookup != END_OF_LIST(&shipp->subsys_list)){
|
|
// turret
|
|
if(!strcmp(lookup->system_info->subobj_name, subsys_name)){
|
|
return lookup;
|
|
}
|
|
|
|
// next
|
|
lookup = GET_NEXT(lookup);
|
|
}
|
|
|
|
// didn't find it
|
|
return NULL;
|
|
}
|
|
|
|
// returns 0 if no conflict, 1 if conflict, -1 on some kind of error with wing struct
|
|
int wing_has_conflicting_teams(int wing_index)
|
|
{
|
|
int first_team, idx;
|
|
|
|
// sanity checks
|
|
Assert((wing_index >= 0) && (wing_index < num_wings) && (Wings[wing_index].current_count > 0));
|
|
if((wing_index < 0) || (wing_index >= num_wings) || (Wings[wing_index].current_count <= 0)){
|
|
return -1;
|
|
}
|
|
|
|
// check teams
|
|
Assert(Wings[wing_index].ship_index[0] >= 0);
|
|
if(Wings[wing_index].ship_index[0] < 0){
|
|
return -1;
|
|
}
|
|
first_team = Ships[Wings[wing_index].ship_index[0]].team;
|
|
for(idx=1; idx<Wings[wing_index].current_count; idx++){
|
|
// more sanity checks
|
|
Assert(Wings[wing_index].ship_index[idx] >= 0);
|
|
if(Wings[wing_index].ship_index[idx] < 0){
|
|
return -1;
|
|
}
|
|
|
|
// if we've got a team conflict
|
|
if(first_team != Ships[Wings[wing_index].ship_index[idx]].team){
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// no conflict
|
|
return 0;
|
|
}
|
|
|
|
// get the team of a reinforcement item
|
|
int ship_get_reinforcement_team(int r_index)
|
|
{
|
|
int wing_index;
|
|
p_object *objp;
|
|
|
|
// sanity checks
|
|
Assert((r_index >= 0) && (r_index < Num_reinforcements));
|
|
if((r_index < 0) || (r_index >= Num_reinforcements)){
|
|
return -1;
|
|
}
|
|
|
|
// if the reinforcement is a ship
|
|
objp = mission_parse_get_arrival_ship( Reinforcements[r_index].name );
|
|
if(objp != NULL){
|
|
return objp->team;
|
|
}
|
|
|
|
// if the reinforcement is a ship
|
|
wing_index = wing_lookup(Reinforcements[r_index].name);
|
|
if(wing_index >= 0){
|
|
// go through the ship arrival list and find the first ship in this wing
|
|
objp = GET_FIRST(&ship_arrival_list);
|
|
while( objp != END_OF_LIST(&ship_arrival_list) ) {
|
|
// check by wingnum
|
|
if (objp->wingnum == wing_index) {
|
|
return objp->team;
|
|
}
|
|
|
|
// next
|
|
objp = GET_NEXT(objp);
|
|
}
|
|
}
|
|
|
|
// no team ?
|
|
return -1;
|
|
}
|
|
|
|
// determine if the given texture is used by a ship type. return ship info index, or -1 if not used by a ship
|
|
int ship_get_texture(int bitmap)
|
|
{
|
|
int idx;
|
|
|
|
// check all ship types
|
|
for(idx=0; idx<Num_ship_types; idx++){
|
|
if((Ship_info[idx].modelnum >= 0) && model_find_texture(Ship_info[idx].modelnum, bitmap) == 1){
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
// couldn't find the texture
|
|
return -1;
|
|
}
|
|
|
|
// update artillery lock info
|
|
#define CLEAR_ARTILLERY_AND_CONTINUE() { if(aip != NULL){ aip->artillery_objnum = -1; aip->artillery_sig = -1; aip->artillery_lock_time = 0.0f;} continue; }
|
|
float artillery_dist = 10.0f;
|
|
DCF(art, "")
|
|
{
|
|
dc_get_arg(ARG_FLOAT);
|
|
artillery_dist = Dc_arg_float;
|
|
}
|
|
void ship_update_artillery_lock()
|
|
{
|
|
#if defined(MULTIPLAYER_BETA_BUILD) || defined(FS2_DEMO)
|
|
return;
|
|
#else
|
|
ai_info *aip = NULL;
|
|
weapon_info *tlaser = NULL;
|
|
mc_info *cinfo = NULL;
|
|
int c_objnum;
|
|
vector temp, local_hit;
|
|
ship *shipp;
|
|
ship_obj *so;
|
|
|
|
// update all ships
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ){
|
|
// get the ship
|
|
if((so->objnum >= 0) && (Objects[so->objnum].type == OBJ_SHIP) && (Objects[so->objnum].instance >= 0)){
|
|
shipp = &Ships[Objects[so->objnum].instance];
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
// get ai info
|
|
if(shipp->ai_index >= 0){
|
|
aip = &Ai_info[shipp->ai_index];
|
|
}
|
|
|
|
// if the ship has no targeting laser firing
|
|
if((shipp->targeting_laser_objnum < 0) || (shipp->targeting_laser_bank < 0)){
|
|
CLEAR_ARTILLERY_AND_CONTINUE();
|
|
}
|
|
|
|
// if he didn't hit any objects this frame
|
|
if(beam_get_num_collisions(shipp->targeting_laser_objnum) <= 0){
|
|
CLEAR_ARTILLERY_AND_CONTINUE();
|
|
}
|
|
|
|
// get weapon info for the targeting laser he's firing
|
|
Assert((shipp->weapons.current_primary_bank >= 0) && (shipp->weapons.current_primary_bank < 2));
|
|
if((shipp->weapons.current_primary_bank < 0) || (shipp->weapons.current_primary_bank >= 2)){
|
|
continue;
|
|
}
|
|
Assert(shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank] >= 0);
|
|
if(shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank] < 0){
|
|
continue;
|
|
}
|
|
Assert((Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].wi_flags & WIF_BEAM) && (Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].b_info.beam_type == BEAM_TYPE_C));
|
|
if(!(Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].wi_flags & WIF_BEAM) || (Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].b_info.beam_type != BEAM_TYPE_C)){
|
|
continue;
|
|
}
|
|
tlaser = &Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]];
|
|
|
|
// get collision info
|
|
if(!beam_get_collision(shipp->targeting_laser_objnum, 0, &c_objnum, &cinfo)){
|
|
CLEAR_ARTILLERY_AND_CONTINUE();
|
|
}
|
|
if((c_objnum < 0) || (cinfo == NULL)){
|
|
CLEAR_ARTILLERY_AND_CONTINUE();
|
|
}
|
|
|
|
// get the position we hit this guy with in his local coords
|
|
vm_vec_sub(&temp, &cinfo->hit_point_world, &Objects[c_objnum].pos);
|
|
vm_vec_rotate(&local_hit, &temp, &Objects[c_objnum].orient);
|
|
|
|
// if we are hitting a different guy now, reset the lock
|
|
if((c_objnum != aip->artillery_objnum) || (Objects[c_objnum].signature != aip->artillery_sig)){
|
|
aip->artillery_objnum = c_objnum;
|
|
aip->artillery_sig = Objects[c_objnum].signature;
|
|
aip->artillery_lock_time = 0.0f;
|
|
aip->artillery_lock_pos = local_hit;
|
|
|
|
// done
|
|
continue;
|
|
}
|
|
|
|
// otherwise we're hitting the same guy. check to see if we've strayed too far
|
|
if(vm_vec_dist_quick(&local_hit, &aip->artillery_lock_pos) > artillery_dist){
|
|
// hmmm. reset lock time, but don't reset the lock itself
|
|
aip->artillery_lock_time = 0.0f;
|
|
continue;
|
|
}
|
|
|
|
// finally - just increment the lock time
|
|
aip->artillery_lock_time += flFrametime;
|
|
|
|
// TEST CODE
|
|
if(aip->artillery_lock_time >= 2.0f){
|
|
HUD_printf("Firing artillery");
|
|
|
|
vector temp;
|
|
vm_vec_unrotate(&temp, &aip->artillery_lock_pos, &Objects[aip->artillery_objnum].orient);
|
|
vm_vec_add2(&temp, &Objects[aip->artillery_objnum].pos);
|
|
ssm_create(&temp, &Objects[so->objnum].pos, 0, NULL);
|
|
|
|
// reset the artillery
|
|
aip->artillery_lock_time = 0.0f;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// checks if a world point is inside the extended bounding box of a ship
|
|
// may not work if delta box is large and negative (ie, adjusted box crosses over on itself - min > max)
|
|
int check_world_pt_in_expanded_ship_bbox(vector *world_pt, object *objp, float delta_box)
|
|
{
|
|
Assert(objp->type == OBJ_SHIP);
|
|
|
|
vector temp, ship_pt;
|
|
polymodel *pm;
|
|
vm_vec_sub(&temp, world_pt, &objp->pos);
|
|
vm_vec_rotate(&ship_pt, &temp, &objp->orient);
|
|
|
|
pm = model_get(Ships[objp->instance].modelnum);
|
|
|
|
return (
|
|
(ship_pt.xyz.x > pm->mins.xyz.x - delta_box) && (ship_pt.xyz.x < pm->maxs.xyz.x + delta_box)
|
|
&& (ship_pt.xyz.y > pm->mins.xyz.y - delta_box) && (ship_pt.xyz.y < pm->maxs.xyz.y + delta_box)
|
|
&& (ship_pt.xyz.z > pm->mins.xyz.z - delta_box) && (ship_pt.xyz.z < pm->maxs.xyz.z + delta_box)
|
|
);
|
|
}
|
|
|
|
|
|
// returns true when objp is ship and is tagged
|
|
int ship_is_tagged(object *objp)
|
|
{
|
|
ship *shipp;
|
|
if (objp->type == OBJ_SHIP) {
|
|
shipp = &Ships[objp->instance];
|
|
if ( (shipp->tag_left > 0) || (shipp->level2_tag_left > 0) ) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// get maximum ship speed (when not warping in or out)
|
|
float ship_get_max_speed(ship *shipp)
|
|
{
|
|
float max_speed;
|
|
|
|
int ship_info_index = shipp->ship_info_index;
|
|
|
|
// max overclodk
|
|
max_speed = Ship_info[ship_info_index].max_overclocked_speed;
|
|
|
|
// normal max speed
|
|
max_speed = max(max_speed, Ship_info[ship_info_index].max_vel.xyz.z);
|
|
|
|
// afterburn
|
|
max_speed = max(max_speed, Ship_info[ship_info_index].afterburner_max_vel.xyz.z);
|
|
|
|
return max_speed;
|
|
}
|
|
|
|
// determin warp speed of ship
|
|
float ship_get_warp_speed(object *objp)
|
|
{
|
|
Assert(objp->type == OBJ_SHIP);
|
|
float shipfx_calculate_warp_speed(object *);
|
|
return shipfx_calculate_warp_speed(objp);
|
|
}
|
|
|
|
// returns true if ship is beginning to speed up in warpout
|
|
int ship_is_beginning_warpout_speedup(object *objp)
|
|
{
|
|
Assert(objp->type == OBJ_SHIP);
|
|
|
|
ai_info *aip;
|
|
|
|
aip = &Ai_info[Ships[objp->instance].ai_index];
|
|
|
|
if (aip->mode == AIM_WARP_OUT) {
|
|
if ( (aip->submode == AIS_WARP_3) || (aip->submode == AIS_WARP_4) || (aip->submode == AIS_WARP_5) ) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// given a ship info type, return a species
|
|
int ship_get_species_by_type(int ship_info_index)
|
|
{
|
|
// sanity
|
|
if((ship_info_index < 0) || (ship_info_index >= Num_ship_types)){
|
|
return -1;
|
|
}
|
|
|
|
// return species
|
|
return Ship_info[ship_info_index].species;
|
|
}
|
|
|
|
// return the length of a ship
|
|
float ship_get_length(ship* shipp)
|
|
{
|
|
polymodel *pm = model_get(shipp->modelnum);
|
|
return (pm->maxs.xyz.z - pm->mins.xyz.z);
|
|
}
|
|
|