Descent3/Descent3/object.cpp
Azamat H. Hackimov 9416458f9c Cleanup mono debugging code
Remove unused mono debug code.
2024-06-03 01:40:33 +03:00

3517 lines
96 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/main/object.cpp $
* $Revision: 315 $
* $Date: 3/20/00 12:18p $
* $Author: Matt $
*
* Object management functions
*
* $Log: /DescentIII/main/object.cpp $
*
* 315 3/20/00 12:18p Matt
* Merge of Duane's post-1.3 changes.
* Memset ai_frame to zeros after malloc.
*
* 314 1/26/00 9:20p Jeff
* added support for IntelliVIBE DLL
*
* 313 10/25/99 9:52a Matt
* Undid a Mac change (Duane says it's ok)
*
* 312 10/23/99 12:51a Matt
* Fixed compile warning
*
* 311 10/22/99 10:44p Jeff
* mac merge bug fix
*
* 310 10/22/99 3:43p Matt
* Mac merge
*
* 309 10/20/99 5:40p Chris
* Added the Red Guidebot
*
* 308 10/08/99 4:24p 3dsmax
* some ambient sound fix done by MattT
*
* 307 9/18/99 9:24p Jeff
* object position history code (needed for motion blur effect)
*
* 306 5/22/99 1:54a Matt
* Got rid of debug code mistakenly checked in.
*
* 305 5/21/99 3:43a Matt
*
* 304 5/20/99 9:03p Matt
* Changed trigger system so that collisions with rendered portals don't
* set off triggers on the portals.
*
* 303 5/20/99 3:00p Jason
* added support for negative respawn scalars
*
* 302 5/13/99 7:18p Jeff
* fixed unattach bug in delete dead
*
* 301 5/10/99 8:29p Jason
* robots no longer burn on terrain that causes damage
*
* 300 5/08/99 4:12p Chris
* Added AI hearing noises... version 1
*
* 299 5/06/99 12:52p Jason
* fixed set on fire bug
*
* 298 5/05/99 5:18p Jason
* Invul generic objects don't burn on damaging terrain
*
* 297 5/05/99 5:10a Matt
* Fixed a bug with dying & animating objects.
*
* 296 4/29/99 4:50p Chris
* Fixed matcen and Osiris created objects so thier ambient sounds play
*
* 295 4/29/99 3:09p Jason
* fixed a potential bug with object deaths
*
* 294 4/27/99 10:25p Jason
* slowed down afterburner outside just a bit
*
* 293 4/27/99 9:13a Matt
* When clearing a room's secret flag, clear the flag from all the
* connected rooms.
*
* 292 4/26/99 2:30p Jason
* fixed bug with secret messages
*
* 291 4/26/99 2:28p Jason
* added persistant hud messages
*
* 290 4/25/99 5:20p Jason
* fixed some more coop bugs
*
* 289 4/24/99 2:00p Chris
* Removed the slow terrain cell verify check from ObjMoveOne when Del+Y
* is in effect
*
* 288 4/23/99 7:18p Jason
* fixed some multiplayer issues with observer mode
*
* 287 4/22/99 7:13p Jeff
* fixed bug deleting ghost object in multiplayer
*
* 286 4/22/99 4:19p Chris
* Checked in with the extended DEL+Y terrain node check stuff
*
* 285 4/21/99 3:01p Matt
* Added a new type for dying objects that have AI, instead of keeping a
* flag in the dying info.
*
* 284 4/21/99 11:05a Kevin
* new ps_rand and ps_srand to replace rand & srand
*
* 283 4/20/99 12:24p Matt
* Re-revised the ObjInit() system. ObjInit() again does all the
* initialization stuff, but now it's passed a lot more information so
* those fields can be set before the rest of the initialization takes
* place.
*
* 282 4/18/99 8:13p Chris
* Fixed the floating flare problems (where windows where broken out and
* the flare remained)
*
* 281 4/18/99 5:33p Jason
* fixed object multiplayer problem
*
* 280 4/16/99 12:33a Matt
* Disable Soar on non-Windows systems.
*
* 279 4/15/99 1:41a Jeff
* changes for linux compile
*
* 278 4/14/99 3:56a Jeff
* fixed case mismatch in #includes
*
* 277 4/12/99 6:15p Samir
* Sound priorities pass 1
*
* 276 4/12/99 8:18a Chris
*
* 275 4/12/99 8:12a Chris
*
* 274 4/12/99 8:06a Chris
* Added an ASSERT to check init code that doesn't call
* ComputeDefaultSize() for polygon objects
*
* 273 4/10/99 5:56p Matt
* Cleaned up object initialization code, including create object & load
* object.
*
* 272 4/09/99 12:06p Jason
* made model setup code faster
*
* 271 4/05/99 10:54a Matt
* Added auto-waypoint system
*
* 270 4/03/99 4:24p Jason
* sped up attached vis effects by a large amount
*
* 269 3/28/99 5:56p Matt
* Added sparking effect for objects
*
* 268 3/16/99 9:01a Kevin
* Fixed generic check to also check for doors.
*
* 267 3/12/99 7:47p Jeff
* only call applydamagetogeneric for generic objects
*
* 266 3/04/99 6:12p Jason
* fixed bug with terrain LOD
*
* 265 3/01/99 8:34p Matt
* Made room damage system set the player on fire instead of applying
* damage directly. This system still needs work, but is ok for now.
*
* 264 3/01/99 6:27p Matt
* Cleaned up handling of the outside-mine flag
*
* 263 3/01/99 3:15p Jason
* fixed multiplayer object deletion bug
*
* 262 2/28/99 6:36p Kevin
* More progress on load/save
*
* 261 2/27/99 5:40p Jason
* fixed bug where clients weren't getting remove messages for timed out
* powerups
*
* 260 2/25/99 11:01a Matt
* Added new explosion system.
*
* 259 2/25/99 10:29a Jason
* fixed powerup stalling bug in netgames
*
* 258 2/24/99 1:02p Jason
* various fixes for multiplayer
*
* 257 2/23/99 7:35p Jeff
* removed assert/return in unghost if the item is in a player's inventory
* (needs to be called when sending over objects in a multiplayer game,
* causes an assert)
*
* 256 2/23/99 5:13p Kevin
* Fixed it so objects with lightmaps will retain their lightmaps when a
* game is saved & loaded.
*
* 255 2/23/99 12:39p Jason
* added more options for generic objects
*
* 254 2/22/99 3:26a Matt
* Deleted obsolete code
*
* 253 2/22/99 1:20a Jeff
* added support for inventory (simple) in dallas. Moved end-level
* sequence to use IGC. Add position clipboard stuff for dallas. Fixed
* some inventory bug with storing object handles
*
* 252 2/21/99 4:31p Chris
* Improving the level goal system... Not done.
*
* 251 2/21/99 4:20p Matt
* Added SoundSource objects (and reformatted parts of the object header
* files).
*
* 250 2/19/99 5:16p Chris
* Added the child died event
*
* 249 2/15/99 11:22a Jason
* took out client-side prediction
*
* 248 2/13/99 12:36a Jeff
* new object flag. set for when an object is currently in a player's
* inventory
*
* 247 2/12/99 5:59p Jason
* more permissable server stuff
*
* 246 2/12/99 3:38p Jason
* added client side interpolation
*
* 245 2/10/99 2:49p Matt
* Renamed OBJECT_HANDLE_INVALID to OBJECT_HANDLE_BAD
*
* 244 2/10/99 1:47p Matt
* Changed object handle symbolic constants
*
* 243 2/08/99 5:25p Jeff
* removed all calls to MultiSendRemoveObject, incorportated into
* SetObjectDeadFlag. Fixes sequencing issues in multiplayer
*
* 242 2/07/99 1:20a Jeff
* added new multiplayer game events EVT_GAMEOBJKILLED and
* EVT_GAMEOBJDESTROYED
*
* 241 2/05/99 1:27p Matt
* Only do cycled animations for generic objects
*
* 240 2/03/99 6:32p Jason
* more changes for world states
*
* 239 2/03/99 5:49p Matt
* Added room damage system
*
* 238 2/02/99 8:43a Chris
* I made buildings with AI work correctly (ie really big robots should be
* buildings)
* anim to and from states are now shorts instead of bytes
*
* 237 2/01/99 4:17p Jason
* more changes for multisafe
*
* 236 1/31/99 7:26p Matt
* Renamed a bunch of functions to have HUD capitalized
*
* 235 1/29/99 5:22p Jeff
* localization
*
* 234 1/27/99 6:48p Jeff
* better error handling for ghost/unghost (release build)
*
* 233 1/24/99 1:24a Jason
* fixed chase camera being deleted wrong
*
* 232 1/23/99 2:49p Kevin
* Fixed player movement masking
*
* 231 1/22/99 8:53p Jeff
* added custom-default script overrides
*
* 230 1/22/99 7:12p Jason
* changed terrain speed
*
* 229 1/22/99 4:23p Jason
* sped up afterburner temporarily
*
* 228 1/21/99 11:15p Jeff
* pulled out some structs and defines from header files and moved them
* into seperate header files so that multiplayer dlls don't require major
* game headers, just those new headers. Side effect is a shorter build
* time. Also cleaned up some header file #includes that weren't needed.
* This affected polymodel.h, object.h, player.h, vecmat.h, room.h,
* manage.h and multi.h
*
* 227 1/21/99 6:50p Jason
* added test impact mortar
*
* 226 1/21/99 3:34p Jason
* added liquid code
*
* 225 1/20/99 8:06p Jeff
* added members into DLLinfo struct for game change segment events, pass
* them over on execute dll packets
*
* 224 1/20/99 2:13a Chris
* It is now possible for robots to have special immunities, resistances,
* and vunerabilities
*
* 223 1/19/99 4:22p Matt
* Added the ability for objects to have their own lighting info,
* different from the default lighting for that type of object.
*
* 222 1/19/99 9:20a Kevin
* Added afterburner masking
*
* 221 1/18/99 6:18p Kevin
* Added controller masking to DALLAS
*
* 220 1/18/99 2:46p Matt
* Combined flags & flags2 fields in object struct
*
* 219 1/18/99 12:01a Jeff
* fixed EVT_GAMEPLAYERCHANGESEG event (would only get called if the
* server changed seg) and added EVT_GAMEOBJCHANGESEG
*
* 218 1/15/99 7:52p Chris
* Updated ObjSetPos() to include a f_update_attach_children flag
*
* 217 1/15/99 2:53p Jason
* fixed multiplayer ping bug
*
* 216 1/13/99 6:31p Jason
* added punch to the afterburner
*
* 215 1/13/99 2:27p Jeff
* added an is_dying flag for evt_destroy to determine whether the event
* is due to level end or because it really is dying
*
* 214 1/12/99 11:44p Jason
* Fixed bug with accelerated ping objects
*
* 213 1/08/99 2:56p Samir
* Ripped out OSIRIS1.
*
* 212 1/06/99 7:02p Jeff
* added a multiplayer event for game controls
*
* 211 1/05/99 5:09p Jason
* added permissable server networking (ala Quake/Unreal) to Descent3
*
* 210 12/31/98 7:35p Jeff
* don't allow players to be Ghosted (turned into OBJ_DUMMY). Also, for
* ObjDelete[Dead] unghost a ghosted object before killing it
*
* 209 12/30/98 12:31p Jeff
* when freeing object scripts, the type is changed before it's freed in
* ObjDelete. Also, when destroyed, OBJ_ROBOT becomes OBJ_DEBRIS and
* Osiris was not handling this.
*
* 208 12/28/98 10:16a Jason
* fixed code that was causing some weapons to disappear
*
* 207 12/21/98 11:30p Matt
* Added names for objects
*
* 206 12/17/98 12:08p Jeff
* first checkin of new implementation of OSIRIS (old OSIRIS no longer
* works)
*
* 205 12/10/98 7:09p Jason
* added cloak fade
*
* 204 12/08/98 4:27p Chris
* Removed MT_WALKING hack
*
* 203 12/01/98 3:35p Matt
* Got rear view working.
*
* 202 12/01/98 3:15p Jason
* play refueling sound in 3d for multiplayer
*
* 201 11/17/98 4:16p Kevin
* Demo recording system
*
* 200 11/13/98 4:25p Jason
* changes for better weapon effects
*
* 199 11/13/98 2:27p Samir
* took out music code.
*
* 198 11/11/98 2:46p Kevin
* Demo recording system work
*
* 197 11/10/98 4:48p Jason
* sped up afterburner recharge time
*
* 196 11/10/98 11:16a Jason
* fixed some multiplayer bugs with powerups disappearing
*
*
* 195 11/06/98 11:05a Kevin
* Added code for demo play back
*
* 194 11/02/98 6:49p Jason
* fixed ifdef release problem
*
* 193 11/02/98 4:04p Luke
* Temp. disabled walkers inside
*
* 192 10/30/98 4:24p Jason
* changes for multiplayer
*
* 191 10/29/98 5:20p Chris
* Player ships collide smaller now
*
* 190 10/28/98 11:51a Jason
* fixed some multiplayer problems, plus redid coop a bit to make it
* cleaner
*
* 189 10/21/98 12:13p Jason
* took out guided in multi
*
* 188 10/20/98 5:47p Kevin
* Gunboy and other fixes
*
* 187 10/20/98 1:19p Jason
* fixed afterburner and Of2_SERVER_OBJECT bug
*
* 186 10/20/98 10:50a Sean
*
* 185 10/19/98 11:46p Jason
* changes for multiplayer debug layer
*
* 184 10/19/98 10:45p Jason
* toned down afterburner outside
*
* 183 10/19/98 7:51p Kevin
* performance testing
*
* 182 10/19/98 7:18p Matt
* Added system to support different types of damage to the player and
* have these different types make different sounds.
*
* 181 10/19/98 1:21p Jason
* made afterburner faster
*
* 180 10/18/98 11:07p Matt
* Made ClearTransientObjects() work.
*
* 179 10/17/98 5:50p Jeff
* hooked in rtperformance (run time performance) library
*
* 178 10/16/98 4:18p Chris
* Fixed the 'flare thing'
*
* 177 10/16/98 3:39p Chris
* Improved the object linking system and AI and physics
*
* 176 10/15/98 8:47a Matt
* Changed _DEBUG/RELEASE inconsistancy with slew
*
* 175 10/14/98 1:02p Jason
* fixed FindSoundName issues
*
* 174 10/14/98 12:43a Matt
* Fixed error handling for when an object can't be initialized.
*
* 173 10/13/98 1:08p Chris
* Greatly improved the AI's use of paths. Improved visability checking
* algorithm. Probably needs a second pass for further cleanup.
*
* 172 10/11/98 10:53p Matt
* Fixed some zero-length malloc()s
*
* 171 10/11/98 2:39p Matt
* If ObjInit() fails, ObjCreate() now returns an error.
*
* 170 10/08/98 4:23p Kevin
* Changed code to comply with memory library usage. Always use mem_malloc
* , mem_free and mem_strdup
*
* 169 10/06/98 5:46p Kevin
* Added new configuration for demo
*
* 168 10/06/98 2:59p Jason
* added timedemo function
*
* 167 10/06/98 11:27a Jason
* added new death type for robots
*
* 166 10/03/98 8:05p Matt
* Added asserts
*
* 165 9/28/98 6:23p Chris
* Changed multi_anim to custom_anim
*
* 164 9/23/98 2:55p Chris
* Improved auto-leveling and added newbie leveling
*
* 163 9/22/98 6:48p Jason
* fixed dedicated server problems
*
* 162 9/22/98 6:04p Samir
* ifdef DoAI to 1 if in release version.
*
* 161 9/22/98 1:02p Matt
* Don't set previous room info for an object if the object was moved in
* the editor.
*
* 160 9/22/98 12:40p Matt
* Cleaned up the object per-frame processing code.
*
* 159 9/17/98 3:03p Jason
* added lightning and invul hit effects
*
* 158 9/10/98 12:18p Jason
* more changes to afterburner/thrust effect
*
* 157 9/09/98 7:09p Jason
* changed afterburner effect for ships
*
* 156 9/09/98 4:30p Jason
* fixed a remaining (and insidious) bug with gunpoint instancing
*
* 155 8/31/98 6:12p Luke
* Moved the multi turret deletion code to somewhere where I can debug
* things
*
* 154 8/24/98 4:48p Jason
* undid my last rev
*
* 153 8/24/98 2:52p Jason
* fixed instance problem
*
* 152 8/19/98 11:30a Jason
* fixed afterburner problem with low framerates
*
* 151 8/17/98 12:10p Chris
* Fixed MAJOR bug in getting gunpoint positions
*
* 150 8/14/98 6:21p Chris
* Added another safety check for docycledanim
*
* 149 8/12/98 6:37p Jeff
* added functions to ghost an object (make it's type to OBJ_DUMMY) and
* unghost
*
* 148 8/12/98 12:04p Chris
* Attach system version .5 (still needs more work to be multiplayer
* friendly)
*
* 147 8/07/98 8:25p Chris
* Improved the attach system
*
* 146 8/07/98 6:54p Jeff
* afterburner powerup effect added
*
* 145 8/03/98 4:29p Chris
* Added additional support for the attach system
*
* 144 7/30/98 11:09a Jason
* added weapons that freeze and deform terrain
*
* 143 7/28/98 5:43p Samir
* added room change hook for music system,
*
* 142 7/22/98 3:16p Jason
* added observer mode
*
* 141 7/20/98 10:42a Jason
* added more player scalars, plus afterburner changes
*
* 140 7/16/98 12:51p Mark
* Fixed a potential bug
*
* 139 7/15/98 2:33p Jason
* added scalar variables for various player skills
*
* 138 7/15/98 12:48p Jeff
* put in calls to handle new multiplayer event...when an object's shields
* change
*
* 137 7/14/98 5:52p Kevin
* Packet loss measurements and auto pps adjusting
*
* 136 7/14/98 3:54p Sean
* CHRIS -- Fixed a bug in free'ing multi_turret_info on objects that are
* not polygon models (i.e. they don't have that info and it is union'ed
* to something else so it is not NULL)
*
* 135 7/09/98 7:50p Jeff
* added ObjGetUltimateParent to find out the original parent of an
* object, traces the family tree
*
* 134 7/09/98 3:30p Chris
* Fixed communication problem between systems
*
* 133 7/09/98 11:34a Chris
* Turrets are interpolated.
*
* 132 7/08/98 11:38a Chris
* Improved the turret info passing in multiplayer
*
* 131 7/07/98 3:26p Chris
* Added changes for turret updates
*
* 130 7/02/98 2:47p Chris
* Dynamic weapon info is now dynamically allocated
*
* 129 7/01/98 4:35p Chris
* More multiplayer sync issues
*
* 128 7/01/98 2:02p Chris
* Added the sound for animations
*
* 127 6/30/98 6:36p Chris
* Added rev .1 of multiplayer animations - BTW It is totally not done.
*
* 126 6/30/98 4:28p Chris
* Checked in some missing commas
*
* 125 5/26/98 7:07p Samir
* reset script.is_custom to 1 when calling ObjCreate.
*
* 124 5/22/98 4:45p Chris
* Powerups us the correct anim time
*
* 123 5/19/98 4:42a Chris
* Added shockwave's -- enjoy. :)
*
* 122 5/15/98 5:41p Jason
* implemented volume lighting system
*
* 121 5/11/98 4:40p Chris
* AI info is now a malloc'ed pointer
*
* 120 5/04/98 6:47p Craig
* Rooms cannot cycle through anims
*
* 119 5/04/98 12:30p Matt
* ObjCreate() now takes object id as a uint16_t
*
* 118 5/04/98 12:28p Matt
* Added shard objects
*
* 117 5/01/98 3:41p Chris
*
* 116 5/31/98 3:08p Chris
* Allowed for death animations
*
* 115 4/27/98 1:14p Jason
* cleaned up afterburner stuff
*
* 114 4/23/98 12:44p Jason
* made AI toggleable to test framerate - this is temporary
*
* 113 4/22/98 4:15p Chris
* Improved DebugBlockPrint
*
* 112 4/22/98 3:50p Chris
* Added DebugBlockPrint
*
* 111 4/22/98 2:10p Matt
* Made FreeAllObjects() free the player object (object 0), since objects
* now contain malloc'd data and must be freed.
*
* 110 4/20/98 6:24p Chris
* Bulletproofing collision code
*
* 109 4/19/98 5:00p Jason
* added cool napalm effect, plus made object effects dynamically
* allocated
*
* 108 4/17/98 1:59p Jason
* added cool object effects
*
* 107 4/16/98 2:56p Chris
* Updated buddy support and intergrated scripting and AI
*
* 106 4/15/98 8:38p Jason
* fixed some multiplayer issues
*
* 105 4/15/98 12:56p Chris
* Updated parent_handle support and added buddy bot support
*
* 104 4/13/98 2:21p Chris
* Fixed some collision problems dealing with AABBs and Polymodel paging
* in.
*
* 103 4/13/98 12:42p Jason
* changed afterburner effect
*
* 102 4/10/98 2:42p Jason
* fixed afterburner blobs to be a stream
*
* 101 4/10/98 2:16p Jason
* fixed guided missile problems
*
* 100 4/09/98 5:18p Jason
* got guided working in multiplayer
*
* 99 4/09/98 3:04p Jason
* temp fix for afterburner running out of fuel
*
* 98 4/09/98 2:23p Jason
* added guided/afterburner stuff
*
* 97 4/07/98 1:12p Jason
* make powerup respawn a variable in multiplayer games
*
* 96 4/03/98 11:04a Jason
* added a multplayer dll hook for when a player changes a room
*
* 95 4/01/98 6:23p Jason
* added a slew of stuff for multiplayer
*
* 94 3/31/98 5:56p Jason
* changes for multiplay
*
* 93 3/31/98 4:23p Chris
* Added a new AIpath system
*
* 92 3/20/98 9:34p Jason
* added SetObjectDeadFlag inlined function
*
* 91 3/13/98 5:55p Chris
* Added the new collision spheres
*
* 90 3/12/98 7:30p Chris
* Added ObjSetOrient
*
* 89 2/25/98 3:01p Jason
* added splinter objects to FreeObjects
*
* 88 2/25/98 2:05p Jason
* did FOV and object visibility changes
*
* 87 2/20/98 1:46p Chris
* JASON: Made dynamic lighting only occur if the object is rendered
*
* 86 2/19/98 11:17a Chris
* Tweaked the BIG_OBJECT system
*
* 85 2/17/98 3:46p Matt
* Revamped weapon system and got sounds for spray and fusion weapons
* working. Still need to implements getting continuous & cutoff sounds
* from player ship.
*
* 84 2/16/98 2:49p Chris
* Made the MAX_SUBOBJECTS determine the number of normalized_time values
* to be processed. No longer a 'literal' problem.
*
* 83 2/13/98 6:59p Chris
* FIxed the normalized_time array from being smaller than the number of
* subobjects. Also update newstyle_fi.cpp
*
* 82 2/12/98 8:48p Matt
* Changed controls system to keep the reading of the controls seperate
* from using the results. Got rid of the Controls global.
*
* 81 2/11/98 2:04p Jason
* got spawning weapons working
*
* 80 2/09/98 3:20p Matt
* Changed assert to allow OBJECT_HANDLE_INVALID to be pased to ObjGet()
*
* 79 2/05/98 3:00p Matt
* Made ObjSetPos() take optional orienation. Got rid of unlink & relink
* funcs from header.
*
* 78 2/04/98 6:09p Matt
* Changed object room number to indicate a terrain cell via a flag. Got
* rid of the object flag which used to indicate terrain.
*
* 77 2/03/98 1:06p Jason
* tidied up some multiplayer loose ends
*
* 76 1/30/98 5:26p Chris
* Allow all non-AI objects to animate
*
* 75 1/29/98 5:49p Matt
* Changed old camera object type to be viewer object (for editor), and
* now camera objects will just be for game cameras.
*
* 74 1/27/98 12:26p Samir
* Added GetObjectPointInWorld.
*
* 73 1/23/98 11:21a Jason
* incremental multiplayer checkin
*
* 72 1/21/98 1:11p Jason
* incremental checkin for multiplayer
*
* 71 1/20/98 6:01p Jason
* first pass at getting multiplayer deaths working
*
* 70 1/20/98 4:12p Samir
* New script housekeeping system.
*
* 69 1/20/98 12:27p Luke
* Added an assert to make sure that we are not linking to an external
* room
*
* 68 1/20/98 12:10p Jason
* implemented vis effect system
*
* 67 1/19/98 2:44p Samir
* Use one script per object and started parameter passing support.
*
* 66 1/19/98 12:38p Jason
* took out mprintf in ObjCreate
*
* 65 1/19/98 10:04a Matt
* Added new object handle system
*
* 64 1/15/98 6:49p Jason
* incremental checkin for multiplayer
*
* 63 12/30/97 5:45p Jason
* added toggleable fog
*
* 62 12/19/97 2:46p Jason
* implemented bitmap paging routines
*
* 61 12/12/97 3:42p Jason
* free all objects on shutdown
*
* 60 12/01/97 9:56a Chris
* Removed FLARE and CNTRL_CENTER types
*
* 59 11/10/97 12:27p Jason
* added objects live mprintf
*
* 58 10/28/97 12:22p Chris
* Added a movement type of walking
*
* 57 10/23/97 5:36p Jason
* added splinter objects (probably temporary)
*
* 56 10/22/97 5:14p Chris
* Allow for non-AI objects to animate
*
* 55 10/20/97 4:46p Jason
* changes for explosions
*
* 54 10/14/97 6:53p Chris
* Added the initial SOAR support
*
* 53 10/08/97 6:35p Samir
* Dying player ships get marked as destroyed. When death sequence is
* over, they are marked as dead. This is needed to better control
* movement of the dying ship.
*
* 52 10/03/97 5:18p Chris
* Added OBJ_LINE as a new type
*
* 51 10/02/97 1:04p Jason
* implemented FreeAllObjects
*
* 50 10/01/97 7:51p Matt
* Added code for external rooms
*
* 49 9/30/97 6:40p Jason
* got lightmap stuff sort of working with objects
*
* 48 9/22/97 6:20p Matt
* Killed include of stub.h, and removed references to stuff that had been
* in stub.h
*
* 47 9/19/97 6:58p Chris
* Fixed some bugs with the big object system and removed some annoying
* mprintf's
*
* 46 9/19/97 1:01p Chris
* Better big object support
*
* 45 9/18/97 1:25p Matt
* Cleaned up code, added the text name for door objects, and other small
* changes.
*
* 44 9/17/97 11:16a Matt
* Ripped out segment code
*
* 43 9/16/97 5:04p Matt
* Changed conditional for debug code
*
* 42 9/15/97 12:42p Samir
* Added include of stdlib.
*
* 41 9/15/97 5:21a Chris
* Added sphere2poly support
*
* 40 9/12/97 6:36p Chris
* Added collision terrain segment support
* Added some LARGE OBJECT collision support
*
* 39 9/12/97 10:12a Matt
* Added trigger check
*
* 38 9/05/97 3:22p Jason
* changes for lit objects
*
* 37 9/05/97 1:29p Jason
* revamped generic object lighting
*
* 36 9/05/97 12:26p Samir
* Player initiates death sequence if killed.
*
* 35 9/04/97 5:26p Jason
* got pulsing objects to work
*
* 34 9/04/97 12:11p Jason
* added flickering generic objects
*
* 33 9/03/97 6:17p Jason
* added code to support powerups/robots/etc that cast light
*
* 32 9/03/97 3:53p Jason
* got objects to cast light
*
* 31 8/25/97 7:32p Chris
*
* 30 8/25/97 6:18p Chris
*
* 29 8/25/97 5:14p Craig
*
* 28 8/25/97 12:41p Chris
* Made sure that all object movements and mine changes result in AABB
* updates.
*
* 27 8/22/97 10:51a Samir
* call destroy script if object is being killed.
*
* 26 8/21/97 7:47p Matt
* ObjSetPos() wasn't relinking the object when the room changed
*
* 25 8/21/97 5:57p Samir
* Added code that kills scripts when object is being deleted.
*
* 24 8/20/97 5:04p Samir
* Added frame interval scripting.
*
* 23 8/18/97 1:45a Chris
* Fix some bugs.
*
* 22 8/12/97 3:56p Matt
* Added new object type: OBJ_BUILDING
*
* 21 8/12/97 1:13p Chris
* Added AABBs.
*
* 20 8/11/97 6:48p Matt
* Rewrote static object remap system
*
* 19 8/11/97 1:53p Matt
* Ripped out robot & powerup pages, and added generic page
*
* 18 8/04/97 5:29p Matt
* Removed some not-to-be-used room code
*
* 17 8/04/97 4:08p Matt
* Added ObjSetPos() that takes the new room number
*
* 16 8/04/97 12:37p Chris
* We now support fvi in Rooms :)
*
* 15 7/29/97 10:27a Jason
* added RenderOBject abstraction layer
*
* 14 7/28/97 1:14p Chris
* Added support for sub-object visability. Plus, debris.
*
* 13 7/16/97 4:09p Jason
* ripped out temporary object lighting
*
*
* 12 7/16/97 3:43p Jason
* took off lighting of objects
*
* 11 7/15/97 5:32p Jason
* got simple static lighting working with objects
*
* 10 7/15/97 4:59p Chris
*
* 9 7/15/97 4:59p Chris
* added support for AI and animations
*
* 50 6/24/97 6:54p Mark
*
* 49 6/24/97 6:07p Matt
* Use DrawPolygonModel() directly, instead of DrawPolygonObject()
*
* 48 6/06/97 4:07p Jason
* verify tmap2 textures if in editor mode
*
* 47 5/16/97 2:17p Samir
* ResetPlayerObject added.
*
* 46 5/15/97 6:09p Chris
*
* 45 5/14/97 3:41p Chris
*
* 44 5/14/97 12:32p Matt
* Took out timer stuff for refuel sound; instead, we set the execlusive
* flag for the sound itself.
*
* 43 5/13/97 10:31p Matt
* Added code for refueling centers.
* Dead objects now get deleted right after the simulation is done.
*
* 42 4/30/97 5:43p Jason
* remap polymodels when pagefile is done loading
*
* 41 4/24/97 5:42p Jason
* got fireball vclips working
*
* 40 4/17/97 2:48p Samir
* ReadFlyingControls renamed from read_flying_controls to match naming
* convention rules.
*
* 39 4/14/97 4:23p Jason
* made objects work with DrawWeaponObject
*
* 38 4/14/97 3:48p Jason
* changed RT_LASER to RT_WEAPON
*
* 37 4/04/97 4:33p Chris
*
* 36 4/04/97 2:45p Matt
* Removed prototypes for deleted functions
*
* 35 4/02/97 3:44p Matt
* Find a valid Ships[] entry, instead of assuming that Ships[0] is valid.
*
* 34 4/02/97 2:26p Jason
* made RemapEverything actually call every remap function
*
* 33 4/01/97 10:49p Matt
* Get player ship model num from Ship page
*
* 32 3/27/97 10:45a Chris
* Set player mass and drag info
*
* 31 3/26/97 3:29p Jason
* made robots work/render
*
* 30 3/26/97 3:02p Chris
* Fixed a bug in ObjMoveAll... It only moved every other object.
*
* 29 3/24/97 4:11p Chris
* Fixed a bug by the TerrainMaster... It seems that !obj->flags &
* OF_OVER_TERRAIN is not the same as !(obj->flags & OF_OVER_TERRAIN) and
* it isn't and shouldn't be. :)
*
* 28 3/21/97 5:01p Jason
* incremental terrain improvments
*
*
* 27 3/20/97 5:45p Jason
* made object work with and render on terrain
*
* 26 3/17/97 12:11p Chris
*
* 25 3/17/97 11:56a Chris
* Forced player object to have valid mass and drag. Will fix this
* correctly at a later date.
*
* 24 3/14/97 12:18p Chris
* Tweak physics and remove braking for now.
*
* 23 3/12/97 6:31p Chris
* Added player flight controls while in game
*
* 22 3/05/97 3:09p Jason
* added RemapDoors to RemapEverything
*
* 21 3/5/97 1:40 PM Jeremy
* only #include editor headers when EDITOR is defined
*
* 20 2/28/97 2:37p Matt
* Draw wireframe box around editor's current object
*
* 19 2/27/97 5:10p Samir
* Added crude trigger checking while flying.
*
* 18 2/18/97 6:31p Matt
* Changed Frame_time to Frametime, & added Gametime
*
* 17 2/13/97 12:16p Jason
* changes for powerup remapping
*
* 16 2/12/97 5:37p Jason
* changed GamePowerups to Powerups to keep from
* getting lynched
*
* 15 2/11/97 6:51p Matt
* Ripped out some old stuff & renamed a function
*
* 14 2/10/97 5:39p Matt
* Fixed to compile with EDITOR defined
*
* 13 2/07/97 5:48p Matt
* Renamed vector.h to vecmat.h to fix DevStudio problem
*
* $NoKeywords: $
*/
#include <stdlib.h>
#include <string.h> // for memset
#include <stdio.h>
#include "object.h"
#include "pserror.h"
#include "vecmat.h"
#include "mono.h"
#include "descent.h"
#include "player.h"
#include "slew.h"
#include "game.h"
#include "trigger.h"
#include "PHYSICS.H"
#include "collide.h"
#include "door.h"
#include "controls.h"
#include "terrain.h"
#include "polymodel.h"
#include "gametexture.h"
#include "ship.h"
#include "soundload.h"
#include "weapon.h"
#include "objinit.h"
#include "fireball.h"
#include "hlsoundlib.h"
#include "sounds.h"
#include "AIMain.h"
#include "room.h"
#include "objinfo.h"
#include "lighting.h"
#include "findintersection.h"
#include "lightmap_info.h"
#include "object_lighting.h"
#include "soar.h"
#include "splinter.h"
#include "ObjScript.h"
#include "viseffect.h"
#include "multi.h"
#include "game2dll.h"
#include "robot.h"
#include "damage.h"
#include "attach.h"
#include "dedicated_server.h"
#include "hud.h"
#include "demofile.h"
#include "rtperformance.h"
#include "osiris_dll.h"
#include "gameloop.h"
#include "mem.h"
#include "stringtable.h"
#include "levelgoal.h"
#include "psrand.h"
#include "vibeinterface.h"
#ifdef EDITOR
#include "editor\d3edit.h"
#endif
#include <algorithm>
/*
* Global variables
*/
object *Player_object = NULL; // the object that is the player
object *Viewer_object = NULL; // which object we are seeing from
static int16_t free_obj_list[MAX_OBJECTS];
// Data for objects
// -- Object stuff
// info on the various types of objects
object Objects[MAX_OBJECTS];
tPosHistory Object_position_samples[MAX_OBJECT_POS_HISTORY];
uint8_t Object_position_head;
int16_t Object_map_position_history[MAX_OBJECTS];
int16_t Object_map_position_free_slots[MAX_OBJECT_POS_HISTORY];
uint16_t Num_free_object_position_history;
int Num_objects = 0;
int Highest_object_index = 0;
int Highest_ever_object_index = 0;
int print_object_info = 0;
#ifdef EDITOR
// This array matches the object types in object.h
char *Object_type_names[MAX_OBJECT_TYPES] = {
"WALL", // OBJ_WALL 0
"FIREBALL", // OBJ_FIREBALL 1
"ROBOT", // OBJ_ROBOT 2
"SHARD", // OBJ_SHARD 3
"PLAYER", // OBJ_PLAYER 4
"WEAPON", // OBJ_WEAPON 5
"VIEWER", // OBJ_VIEWER 6
"POWERUP", // OBJ_POWERUP 7
"DEBRIS", // OBJ_DEBRIS 8
"CAMERA", // OBJ_CAMERA 9
"SHOCKWV", // OBJ_SHOCKWAVE 10
"CLUTTER", // OBJ_CLUTTER 11
"GHOST", // OBJ_GHOST 12
"LIGHT", // OBJ_LIGHT 13
"COOP", // OBJ_COOP 14
"UNUSED", // OBJ_MARKER 15
"BUILDING", // OBJ_BUILDING 16
"DOOR", // OBJ_DOOR 17
"ROOM", // OBJ_ROOM 18
"LINE", // OBJ_PARTICLE 19
"SPLINTER", // OBJ_SPLINTER 20
"DUMMY", // OBJ_DUMMY 21
"OBSERVER", // OBJ_OBSERVER 22
"DEBUG LINE", // OBJ_DEBUG_LINE 23
"SOUNDSOURCE", // OBJ_SOUNDSOURCE 24
"WAYPOINT", // OBJ_WAYPOINT 25
};
#endif
int Num_big_objects = 0;
int16_t BigObjectList[MAX_BIG_OBJECTS]; // DAJ_MR utb int
/*
* Local Function Prototypes
*/
// Do refueling centers & damage rooms
void DoSpecialPlayerStuff(object *obj);
/*
* Functions
*/
#ifndef RELEASE
// set viewer object to next object in array
void ObjGotoNextViewer() {
int i, start_obj = 0;
start_obj = Viewer_object - Objects; // get viewer object number
for (i = 0; i <= Highest_object_index; i++) {
start_obj++;
if (start_obj > Highest_object_index)
start_obj = 0;
if (Objects[start_obj].type != OBJ_NONE) {
Viewer_object = &Objects[start_obj];
return;
}
}
Error("Couldn't find a viewer object!");
}
// set viewer object to next object in array
void obj_goto_prev_viewer() {
int i, start_obj = 0;
start_obj = Viewer_object - Objects; // get viewer object number
for (i = 0; i <= Highest_object_index; i++) {
start_obj--;
if (start_obj < 0)
start_obj = Highest_object_index;
if (Objects[start_obj].type != OBJ_NONE) {
Viewer_object = &Objects[start_obj];
return;
}
}
Error("Couldn't find a viewer object!");
}
#endif
object *obj_find_first_of_type(int type) {
int i;
for (i = 0; i <= Highest_object_index; i++)
if (Objects[i].type == type)
return (&Objects[i]);
return NULL;
}
int obj_return_num_of_type(int type) {
int i, count = 0;
for (i = 0; i <= Highest_object_index; i++)
if (Objects[i].type == type)
count++;
return (count);
}
int obj_return_num_of_typeid(int type, int id) {
int i, count = 0;
for (i = 0; i <= Highest_object_index; i++)
if (Objects[i].type == type && Objects[i].id == id)
count++;
return (count);
}
int ObjAllocate(void);
// Creates the player object in the center of the given room
void CreatePlayerObject(int roomnum) {
int objnum;
vector pos;
ComputeRoomCenter(&pos, &Rooms[roomnum]);
objnum = ObjCreate(OBJ_PLAYER, 0, roomnum, &pos, NULL);
ASSERT(objnum == 0); // player must be object 0
Player_object = Viewer_object = &Objects[objnum];
}
void InitBigObjects() { Num_big_objects = 0; }
void BigObjAdd(int objnum) {
ASSERT(Num_big_objects < MAX_BIG_OBJECTS);
if (Num_big_objects >= MAX_BIG_OBJECTS) {
Int3();
return;
}
Objects[objnum].flags |= OF_BIG_OBJECT;
BigObjectList[Num_big_objects++] = objnum;
}
void BigObjRemove(int objnum) {
int i;
Objects[objnum].flags &= (~OF_BIG_OBJECT);
for (i = 0; i < Num_big_objects; i++) {
if (BigObjectList[i] == objnum) {
Num_big_objects--;
break;
}
}
while (i < Num_big_objects) {
BigObjectList[i] = BigObjectList[i + 1];
i++;
}
ASSERT(Num_big_objects < MAX_BIG_OBJECTS);
ASSERT(Num_big_objects >= 0);
}
// Resets the object list: sets all objects to unused, intializes handles, & sets roomnums to -1
// Called by the editor to init a new level.
void ResetObjectList() {
int i;
// Init data for each object
for (i = 0; i < MAX_OBJECTS; i++) {
Objects[i].handle = i;
Objects[i].type = OBJ_NONE;
Objects[i].roomnum = -1;
}
// Build the free object list
ResetFreeObjects();
// Say no big objects
InitBigObjects();
ObjResetPositionHistory();
}
// sets up the free list & init player & whatever else
void InitObjects() {
int i;
// Initialize the collision system
CollideInit();
// Mark all the objects as unused
ResetObjectList();
// Make sure no rooms are using any objects
for (i = 0; i < MAX_ROOMS; i++) {
Rooms[i].objects = -1;
Rooms[i].vis_effects = -1;
}
InitVisEffects();
atexit(FreeAllObjects);
}
// link the object into the list for its room
void ObjLink(int objnum, int roomnum) {
object *obj = &Objects[objnum];
ASSERT(objnum != -1);
ASSERT(obj->roomnum == -1);
ASSERT(!(obj->flags & OF_BIG_OBJECT));
if ((obj->size >= MIN_BIG_OBJ_RAD) && (!ROOMNUM_OUTSIDE(roomnum))) {
BigObjAdd(objnum);
}
obj->roomnum = roomnum;
if (ROOMNUM_OUTSIDE(roomnum)) {
int cellnum = CELLNUM(roomnum);
obj->next = Terrain_seg[cellnum].objects;
Terrain_seg[cellnum].objects = objnum;
ASSERT(obj->next != objnum);
} else {
ASSERT(roomnum >= 0 && roomnum <= Highest_room_index);
ASSERT(!(Rooms[roomnum].flags & RF_EXTERNAL));
obj->next = Rooms[roomnum].objects;
Rooms[roomnum].objects = objnum;
ASSERT(obj->next != objnum);
}
obj->prev = -1;
if (obj->next != -1)
Objects[obj->next].prev = objnum;
ASSERT(Objects[0].next != 0);
if (Objects[0].next == 0)
Objects[0].next = -1;
ASSERT(Objects[0].prev != 0);
if (Objects[0].prev == 0)
Objects[0].prev = -1;
}
void ObjUnlink(int objnum) {
object *obj = &Objects[objnum];
ASSERT(objnum != -1);
// If object is already unlinked, do nothing
if (obj->roomnum == -1) {
return;
}
if (obj->flags & OF_BIG_OBJECT) {
BigObjRemove(objnum);
}
if (OBJECT_OUTSIDE(obj)) {
int cellnum = CELLNUM(obj->roomnum);
terrain_segment *seg = &Terrain_seg[cellnum];
if (obj->prev == -1)
seg->objects = obj->next;
else
Objects[obj->prev].next = obj->next;
if (obj->next != -1)
Objects[obj->next].prev = obj->prev;
} else {
room *rp = &Rooms[obj->roomnum];
if (obj->prev == -1)
rp->objects = obj->next;
else
Objects[obj->prev].next = obj->next;
if (obj->next != -1)
Objects[obj->next].prev = obj->prev;
}
// Mark as not linked
obj->roomnum = -1;
ASSERT(Objects[0].next != 0);
ASSERT(Objects[0].prev != 0);
}
//-----------------------------------------------------------------------------
// Scan the object list, freeing down to num_used objects
// Returns number of slots freed.
int FreeObjectSlots(int num_used) {
int i, olind;
int obj_list[MAX_OBJECTS];
int num_already_free, num_to_free, original_num_to_free;
olind = 0;
num_already_free = MAX_OBJECTS - Highest_object_index - 1;
if (MAX_OBJECTS - num_already_free < num_used) {
return 0;
}
for (i = 0; i <= Highest_object_index; i++) {
if (Objects[i].flags & OF_DEAD) {
num_already_free++;
if (MAX_OBJECTS - num_already_free < num_used)
return num_already_free;
} else {
switch (Objects[i].type) {
case OBJ_NONE:
num_already_free++;
if (MAX_OBJECTS - num_already_free < num_used)
return 0;
break;
case OBJ_WALL:
Int3(); // This is curious. What is an object that is a wall?
break;
case OBJ_FIREBALL:
case OBJ_WEAPON:
case OBJ_DEBRIS:
case OBJ_SPLINTER:
obj_list[olind++] = i;
break;
default:
break;
}
}
}
num_to_free = MAX_OBJECTS - num_used - num_already_free;
original_num_to_free = num_to_free;
if (num_to_free > olind) {
mprintf(1, "Warning: Asked to free %i objects, but can only free %i.\n", num_to_free, olind);
num_to_free = olind;
}
for (i = 0; i < num_to_free; i++)
if (Objects[obj_list[i]].type == OBJ_DEBRIS) {
num_to_free--;
mprintf(0, "Freeing DEBRIS object %3i\n", obj_list[i]);
SetObjectDeadFlag(&Objects[obj_list[i]]);
}
if (!num_to_free)
return original_num_to_free;
if (!num_to_free)
return original_num_to_free;
//@@for (i=0; i<num_to_free; i++)
//@@ if ((Objects[obj_list[i]].type == OBJ_WEAPON)&& (Objects[obj_list[i]].id == FLARE_ID)) {
//@@ num_to_free--;
//@@ Objects[obj_list[i]].flags |= OF_DEAD;
//@@ }
if (!num_to_free)
return original_num_to_free;
for (i = 0; i < num_to_free; i++)
if (Objects[obj_list[i]].type == OBJ_WEAPON) {
num_to_free--;
mprintf(0, "Freeing WEAPON object %3i\n", obj_list[i]);
SetObjectDeadFlag(&Objects[obj_list[i]]);
}
return original_num_to_free - num_to_free;
}
// returns the number of a free object, updating Highest_object_index.
// Generally, ObjCreate() should be called to get an object, since it
// fills in important fields and does the linking.
// returns -1 if no free objects
int ObjAllocate(void) {
int objnum;
if (Num_objects >= MAX_OBJECTS - 2) {
int num_freed;
num_freed = FreeObjectSlots(MAX_OBJECTS - 10);
mprintf(0, " *** Freed %i objects in frame %i\n", num_freed, FrameCount);
}
if (Num_objects >= MAX_OBJECTS) {
mprintf(1, "Object creation failed - too many objects!\n");
return -1;
}
objnum = free_obj_list[Num_objects++];
if (objnum > Highest_object_index) {
Highest_object_index = objnum;
if (Highest_object_index > Highest_ever_object_index) {
Highest_ever_object_index = Highest_object_index;
mprintf(0, "Highest ever Object %d\n", Highest_ever_object_index);
}
}
return objnum;
}
// frees up an object. Generally, ObjDelete() should be called to get
// rid of an object. This function deallocates the object entry after
// the object has been unlinked
void ObjFree(int objnum) {
ObjFreePositionHistory(&Objects[objnum]);
free_obj_list[--Num_objects] = objnum;
ASSERT(Num_objects >= 0);
if (objnum == Highest_object_index)
while (Objects[--Highest_object_index].type == OBJ_NONE)
;
}
void ObjSetAABB(object *obj) {
vector object_rad;
if (obj->type == OBJ_ROOM) {
obj->min_xyz = Rooms[obj->id].min_xyz;
obj->max_xyz = Rooms[obj->id].max_xyz;
} else if (obj->flags & OF_POLYGON_OBJECT && obj->type != OBJ_WEAPON && obj->type != OBJ_DEBRIS &&
obj->type != OBJ_POWERUP && obj->type != OBJ_PLAYER) {
vector offset_pos;
object_rad.x = object_rad.y = object_rad.z = Poly_models[obj->rtype.pobj_info.model_num].anim_size;
offset_pos = obj->pos + obj->anim_sphere_offset;
obj->min_xyz = offset_pos - object_rad;
obj->max_xyz = offset_pos + object_rad;
} else {
object_rad.x = obj->size;
object_rad.y = obj->size;
object_rad.z = obj->size;
obj->min_xyz = obj->pos - object_rad;
obj->max_xyz = obj->pos + object_rad;
}
}
//-----------------------------------------------------------------------------
// initialize a new object. adds to the list for the given room
// returns the object number
int ObjCreate(uint8_t type, uint16_t id, int roomnum, vector *pos, const matrix *orient, int parent_handle) {
int objnum;
object *obj;
int handle;
ASSERT(type != OBJ_NONE);
if (ROOMNUM_OUTSIDE(roomnum)) {
ASSERT(CELLNUM(roomnum) <= TERRAIN_WIDTH * TERRAIN_DEPTH);
ASSERT(CELLNUM(roomnum) >= 0);
roomnum = GetTerrainRoomFromPos(pos);
if (roomnum == -1)
return -1;
}
// Get next free object
objnum = ObjAllocate();
if (objnum == -1) // no free objects
return -1;
obj = &Objects[objnum];
// Make sure the object is ok
ASSERT(obj->type == OBJ_NONE); // make sure unused
ASSERT(obj->roomnum == -1); // make sure unlinked
// Compute the new handle
handle = obj->handle + HANDLE_COUNT_INCREMENT;
// Check for handle wrap
if ((handle & HANDLE_COUNT_MASK) == HANDLE_COUNT_MASK) // Going to wrap!
Int3(); // Show this to Matt, or email him if he's not here
// Initialize the object
if (!ObjInit(obj, type, id, handle, pos, Gametime, parent_handle)) { // Couldn't init!
obj->type = OBJ_NONE; // mark as unused
ObjFree(objnum); // de-allocate object
return -1;
}
#ifdef _DEBUG
if (print_object_info) {
mprintf(0, "Created object %d of type %d\n", objnum, obj->type);
}
#endif
// Set the object's orietation
// THIS MUST BE DONE AFTER ObjInit (as ObjInit could load in a polymodel and set the anim and wall offsets)
ObjSetOrient(obj, orient ? orient : &Identity_matrix);
// Link object into room or terrain cell
ObjLink(objnum, roomnum);
// Type specific should have set up the size, so now we can compute the bounding box.
ObjSetAABB(obj);
// Let the demo system know about this object
DemoWriteObjCreate(type, id, roomnum, pos, orient, parent_handle, obj);
ObjInitPositionHistory(obj);
// Check bad init code that doesn't call ComputeDefaultSize()
ASSERT(!(obj->type != OBJ_ROOM && obj->type != OBJ_DEBRIS && (obj->flags & OF_POLYGON_OBJECT) &&
!(Poly_models[obj->rtype.pobj_info.model_num].flags & PMF_SIZE_COMPUTED)));
// Done!
return objnum;
}
void ObjInitPositionHistory(object *obj) {
ASSERT(Object_map_position_history[OBJNUM(obj)] == -1);
// If it is one of the types of object's that we have to sample positions for
// Initialize that
switch (obj->type) {
case OBJ_ROBOT:
case OBJ_WEAPON:
case OBJ_DEBRIS:
if (Num_free_object_position_history > 0) {
// we have a free slot
Num_free_object_position_history--;
int free_slot = Object_map_position_free_slots[Num_free_object_position_history];
Object_map_position_history[OBJNUM(obj)] = free_slot;
int i;
for (i = 0; i < MAX_POSITION_HISTORY; i++) {
Object_position_samples[free_slot].pos[i] = obj->pos;
}
} else {
// out of slots!
Object_map_position_history[OBJNUM(obj)] = -1;
}
break;
default:
// not saving positions
Object_map_position_history[OBJNUM(obj)] = -1;
break;
}
}
void ObjFreePositionHistory(object *obj) {
int objnum = OBJNUM(obj);
if (Object_map_position_history[objnum] != -1) {
int slot_to_free = Object_map_position_history[objnum];
Object_map_position_history[objnum] = -1;
Object_map_position_free_slots[Num_free_object_position_history] = slot_to_free;
Num_free_object_position_history++;
ASSERT(Num_free_object_position_history <= MAX_OBJECT_POS_HISTORY);
}
}
void ObjResetPositionHistory(void) {
Num_free_object_position_history = MAX_OBJECT_POS_HISTORY;
Object_position_head = 0;
int i;
for (i = 0; i < MAX_OBJECTS; i++) {
Object_map_position_history[i] = -1;
if (i < MAX_OBJECT_POS_HISTORY) {
// this array is guaranteed smaller than Objects[]
// so instead of looping through an array again just for
// this, I'll stick it in this loop.
Object_map_position_free_slots[i] = i;
}
}
for (i = 0; i < MAX_POSITION_HISTORY; i++) {
Last_position_history_update[i] = 0 - (0.1f * i);
}
}
void ObjReInitPositionHistory(void) {
ObjResetPositionHistory();
int i;
for (i = 0; i <= Highest_object_index; i++) {
if (Objects[i].type != OBJ_NONE) {
ObjInitPositionHistory(&Objects[i]);
}
}
}
extern void newdemo_record_guided_end();
// remove object from the world
void ObjDelete(int objnum) {
object *obj = &Objects[objnum];
if (obj->type == OBJ_DUMMY) {
// unghost the object before destroying it
ObjUnGhostObject(objnum);
}
/* if ((Game_mode & GM_MULTI) && Netgame.local_role==LR_CLIENT && Objects[objnum].type==OBJ_POWERUP)
{
mprintf(0,"Deleting object number %d type=%d id=%d\n",objnum,obj->type,obj->id);
}*/
if ((Game_mode & GM_MULTI) && (obj->flags & OF_SERVER_OBJECT)) {
if (Netgame.local_role == LR_CLIENT && NetPlayers[Player_num].sequence > NETSEQ_OBJECTS &&
NetPlayers[Player_num].sequence != NETSEQ_LEVEL_END) {
if (!(obj->flags & OF_SERVER_SAYS_DELETE)) {
mprintf(0, "Illegally deleting server object! Objnum=%d type=%d id=%d\n", objnum, obj->type, obj->id);
Int3();
return;
}
}
}
if (obj->flags & OF_POLYGON_OBJECT) {
polyobj_info *p_info = &obj->rtype.pobj_info;
if (p_info->multi_turret_info.keyframes != NULL) {
mem_free(p_info->multi_turret_info.keyframes);
mem_free(p_info->multi_turret_info.last_keyframes);
p_info->multi_turret_info.keyframes = NULL;
p_info->multi_turret_info.last_keyframes = NULL;
}
}
ASSERT(objnum != -1);
// ASSERT(objnum != 0 );
ASSERT(obj->type != OBJ_NONE);
// ASSERT(obj != Player_object);
//?? if (obj->type==OBJ_WEAPON && obj->id==GUIDEDMISS_ID) {
//?? int pnum=Objects[obj->ctype.laser_info.parent_num].id;
//?? mprintf(0,"Deleting a guided missile! Player %d\n\n",pnum);
//??
//?? if (pnum!=Player_num) {
//?? mprintf(0,"deleting missile that belongs to %d (%s)!\n",pnum,Players[pnum].callsign);
//?? Guided_missile[pnum]=NULL;
//?? }
//?? else if (Newdemo_state==ND_STATE_RECORDING)
//?? newdemo_record_guided_end();
//??
//?? }
if (obj == Viewer_object) // deleting the viewer?
Viewer_object = Player_object; //..make the player the viewer
// Delete this chase camera if needed
if (Player_camera_objnum == objnum)
Player_camera_objnum = -1;
//?? if (obj->flags & OF_ATTACHED) //detach this from object
//?? obj_detach_one(obj);
//??
//?? if (obj->attached_obj != -1) //detach all objects from this
//?? obj_detach_all(obj);
#ifdef _DEBUG
if (print_object_info) {
mprintf(0, "Deleting object %d of type %d\n", objnum, Objects[objnum].type);
}
#endif
if (obj->type == OBJ_WEAPON && obj->ctype.laser_info.parent_type == OBJ_PLAYER) {
int pnum = Objects[(obj->parent_handle & HANDLE_OBJNUM_MASK)].id;
if (Players[pnum].guided_obj == obj) {
mprintf(0, "Deleting a guided missile!");
Players[pnum].guided_obj = NULL;
}
}
if (obj->type == OBJ_WEAPON && obj->ctype.laser_info.parent_type == OBJ_PLAYER) {
int pnum = Objects[(obj->parent_handle & HANDLE_OBJNUM_MASK)].id;
if (Players[pnum].user_timeout_obj == obj) {
mprintf(0, "Deleting a timeout missile!");
Players[pnum].user_timeout_obj = NULL;
}
}
ObjUnlink(objnum);
ASSERT(Objects[0].next != 0);
// Update powerup respawn point
if ((Game_mode & GM_MULTI) && Netgame.local_role == LR_SERVER) {
if (obj->type == OBJ_POWERUP && Object_info[obj->id].respawn_scalar > 0) {
for (int i = 0; i < Num_powerup_respawn; i++) {
if (Powerup_respawn[i].used && Powerup_respawn[i].objnum == objnum) {
Powerup_respawn[i].used = 0;
Powerup_respawn[i].objnum = -1;
Powerup_timer[Num_powerup_timer].respawn_time =
Gametime + (Netgame.respawn_time * Object_info[obj->id].respawn_scalar);
Powerup_timer[Num_powerup_timer].id = obj->id;
Num_powerup_timer++;
mprintf(0, "Adding powerup id %d to respawn list! count=%d\n", obj->id, Num_powerup_timer);
}
}
}
}
// Kill any script thread associated with this object.
FreeObjectScripts(obj, false);
if (obj->custom_default_script_name) {
mem_free(obj->custom_default_script_name);
obj->custom_default_script_name = NULL;
}
if (obj->custom_default_module_name) {
mem_free(obj->custom_default_module_name);
obj->custom_default_module_name = NULL;
}
obj->type = OBJ_NONE; // unused!
obj->roomnum = -1; // zero it!
// Free lightmap memory
if (obj->lm_object.used)
ClearObjectLightmaps(obj);
// Free up effects memory
if (obj->effect_info) {
mem_free(obj->effect_info);
obj->effect_info = NULL;
}
if (obj->ai_info != NULL) {
AIDestroyObj(obj);
mem_free(obj->ai_info);
obj->ai_info = NULL;
}
if (obj->dynamic_wb != NULL) {
mem_free(obj->dynamic_wb);
obj->dynamic_wb = NULL;
}
if (obj->attach_children != NULL) {
mem_free(obj->attach_children);
obj->attach_children = NULL;
}
if (obj->name) {
mem_free(obj->name);
obj->name = NULL;
}
if (obj->lighting_info) {
mem_free(obj->lighting_info);
obj->lighting_info = NULL;
}
ObjFree(objnum);
}
// Frees all the objects that are currently in use
void FreeAllObjects() {
int objnum;
for (objnum = 0; objnum <= Highest_object_index; objnum++)
if (Objects[objnum].type != OBJ_NONE) {
Objects[objnum].flags |= OF_SERVER_SAYS_DELETE;
Objects[objnum].flags &= ~OF_INPLAYERINVENTORY;
ObjDelete(objnum);
}
FreeAllVisEffects();
}
// ------------------------------------------------------------------------------------------------------------------
void ObjDeleteDead() {
int i;
object *objp;
int local_dead_player_object = -1;
// Move all objects
objp = Objects;
for (i = 0; i <= Highest_object_index; i++) {
if ((objp->type != OBJ_NONE) && (objp->flags & OF_DEAD)) {
if (objp->flags & OF_INFORM_DESTROY_TO_LG) {
Level_goals.Inform(LIT_OBJECT, LGF_COMP_DESTROY, objp->handle);
}
if (objp->flags & OF_INPLAYERINVENTORY) {
// this object is in a player inventory somewhere...remove it first!
InventoryRemoveObject(objp->handle);
}
if (objp->type == OBJ_DUMMY) {
// unghost the object before deleting it
ObjUnGhostObject(i);
// tell clients to unghost
if ((Game_mode & GM_MULTI) && (Netgame.local_role != LR_CLIENT)) {
MultiSendGhostObject(&Objects[i], false);
}
}
if (objp->flags & OF_POLYGON_OBJECT) {
if (objp->flags & OF_ATTACHED) {
tOSIRISEventInfo ei;
ei.evt_child_died.it_handle = objp->handle;
Osiris_CallEvent(ObjGet(objp->attach_ultimate_handle), EVT_CHILD_DIED, &ei);
}
UnattachFromParent(objp);
UnattachChildren(objp);
}
// Execute script for death
tOSIRISEventInfo ei;
ei.evt_destroy.is_dying = 1;
Osiris_CallEvent(objp, EVT_DESTROY, &ei);
if (Game_mode & GM_MULTI) {
DLLInfo.me_handle = DLLInfo.it_handle = objp->handle;
CallGameDLL(EVT_GAMEOBJDESTROYED, &DLLInfo);
}
// detach any scripts attached
Osiris_DetachScriptsFromObject(objp);
// if it's flag to tell the clients to remove is set, tell them now
if ((Game_mode & GM_MULTI) && (Netgame.local_role != LR_CLIENT)) {
bool removed = false;
if (objp->flags & OF_SEND_MULTI_REMOVE_ON_DEATH) {
// Tell all clients to remove this object
MultiSendRemoveObject(objp, 0);
removed = true;
}
if (!removed && objp->flags & OF_SEND_MULTI_REMOVE_ON_DEATHWS) {
// Tell all clients to remove this object with sound
MultiSendRemoveObject(objp, 1);
removed = true;
}
}
if (IS_GUIDEBOT(objp)) {
int i;
int handle = objp->handle;
for (i = 0; i < MAX_PLAYERS; i++) {
if (objp->handle == Buddy_handle[i]) {
Buddy_handle[i] = OBJECT_HANDLE_NONE;
break;
}
}
}
// check if player being killed.
if (objp->type != OBJ_PLAYER) {
ObjDelete(i);
}
}
objp++;
}
// Delete our visual effects
VisEffectDeleteDead();
}
// when an object has moved into a new room, this function unlinks it
// from its old room and links it into the new room
void ObjRelink(int objnum, int newroomnum) {
ASSERT((objnum >= 0) && (objnum <= Highest_object_index));
if (!ROOMNUM_OUTSIDE(newroomnum)) {
ASSERT((newroomnum <= Highest_room_index) && (newroomnum >= 0));
ASSERT(Rooms[newroomnum].used);
} else
ASSERT(CELLNUM(newroomnum) >= 0 && CELLNUM(newroomnum) <= (TERRAIN_WIDTH * TERRAIN_DEPTH));
ObjUnlink(objnum);
ObjLink(objnum, newroomnum);
}
void DoCycledAnim(object *obj) {
float from;
float to;
float delta;
float spc;
float anim_time = Frametime;
if (!(obj->flags & OF_POLYGON_OBJECT))
return;
from = Object_info[obj->id].anim[MC_STANDING].elem[AS_ALERT].from;
to = Object_info[obj->id].anim[MC_STANDING].elem[AS_ALERT].to;
spc = Object_info[obj->id].anim[MC_STANDING].elem[AS_ALERT].spc;
if (spc <= 0.0)
return;
ASSERT(from <= to);
if (obj->rtype.pobj_info.anim_frame < from || obj->rtype.pobj_info.anim_frame > to) {
mprintf(0, "NonAI-Animation: Correcting for an incorrect frame number\n");
obj->rtype.pobj_info.anim_frame = from;
}
delta = to - from;
ASSERT(delta >= 0.0f);
if (delta > 0.0f) {
// Frames per second will be on the animation page
float step = (1.0f / (spc / (delta))) * anim_time;
while (step > delta) {
step -= delta;
}
ASSERT(step >= 0.0f);
obj->rtype.pobj_info.anim_frame += step;
if (obj->rtype.pobj_info.anim_frame >= to) {
// We are guarenteed that this is between 'to' and 'from' from the above asserts() :)
obj->rtype.pobj_info.anim_frame -= delta;
}
} else {
obj->rtype.pobj_info.anim_frame = from;
}
ASSERT(obj->rtype.pobj_info.anim_frame >= from && obj->rtype.pobj_info.anim_frame <= to);
}
void TimeoutObject(object *obj) {
switch (obj->type) {
case OBJ_WEAPON:
TimeoutWeapon(obj);
break;
case OBJ_SHOCKWAVE:
DoConcussiveForce(obj, obj->parent_handle);
break;
default:
break;
}
}
// Does afterburn effects to a player ship
void DoPlayerAfterburnControl(game_controls *controls, object *objp) {
static int afterburner_id = -1;
int slot = objp->id;
if (controls->afterburn_thrust > 0) {
Players[slot].last_afterburner_time = Gametime;
if (Players[slot].afterburn_time_left > 0) {
float punch_scalar = 1.0;
if (Players[slot].afterburn_time_left == (AFTERBURN_TIME))
AddToShakeMagnitude(objp->mtype.phys_info.mass / 2);
if (Players[slot].afterburn_time_left > (AFTERBURN_TIME * .90))
punch_scalar = 1.8f;
else if (Players[slot].afterburn_time_left > (AFTERBURN_TIME * .80) &&
Players[slot].afterburn_time_left < (AFTERBURN_TIME * .90)) {
float norm = Players[slot].afterburn_time_left - (AFTERBURN_TIME * .80);
norm /= (AFTERBURN_TIME * .1);
punch_scalar = 1.0 + (norm * .8);
}
if (OBJECT_OUTSIDE(objp))
controls->forward_thrust = controls->afterburn_thrust * 1.6 * punch_scalar;
else
controls->forward_thrust = controls->afterburn_thrust * 1.6 * punch_scalar;
Players[slot].flags |= PLAYER_FLAGS_AFTERBURN_ON | PLAYER_FLAGS_THRUSTED;
Players[slot].afterburn_time_left -= Frametime;
if (Players[slot].afterburn_time_left < 0)
Players[slot].afterburn_time_left = 0;
} else {
Players[slot].afterburn_time_left = 0;
Players[slot].flags &= ~PLAYER_FLAGS_AFTERBURN_ON;
}
} else {
Players[slot].flags &= ~PLAYER_FLAGS_AFTERBURN_ON;
if (Players[slot].afterburn_time_left < AFTERBURN_TIME) {
if (Players[slot].energy > 5.0) {
float useage;
if (afterburner_id == -1)
afterburner_id = FindObjectIDName("Afterburner");
if (afterburner_id != -1 && Players[slot].inventory.CheckItem(OBJ_POWERUP, afterburner_id))
useage = Frametime * 2;
else
useage = Frametime;
Players[slot].afterburn_time_left += useage;
Players[slot].afterburn_time_left = std::min<float>(AFTERBURN_TIME, Players[slot].afterburn_time_left);
Players[slot].energy -= (useage);
}
}
}
}
// Deal with the player's rear view
void CheckRearView(int down_count, bool down_state) {
player *player = &Players[Player_num];
#define LEAVE_TIME (1.0 / 16) // How long until we decide key is down
static bool leave_mode;
static float entry_time;
if (down_count) { // key/button has gone down
if (player->flags & PLAYER_FLAGS_REARVIEW) { // rear view was active, so turn it off
player->flags &= ~PLAYER_FLAGS_REARVIEW;
} else { // rear view wasn't active, so turn it on
player->flags |= PLAYER_FLAGS_REARVIEW;
leave_mode = 0; // means wait for another key
entry_time = Gametime;
}
} else { // key didn't go down this frame; check if it used to be down
if (down_state) { // key is still down
if (!leave_mode && ((Gametime - entry_time) > LEAVE_TIME))
leave_mode = 1;
} else {
if (leave_mode && (player->flags & PLAYER_FLAGS_REARVIEW)) {
player->flags &= ~PLAYER_FLAGS_REARVIEW;
}
}
}
}
extern int Timedemo_frame;
float Timedemo_timecount;
// Do the controls for a player-type object
void DoFlyingControl(object *objp) {
game_controls controls;
if (Dedicated_server)
return;
ASSERT(objp->control_type == CT_FLYING);
if ((objp->type != OBJ_PLAYER && objp->type != OBJ_OBSERVER) || objp->id != Player_num)
return;
if (Timedemo_frame != -1) {
matrix temp_mat, rot_mat;
if (Timedemo_frame == 0) {
Timedemo_timecount = 0;
} else {
Timedemo_timecount += Frametime;
}
vm_AnglesToMatrix(&temp_mat, 0, 65536 / 360, 0);
rot_mat = objp->orient * temp_mat;
ObjSetOrient(objp, &rot_mat);
Timedemo_frame++;
if (Timedemo_frame == 360) {
// Calculate timedemo framerate
float fps = 1.0 / (Timedemo_timecount / 360.0);
AddHUDMessage(TXT_TIMEDEMO, fps);
Timedemo_frame = -1;
}
return;
}
if (Demo_flags == DF_PLAYBACK) {
// If we are playing back a demo, bail early and don't process controls
return;
}
// Read the controllers
ReadPlayerControls(&controls);
if ((objp->type == OBJ_PLAYER) && (Players[objp->id].controller_bitflags != 0xffffffff)) {
player *pp = &Players[objp->id];
if (!(pp->controller_bitflags & PCBF_FORWARD)) {
if (controls.forward_thrust > 0.0f)
controls.forward_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_REVERSE)) {
if (controls.forward_thrust < 0.0f)
controls.forward_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_LEFT)) {
if (controls.sideways_thrust < 0.0f)
controls.sideways_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_RIGHT)) {
if (controls.sideways_thrust > 0.0f)
controls.sideways_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_UP)) {
if (controls.vertical_thrust > 0.0f)
controls.vertical_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_DOWN)) {
if (controls.vertical_thrust < 0.0f)
controls.vertical_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_PITCHUP)) {
if (controls.pitch_thrust > 0.0f)
controls.pitch_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_PITCHDOWN)) {
if (controls.pitch_thrust < 0.0f)
controls.pitch_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_HEADINGLEFT)) {
if (controls.heading_thrust < 0.0f)
controls.heading_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_HEADINGRIGHT)) {
if (controls.heading_thrust > 0.0f)
controls.heading_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_BANKLEFT)) {
if (controls.bank_thrust < 0.0f)
controls.bank_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_BANKRIGHT)) {
if (controls.bank_thrust > 0.0f)
controls.bank_thrust = 0.0f;
}
if (!(pp->controller_bitflags & PCBF_PRIMARY)) {
controls.fire_primary_down_count = 0;
controls.fire_primary_down_time = 0;
controls.fire_primary_down_state = false;
}
if (!(pp->controller_bitflags & PCBF_SECONDARY)) {
controls.fire_secondary_down_count = 0;
controls.fire_secondary_down_time = 0;
controls.fire_secondary_down_state = false;
}
if (!(pp->controller_bitflags & PCBF_AFTERBURNER)) {
controls.afterburn_thrust = 0.0f;
}
}
// Update IntelliVIBE
if (objp == Player_object) {
VIBE_DoControls(&controls);
}
// Send an event to the Game DLLs so they can do any processing of game controls
if (Game_mode & GM_MULTI) {
DLLInfo.me_handle = DLLInfo.it_handle = objp->handle;
DLLInfo.special_data = (uint8_t *)&controls;
CallGameDLL(EVT_GAMEDOCONTROLS, &DLLInfo);
}
// Deal with afterburner
if (objp->type == OBJ_PLAYER) {
ASSERT(Player_num == objp->id);
// Reset thrusting
Players[objp->id].flags &= ~PLAYER_FLAGS_THRUSTED;
DoPlayerAfterburnControl(&controls, objp);
}
// Update object's physics
if (objp->movement_type == MT_PHYSICS) {
if (objp->type == OBJ_PLAYER && Players[objp->id].guided_obj != NULL) {
ASSERT(objp->id == Player_num);
object *g_obj = Players[objp->id].guided_obj;
g_obj->mtype.phys_info.rotthrust.x = controls.pitch_thrust * g_obj->mtype.phys_info.full_rotthrust;
g_obj->mtype.phys_info.rotthrust.z = controls.bank_thrust * g_obj->mtype.phys_info.full_rotthrust;
g_obj->mtype.phys_info.rotthrust.y = controls.heading_thrust * g_obj->mtype.phys_info.full_rotthrust;
} else {
player *playp = &Players[objp->id];
objp->mtype.phys_info.rotthrust.x =
controls.pitch_thrust * objp->mtype.phys_info.full_rotthrust * playp->turn_scalar;
objp->mtype.phys_info.rotthrust.z =
controls.bank_thrust * objp->mtype.phys_info.full_rotthrust * playp->turn_scalar;
objp->mtype.phys_info.rotthrust.y =
controls.heading_thrust * objp->mtype.phys_info.full_rotthrust * playp->turn_scalar;
}
float speed_scalar = 1.0;
// If thrusting foward, show glow
if (objp->type == OBJ_PLAYER && controls.forward_thrust > 0)
Players[objp->id].flags |= PLAYER_FLAGS_THRUSTED;
if (objp->effect_info && objp->effect_info->type_flags & EF_FREEZE)
speed_scalar = objp->effect_info->freeze_scalar;
if (OBJECT_OUTSIDE(objp))
speed_scalar *= 1.3f;
objp->mtype.phys_info.thrust = speed_scalar * Players[objp->id].movement_scalar *
((objp->orient.fvec * controls.forward_thrust * objp->mtype.phys_info.full_thrust) +
(objp->orient.uvec * controls.vertical_thrust * objp->mtype.phys_info.full_thrust) +
(objp->orient.rvec * controls.sideways_thrust * objp->mtype.phys_info.full_thrust));
}
// Process player-specific stuff
if (objp->type == OBJ_PLAYER) {
// Process weapon firing
FireWeaponFromPlayer(objp, PW_PRIMARY, controls.fire_primary_down_count, controls.fire_primary_down_state,
controls.fire_primary_down_time);
FireWeaponFromPlayer(objp, PW_SECONDARY, controls.fire_secondary_down_count, controls.fire_secondary_down_state,
controls.fire_secondary_down_time);
// Deal with the flare
for (int i = 0; i < controls.fire_flare_down_count; i++)
FireFlareFromPlayer(objp);
// Do rear view
CheckRearView(controls.rearview_down_count, controls.rearview_down_state);
}
}
// Do any special effects processing for this object
void ObjDoEffects(object *obj) {
if (!obj->effect_info)
return;
static int napalm_id = -1;
if (napalm_id == -1)
napalm_id = FindWeaponName("Napalm");
if (obj->effect_info->type_flags & EF_VOLUME_CHANGING) {
obj->effect_info->volume_change_time -= Frametime;
if (obj->effect_info->volume_change_time <= 0.0f) {
obj->effect_info->type_flags &= ~EF_VOLUME_CHANGING;
}
}
if (obj->effect_info->type_flags & EF_FADING_IN) {
obj->effect_info->fade_time -= Frametime;
if (obj->effect_info->fade_time <= 0.0f) {
obj->effect_info->type_flags &= ~EF_FADING_IN;
}
}
if (obj->effect_info->type_flags & EF_CLOAKED) {
obj->effect_info->cloak_time -= Frametime;
if (obj->effect_info->cloak_time <= 0.0f) {
// reappear the object
MakeObjectVisible(obj);
}
}
if (obj->effect_info->type_flags & EF_FADING_OUT) {
obj->effect_info->fade_time -= Frametime;
if (obj->effect_info->fade_time <= 0.0f) {
obj->effect_info->type_flags &= ~EF_FADING_OUT;
obj->effect_info->type_flags |= EF_CLOAKED;
}
}
if (obj->effect_info->type_flags & EF_LIQUID) {
obj->effect_info->liquid_time_left -= Frametime;
if (obj->effect_info->liquid_time_left <= 0.0f) {
// Stop doing liquid effect
obj->effect_info->type_flags &= ~EF_LIQUID;
if (obj == Viewer_object)
Render_FOV = D3_DEFAULT_FOV;
} else {
if (obj == Viewer_object) {
int inttime = Gametime;
float normtime = Gametime - inttime;
float scalar = FixSin(normtime * 65535 * 4);
scalar *= ((float)obj->effect_info->liquid_mag);
if (obj->effect_info->liquid_time_left < 1)
scalar *= (obj->effect_info->liquid_time_left);
Render_FOV = D3_DEFAULT_FOV + scalar;
}
}
}
if (obj->effect_info->type_flags & EF_DEFORM) {
obj->effect_info->deform_time -= Frametime;
if (obj->effect_info->deform_time <= 0.0f) {
// Stop deforming
obj->effect_info->type_flags &= ~EF_DEFORM;
}
}
if (obj->effect_info->type_flags & EF_FREEZE) {
obj->effect_info->freeze_scalar += (Frametime / 3);
if (obj->effect_info->freeze_scalar >= 1.0f) {
// Stop freezing
obj->effect_info->type_flags &= ~EF_FREEZE;
}
}
if (obj->effect_info->type_flags & EF_NAPALMED) {
if ((Gametime - obj->effect_info->last_damage_time) > 1.0) {
obj->effect_info->last_damage_time = Gametime;
// Apply damage to this napalmed object
object *killer;
if (obj->effect_info->damage_handle != OBJECT_HANDLE_NONE) {
uint32_t sig = obj->effect_info->damage_handle & HANDLE_COUNT_MASK;
int objnum = obj->effect_info->damage_handle & HANDLE_OBJNUM_MASK;
if ((Objects[objnum].handle & HANDLE_COUNT_MASK) != sig)
killer = NULL;
else
killer = &Objects[objnum];
} else
killer = NULL;
if (obj->type == OBJ_PLAYER) {
ApplyDamageToPlayer(obj, killer, PD_NONE, obj->effect_info->damage_per_second);
} else if (IS_GENERIC(obj->type) || (obj->type == OBJ_DOOR)) {
ApplyDamageToGeneric(obj, killer, GD_FIRE, obj->effect_info->damage_per_second);
}
}
obj->effect_info->damage_time -= Frametime;
if (obj->effect_info->damage_time <= 0) {
obj->effect_info->type_flags &= ~EF_NAPALMED;
obj->effect_info->last_damage_time = 0;
Sound_system.StopSoundLooping(obj->effect_info->sound_handle);
obj->effect_info->sound_handle = SOUND_NONE_INDEX;
}
// Drop some smoke/fire and stuff
AttachRandomNapalmEffectsToObject(obj);
}
// Do sparking stuff
if (obj->effect_info->type_flags & EF_SPARKING) {
int num_bolts = 0;
// Create spark effects
obj->effect_info->spark_timer -= Frametime;
while (obj->effect_info->spark_timer < 0.0) {
obj->effect_info->spark_timer += obj->effect_info->spark_delay;
num_bolts++;
}
CreateElectricalBolts(obj, num_bolts);
// Update timer
obj->effect_info->spark_time_left -= Frametime;
if (obj->effect_info->spark_time_left <= 0.0f) {
obj->effect_info->type_flags &= ~EF_SPARKING;
}
}
}
// Check passed-through faces for triggers, etc.
void ObjCheckTriggers(object *objp) {
for (int i = 0; i < Fvi_num_recorded_faces; i++) {
int roomnum = Fvi_recorded_faces[i].room_index;
int facenum = Fvi_recorded_faces[i].face_index;
CheckTrigger(roomnum, facenum, objp, TT_PASS_THROUGH);
}
}
#ifdef _DEBUG
void debug_check_terrain_objects() {
int i;
int j;
for (i = 0; i < TERRAIN_WIDTH * TERRAIN_DEPTH; i++) {
j = Terrain_seg[i].objects;
while (j != -1) {
ASSERT(j >= 0 && j <= Highest_object_index);
j = Objects[j].next;
}
}
}
#endif
#ifdef _DEBUG
extern int DoAI;
#else
#define DoAI 1
#endif
//--------------------------------------------------------------------
// Process an object for the current frame
void ObjDoFrame(object *obj) {
int previous_room = obj->roomnum;
float save_frametime = Frametime;
ASSERT(obj->type != OBJ_NONE);
// Clear attached vis for this frame
obj->lowest_attached_vis = -1;
// If this is a permissable netgame, accelerate this object if need by
if ((Game_mode & GM_MULTI) && Netgame.local_role == LR_CLIENT && (obj->flags & OF_PING_ACCELERATE)) {
Frametime += (NetPlayers[Player_num].ping_time / 2.0);
obj->flags &= ~OF_PING_ACCELERATE;
}
// Update previous position before moving
obj->last_pos = obj->pos;
// Inevitable countdown towards death
if (obj->flags & OF_USES_LIFELEFT)
obj->lifeleft -= Frametime;
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Process the control for this object
switch (obj->control_type) {
case CT_POWERUP:
case CT_NONE:
break;
case CT_FLYING: {
RTP_STARTINCTIME(ct_flying_time);
DoFlyingControl(obj);
RTP_ENDINCTIME(ct_flying_time);
} break;
case CT_AI:
if (DoAI) {
RTP_STARTINCTIME(ct_aidoframe_time);
AIDoFrame(obj);
RTP_ENDINCTIME(ct_aidoframe_time);
}
break;
case CT_WEAPON: {
RTP_STARTINCTIME(ct_weaponframe_time);
WeaponDoFrame(obj);
RTP_ENDINCTIME(ct_weaponframe_time);
} break;
case CT_EXPLOSION: {
RTP_STARTINCTIME(ct_explosionframe_time);
DoExplosionFrame(obj);
RTP_ENDINCTIME(ct_explosionframe_time);
} break;
case CT_DEBRIS: {
RTP_STARTINCTIME(ct_debrisframe_time);
DoDebrisFrame(obj);
RTP_ENDINCTIME(ct_debrisframe_time);
} break;
case CT_SPLINTER: {
RTP_STARTINCTIME(ct_splinterframe_time);
DoSplinterFrame(obj);
RTP_ENDINCTIME(ct_splinterframe_time);
} break;
case CT_DYING:
DoDyingFrame(obj);
break;
case CT_DYING_AND_AI:
if (DoAI)
AIDoFrame(obj);
DoDyingFrame(obj);
break;
case CT_SOUNDSOURCE:
break; // do nothing
case CT_SOAR:
break;
#ifdef _DEBUG
case CT_SLEW:
SlewFrame(obj);
break;
#endif
default:
Error("Unknown control type %d in object %i, handle/type/id = %i/%i/%i", obj->control_type, obj - Objects,
obj->handle, obj->type, obj->id);
break;
}
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Update cycled animation
if ((obj->control_type != CT_AI) && (obj->control_type != CT_DYING_AND_AI) && (obj->control_type != CT_DEBRIS) &&
IS_GENERIC(obj->type) && (obj->type != OBJ_ROOM)) {
if (Object_info[obj->id].anim && Object_info[obj->id].anim[MC_STANDING].elem[AS_ALERT].to) {
RTP_STARTINCTIME(cycle_anim);
DoCycledAnim(obj);
RTP_ENDINCTIME(cycle_anim);
}
}
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Do freeze stuff on AI controlled robots
if ((obj->type == OBJ_ROBOT || obj->type == OBJ_BUILDING) && obj->control_type == CT_AI && obj->effect_info &&
(obj->effect_info->type_flags & EF_FREEZE))
obj->mtype.phys_info.velocity *= obj->effect_info->freeze_scalar;
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Check for object dead of old age
if ((obj->flags & OF_USES_LIFELEFT) && (obj->lifeleft < 0)) {
bool kill_it = true;
bool tell_clients = false;
ASSERT(obj->type != OBJ_PLAYER);
if (Game_mode & GM_MULTI) {
if (Netgame.local_role == LR_CLIENT) {
if (obj->type == OBJ_POWERUP || obj->type == OBJ_ROBOT || obj->type == OBJ_CLUTTER ||
obj->type == OBJ_BUILDING) {
ASSERT(obj->flags & OF_SERVER_OBJECT);
if (!(obj->flags & OF_SERVER_SAYS_DELETE)) {
kill_it = false;
obj->lifeleft = .001f;
}
}
} else {
if (obj->type == OBJ_POWERUP || obj->type == OBJ_ROBOT || obj->type == OBJ_CLUTTER || obj->type == OBJ_BUILDING)
tell_clients = true;
}
}
if (kill_it) {
TimeoutObject(obj);
SetObjectDeadFlag(obj, tell_clients);
}
}
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// If object is dead, don't do any more processing on it
if (obj->flags & OF_DEAD) {
Frametime = save_frametime;
return;
}
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Do the movement for this object
switch (obj->movement_type) {
case MT_NONE:
if ((obj->flags & OF_STUCK_ON_PORTAL) &&
!(Rooms[obj->mtype.phys_info.stuck_room].portals[obj->mtype.phys_info.stuck_portal].flags & PF_RENDER_FACES)) {
if (IS_GENERIC(obj->type) && (obj->flags & OF_CLIENT_KNOWS))
SetObjectDeadFlag(obj, true);
else
SetObjectDeadFlag(obj, false);
}
break; // this doesn't move
case MT_PHYSICS: {
RTP_STARTINCTIME(mt_physicsframe_time);
do_physics_sim(obj);
ObjCheckTriggers(obj);
RTP_ENDINCTIME(mt_physicsframe_time);
} break;
case MT_WALKING: {
RTP_STARTINCTIME(mt_walkingframe_time);
do_walking_sim(obj);
ObjCheckTriggers(obj);
RTP_ENDINCTIME(mt_walkingframe_time);
} break;
case MT_SHOCKWAVE: {
RTP_STARTINCTIME(mt_shockwave_time);
DoConcussiveForce(obj, obj->parent_handle);
RTP_ENDINCTIME(mt_shockwave_time);
} break;
case MT_OBJ_LINKED:
PhysicsLinkList[Physics_NumLinked++] = OBJNUM(obj);
break;
default:
Int3(); // unknown movement type
}
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Do special effects stuff to object
if (obj->effect_info) {
RTP_STARTINCTIME(obj_doeffect_time);
ObjDoEffects(obj);
RTP_ENDINCTIME(obj_doeffect_time);
}
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Deal with special player movement stuff
if (obj->type == OBJ_PLAYER) {
RTP_STARTINCTIME(obj_move_player_time);
DoSpecialPlayerStuff(obj);
RTP_ENDINCTIME(obj_move_player_time);
}
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Handle interval event for script.
do {
RTP_STARTINCTIME(obj_d3xint_time);
//@$-D3XExecScript(obj, EVT_INTERVAL, obj);
tOSIRISEventInfo ei;
ei.evt_interval.frame_time = Frametime;
ei.evt_interval.game_time = Gametime;
Osiris_CallEvent(obj, EVT_INTERVAL, &ei);
RTP_ENDINCTIME(obj_d3xint_time);
} while (0); // this do{}while(0) is here because the RTP_STARTINCTIME/RTP_ENDINCTIME must be in the same scope
// and not share scope with another RTP_STARTINCTIME
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Cast light
do {
RTP_STARTINCTIME(obj_objlight_time);
DoObjectLight(obj);
RTP_ENDINCTIME(obj_objlight_time);
} while (0); // this do{}while(0) is here because the RTP_STARTINCTIME/RTP_ENDINCTIME must be in the same scope
// and not share scope with another RTP_STARTINCTIME
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// if this object is walking/rolling on the terrain, make it not LOD at that point
if (obj->movement_type == MT_WALKING && (obj->flags & OF_RENDERED) && OBJECT_OUTSIDE(obj))
TurnOffLODForCell(CELLNUM(obj->roomnum));
// #ifdef _DEBUG
// if(Physics_player_verbose)
// {
// debug_check_terrain_objects();
// }
// #endif _DEBUG
// Mark object as not rendered for this frame
obj->flags &= ~OF_RENDERED;
Frametime = save_frametime;
}
int Max_used_objects = MAX_OBJECTS - 20;
float Last_position_history_update[MAX_POSITION_HISTORY]; // gametime of the last position history update of the object
float Last_position_history_update_time = 0.0f;
//--------------------------------------------------------------------
// Process all objects for the current frame
#define OBJ_POS_SAMPLE_TIME (1.0f / (((float)MAX_POSITION_HISTORY) * 20.0f)) // super sample at 20 fps
void ObjDoFrameAll() {
int i;
object *objp;
int objs_live = 0;
bool update_position_history = false;
if (Last_position_history_update_time > Gametime) {
// the Gametime rolled over (new level)
update_position_history = true;
}
if (Gametime >= Last_position_history_update_time + OBJ_POS_SAMPLE_TIME) {
update_position_history = true;
}
if (update_position_history) {
Object_position_head--;
if (Object_position_head == 255)
Object_position_head = MAX_POSITION_HISTORY - 1;
Last_position_history_update[Object_position_head] = Gametime;
Last_position_history_update_time = Gametime;
}
// MT: seed code removed 9/20/99 because it breaks the randomness of the
// ambient sound patterns.
// IMPORTANT NOTE: Because this code could have far-reaching consequences,
// please DO NOT check this in until after the regular patches for D3 are
// done. This code should only go in for the Mercenary patch, which will
// get adequate testing to make sure nothing serious has broken.
//@@ // Seed timer for this frame
//@@ int seed=Gametime*1000;
//@@ ps_srand (seed);
// Does control for soar objects
if (Soar_active)
SoarTick(Frametime);
Physics_NumLinked = 0;
// Process each object
for (i = 0, objp = Objects; i <= Highest_object_index; i++, objp++) {
if ((objp->type != OBJ_NONE) && (!(objp->flags & OF_DEAD))) {
RTP_STARTINCTIME(obj_do_frm);
ObjDoFrame(objp);
if (update_position_history) {
int slot = Object_map_position_history[OBJNUM(objp)];
if (slot != -1) {
Object_position_samples[slot].pos[Object_position_head] = objp->pos;
}
}
RTP_ENDINCTIME(obj_do_frm);
objs_live++;
}
}
// Account for linked objects
for (i = 0; i < Physics_NumLinked; i++) {
RTP_STARTINCTIME(phys_link);
DoPhysLinkedFrame(&Objects[PhysicsLinkList[i]]);
RTP_ENDINCTIME(phys_link);
}
// Move vis effects
{
RTP_STARTINCTIME(vis_eff_move);
VisEffectMoveAll();
RTP_ENDINCTIME(vis_eff_move);
}
// Blend all lights that are needed
BlendAllLightingEdges();
mprintf_at(1, 5, 40, "Objs=%d ", objs_live);
// Delete everything that died
ObjDeleteDead();
}
#define FUELCEN_SOUND_DELAY 0.25 // play every quarter second
#define FUELCEN_GIVE_RATE 25 // give 25 units per second
// Give the player fuel. Called when the player is in an energy center
void RefuelPlayer(object *objp) {
float max, amount;
ASSERT(objp->type == OBJ_PLAYER);
ASSERT(Rooms[objp->roomnum].flags & RF_FUELCEN);
max = INITIAL_ENERGY - Players[objp->id].energy;
amount = FUELCEN_GIVE_RATE * Frametime;
if (amount > max)
amount = max;
if ((amount > 0)) {
Players[objp->id].energy += amount;
Sound_system.Play3dSound(SOUND_REFUELING, SND_PRIORITY_HIGH, objp);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.01f;
hear.curiosity_level = 1.0f;
hear.max_dist = Sounds[SOUND_REFUELING].max_distance;
AINotify(objp, AIN_HEAR_NOISE, (void *)&hear);
// if (Game_mode & GM_MULTI)
// multi_send_play_sound(SOUND_REFUEL_STATION_GIVING_FUEL, F1_0/2);
}
}
// Clears the secret flag in the specified room, and recursively, all connected rooms
void ClearSecretFlags(room *rp) {
int p;
portal *pp;
rp->flags &= ~RF_SECRET;
for (p = 0, pp = rp->portals; p < rp->num_portals; p++, pp++) {
room *rp2 = &Rooms[pp->croom];
if (rp2->flags & RF_SECRET)
ClearSecretFlags(rp2);
}
}
// Prints out a message when you've found a secret
#define SECRET_COLOR GR_RGB(0, 128, 255)
void DoSecretForPlayer(room *rp, object *objp) {
int y = Game_window_h / 4;
AddPersistentHUDMessage(SECRET_COLOR, HUD_MSG_PERSISTENT_CENTER, y, 5, HPF_FADEOUT + HPF_FREESPACE_DRAW,
SOUND_GAME_MESSAGE, TXT_FOUND_SECRET);
ClearSecretFlags(rp);
}
// --------------------------------------------------------------------
// Do refueling centers & damage rooms
void DoSpecialPlayerStuff(object *objp) {
if (!OBJECT_OUTSIDE(objp)) {
room *rp = &Rooms[objp->roomnum];
// Check for refueling
if (rp->flags & RF_FUELCEN)
RefuelPlayer(objp);
if (objp->id == Player_num && (rp->flags & RF_SECRET))
DoSecretForPlayer(rp, objp);
// Check for damage room
if (rp->damage > 0.0) {
if (!(objp->effect_info->type_flags & EF_NAPALMED)) {
// Set the player on fire
ASSERT(objp->effect_info);
objp->effect_info->type_flags |= EF_NAPALMED;
objp->effect_info->damage_time = 1.0;
objp->effect_info->damage_per_second = rp->damage;
if (Gametime - objp->effect_info->last_damage_time > 1.0f)
objp->effect_info->last_damage_time = 0;
objp->effect_info->damage_handle = OBJECT_HANDLE_NONE;
if (objp->effect_info->sound_handle == SOUND_NONE_INDEX)
objp->effect_info->sound_handle = Sound_system.Play3dSound(SOUND_PLAYER_BURNING, SND_PRIORITY_HIGHEST, objp);
}
}
// Check for & set waypoint
if (rp->flags & RF_WAYPOINT)
SetAutoWaypoint(objp);
} else { // outside
// Check for & set waypoint
// if (Terrain_seg[CELLNUM(objp->roomnum)].flags & TF_WAYPOINT)
// SetAutoWaypoint(objp);
}
}
// Builds the free object list by scanning the list of free objects & adding unused ones to the list
// Also sets Highest_object_index
void ResetFreeObjects() {
int i;
Highest_object_index = -1;
for (i = Num_objects = MAX_OBJECTS; --i >= 0;)
if (Objects[i].type == OBJ_NONE)
free_obj_list[--Num_objects] = i;
else if (Highest_object_index == -1)
Highest_object_index = i;
}
// sets the orientation of an object. This should be called to orient an object
void ObjSetOrient(object *obj, const matrix *orient) {
// Accounts for if the orientation was set and then this function is being used
// to update the other stuff
if (&obj->orient != orient)
obj->orient = *orient;
// Recompute the orientation dependant information
if (obj->flags & OF_POLYGON_OBJECT) {
if (obj->type != OBJ_WEAPON && obj->type != OBJ_DEBRIS && obj->type != OBJ_POWERUP && obj->type != OBJ_ROOM) {
matrix m;
m = obj->orient;
vm_TransposeMatrix(&m);
obj->wall_sphere_offset = Poly_models[obj->rtype.pobj_info.model_num].wall_size_offset * m;
obj->anim_sphere_offset = Poly_models[obj->rtype.pobj_info.model_num].anim_size_offset * m;
} else {
obj->wall_sphere_offset = Zero_vector;
obj->anim_sphere_offset = Zero_vector;
}
}
}
// sets the position of an object. This should be called to move an object
void ObjSetPos(object *obj, vector *pos, int roomnum, matrix *orient, bool f_update_attached_children) {
int oldroomnum = obj->roomnum;
vector old_pos = obj->pos;
// Reset the position & recalculate the AABB
obj->pos = *pos;
ObjSetAABB(obj);
// Reset the orientation if changed
if (orient != NULL)
ObjSetOrient(obj, orient);
// Clear the outside-mine flag
obj->flags &= ~OF_OUTSIDE_MINE;
// If changed rooms, do a bunch of stuff
if (obj->roomnum != roomnum) {
// Let the script know
tOSIRISEventInfo ei;
ei.evt_changeseg.room_num = roomnum;
Osiris_CallEvent(obj, EVT_CHANGESEG, &ei);
if (obj->type == OBJ_PLAYER && !ROOMNUM_OUTSIDE(roomnum) && (Rooms[roomnum].flags & RF_INFORM_RELINK_TO_LG)) {
Level_goals.Inform(LIT_INTERNAL_ROOM, LGF_COMP_ENTER, roomnum);
}
// Relink the object
ObjRelink(OBJNUM(obj), roomnum);
// Call DLL function if the server player changed rooms
if ((Game_mode & GM_MULTI) && (Netgame.local_role == LR_SERVER)) {
DLLInfo.me_handle = obj->handle;
DLLInfo.it_handle = obj->handle;
DLLInfo.oldseg = oldroomnum;
DLLInfo.newseg = obj->roomnum;
if (obj->type == OBJ_PLAYER) {
CallGameDLL(EVT_GAMEPLAYERCHANGESEG, &DLLInfo);
} else {
CallGameDLL(EVT_GAMEOBJCHANGESEG, &DLLInfo);
}
}
// Slowly change volume lighting if going between rooms, if not in the editor
if (GetFunctionMode() != EDITOR_MODE) {
if ((obj->effect_info != NULL) && (obj->effect_info->type_flags & EF_VOLUME_LIT)) {
if (!ROOMNUM_OUTSIDE(oldroomnum) && !ROOMNUM_OUTSIDE(roomnum)) {
if (!(obj->effect_info->type_flags & EF_VOLUME_CHANGING)) {
obj->effect_info->type_flags |= EF_VOLUME_CHANGING;
obj->effect_info->volume_change_time = 1.0;
obj->effect_info->volume_old_room = oldroomnum;
obj->effect_info->volume_old_pos = old_pos;
}
} else // either old or new room was outside, so don't do volume changing
obj->effect_info->type_flags &= ~EF_VOLUME_CHANGING;
}
}
}
}
// Delete objects, such as weapons & explosions, that shouldn't stay between levels
// if clear_all is set, clear even proximity bombs
void ClearTransientObjects(int clear_all) {
int objnum;
object *objp;
for (objnum = 0, objp = Objects; objnum <= Highest_object_index; objnum++, objp++) {
if (((objp->type == OBJ_WEAPON) && !(Weapons[objp->id].flags & WF_COUNTERMEASURE)) ||
(objp->type == OBJ_FIREBALL) || (objp->type == OBJ_DEBRIS) || (objp->type == OBJ_SHARD) ||
(objp->type == OBJ_SHOCKWAVE) || (objp->type == OBJ_PARTICLE) || (objp->type == OBJ_SPLINTER) ||
((objp->type != OBJ_NONE) && (objp->flags & OF_DYING))) {
mprintf(0, "Clearing object %d type = %d\n", objnum, objp->type);
ObjDelete(objnum);
}
}
}
// Remaps all static powerups,sounds,etc to their appropriate indices
void RemapEverything() {
RemapStaticIDs();
RemapDoors();
RemapShips();
RemapWeapons();
RemapSounds();
RemapPolyModels();
}
// Retruns a pointer to an object given its handle. Returns NULL if object no longer exists.
object *ObjGet(int handle) {
if (handle == OBJECT_HANDLE_NONE)
return NULL;
if (handle == OBJECT_HANDLE_BAD) {
Int3();
return NULL;
}
int objnum = handle & HANDLE_OBJNUM_MASK;
object *objp;
ASSERT((handle & HANDLE_COUNT_MASK) != 0); // count == 0 means never-used object
ASSERT(objnum < MAX_OBJECTS);
objp = &Objects[objnum];
if ((objp->type != OBJ_NONE) && (objp->handle == handle))
return objp;
else
return NULL;
}
void GetObjectPointInWorld(vector *dest, object *obj, int subnum, int vertnum) {
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
bsp_info *sm = &pm->submodel[subnum];
float normalized_time[MAX_SUBOBJECTS];
int i;
if (!pm->new_style)
return;
for (i = 0; i < MAX_SUBOBJECTS; i++)
normalized_time[i] = 0.0;
SetModelAnglesAndPos(pm, normalized_time);
vector pnt = sm->verts[vertnum];
int mn = subnum;
matrix m;
// Instance up the tree for this gun
while (mn != -1) {
vector tpnt;
vm_AnglesToMatrix(&m, pm->submodel[mn].angs.p, pm->submodel[mn].angs.h, pm->submodel[mn].angs.b);
vm_TransposeMatrix(&m);
tpnt = pnt * m;
pnt = tpnt + pm->submodel[mn].offset + pm->submodel[mn].mod_pos;
mn = pm->submodel[mn].parent;
}
// now instance for the entire object
m = obj->orient;
vm_TransposeMatrix(&m);
*dest = pnt * m;
*dest += obj->pos;
}
bool ObjGetAnimUpdate(uint16_t objnum, custom_anim *multi_anim_info) {
object *obj = &Objects[objnum];
if ((objnum >= 0) && (obj->type != OBJ_NONE) && (obj->type != OBJ_WEAPON) && (obj->flags & OF_POLYGON_OBJECT)) {
polyobj_info *pm = &obj->rtype.pobj_info;
ai_frame *ai_info = obj->ai_info;
multi_anim_info->server_time = Gametime;
multi_anim_info->server_anim_frame = (uint16_t)(pm->anim_frame * 256.0f);
multi_anim_info->anim_start_frame = pm->anim_start_frame;
multi_anim_info->anim_end_frame = pm->anim_end_frame;
multi_anim_info->anim_time = pm->anim_time;
multi_anim_info->max_speed = pm->max_speed;
multi_anim_info->flags = 0;
if (pm->anim_flags & AIAF_LOOPING)
multi_anim_info->flags |= FMA_LOOPING;
if (obj->ai_info != NULL) {
multi_anim_info->anim_sound_index = obj->ai_info->last_played_sound_index;
multi_anim_info->flags |= FMA_HAS_AI;
} else {
multi_anim_info->anim_sound_index = -1;
}
return true;
}
return false;
}
void SetObjectControlType(object *obj, int control_type) {
ASSERT(obj);
ASSERT(OBJNUM(obj) >= 0 && OBJNUM(obj) < MAX_OBJECTS);
if ((control_type == CT_AI) && (obj->ai_info == NULL)) {
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
polyobj_info *p_info = &obj->rtype.pobj_info;
int num_wbs = pm->num_wbs;
int count = 0;
int i;
obj->ai_info = (ai_frame *)mem_malloc(sizeof(ai_frame));
memset(obj->ai_info, 0x00, sizeof(ai_frame)); // DAJ clear the baby
for (i = 0; i < num_wbs; i++) {
ASSERT(pm->poly_wb[i].num_turrets >= 0 && pm->poly_wb[i].num_turrets <= 6400);
count += pm->poly_wb[i].num_turrets;
}
p_info->multi_turret_info.num_turrets = count;
if ((count > 0) && (p_info->multi_turret_info.keyframes == NULL)) {
int cur = 0;
p_info->multi_turret_info.time = 0;
p_info->multi_turret_info.keyframes = (float *)mem_malloc(sizeof(float) * count);
p_info->multi_turret_info.last_keyframes = (float *)mem_malloc(sizeof(float) * count);
p_info->multi_turret_info.flags = 0;
}
}
obj->control_type = control_type;
if (obj->control_type == CT_AI || obj->type == OBJ_PLAYER) {
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
int num_wbs = pm->num_wbs;
if (obj->dynamic_wb == NULL) {
if (obj->type == OBJ_PLAYER) {
obj->dynamic_wb = (dynamic_wb_info *)mem_malloc(sizeof(dynamic_wb_info) * MAX_WBS_PER_OBJ);
} else {
if (num_wbs)
obj->dynamic_wb = (dynamic_wb_info *)mem_malloc(sizeof(dynamic_wb_info) * num_wbs);
}
}
}
}
void ObjSetAnimUpdate(uint16_t objnum, custom_anim *multi_anim_info) {
object *obj = &Objects[objnum];
polyobj_info *pm;
if ((objnum >= 0) && (obj->type != OBJ_NONE) && (obj->type != OBJ_WEAPON) && (obj->flags & OF_POLYGON_OBJECT)) {
pm = &obj->rtype.pobj_info;
pm->multi_anim_info = *multi_anim_info;
// Make sure we mark it as current and
pm->multi_anim_info.flags |= FMA_VALID;
pm->multi_anim_info.flags &= ~FMA_CURRENT;
}
}
void ObjGetTurretUpdate(uint16_t objnum, multi_turret *multi_turret_info) {
object *obj = &Objects[objnum];
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
polyobj_info *p_info = &obj->rtype.pobj_info;
if ((objnum >= 0) && (obj->type != OBJ_NONE) && (obj->type != OBJ_WEAPON) && (obj->flags & OF_POLYGON_OBJECT) &&
(p_info->multi_turret_info.num_turrets)) {
int i;
int j;
int count = 0;
multi_turret_info->time = Gametime;
multi_turret_info->num_turrets = p_info->multi_turret_info.num_turrets;
for (i = 0; i < pm->num_wbs; i++) {
for (j = 0; j < pm->poly_wb[i].num_turrets; j++) {
multi_turret_info->keyframes[count++] = obj->dynamic_wb[i].norm_turret_angle[j];
}
}
}
}
void ObjSetTurretUpdate(uint16_t objnum, multi_turret *multi_turret_info) {
object *obj = &Objects[objnum];
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
polyobj_info *p_info = &obj->rtype.pobj_info;
if ((objnum >= 0) && (obj->type != OBJ_NONE) && (obj->type != OBJ_WEAPON) && (obj->flags & OF_POLYGON_OBJECT) &&
(p_info->multi_turret_info.num_turrets)) {
int i;
p_info->multi_turret_info.time = multi_turret_info->time;
for (i = 0; i < p_info->multi_turret_info.num_turrets; i++) {
p_info->multi_turret_info.keyframes[i] = multi_turret_info->keyframes[i];
}
p_info->multi_turret_info.flags = FMT_NEW_DATA;
} else {
mprintf(0, "Woops, no turret here to update!\n");
}
}
// Returns the original parent for the given object. Returns self if it has no parent
object *ObjGetUltimateParent(object *child) {
ASSERT(child);
object *ret = child;
int handle;
handle = child->parent_handle;
while (ObjGet(handle)) {
ret = ObjGet(handle);
handle = ret->parent_handle;
}
return ret;
}
// Sets an object to a type OBJ_DUMMY (saves it's old type) so it won't be renderered, etc, but still alive
void ObjGhostObject(int objnum) {
ASSERT(objnum >= 0 && objnum < MAX_OBJECTS);
if (objnum < 0 || objnum >= MAX_OBJECTS)
return;
object *obj = &Objects[objnum];
ASSERT(obj->type != OBJ_NONE);
ASSERT(obj->type != OBJ_DUMMY); // Don't ghost a ghosted object!!
ASSERT(obj->type != OBJ_PLAYER);
ASSERT(obj->type != OBJ_GHOST);
if (obj->type == OBJ_NONE || obj->type == OBJ_DUMMY || obj->type == OBJ_PLAYER || obj->type == OBJ_GHOST)
return;
obj->dummy_type = obj->type;
obj->type = OBJ_DUMMY;
}
// Restores a ghosted object back to it's old type
void ObjUnGhostObject(int objnum) {
ASSERT(objnum >= 0 && objnum < MAX_OBJECTS);
if (objnum < 0 || objnum >= MAX_OBJECTS)
return;
object *obj = &Objects[objnum];
ASSERT(obj->type == OBJ_DUMMY);
if (obj->type != OBJ_DUMMY)
return;
//@@ASSERT (!(obj->flags&OF_INPLAYERINVENTORY));
//@@if(obj->flags&OF_INPLAYERINVENTORY)
//@@ return;
if (obj->flags & OF_INPLAYERINVENTORY) {
mprintf(0, "UnGhosting Object in that is currently in a player's inventory!\n");
}
obj->type = obj->dummy_type;
obj->dummy_type = OBJ_NONE;
}