Files
Descent3/Descent3/WeaponFire.cpp
Jan Engelhardt a7398dd65e Replace Zero_vector by vector{}
As far as the set of .cpp files which are using vecmat.h are
concerned, `Zero_vector` is out of reach for the compiler optimizer,
because it is extern / lives in a separate translation unit. An
expression like `x == Zero_vector` or `v = Zero_vector` thus has to
perform memory loads (for Zero_vector's x,y,z parts) before
comparison or copying, respectively. By using an immediate zero
vector `vector{}` instead, the unnecessary extra loads should go
away.

I present exhibit A:

```
void copyxx(vector *x) { *x = Zero_vector; }

4905e0: 48 8b 05 41 c0 56 00  movq 0x56c041(%rip),%rax  # 9fc628 <Zero_vector>
4905e7: 48 89 07              movq %rax,(%rdi)
4905ea: 8b 05 40 c0 56 00     movl 0x56c040(%rip),%eax  # 9fc630 <Zero_vector+0x8>
4905f0: 89 47 08              movl %eax,0x8(%rdi)
4905f3: c3                    ret
```

vs.

```
void copyxx(vector *x) { *x = vector{}; }

4905c0: 48 c7 07 00 00 00 00  movq $0x0,(%rdi)
4905c7: c7 47 08 00 00 00 00  movl $0x0,0x8(%rdi)
4905ce: c3                    ret
```
2025-06-03 12:01:17 +02:00

3420 lines
103 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/WeaponFire.cpp $
* $Revision: 279 $
* $Date: 9/20/01 5:33p $
* $Author: Matt $
*
* Weapon firing functions
*
* $Log: /DescentIII/Main/WeaponFire.cpp $
*
* 279 9/20/01 5:33p Matt
* Fixed weird omega cannon bugs (I hope) by only letting the omega
* particles collide from inside AquireElectricalTarget().
*
* 278 9/10/01 5:18p Matt
* For continuous-fire energy weapons (the omega cannon) use energy at a
* continuous (i.e., not frame-rate dependent) way.
*
* 277 9/05/01 4:14p Matt
* Only call StopWeapon() when dealing with a primary weapon. This change
* fixes odd primary weapon behavior when holding down the secondary fire
* button when out of secondaries.
*
* 276 8/29/01 4:26p Matt
* Add command-line switch to turn off suturation for omega cannon.
*
* 275 2/01/00 3:48a Jason
* Made lock image not display if the homing weapon is no longer locked on
*
* 274 1/26/00 9:20p Jeff
* added support for IntelliVIBE DLL
*
* 273 11/02/99 4:22p Chris
* Fixed problem with being able to fire dual fire secondaries when you
* only have one missle left
*
* 272 10/22/99 2:39p Jay
* Update for the FinalBoss Laser Effect
*
* 271 10/21/99 9:30p Jeff
* B.A. Macintosh code merge
*
* 270 10/20/99 5:40p Chris
* Added the Red Guidebot
*
* 269 9/01/99 6:57p Jason
* fixed guided missiles and timeout missiles so they work in multiplayer
*
* 268 7/27/99 12:17p Chris
* Only play the homing sound if there is a direct line of sight between
* the player and the homing object
*
* 267 7/20/99 6:51p Chris
* The Omega Cannon no longer locks on the GB
*
* 266 5/22/99 10:38p Jason
* fixed dumb bug with last rev
*
* 265 5/22/99 10:27p Jason
* changes for multiplayer and buildings, ai
*
* 264 5/21/99 10:35p Jason
* fixed 3rd person firing spray effects through walls
*
* 263 5/21/99 5:08a Chris
* Fixed the weapon speed scaling for TEAM_REBEL robots
*
* 262 5/20/99 3:48p Chris
* Rebel stuff isn't usually scaled by diff anymore
*
* 261 5/20/99 3:10p Jason
* fixed omega homing on invisible objects
*
* 260 5/19/99 3:25p Jason
* fixed wrong ordering of InitObjectScripts and MultiSendObject
*
* 259 5/17/99 6:07p Chris
* Tweaked min fire spread for diff levels
*
* 258 5/10/99 11:57p Chris
* Now related objects are not targeted by an omega cannon (I.e. things
* like a flag or something attached)
*
* 257 5/09/99 11:57p Jason
* fixed electrical weapon bug
*
* 256 5/09/99 1:24p Jason
* made chaff multiplayer friendly
*
* 255 5/09/99 1:13p Matt
* Three things: (1) Made players & chaff related, so they don't collide;
* (2) Made homing missiles prefer chaff; (3) Fixed a fire spread bug.
*
* 254 5/08/99 10:38p Chris
* Further scaled the game. :)
*
* 253 5/08/99 3:30p Jason
* fixed MassDriver zoom bug
*
* 252 5/07/99 4:56p Chris
* Doing boa vis around a bunch of code. :)
*
* 251 5/05/99 6:30p Jason
* toned down mass driver effect
*
* 250 5/05/99 1:20a Chris
* Fixed homers.
*
* 249 5/02/99 2:00a Jason
* added assert to help track down DoSprayEffect crash
*
* 248 5/01/99 2:34p Jason
* fixed omega problems
*
* 247 4/28/99 11:28a Jason
* made real viseffects not cast light
*
* 246 4/24/99 4:33p Jason
* made fusion damage sound not be irritating
*
* 245 4/23/99 3:33p Jason
* fixed napalm going through walls
*
* 244 4/23/99 1:00p Chris
* Improved conditions for LARGE objects - i.e. buildings
*
* 243 4/23/99 11:56a Jason
* fixed mass driver mess up on robots
*
* 242 4/21/99 11:05a Kevin
* new ps_rand and ps_srand to replace rand & srand
*
* 241 4/20/99 8:15p Chris
* Fixed bug with firing directly at objects(if vec is NULL, don't use the
* flag)
*
* 240 4/18/99 5:49p Chris
* Fixed smart missles
*
* 239 4/16/99 3:48p Jason
* made vauss streamers a little cooler
*
* 238 4/14/99 3:57a Jeff
* fixed case mismatch in #includes
*
* 237 4/13/99 12:16p Jason
* mass driver...again!
*
* 236 4/12/99 7:05p Jason
* fixed omega lighting
*
* 235 4/12/99 3:03p Jason
* added recoil to player objects when firing weapons
*
* 234 4/12/99 8:30a Chris
* Moving building don't collide with powerups
*
* 233 4/10/99 5:09p Samir
* prioritize sounds, pass 1
*
* 232 4/09/99 7:27p Jason
* added better mass driver effect
*
* 231 4/09/99 12:06p Jason
* made model setup code faster
*
* 230 4/08/99 6:00p Jason
* more tweaks for mass driver
*
* 229 4/06/99 2:45p Jason
* don't play cuttoff sound if in permissable netgame
*
* 228 4/06/99 1:51p Jason
* added new mass driver effect
*
* 227 4/05/99 4:40p Jason
* added groovy new smoke trails
*
* 226 4/01/99 1:11p Jason
* made vis effects better for the lighting system
*
* 225 3/08/99 5:24p Jason
* fixed mass driver visual
*
* 224 3/08/99 11:37a Chris
* Fixed mod_pos problems
*
* 223 3/08/99 11:13a Jason
* made omegas homing cone a little more restricted
*
* 222 3/03/99 11:04a Chris
*
* 221 3/03/99 10:51a Chris
* Robots dont collide with openned doors
*
* 220 3/03/99 3:56a Chris
* Improve diff level stuff
*
* 219 3/03/99 3:36a Matt
* Fixed ammo usage problem
*
* 218 3/02/99 7:22p Chris
* Add the 'fire at target' check box for weapon batteries
*
* 217 3/02/99 3:39p Jason
* made quaded weapons use double energy/ammo
*
* 216 3/02/99 2:20p Jason
* doh! Fixed bug with my last rev
*
* 215 3/02/99 2:19p Jason
* fixed energy/ammo usage bug
*
* 214 2/10/99 4:02p Chris
* Updated the attach system so that if a robot is idling, the AI will not
* update the attached subobjects (if the object is still moving, physics
* will do it).
*
* 213 2/10/99 1:47p Matt
* Changed object handle symbolic constants
*
* 212 2/08/99 5:29p Jason
* added weapon options
*
* 211 2/08/99 11:05a Jason
* took off impact mortar blobbly explosion
*
* 210 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
*
* 209 1/31/99 7:26p Matt
* Renamed a bunch of functions to have HUD capitalized
*
* 208 1/29/99 4:59p Chris
* Fixed a bug where the omega cannon would mess up weapons
*
* 207 1/25/99 7:43a Chris
* Added the GUID (Goal Unique Id) and added the ability for weapon
* batteries to always fire exactly forward.
*
* 206 1/24/99 8:18p Chris
* Updated AI and OSIRIS. Externalized fireball constants for spew and
* sparks. Added CreateRandomSparks to OSIRIS, renamed a bunch of AI
* Notify names to use underscores. Fixed a memory access leak in the
* matcen effect code.
*
* 205 1/24/99 3:11a Jason
* fixed weapon fire no glow bug
*
* 204 1/21/99 11:29p Jason
* made no_glow flag work correctly with weapons
*
* 203 1/21/99 11:15p Jeff
* pulled out some structs and defines from header files and moved them
* into separate 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
*
* 202 1/21/99 7:02p Jason
* weapon effect changes
*
* 201 1/21/99 6:50p Jason
* added test impact mortar
*
* 200 1/14/99 12:21p Jason
* fixed up on/off weapons in multiplayer
*
* 199 1/13/99 12:43p Jason
* added some more detail settings
*
* 198 1/08/99 2:56p Samir
* Ripped out OSIRIS1.
*
* 197 1/07/99 11:43a Jason
* made zoom weapons shake your ship more
*
* 196 1/05/99 5:09p Jason
* added permissable server networking (ala Quake/Unreal) to Descent3
*
* 195 1/05/99 12:24p Chris
* More OSIRIS improvements
*
* 194 12/11/98 5:08p Jason
* changed mass driver effect
*
* 193 12/09/98 2:06p Jason
* changed mass driver tracers
*
* 192 12/08/98 4:28p Chris
* Improved ObjRelated code. Attach objects are correctly functional with
* weapons and other attached objects
*
* 191 12/02/98 5:52p Chris
* Fire spread is now partially scaled by difficulty level
*
* 190 12/02/98 10:30a Jason
* added additional damage types for client-side multiplayer
*
* 189 11/30/98 5:48p Jason
* toned down fusion screen color
*
* 188 11/23/98 3:11p Kevin
* Demo system
*
* 187 11/18/98 5:50p Jeff
* added some cheap recoil effects for ForceFeedback...not fully
* implemented
*
* 186 11/17/98 4:17p Kevin
* Demo recording system
*
* 185 11/16/98 10:30a Jason
* fixed user timeout missile
*
* 184 11/13/98 4:46p Jason
* fixed smart missile
*
* 183 11/13/98 4:25p Jason
* changes for better weapon effects
*
* 182 11/13/98 12:30p Jason
* changes for weapons
*
* 181 11/03/98 6:16p Chris
* Starting to make on/off and spray weapons accessable to robots
*
* 180 10/28/98 7:05p Jason
* made napalm work better
*
* 179 10/22/98 5:35p Chris
*
* 178 10/22/98 2:58p Chris
* Difficulty levels are in beta
*
* 177 10/21/98 10:33a Matt
* Fixed a problem where the napalm kept firing & making sound even when
* out of fuel.
*
* 176 10/21/98 7:33a Chris
* Dead objects are not-targetable
*
* 175 10/20/98 12:34p Chris
* Robots can now fire when players are right on them
*
* 174 10/19/98 11:46p Jason
* changes for multiplayer debug layer
*
* 173 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.
*
* 172 10/17/98 6:14p Chris
* Facing explosings happen from the object position - Planar explosions
* happen at the wall
*
* 171 10/17/98 2:35p Jason
* changed some potential RT_POLYOBJ problems to OF_POLYGON_OBJECT
*
* 170 10/16/98 4:18p Chris
* Fixed the 'flare thing'
*
* 169 10/16/98 3:42p Chris
*
* 168 10/16/98 3:39p Chris
* Improved the object linking system and AI and physics
*
* 167 10/13/98 12:58p Matt
* Allow a weapon to fire if it has non-zero ammo, even if it doesn't have
* as much as the weapon requires. This assures that the ammo will go all
* the way to zero.
*
* 166 10/12/98 10:20a Chris
* Weapon hits happen at the collision point
*
* 165 10/09/98 8:13p Craig
* Fixed a bad merge
*
* 164 10/09/98 7:47p Chris
* Added ObjSetDeadFlag
*
* 163 10/07/98 12:40p Jason
* fixed remaining weapon firing problems
*
* 162 10/06/98 5:46p Kevin
* Added new configuration for demo
*
* 161 10/05/98 4:31p Chris
* Homers should only track a firer's enemy
*
* 160 10/01/98 6:56p Jason
* turned off energy weapon hit effects if the object getting hit is the
* viewer - this helps cut down on the general clutter in heavy combat
*
* 159 9/30/98 4:05p Jason
* more parenting fun
*
* 158 9/28/98 10:54a Jason
* tidied up some napalm stuff
*
* 157 9/28/98 10:40a Chris
* Fixed a homing bug with cloaked
*
* 156 9/24/98 2:36p Jason
* took out gaps from first person napalm
*
* 155 9/22/98 1:02p Chris
* Improved which weapons can use the precomputed wall collisions
*
* 154 9/17/98 3:32p Jason
* tweaked omega effect
*
* 153 9/11/98 12:26p Jason
* got energy to shield and fusion damage working in multiplayer
*
* 152 9/10/98 5:56p Chris
* Added more shit to the matcen code :)
*
* 151 9/09/98 4:28p Jason
* added better weapon effects
*
* 150 9/08/98 5:35p Jason
* added WF_NO_ROTATE flag to weapons
*
* 149 8/26/98 1:39p Jason
* added weapon fired event
*
* 148 8/25/98 6:45p Chris
*
* 147 8/25/98 4:21p Chris
* Fixed an attached related problem
*
* 146 8/21/98 2:06p Jason
* fixed a slew of weapons bugs
*
* 145 8/19/98 2:48p Jason
* fixed some weapon firing and weapon exploding bugs
*
* 144 8/17/98 2:59p Chris
* Improved parented of fired weapons from attached objects - they now use
* the ultimate parent
*
* 143 8/17/98 12:10p Chris
* Fixed MAJOR bug in getting gunpoint positions
*
* 142 8/15/98 6:11p Chris
* Added 13 new fields. AI fire spread works.
*
* 141 8/12/98 3:33p Jason
* fixed omega cannon
*
* 140 8/06/98 1:00p Chris
* Added new homing flags
*
* 139 8/06/98 10:59a Jason
* added zoom weapons
*
* 138 8/03/98 5:56p Jason
* got fusion cannon working correctly
*
* 137 8/03/98 3:59p Chris
* Added support for FQ_IGNORE_WEAPONS, added .000001 attach code, fix a
* bug in polymodel collision detection
*
* 136 8/03/98 1:09p Jason
* added some more weapons flags
*
* 135 7/31/98 11:52a Chris
* Weapons can be persistent. Added ability for objects to be manually
* set for no object collisions.
*
* 134 7/30/98 11:09a Jason
* added weapons that freeze and deform terrain
*
* 133 7/28/98 12:44p Jason
* fixed robot spawning weapon bug
*
* 132 7/23/98 12:29p Chris
*
* 131 7/22/98 6:42p Chris
* A relink fix for points on a wall
*
* 130 7/22/98 6:31p Chris
* Fixed a relink bug
*
* 129 7/20/98 4:05p Chris
* Fixed a bug with not being able to fire while in powerups
*
* 128 7/20/98 2:48p Chris
* Removed hit_time (not neccessary)
*
* 127 7/20/98 2:26p Chris
*
* 126 7/20/98 2:06p Chris
* Added the precomputation of some weapons (fixed speed weapons). This
* algorithm can be expanded to include some missiles with some minor
* work.
*
* 125 7/20/98 12:48p Jason
* fixed bug with my last rev
*
* 124 7/20/98 10:43a Jason
* added more player scalars, plus afterburner changes
*
* 123 7/19/98 10:38p Chris
*
* 122 7/19/98 9:44p Chris
* Greatly improved homing performance
*
* 121 7/17/98 5:57p Jason
* misc multiplayer changes
*
* 120 7/15/98 12:48p Jeff
* put in calls to handle new multiplayer event...when an object's shields
* change
*
* 119 7/07/98 6:51p Jason
* fixed countermeasures for Jeffs multiplay
*
* 118 7/06/98 6:57p Chris
* Added some parenting limits
*
* 117 7/06/98 11:51a Jason
* added accessor function for countermeasures
*
* 116 7/01/98 12:55p Jason
* more changes for countermeasures
*
* 115 7/01/98 12:12p Jason
* added countermeasures
*
* 114 6/30/98 11:39a Jason
* added AdditionalDamage packet type for multiplayer
*
* 113 6/29/98 3:08p Jason
* added on/off weapons
*
* 112 6/29/98 12:34p Chris
* Fighting robots get some notification of hostile fire
*
* 111 6/26/98 6:53p Kevin
* Coop mode
*
* 110 6/26/98 2:04p Jason
* fixed some graphical problems with the omega
*
* 109 6/24/98 4:16p Jason
* adjusted omega effect
*
* 108 6/24/98 3:56p Jason
* added code for new omega cannon
*
* 107 6/22/98 6:26p Jason
* added gravity field effect for weapons
*
* 106 6/19/98 12:04p Jason
*
* 105 6/16/98 10:54a Jeff
*
* 104 6/15/98 6:29p Chris
* Added FQ_NO_RELINK to the homing code
*
* 103 6/15/98 6:08p Chris
* Hit_type to hit_type[0]
*
* 102 6/15/98 4:00p Jason
* replaced monochromatic polymodel lighting with rgb lighting
*
* 101 5/26/98 11:36a Matt
* Changed small view system to allow the popup window in any of the three
* positions, to allow any window to be the "bigger" size, and to restore
* the old view when a popup view goes away.
*
* 100 5/22/98 11:59a Chris
* Fixed improper uses of FindSoundName and made the proper static sounds
*
* 99 5/21/98 12:34p Jason
* made particles drop differently
*
* 98 5/19/98 4:42a Chris
* Added shockwave's -- enjoy. :)
*
* 97 5/18/98 8:05p Chris
* Matter weapons without spawn explode on no lifeleft -- we probably
* could use a flag for this
*
* 96 5/18/98 3:52p Chris
* More error checking for invalid gun numbers. :)
*
* 95 5/04/98 11:32a Chris
* Made it ok for non-polyobjs to fire things
*
* 94 5/04/98 1:12a Chris
* Added parenting bug checking
*
* 93 4/30/98 12:22p Jason
* did some lo-res model optimizations
*
* 92 4/24/98 3:20p Jason
* took redundant out smoke trails
*
* 91 4/19/98 5:00p Jason
* added cool napalm effect, plus made object effects dynamically
* allocated
*
* 90 4/17/98 4:58p Jason
* fixed weapon impact spawning to spread out from the normal of the wall
* it hits
*
* 89 4/17/98 1:59p Jason
* added cool object effects
*
* 88 4/15/98 2:33p Jason
* changed some smoke trail stuff
*
* 87 4/15/98 12:22p Jason
* lots of miscellaneous stuff pertaining to lighting and vis effects
*
* 86 4/10/98 6:56p Chris
* Fixed problem with missiles after they are done thrusting
*
* 85 4/10/98 2:16p Jason
* fixed guided missile problems
*
* 84 4/10/98 12:39p Jason
* added expanding explosion bitmaps
*
* 83 4/09/98 4:53p Jason
* fixed particle bug
*
* 82 4/09/98 3:16p Chris
* Updated the ObjectsRelated code.
*
* 81 4/09/98 2:23p Jason
* added guided/afterburner stuff
*
* 80 4/09/98 12:05p Chris
* Added parenting for all object types. :)
*
* 79 4/07/98 3:31p Jason
* got particle effects working with weapons
*
* 78 4/06/98 1:07p Chris
* Fixed bug with cloaked players
*
* 77 3/20/98 4:47p Chris
* Made homing sound play faster
*
* 76 3/20/98 3:54p Chris
* Working on adding sounds to the game. :)
*
* 75 3/19/98 12:53p Chris
* Fixed problem with not being able to home on the Highest_object_index
*
* 74 3/10/98 1:00p Chris
*
* 73 3/10/98 12:51p Chris
* Added a truely D2 style homing missile
*
* 72 3/09/98 1:18p Chris
* Made the lock hueristic totally dot based. It makes the most sense.
*
* 71 3/09/98 10:49a Chris
* Worked on the homing code
*
* 70 3/09/98 8:12a Chris
* Added the start of the homing code
*
* 69 3/06/98 3:11p Chris
* Fixed a bug I added with the last checkin
*
* 68 3/06/98 2:15p Chris
* Changed target_object to target_handle
*
* 67 3/05/98 2:50p Chris
* A bunch of SOAR updates
*
* 66 3/02/98 4:29p Jason
* fixed bitmap weapon spinning
*
* 65 2/18/98 1:34a Jason
* fixed foolhardy assertions
*
* 64 2/17/98 11:32p Matt
* Fixed player wepon fire problem when down_count != 0 && down_time == 0
* Add function to clear out player firing activity for when die or switch
* weapons
*
* 63 2/17/98 8:19p Matt
* Added looping & release sounds for player weapons
*
* 62 2/17/98 4:06p Jason
* Only explode timedout weapons if they have spawn
*
* 61 2/17/98 3:47p Matt
* Revamped weapon system and got sounds for spray and fusion weapons
* working. Still need to implements getting continuous & cutoff sounds
* from player ship.
*
* 60 2/16/98 3:02p Matt
* Changed normalized_time literal to a symbolic constant, as per ChrisP's
* instructions.
*
* 59 2/12/98 8:48p Matt
* Changed controls system to keep the reading of the controls separate
* from using the results. Got rid of the Controls global.
*
* 58 2/11/98 4:10p Jason
* got spawning impact weapons working
*
* 57 2/11/98 2:04p Jason
* got spawning weapons working
*
* 56 2/09/98 3:20p Matt
* Added missile camera system
*
* 55 2/05/98 6:29p Jason
* added user settable explode time/size
*
* 54 2/05/98 12:37p Jason
* added more weapon effects
*
* 53 2/04/98 9:28p Jason
* added some new weapons effects
*
* 52 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.
*
* 51 2/04/98 11:16a Jason
* added muzzle flash
*
* 50 2/03/98 12:03p Chris
* Updated how PF_USES_PARENT_VELOCITY works
*
* 49 2/02/98 4:07p Jason
* added a couple of weapons
*
* 48 1/29/98 6:06p Jason
* added new weapons
*
* 47 1/28/98 5:37p Jason
* added streamer weapons
*
* 46 1/28/98 12:00p Jason
* more changes for multiplayer
*
* 45 1/26/98 11:02a Jason
* fixed auto-fire bug
*
* 44 1/26/98 11:01a Jason
* incremental checkin for multiplayer
*
* 43 1/23/98 6:25p Jason
* Got spray weapons working
*
* 42 1/23/98 11:21a Jason
* incremental multiplayer checkin
*
* 41 1/20/98 12:10p Jason
* implemented vis effect system
*
* 40 1/19/98 10:04a Matt
* Added new object handle system
*
* 39 1/13/98 3:09p Jason
* added glow effect for engines
*
* 38 1/13/98 1:57p Chris
* Reduced parent relative velocity to 10% of the parent's velocity
*
* 37 1/10/98 2:24p Jason
* better multiplayer code checked in
*
* 36 12/22/97 6:19p Chris
* Moved weapon battery firing sound off the projectile (weapon) and into
* the weapon battery.
*
* 35 12/08/97 3:28p Jason
* tweaked explosions
*
* 34 12/03/97 11:54a Jason
* added designer-settable smoketrails
*
* 33 11/28/97 3:37p Jason
* fixed bug with alpha rendering
*
* 32 11/20/97 4:59p Jason
* tweaked some explosion stuff
*
* 31 11/18/97 4:37p Jason
* implemented some more changes to event system
*
* 30 11/13/97 5:20p Jason
* made bitmap weapons spin faster
*
* 29 11/12/97 5:35p Jason
* made energy/ammo usage work with weapon batteries
*
* 28 11/12/97 1:13p Jason
* added weapons that can ramp up
*
* 27 11/12/97 10:42a Chris
* Added an AI notify
*
* 26 11/07/97 5:33p Jason
* improved omega cannon effect
*
* 25 11/06/97 12:48p Chris
* Fixed a bug with the multi-configuration for a single wb. Actually, it
* will allow us to do some interesting things (like a weapon battery
* page).
*
* 24 11/06/97 6:17p Jason
* added support for electrical weapons
*
* 23 11/04/97 6:18p Chris
* Added support so that the player ship uses the robot's firing dialog.
*
* 22 11/03/97 4:31p Jason
* fixed bug when drawing bitmap weapons
*
* 21 10/30/97 4:02p Matt
* Added the flare
*
* 20 10/23/97 11:14a Jason
* changed some fireball DEFINEs
*
* 19 10/22/97 4:19p Jason
* added smoke trail effects
*
* 18 10/03/97 11:59a Samir
* Take care of setting player weapon usage stats.
*
* 17 9/17/97 11:02a Chris
* Fixed FindGunPoint's object transversal code (handles multiple base
* subobjects). Removed dependance on segment.h.
*
* 16 9/16/97 4:51p Matt
* Changed conditional for debug code
*
* 15 9/12/97 6:37p Chris
*
* 14 9/12/97 6:34p Chris
* My lasers collided with robot's lasers -- no more.
*
* 13 9/11/97 3:13p Samir
* Fixed missile autoeselection
*
* 12 9/05/97 12:25p Samir
* Added autoselection of weapons.
*
* 11 9/03/97 6:17p Jason
* added code to support powerups/robots/etc that cast light
*
* 10 9/03/97 3:54p Jason
* got objects to cast light
*
* 9 9/03/97 2:12p Chris
* Added new weapon battery system and made the animation system usable.
*
* 8 8/22/97 3:03a Chris
*
* 7 8/18/97 1:43a Chris
* Used new fvi_FindIntersection hit_seg format. FVIF_OUTSIDE bit.
*
* 6 8/07/97 5:19p Chris
* Expanded the weapon system
*
* 5 8/06/97 4:33p Chris
* Expanded the weapons page
*
* 4 8/04/97 5:35p Chris
*
* 3 7/28/97 1:12p Chris
* Fixed multiple bugs in the weapon system. Added fvi checking for
* weapon firing. Added a correct method of parenting weapons
* and for sibling collisions.
*
* $NoKeywords: $
*/
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include "weapon.h"
#include "descent.h"
#include "polymodel.h"
#include "room.h"
#include "object.h"
#include "player.h"
#include "hud.h"
#include "hlsoundlib.h"
#include "gameevent.h"
#include "fireball.h"
#include "findintersection.h"
#include "log.h"
#include "robotfire.h"
#include "AIMain.h"
#include "damage.h"
#include "sounds.h"
#include "viseffect.h"
#include "vclip.h"
#include "SmallViews.h"
#include "soundload.h"
#include "stringtable.h"
#include "multi.h"
#include "game2dll.h"
#include "gameloop.h"
#include "collide.h"
#include "demofile.h"
#include "difficulty.h"
#include "D3ForceFeedback.h"
#include "config.h"
#include "ObjScript.h"
#include "doorway.h"
#include "pserror.h"
#include "psrand.h"
#include "BOA.h"
static bool AreObjectsAttached(const object *obj1, const object *obj2) {
const bool f_o1_a = (obj1->flags & OF_ATTACHED) != 0;
const bool f_o2_a = (obj2->flags & OF_ATTACHED) != 0;
if (f_o1_a || f_o2_a) {
const int o1_uh = obj1->attach_ultimate_handle;
const int o2_uh = obj2->attach_ultimate_handle;
if ((f_o1_a) && ((o1_uh == obj2->handle) || (f_o2_a && (o1_uh == o2_uh))))
return true;
if ((f_o2_a) && (o2_uh == obj1->handle))
return true;
}
return false;
}
bool ObjectsAreRelated(int o1, int o2) {
if ((o1 < 0) || (o2 < 0))
return false;
const object *obj1 = &Objects[o1];
const object *obj2 = &Objects[o2];
ASSERT(obj1->handle != OBJECT_HANDLE_NONE);
ASSERT(obj2->handle != OBJECT_HANDLE_NONE);
if (obj1->movement_type == MT_OBJ_LINKED || obj2->movement_type == MT_OBJ_LINKED)
return true;
if (obj1->type != OBJ_SHOCKWAVE && (obj1->mtype.phys_info.flags & PF_NO_COLLIDE)) {
return true;
}
if (obj2->type != OBJ_SHOCKWAVE && (obj2->mtype.phys_info.flags & PF_NO_COLLIDE)) {
return true;
}
if (((obj1->type == OBJ_PLAYER) && ((obj2->type == OBJ_ROBOT) && (obj2->id == GENOBJ_CHAFFCHUNK))) ||
((obj2->type == OBJ_PLAYER) && ((obj1->type == OBJ_ROBOT) && (obj1->id == GENOBJ_CHAFFCHUNK))))
return true;
if (((obj1->type == OBJ_BUILDING) && (obj1->movement_type != MT_NONE) && (obj2->type == OBJ_POWERUP)) ||
((obj2->type == OBJ_BUILDING) && (obj2->movement_type != MT_NONE) && (obj1->type == OBJ_POWERUP))) {
return true;
}
if (obj1->type == OBJ_DOOR && DoorwayGetPosition(&Rooms[obj1->roomnum]) == 1.0f && obj2->type == OBJ_ROBOT)
return true;
if (obj2->type == OBJ_DOOR && DoorwayGetPosition(&Rooms[obj2->roomnum]) == 1.0f && obj1->type == OBJ_ROBOT)
return true;
if (AreObjectsAttached(obj1, obj2))
return true;
if (obj1->type != OBJ_WEAPON && obj2->type != OBJ_WEAPON) {
if (((Gametime < obj1->creation_time + 3.0f) && obj1->parent_handle == obj2->handle) ||
((Gametime < obj2->creation_time + 3.0f) && obj2->parent_handle == obj1->handle))
return true;
else
return false;
}
if (obj1->type == OBJ_WEAPON && obj1->movement_type == MT_PHYSICS && (obj1->mtype.phys_info.flags & PF_PERSISTENT) &&
obj1->ctype.laser_info.last_hit_handle == obj2->handle)
return true;
if (obj2->type == OBJ_WEAPON && obj2->movement_type == MT_PHYSICS && (obj2->mtype.phys_info.flags & PF_PERSISTENT) &&
obj2->ctype.laser_info.last_hit_handle == obj1->handle)
return true;
// See if o2 is the parent of o1
if (obj1->type == OBJ_WEAPON && (obj1->mtype.phys_info.flags & PF_NO_COLLIDE_PARENT)) {
if (obj1->parent_handle == obj2->handle)
return true;
object *t1 = ObjGet(obj1->parent_handle);
if (t1) {
if (AreObjectsAttached(obj2, t1))
return true;
}
}
// See if o1 is the parent of o2
if (obj2->type == OBJ_WEAPON && (obj2->mtype.phys_info.flags & PF_NO_COLLIDE_PARENT)) {
if (obj2->parent_handle == obj1->handle)
return true;
object *t2 = ObjGet(obj2->parent_handle);
if (t2) {
if (AreObjectsAttached(obj1, t2))
return true;
}
}
// They must both be weapons
if (obj1->type != OBJ_WEAPON || obj2->type != OBJ_WEAPON) {
return false;
}
// Here is the 09/07/94 change -- Siblings must be identical, others can hurt each other
// See if they're siblings...
if (obj1->parent_handle == obj2->parent_handle) {
if ((obj1->mtype.phys_info.flags & PF_HITS_SIBLINGS) || (obj2->mtype.phys_info.flags & PF_HITS_SIBLINGS)) {
return false; // if either is proximity, then can blow up, so say not related
} else {
return true;
}
}
// Otherwise, it is two weapons and by default, they should not collide
return true;
}
bool Enable_omega_collions = false;
// Picks out a target for an elecrical weapon to fire on
void AquireElectricalTarget(object *obj) {
int i;
float closest_dist = 99999, dist;
object *closest_obj = NULL, *hit_obj_ptr;
if (obj->mtype.phys_info.flags & PF_HOMING) {
for (i = 0; i <= Highest_object_index; i++) {
hit_obj_ptr = &Objects[i];
if ((hit_obj_ptr->render_type == RT_NONE) ||
!(hit_obj_ptr->type == OBJ_PLAYER || hit_obj_ptr->type == OBJ_ROBOT ||
(hit_obj_ptr->type == OBJ_BUILDING && hit_obj_ptr->ai_info)))
continue;
if (ObjectsAreRelated(OBJNUM(obj), i))
continue;
if (obj == hit_obj_ptr)
continue;
if (hit_obj_ptr->flags & OF_DEAD)
continue;
// The GB has an "electric shield" that works kind of like a Faraday's Cage (it also
// make him less annoying as a player's own Omega will not track his GB)
if (IS_GUIDEBOT(hit_obj_ptr))
continue;
// Make sure this target is within our view cone
vector subvec = hit_obj_ptr->pos - obj->pos;
vm_NormalizeVectorFast(&subvec);
if (vm_DotProduct(&subvec, &obj->orient.fvec) < .8)
continue;
// The distance is actually the objects' centers minus some of the hit object's radius (I set it to 80%)
if ((hit_obj_ptr->flags & OF_POLYGON_OBJECT) && hit_obj_ptr->type != OBJ_WEAPON &&
hit_obj_ptr->type != OBJ_POWERUP && hit_obj_ptr->type != OBJ_DEBRIS) {
vector pos;
pos = hit_obj_ptr->pos + hit_obj_ptr->wall_sphere_offset;
dist = vm_VectorDistanceQuick(&pos, &obj->pos) - Poly_models[hit_obj_ptr->rtype.pobj_info.model_num].wall_size;
} else {
dist = vm_VectorDistanceQuick(&hit_obj_ptr->pos, &obj->pos) - hit_obj_ptr->size;
}
if (dist < 0.0f)
dist = 0.0f;
if (dist > 50)
continue;
if (dist <= closest_dist) {
closest_dist = dist;
closest_obj = hit_obj_ptr;
}
}
}
if (closest_obj != NULL) {
float old_shield, new_shield;
if (closest_obj->effect_info && closest_obj != Viewer_object) {
closest_obj->effect_info->type_flags |= EF_DEFORM;
closest_obj->effect_info->deform_time = 1.0;
closest_obj->effect_info->deform_range = .03f;
}
obj->ctype.laser_info.track_handle = closest_obj->handle;
vector subvec = obj->pos - closest_obj->pos;
vm_NormalizeVectorFast(&subvec);
old_shield = closest_obj->shields;
vector save_pos = obj->pos;
vector col_norm = {0, 1, 0};
obj->pos = closest_obj->pos;
Enable_omega_collions = true;
collide_two_objects(obj, closest_obj, &closest_obj->pos, &col_norm);
Enable_omega_collions = false;
obj->pos = save_pos;
new_shield = closest_obj->shields;
object *parent_obj = ObjGet(obj->parent_handle);
if (parent_obj->shields < 100) {
float shields_to_add = (old_shield - new_shield) * .1;
if (parent_obj->shields + shields_to_add > 100)
shields_to_add = 100 - parent_obj->shields;
if (Game_mode & GM_MULTI) {
if (Netgame.local_role == LR_SERVER) {
parent_obj->shields += shields_to_add;
Multi_additional_damage[parent_obj->id] -= shields_to_add;
Multi_additional_damage_type[parent_obj->id] = PD_ENERGY_WEAPON;
DLLInfo.it_handle = -1;
DLLInfo.me_handle = parent_obj->handle;
DLLInfo.fParam = shields_to_add;
CallGameDLL(EVT_GAMEOBJECTSHIELDSCHANGED, &DLLInfo);
}
} else
parent_obj->shields += shields_to_add;
}
} else {
obj->ctype.laser_info.track_handle = OBJECT_HANDLE_NONE;
// Cast a ray to see what we've hit
int fate; // Collision type for response code
fvi_info hit_info; // Hit information
fvi_query fq; // Movement query
vector dest = obj->pos + (obj->orient.fvec * (scalar)50.0);
fq.p0 = &obj->pos;
fq.startroom = obj->roomnum;
fq.p1 = &dest;
fq.rad = (scalar).0001f;
fq.thisobjnum = Objects - obj;
fq.ignore_obj_list = NULL;
fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS;
fate = fvi_FindIntersection(&fq, &hit_info);
vector save_pos = obj->pos;
if (fate == HIT_WALL || fate == HIT_TERRAIN) {
obj->pos = hit_info.hit_pnt;
collide_object_with_wall(obj, 0, hit_info.hit_face_room[0], hit_info.hit_face[0], &hit_info.hit_face_pnt[0],
&hit_info.hit_wallnorm[0], 0);
} else if (fate == HIT_OBJECT || fate == HIT_SPHERE_2_POLY_OBJECT) {
if (CollisionResult[obj->type][Objects[hit_info.hit_object[0]].type] != RESULT_NOTHING) {
obj->pos = hit_info.hit_pnt;
Enable_omega_collions = true;
collide_two_objects(obj, &Objects[hit_info.hit_object[0]], &hit_info.hit_face_pnt[0],
&hit_info.hit_wallnorm[0]);
Enable_omega_collions = false;
}
}
obj->pos = save_pos;
obj->flags &= ~OF_DEAD; // Bring it back from the dead in case the collide code killed it!
}
}
// Creates an weapon and sends it speeding on its way
int CreateAndFireWeapon(vector *pos, vector *dir, object *parent, int weapon_num) {
int objnum;
object *obj;
ASSERT(Weapons[weapon_num].used);
// This can happen in multiplayer -- not single player
if (parent->flags & OF_DEAD)
return -1;
// Find out if it is o.k. to create a weapon here.
fvi_query fq;
fvi_info hit_data;
int fate;
fq.p0 = &parent->pos;
fq.startroom = parent->roomnum;
fq.p1 = pos;
fq.rad = 0;
fq.thisobjnum = OBJNUM(parent);
fq.ignore_obj_list = NULL;
fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_WEAPONS | FQ_IGNORE_POWERUPS |
FQ_IGNORE_NON_LIGHTMAP_OBJECTS; // what about trans walls???
if (parent->type == OBJ_BUILDING) {
fq.flags &= ~FQ_CHECK_OBJS;
}
fate = fvi_FindIntersection(&fq, &hit_data);
if (fate != HIT_NONE) {
LOG_WARNING.printf("Warning: Laser from parent=%d weapon point is in a wall or object, didn't fire!",
OBJNUM(parent));
return -1;
}
objnum = ObjCreate(OBJ_WEAPON, weapon_num, hit_data.hit_room, pos, NULL, parent->handle);
if (objnum < 0) {
LOG_WARNING << "Can't create laser - Out of objects!";
return -1;
}
obj = &Objects[objnum];
// Fill in laser-specific data
obj->ctype.laser_info.multiplier = 1.0;
obj->ctype.laser_info.parent_type = parent->type;
if (parent->flags & OF_ATTACHED)
obj->parent_handle = parent->attach_ultimate_handle;
else
obj->parent_handle = parent->handle;
// Assign parent type to highest level creator. This propagates parent type down from
// the original creator through weapons which create children of their own (ie, smart missile)
object *p = parent;
while (p && (p->type == OBJ_WEAPON)) {
p = ObjGet(p->parent_handle);
if (p) {
obj->ctype.laser_info.parent_type = p->type;
obj->parent_handle = p->handle;
}
}
// Set direction
vm_VectorToMatrix(&obj->orient, dir, &parent->orient.uvec, NULL);
float scalar = 1.0f;
if (!ObjGet(obj->parent_handle) || ObjGet(obj->parent_handle)->type != OBJ_PLAYER)
scalar = (ObjGet(obj->parent_handle)->control_type == CT_AI &&
((ObjGet(obj->parent_handle)->ai_info->flags & AIF_TEAM_MASK) == AIF_TEAM_REBEL))
? 1.0f
: Diff_ai_weapon_speed[DIFF_LEVEL];
// Set thrust
if (obj->mtype.phys_info.flags & PF_USES_THRUST) {
vm_ScaleVector(&obj->mtype.phys_info.thrust, dir, obj->mtype.phys_info.full_thrust * scalar);
// If this is a player, scale the thrust based on the players weapon_speed scalar
if (parent->type == OBJ_PLAYER)
obj->mtype.phys_info.thrust *= Players[parent->id].weapon_speed_scalar;
} else {
vm_MakeZero(&obj->mtype.phys_info.thrust);
vm_MakeZero(&obj->mtype.phys_info.rotthrust);
}
// vm_MakeZero(&obj->mtype.phys_info.rotvel);
// Set the initial velocity
vm_ScaleVector(&obj->mtype.phys_info.velocity, dir, Weapons[weapon_num].phys_info.velocity.z() * scalar);
// If this is a player, scale the velocity based on the players weapon_speed scalar
if (parent->type == OBJ_PLAYER)
obj->mtype.phys_info.velocity *= Players[parent->id].weapon_speed_scalar;
// Set initial velocity to that of the firing object
// Don't do it though if it is a spawned weapon
if ((obj->mtype.phys_info.flags & PF_USES_PARENT_VELOCITY) && parent->type != OBJ_WEAPON) {
float fdot = vm_Dot3Product(parent->mtype.phys_info.velocity, parent->orient.fvec);
vector fvel;
if (fdot > 0.0)
fvel = parent->orient.fvec * fdot;
else
fvel = vector{};
vector rvel = 0.1f * parent->orient.rvec * vm_Dot3Product(parent->mtype.phys_info.velocity, parent->orient.rvec);
vector uvel = 0.1f * parent->orient.uvec * vm_Dot3Product(parent->mtype.phys_info.velocity, parent->orient.uvec);
obj->mtype.phys_info.velocity += fvel + rvel + uvel;
}
if (Weapons[obj->id].flags & WF_ELECTRICAL) {
AquireElectricalTarget(obj);
} else if (obj->movement_type == MT_PHYSICS) {
if (!((obj->mtype.phys_info.flags & PF_USES_PARENT_VELOCITY) && (obj->mtype.phys_info.flags & PF_USES_THRUST))) {
// For now, it only works on fixed velocity weapons
if ((obj->mtype.phys_info.flags & PF_FIXED_VELOCITY) &&
!(obj->mtype.phys_info.flags & (PF_GRAVITY_MASK | PF_HOMING | PF_WIGGLE | PF_GUIDED))) {
fvi_query fq;
fvi_info hit_info;
vector end_pos = obj->pos + obj->mtype.phys_info.velocity * obj->lifeleft;
fq.p0 = &obj->pos;
fq.startroom = obj->roomnum;
fq.p1 = &end_pos;
fq.rad = obj->size;
fq.thisobjnum = OBJNUM(obj);
fq.ignore_obj_list = NULL;
fq.flags = 0;
fq.flags |= FQ_TRANSPOINT;
fate = fvi_FindIntersection(&fq, &hit_info);
if (fate != HIT_NONE && fate != HIT_OUT_OF_TERRAIN_BOUNDS) {
obj->ctype.laser_info.hit_pnt = hit_info.hit_pnt;
obj->ctype.laser_info.hit_wall_pnt = hit_info.hit_face_pnt[0];
obj->ctype.laser_info.hit_room = hit_info.hit_room;
obj->ctype.laser_info.hit_pnt_room = hit_info.hit_face_room[0];
obj->ctype.laser_info.hit_wall_normal = hit_info.hit_wallnorm[0];
obj->ctype.laser_info.hit_face = hit_info.hit_face[0];
obj->ctype.laser_info.hit_status = WPC_HIT_WALL;
// mprintf(0, "WPC: Hit r%d rw%d w%d (%f,%f,%f)\n", hit_info.hit_room,
// hit_info.hit_face_room[0], hit_info.hit_face[0], XYZ(&hit_info.hit_wallnorm[0]));
} else {
obj->ctype.laser_info.hit_status = WPC_NO_COLLISIONS;
// mprintf(0, "WPC: No collisions\n");
}
}
}
}
if (parent && (parent->type == OBJ_PLAYER || parent->type == OBJ_GHOST) && (parent->id == Player_num)) {
// Do ForceFeeback for recoil
DoForceForRecoil(parent, obj);
}
// For smoke effects
obj->ctype.laser_info.last_smoke_pos = obj->pos - (obj->orient.fvec * (obj->size / 2));
obj->ctype.laser_info.casts_light = true;
return objnum;
}
// Steers a homing missile
void HomingTurnTowardObj(object *weapon, object *target) {
vector dir_to_target;
vector movement{};
if (target == NULL)
return;
if (target->movement_type == MT_PHYSICS || target->movement_type == MT_WALKING) {
movement = 0.1f * (target->mtype.phys_info.velocity);
}
dir_to_target = target->pos + movement - weapon->pos;
if (weapon->mtype.phys_info.rotdrag > 0.0f) {
if (vm_Dot3Product(dir_to_target, weapon->orient.rvec) > 0.0) {
weapon->mtype.phys_info.rotthrust.y() = weapon->mtype.phys_info.full_rotthrust;
} else {
weapon->mtype.phys_info.rotthrust.y() = -weapon->mtype.phys_info.full_rotthrust;
}
if (vm_Dot3Product(dir_to_target, weapon->orient.uvec) > 0.0) {
weapon->mtype.phys_info.rotthrust.x() = -weapon->mtype.phys_info.full_rotthrust;
} else {
weapon->mtype.phys_info.rotthrust.x() = weapon->mtype.phys_info.full_rotthrust;
}
weapon->mtype.phys_info.rotthrust.z() = 0.0;
if (!ObjGet(weapon->parent_handle) || ObjGet(weapon->parent_handle)->type != OBJ_PLAYER)
weapon->mtype.phys_info.rotthrust *= Diff_homing_strength[DIFF_LEVEL];
} else {
float scalar;
vm_NormalizeVector(&dir_to_target);
if (!ObjGet(weapon->parent_handle) || ObjGet(weapon->parent_handle)->type != OBJ_PLAYER)
scalar = Diff_homing_strength[DIFF_LEVEL];
else
scalar = 1.0f;
AITurnTowardsDir(weapon, &dir_to_target, weapon->mtype.phys_info.full_rotthrust * scalar);
weapon->mtype.phys_info.velocity =
weapon->orient.fvec * weapon->mtype.phys_info.full_thrust / weapon->mtype.phys_info.drag;
weapon->mtype.phys_info.thrust = weapon->orient.fvec * weapon->mtype.phys_info.full_thrust;
}
}
#define MAX_HOMING_DIST 500.0f
#define CLOSE_HOMING_DIST 15.0
#define MAX_HOMING_SOUND_TIME_DIST 250.00f
#define MAX_HOMING_SOUND_TIME 1.00f
#define MIN_HOMING_SOUND_TIME 0.05f
#define HOMING_CHECK_VIS_INTERVAL 0.25f
#define HOMING_FIND_LOC_INTERVAL 0.20f
object *HomingAquireTarget(object *obj) {
object *track_goal = ObjGet(obj->ctype.laser_info.track_handle);
object *weapon_parent = ObjGet(obj->parent_handle);
// Forces its parent to itself if there is no parent
if (weapon_parent == NULL) {
weapon_parent = obj;
}
// Determine if the current track_goal is valid
if (track_goal) {
bool f_locked = false;
vector to_target = track_goal->pos - obj->pos;
float dist_to_target = vm_NormalizeVector(&to_target) - track_goal->size - obj->size;
if (obj->type == OBJ_GHOST || obj->type == OBJ_DUMMY ||
(obj->type == OBJ_PLAYER && (Players[obj->id].flags & PLAYER_FLAGS_DEAD))) {
f_locked = false;
} else if (obj->effect_info && (obj->effect_info->type_flags & EF_CLOAKED)) {
f_locked = false;
} else if (vm_Dot3Product(to_target, obj->orient.fvec) > Weapons[obj->id].homing_fov) {
if (track_goal == Player_object && Player_object->type == OBJ_PLAYER) {
float sound_delta;
float volume;
if (dist_to_target > MAX_HOMING_SOUND_TIME_DIST) {
sound_delta = MAX_HOMING_SOUND_TIME;
volume = MAX_GAME_VOLUME / 2.0f;
} else {
float percent_near = dist_to_target / (float)MAX_HOMING_SOUND_TIME_DIST;
sound_delta = MAX_HOMING_SOUND_TIME * percent_near;
volume = MAX_GAME_VOLUME / 2.0f + MAX_GAME_VOLUME / 2.0 * (1.0f - percent_near);
if (sound_delta < MIN_HOMING_SOUND_TIME) {
sound_delta = MIN_HOMING_SOUND_TIME;
volume = MAX_GAME_VOLUME;
}
}
if (!(obj->type == OBJ_WEAPON && Weapons[obj->id].flags & WF_SILENT_HOMING) &&
!(obj->mtype.phys_info.flags & PF_GUIDED) &&
Players[Player_object->id].last_homing_warning_sound_time + sound_delta < Gametime) {
fvi_query fq;
fvi_info hit_info;
fq.p0 = &obj->pos;
fq.startroom = obj->roomnum;
fq.p1 = &track_goal->pos;
fq.rad = 0.0f;
fq.thisobjnum = OBJNUM(obj);
fq.ignore_obj_list = NULL;
fq.flags = /*FQ_TRANSPOINT | */ FQ_CHECK_OBJS | FQ_ONLY_DOOR_OBJ | FQ_NO_RELINK;
fvi_FindIntersection(&fq, &hit_info);
// Only plays sound if there is a LOS between the player and the homing weapon
if (hit_info.hit_type[0] == HIT_NONE) {
Sound_system.Play2dSound(SOUND_MISSLE_LOCK, SND_PRIORITY_HIGHEST, volume);
Players[Player_object->id].last_homing_warning_sound_time = Gametime;
}
}
}
if (dist_to_target < CLOSE_HOMING_DIST) {
f_locked = true;
} else if (dist_to_target < MAX_HOMING_DIST) {
if (BOA_IsVisible(obj->roomnum, track_goal->roomnum)) {
if (Gametime > obj->ctype.laser_info.last_track_time + HOMING_CHECK_VIS_INTERVAL) {
obj->ctype.laser_info.last_track_time = Gametime;
fvi_query fq;
fvi_info hit_info;
fq.p0 = &obj->pos;
fq.startroom = obj->roomnum;
fq.p1 = &track_goal->pos;
fq.rad = 0.0f;
fq.thisobjnum = OBJNUM(obj);
fq.ignore_obj_list = NULL;
fq.flags = /*FQ_TRANSPOINT | */ FQ_CHECK_OBJS | FQ_ONLY_DOOR_OBJ | FQ_NO_RELINK;
fvi_FindIntersection(&fq, &hit_info);
if (hit_info.hit_type[0] == HIT_NONE) {
f_locked = true;
}
} else {
f_locked = true;
}
}
}
}
if (!f_locked) {
track_goal = NULL;
obj->ctype.laser_info.track_handle = OBJECT_HANDLE_NONE;
}
}
// Determine a track_goal if there is not one
if (!track_goal) {
int i;
int best_index = -1;
int best_dist = MAX_HOMING_DIST + 1;
float best_dot = -1.0f;
if (Gametime > obj->ctype.laser_info.last_track_time + HOMING_FIND_LOC_INTERVAL) {
obj->ctype.laser_info.last_track_time = Gametime;
for (i = 0; i <= Highest_object_index; i++) {
if (BOA_IsVisible(obj->roomnum, Objects[i].roomnum)) {
if ((i != OBJNUM(weapon_parent)) && ((Objects[i].type == OBJ_BUILDING && Objects[i].ai_info) ||
(Objects[i].type == OBJ_ROBOT) || (Objects[i].type == OBJ_PLAYER))) {
vector to_target = Objects[i].pos - obj->pos;
if (Objects[i].effect_info && Objects[i].effect_info->type_flags & EF_CLOAKED)
continue;
if (weapon_parent && !AIObjEnemy(ObjGetUltimateParent(weapon_parent), ObjGetUltimateParent(&Objects[i])))
continue;
scalar dist_to_target = vm_NormalizeVector(&to_target) - obj->size - Objects[i].size;
scalar cur_dot = vm_Dot3Product(to_target, obj->orient.fvec);
if (cur_dot > Weapons[obj->id].homing_fov) {
// Pick chaff over other objects
if ((Objects[i].type == OBJ_ROBOT) && (Objects[i].id == GENOBJ_CHAFFCHUNK)) {
// Make multiplayer safe!
if (Game_mode & GM_MULTI) {
if (Netgame.local_role == LR_CLIENT) {
if (!(Local_object_list[i] & 0x3)) {
best_index = i;
break;
}
} else {
if (!(i & 0x3)) {
best_index = i;
break;
}
}
} else {
if (ps_rand() < D3_RAND_MAX / 4) { // 1/4 chance of picking chaff
best_index = i;
break;
}
}
} else if (dist_to_target < CLOSE_HOMING_DIST) {
best_index = i;
break;
} else if (dist_to_target < MAX_HOMING_DIST) {
if (cur_dot > best_dot) {
fvi_query fq;
fvi_info hit_info;
fq.p0 = &obj->pos;
fq.startroom = obj->roomnum;
fq.p1 = &Objects[i].pos;
fq.rad = 0.0f;
fq.thisobjnum = OBJNUM(obj);
fq.ignore_obj_list = NULL;
fq.flags = FQ_TRANSPOINT | FQ_CHECK_OBJS | FQ_ONLY_DOOR_OBJ | FQ_NO_RELINK;
fvi_FindIntersection(&fq, &hit_info);
if (hit_info.hit_type[0] == HIT_NONE) {
best_index = i;
best_dist = dist_to_target;
best_dot = cur_dot;
}
}
}
}
}
}
}
if (best_index >= 0) {
track_goal = &Objects[best_index];
obj->ctype.laser_info.track_handle = Objects[best_index].handle;
}
}
}
return track_goal;
}
// Does homing code
void HomingDoFrame(object *obj) {
obj->mtype.phys_info.rotthrust = vector{};
HomingTurnTowardObj(obj, HomingAquireTarget(obj));
}
void WeaponDoFrame(object *obj) {
bool draw_effects = 1;
if (!Detail_settings.Weapon_coronas_enabled)
draw_effects = 0;
if (MAX_WEAPON_NOT_HIT_PARENT_TIME + obj->creation_time < Gametime)
obj->mtype.phys_info.flags &= (~PF_NO_COLLIDE_PARENT);
if ((obj->mtype.phys_info.flags & PF_USES_THRUST) && (obj->ctype.laser_info.thrust_left >= 0.0)) {
obj->ctype.laser_info.thrust_left -= Frametime;
if ((obj->creation_time + 0.1f < Gametime) && (obj->mtype.phys_info.flags & PF_HOMING) &&
!(obj->mtype.phys_info.flags & PF_GUIDED)) {
obj->ctype.laser_info.last_track_time = Gametime - HOMING_CHECK_VIS_INTERVAL;
HomingDoFrame(obj);
}
if (obj->ctype.laser_info.thrust_left <= 0.0) {
obj->mtype.phys_info.flags &= (~PF_USES_THRUST);
vm_MakeZero(&obj->mtype.phys_info.thrust);
vm_MakeZero(&obj->mtype.phys_info.rotthrust);
obj->mtype.phys_info.drag = 0.005f;
obj->mtype.phys_info.rotdrag = 0.005f;
obj->mtype.phys_info.flags |= PF_GRAVITY;
}
}
if ((Weapons[obj->id].flags & WF_SMOKE) && draw_effects) {
if (Weapons[obj->id].flags & WF_PLANAR_SMOKE) {
vector newpos = obj->pos - (obj->orient.fvec * (obj->size / 2));
int visnum = VisEffectCreate(VIS_FIREBALL, BILLBOARD_SMOKETRAIL_INDEX, obj->roomnum, &newpos);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->custom_handle = Weapons[obj->id].smoke_handle;
vis->lifeleft = 1.0;
vis->lifetime = 1.0;
vis->end_pos = obj->ctype.laser_info.last_smoke_pos;
vis->billboard_info.width = 4;
vis->lighting_color = GR_RGB16(Weapons[obj->id].lighting_info.red_light1 * 255.0,
Weapons[obj->id].lighting_info.green_light1 * 255.0,
Weapons[obj->id].lighting_info.blue_light1 * 255.0);
obj->ctype.laser_info.last_smoke_pos = vis->pos;
}
} else {
// Create blobby smoke
// Create extras
vector blobpos = obj->pos - (obj->orient.fvec * obj->size);
float delta_time = Frametime;
vector delta_vec = obj->mtype.phys_info.velocity * delta_time;
float mag = vm_GetMagnitudeFast(&delta_vec);
float extras = (mag * 4) + .5;
int int_extras = extras + 1;
int_extras = std::min(int_extras, 2);
delta_vec /= int_extras;
delta_time /= int_extras;
vector new_blobpos = blobpos;
for (int i = 0; i < int_extras; i++) {
int visnum = CreateFireball(&new_blobpos, SMOKE_TRAIL_INDEX, obj->roomnum, VISUAL_FIREBALL);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->custom_handle = Weapons[obj->id].smoke_handle;
vis->lifeleft -= (i * delta_time);
vis->creation_time -= (i * delta_time);
if (Weapons[obj->id].flags & WF_REVERSE_SMOKE)
vis->flags |= VF_REVERSE;
}
new_blobpos -= delta_vec;
}
}
}
if (Weapons[obj->id].particle_count > 0 && Weapons[obj->id].particle_handle != -1 && draw_effects) {
vector pos = obj->pos - (obj->orient.fvec * obj->size);
weapon *weap = &Weapons[obj->id];
float particle_interval = (1.0 / (float)weap->particle_count);
if (Gametime - obj->ctype.laser_info.last_drop_time > particle_interval) {
CreateRandomParticles(1, &pos, obj->roomnum, weap->particle_handle, weap->particle_size, weap->particle_life);
obj->ctype.laser_info.last_drop_time = Gametime;
}
}
if (Weapons[obj->id].flags & WF_RING) {
float norm = ((Gametime - obj->creation_time) / obj->lifetime);
obj->size = Weapons[obj->id].size + (norm * Weapons[obj->id].size * 8);
}
}
bool WeaponCalcGun(vector *gun_point, vector *gun_normal, object *obj, int gun_num) {
poly_model *pm;
vector pnt;
vector normal;
matrix m;
int mn; // submodel number
float normalized_time[MAX_SUBOBJECTS];
bool f_good_gp = true;
if (!(obj->flags & OF_POLYGON_OBJECT)) {
// mprintf(0,"Object type %d is not a polyobj!\n",obj->type);
if (gun_point)
*gun_point = obj->pos;
if (gun_normal)
*gun_normal = obj->orient.fvec;
return false;
}
pm = &Poly_models[obj->rtype.pobj_info.model_num];
if (pm->n_guns == 0) {
LOG_WARNING.printf("WARNING: Object with no weapons is firing.", gun_num);
if (gun_point)
*gun_point = obj->pos;
if (gun_normal)
*gun_normal = obj->orient.fvec;
return false;
}
SetNormalizedTimeObj(obj, normalized_time);
SetModelAnglesAndPos(pm, normalized_time);
if (gun_num < 0 || gun_num >= pm->n_guns) {
LOG_WARNING.printf("WARNING: Bashing gun num %d to 0.", gun_num);
if (gun_point)
*gun_point = obj->pos;
if (gun_normal)
*gun_normal = obj->orient.fvec;
return false;
}
pnt = pm->gun_slots[gun_num].pnt;
normal = pm->gun_slots[gun_num].norm;
mn = pm->gun_slots[gun_num].parent;
// 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);
if (gun_point)
tpnt = pnt * m;
if (gun_normal)
normal = normal * m;
if (gun_point)
pnt = tpnt + pm->submodel[mn].offset + pm->submodel[mn].mod_pos;
mn = pm->submodel[mn].parent;
}
m = obj->orient;
vm_TransposeMatrix(&m);
if (gun_point)
*gun_point = pnt * m;
if (gun_normal)
*gun_normal = normal * m;
if (gun_point)
*gun_point += obj->pos;
return f_good_gp;
}
#define MIN_FIRE_SPREAD 5000
#define MAX_FIRE_SPREAD 16768 // We vary by difficulty level!!!!!!!! Its the total fire cone (half each way)
// Given an object and a weapon, fires a shot from that object
int FireWeaponFromObject(object *obj, int weapon_num, int gun_num, bool f_force_forward, bool f_force_target) {
ASSERT(Weapons[weapon_num].used);
// Mass driver is hack
static int mass_driver_id = -2;
static int mass_driver_ring = -2;
if (mass_driver_id == -2) {
mass_driver_id = FindWeaponName("Mass Driver");
mass_driver_ring = FindTextureName("CloakRing.tga1");
}
vector laser_pos, laser_dir;
int objnum;
if (gun_num == -1) {
laser_pos = obj->pos;
laser_dir = obj->orient.fvec;
} else {
// Find the initial position of the laser
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
// vector *pnt = &pm->gun_slots[gun_num].pnt;
// vector gun_point;
// matrix m;
if (gun_num >= pm->n_guns) {
// We don't have a gun point for this gun!
LOG_WARNING.printf("Trying to fire from gun %d...we don't have that gun!", gun_num);
laser_pos = obj->pos;
laser_dir = obj->orient.fvec;
} else {
if (f_force_forward) {
WeaponCalcGun(&laser_pos, NULL, obj, gun_num);
laser_dir = obj->orient.fvec;
} else if (f_force_target && obj->ai_info && obj->ai_info->vec_to_target_perceived != vector{}) {
WeaponCalcGun(&laser_pos, NULL, obj, gun_num);
laser_dir = obj->ai_info->vec_to_target_perceived;
} else {
WeaponCalcGun(&laser_pos, &laser_dir, obj, gun_num);
}
}
}
if ((obj->control_type == CT_AI) && (obj->ai_info->fire_spread || (Diff_ai_min_fire_spread[DIFF_LEVEL]))) {
matrix rot_mat;
angle p, h, b;
float diff_scale = DIFF_LEVEL / (MAX_DIFFICULTY_LEVELS - 1);
float fs = (MAX_FIRE_SPREAD * (1.0f - diff_scale)) + (MIN_FIRE_SPREAD * diff_scale);
int16_t fire_spread = obj->ai_info->fire_spread * fs;
if ((obj->control_type == CT_AI && ((obj->ai_info->flags & AIF_TEAM_MASK) != AIF_TEAM_REBEL)) &&
fire_spread < Diff_ai_min_fire_spread[DIFF_LEVEL] * fs) {
fire_spread = Diff_ai_min_fire_spread[DIFF_LEVEL] * fs;
}
int16_t half_fire_spread = fire_spread >> 1;
if (fire_spread > 0) {
p = (ps_rand() % fire_spread) - half_fire_spread;
h = (ps_rand() % fire_spread) - half_fire_spread;
b = (ps_rand() % fire_spread) - half_fire_spread;
vm_AnglesToMatrix(&rot_mat, p, h, b);
laser_dir = laser_dir * rot_mat;
}
}
objnum = CreateAndFireWeapon(&laser_pos, &laser_dir, obj, weapon_num);
if (Game_mode & GM_MULTI) {
if (Netgame.local_role == LR_SERVER) {
if (obj->control_type == CT_AI) {
MultiSendRobotFireWeapon(obj->handle & HANDLE_OBJNUM_MASK, &laser_pos, &laser_dir, (uint16_t)weapon_num);
}
}
}
if (Demo_flags == DF_RECORDING) {
DemoWriteWeaponFire(obj->handle & HANDLE_OBJNUM_MASK, &laser_pos, &laser_dir, (uint16_t)weapon_num, objnum,
gun_num);
}
// bail out of their was an error creating this weapon object
if (objnum == -1)
return -1;
// Record gun number
Objects[objnum].ctype.laser_info.src_gun_num = gun_num;
AINotify(&Objects[objnum], AIN_OBJ_FIRED, obj);
if (obj->type == OBJ_PLAYER && obj == Player_object) {
// Check if missile camera enabled, and if this weapon allows it
if ((Missile_camera_window != -1) && (Weapons[weapon_num].flags & WF_ENABLE_CAMERA)) {
// Check if there's already a missile camera
object *viewer_obj = ObjGet(GetSmallViewer(Missile_camera_window));
if ((viewer_obj == NULL) || (viewer_obj->type != OBJ_WEAPON))
CreateSmallView(Missile_camera_window, Objects[objnum].handle, SVF_POPUP + SVF_BIGGER);
}
}
// if (Weapons[weapon_num].sounds[WSI_FIRE]!=SOUND_NONE_INDEX)
// Sound_system.Play3dSound(Weapons[weapon_num].sounds[WSI_FIRE], &Objects[objnum]);
if (Weapons[weapon_num].sounds[WSI_FLYING] != SOUND_NONE_INDEX)
Sound_system.Play3dSound(Weapons[weapon_num].sounds[WSI_FLYING], SND_PRIORITY_HIGHEST, &Objects[objnum]);
// Do a muzzle flash from this gun
if (Weapons[weapon_num].flags & WF_MUZZLE) {
int visnum;
vector newpos;
if (obj == &Objects[Players[Player_num].objnum]) {
newpos = laser_pos + (laser_dir);
visnum = VisEffectCreate(VIS_FIREBALL, MUZZLE_FLASH_INDEX, obj->roomnum, &newpos);
} else {
newpos = laser_pos + (laser_dir / 2);
visnum = VisEffectCreate(VIS_FIREBALL, MUZZLE_FLASH_INDEX, obj->roomnum, &newpos);
}
// Make this guy live for a very short time
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->lifetime = .08f;
vis->lifeleft = .08f;
vis->size = 1.0f;
vis->movement_type = MT_PHYSICS;
vis->velocity = obj->mtype.phys_info.velocity;
// Make some smoke!
visnum = VisEffectCreate(VIS_FIREBALL, MED_SMOKE_INDEX, obj->roomnum, &newpos);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->lifetime = .3f;
vis->lifeleft = .3f;
vis->movement_type = MT_PHYSICS;
vis->velocity = obj->mtype.phys_info.velocity;
vis->velocity.y() += 10;
}
}
}
// Check for mass driver
if (weapon_num == mass_driver_id) {
// Create a fire trail for this weapon
// find out where this weapon hits
fvi_query fq;
fvi_info hit_data;
vector dest_vec = laser_pos + (laser_dir * 5000);
fq.p0 = &laser_pos;
fq.startroom = obj->roomnum;
fq.p1 = &dest_vec;
fq.rad = 0;
fq.thisobjnum = -1;
fq.ignore_obj_list = NULL;
fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS;
fvi_FindIntersection(&fq, &hit_data);
float mag = vm_VectorDistanceQuick(&hit_data.hit_pnt, &obj->pos);
int visnum;
if (obj == NULL || obj->type != OBJ_BUILDING || stricmp(Object_info[obj->id].name, "FinalbossLITTLETIT") != 0)
visnum = VisEffectCreate(VIS_FIREBALL, MASSDRIVER_EFFECT_INDEX, obj->roomnum, &laser_pos);
else
visnum = VisEffectCreate(VIS_FIREBALL, MERCBOSS_MASSDRIVER_EFFECT_INDEX, obj->roomnum, &laser_pos);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->custom_handle = mass_driver_ring;
vis->lifeleft = 1.5;
vis->lifetime = 1.5;
vis->end_pos = hit_data.hit_pnt;
vis->billboard_info.width = 1;
if (obj == NULL || obj->type != OBJ_BUILDING || stricmp(Object_info[obj->id].name, "FinalbossLITTLETIT") != 0)
vis->lighting_color = GR_RGB16(100, 100, 170);
else
vis->lighting_color = GR_RGB16(240, 10, 10);
vis->flags |= VF_LINK_TO_VIEWER | VF_EXPAND;
vis->size = mag;
}
}
if (Game_mode & GM_MULTI) {
DLLInfo.me_handle = Objects[objnum].handle;
DLLInfo.it_handle = obj->handle;
CallGameDLL(EVT_WEAPONFIRED, &DLLInfo);
}
return objnum;
}
// This define determines how wide this arc is
#define ELECTRICAL_WIDTH .3f
#define ELECTRICAL_ALPHA .2
#include "args.h"
// Draws a lighting like chain of electricity
void DrawElectricalWeapon(object *obj) {
int i, t;
vector center_vecs[20];
vector arc_vec;
g3Point arc_points[100];
vector dest_vector, src_vector, line_norm;
float line_mag;
g3Point *pntlist[20];
vector dir;
object *parent_obj = ObjGet(obj->parent_handle);
float view_dp = 0;
if (!parent_obj)
return;
if (parent_obj->type == OBJ_PLAYER) {
if (Players[parent_obj->id].flags & (PLAYER_FLAGS_DYING | PLAYER_FLAGS_DEAD))
return;
} else {
if (parent_obj->flags & OF_DYING)
return;
}
int bm_handle = GetWeaponFireImage(obj->id, 0);
ASSERT(bm_handle >= 0);
WeaponCalcGun(&src_vector, &dir, parent_obj, obj->ctype.laser_info.src_gun_num);
if (obj->ctype.laser_info.track_handle == OBJECT_HANDLE_NONE || ObjGet(obj->ctype.laser_info.track_handle) == NULL) {
dest_vector = src_vector + (obj->orient.fvec * 50);
} else {
object *obj_to_track = ObjGet(obj->ctype.laser_info.track_handle);
dest_vector = obj_to_track->pos;
if (obj_to_track == Player_object) {
vector subvec = src_vector - dest_vector;
float mag = vm_GetMagnitudeFast(&subvec);
if (mag < .5) {
obj->lifeleft = 0;
return;
}
subvec /= mag;
dest_vector += subvec; // This prevents rendering errors
}
}
// Find out if it is o.k. to create a weapon here.
fvi_query fq;
fvi_info hit_data;
int fate;
fq.p0 = &src_vector;
fq.startroom = obj->roomnum;
fq.p1 = &dest_vector;
fq.rad = 0;
fq.thisobjnum = -1;
fq.ignore_obj_list = NULL;
fq.flags = 0; // what about trans walls???
fate = fvi_FindIntersection(&fq, &hit_data);
if (fate == HIT_WALL || fate == HIT_TERRAIN) {
dest_vector = hit_data.hit_pnt;
CreateRandomSparks((ps_rand() % 4), &dest_vector, obj->roomnum, COOL_SPARK_INDEX);
}
line_norm = dest_vector - src_vector;
line_mag = vm_GetMagnitudeFast(&line_norm);
if (line_mag < .5) {
obj->lifeleft = 0;
return;
}
line_norm /= line_mag;
// Get view dot product for drawing blobs
if (parent_obj != Viewer_object) {
vector temp_line_norm = -line_norm;
view_dp = vm_Dot3Product(Viewer_object->orient.fvec, temp_line_norm);
}
matrix mat;
vm_VectorToMatrix(&mat, &line_norm, NULL, NULL);
// Create the center points of our chain
int num_segments = 20;
int circle_pieces = 5;
if (!Detail_settings.Weapon_coronas_enabled)
num_segments = 10;
float line_scalar = (line_mag / (float)num_segments);
line_norm *= line_scalar;
// Get the src and dest vectors in view space so we know how to compute z
center_vecs[0] = src_vector; // Set first one equal to our origin
center_vecs[num_segments - 1] = dest_vector;
vector from = src_vector;
auto cur_sin = static_cast<unsigned int>(FrameCount) * 5000;
for (i = 1; i < num_segments - 1; i++, from += line_norm, cur_sin += 8000) {
center_vecs[i] = from;
center_vecs[i] += (mat.uvec * FixSin(cur_sin % 65536) * .4f);
center_vecs[i] += (mat.rvec * FixCos(cur_sin % 65536) * .4f);
}
// Now that we have our center points, create a tube
for (i = 0; i < num_segments; i++) {
float size = ELECTRICAL_WIDTH;
for (t = 0; t < circle_pieces; t++) {
float norm = (float)t / (float)circle_pieces;
arc_vec = center_vecs[i];
if (i != num_segments - 1) {
arc_vec += (obj->orient.uvec * size * FixSin(norm * 65536));
arc_vec -= (obj->orient.rvec * size * FixCos(norm * 65536));
}
g3_RotatePoint(&arc_points[i * circle_pieces + t], &arc_vec);
arc_points[i * circle_pieces + t].p3_flags |= PF_UV;
}
}
rend_SetAlphaType(AT_SATURATE_TEXTURE);
rend_SetAlphaValue(ELECTRICAL_ALPHA * 255);
rend_SetOverlayType(OT_NONE);
rend_SetLighting(LS_NONE);
rend_SetZBufferWriteMask(0);
float float_time = (Gametime * 300);
int int_time = float_time;
int_time %= 100;
float vchange = int_time / 100.0;
// Check for no sat flag
bool sat = true;
if (FindArg("-nosatomega"))
sat = false;
// Form our vectors into points and draw!
for (t = (num_segments - 1) * circle_pieces, i = num_segments - 1; i >= 1; i--, t -= circle_pieces) {
rend_SetTextureType(TT_LINEAR);
rend_SetAlphaType(sat ? AT_SATURATE_TEXTURE : AT_CONSTANT);
rend_SetZBufferWriteMask(0);
rend_SetLighting(LS_NONE);
rend_SetAlphaValue(ELECTRICAL_ALPHA * 255);
for (int k = 0; k < circle_pieces; k++) {
int next = (k + 1) % circle_pieces;
arc_points[t + k].p3_u = 0;
arc_points[t + k].p3_v = 1;
arc_points[t + k - circle_pieces].p3_u = 0;
arc_points[t + k - circle_pieces].p3_v = 0;
arc_points[t + next - circle_pieces].p3_u = 1;
arc_points[t + next - circle_pieces].p3_v = 1;
arc_points[t + next].p3_u = 1;
arc_points[t + next].p3_v = 0;
pntlist[0] = &arc_points[t + k];
pntlist[1] = &arc_points[t + k - circle_pieces];
pntlist[2] = &arc_points[t + next - circle_pieces];
pntlist[3] = &arc_points[t + next];
g3_DrawPoly(4, pntlist, bm_handle);
}
if (i != num_segments - 1) {
vector subvec = center_vecs[i + 1] - center_vecs[i];
subvec = center_vecs[i] + (subvec * vchange);
if (parent_obj == Viewer_object) {
DrawColoredDisk(&subvec, .2f, .5f, 1, .6f, 0, ELECTRICAL_WIDTH * 4, sat, 2);
} else {
DrawColoredDisk(&subvec, .2f, .5f, 1, .6f * fabs(view_dp), 0, ELECTRICAL_WIDTH * 4, sat, 2);
}
}
}
rend_SetZBufferWriteMask(1);
// Kill it!
obj->lifeleft = 0;
}
#define FUSION_RAMP_TIME 3.0
#define FUSION_DAMAGE_SOUND_INTERVAL .5
float Last_fusion_damage_time = 0;
// Does that crazy fusion effect, including damaging/shaking your ship
void DoFusionEffect(object *objp, int weapon_type) {
float norm;
int move = 0;
ASSERT(objp->type == OBJ_PLAYER);
ASSERT((weapon_type == PW_PRIMARY) || (weapon_type == PW_SECONDARY));
norm = (Players[objp->id].weapon[weapon_type].firing_time / FUSION_RAMP_TIME);
if (norm > .1) {
float over = norm;
if (over > 1.0)
over = 1.0;
over = (over - .1) / .9;
move = (ps_rand() % 5) - 2;
objp->mtype.phys_info.velocity += (objp->orient.rvec) * ((float)move * over * Frametime * 175.0);
move = (ps_rand() % 5) - 2;
objp->mtype.phys_info.velocity += (objp->orient.uvec) * ((float)move * over * Frametime * 175.0);
}
if (norm > 1.0) {
norm = 1.0;
if ((!(Game_mode & GM_MULTI)) || Netgame.local_role == LR_SERVER) {
if ((Gametime - Last_fusion_damage_time) < FUSION_DAMAGE_SOUND_INTERVAL) {
ApplyDamageToPlayer(objp, objp, PD_ENERGY_WEAPON, (.125 * abs(move)), 0, 255, 0);
} else {
ApplyDamageToPlayer(objp, objp, PD_ENERGY_WEAPON, (.125 * abs(move)));
Last_fusion_damage_time = Gametime;
}
} else {
Multi_requested_damage_type = PD_ENERGY_WEAPON;
Multi_requested_damage_amount += (.125 * abs(move));
}
}
float vals[4];
vals[0] = 1.0;
vals[1] = 0.0;
vals[2] = 1.0;
vals[3] = .25 * norm;
CreateNewEvent(RENDER_EVENT, FUSION_EFFECT, 0, &vals, sizeof(float) * 4, DrawAlphaEvent);
}
// Do the spray effect
void DoSprayEffect(object *obj, otype_wb_info *static_wb, uint8_t wb_index) {
vector laser_pos, laser_dir;
int cur_m_bit;
ASSERT(!(obj->flags & OF_DYING));
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
// Go through each gun point
for (cur_m_bit = 0; cur_m_bit < pm->poly_wb[0].num_gps; cur_m_bit++) {
// Figure out out if this weapon fires from this gunpoint
if (static_wb->gp_fire_masks[obj->dynamic_wb[wb_index].cur_firing_mask] & (0x01 << cur_m_bit)) {
int weapon_num = static_wb->gp_weapon_index[cur_m_bit];
WeaponCalcGun(&laser_pos, &laser_dir, obj, pm->poly_wb[0].gp_index[cur_m_bit]);
int visnum = VisEffectCreate(VIS_FIREBALL, SPRAY_INDEX, obj->roomnum, &laser_pos);
if (visnum < 0)
return;
vis_effect *vis = &VisEffects[visnum];
// Set the initial velocity
vm_ScaleVector(&vis->velocity, &laser_dir, Weapons[weapon_num].phys_info.velocity.z());
// Set initial velocity to that of the firing object
if (Weapons[weapon_num].phys_info.flags & PF_USES_PARENT_VELOCITY) {
scalar fdot = vm_Dot3Product(obj->mtype.phys_info.velocity, obj->orient.fvec);
vector fvel;
if (fdot > (scalar)0.0)
fvel = obj->orient.fvec * fdot;
else
fvel = vector{};
vector rvel = (scalar)0.1f * obj->orient.rvec * vm_Dot3Product(obj->mtype.phys_info.velocity, obj->orient.rvec);
vector uvel = (scalar)0.1f * obj->orient.uvec * vm_Dot3Product(obj->mtype.phys_info.velocity, obj->orient.uvec);
vis->velocity += fvel + rvel + uvel;
}
vis->movement_type = MT_PHYSICS;
vis->custom_handle = Weapons[weapon_num].fire_image_handle;
vis->drag = Weapons[weapon_num].phys_info.drag;
vis->phys_flags = Weapons[weapon_num].phys_info.flags;
vis->mass = Weapons[weapon_num].phys_info.mass;
vis->size = Weapons[weapon_num].size;
vis->lifetime = Weapons[weapon_num].life_time;
vis->lifeleft = vis->lifetime;
// Now create extras so that there are no gaps
if (obj != Viewer_object) {
// Find hit point so we know when to stop
float life_scalar = 1.0;
fvi_query fq;
fvi_info hit_data;
int fate;
vector dest_vector = laser_pos + (vis->velocity * vis->lifetime);
fq.p0 = &laser_pos;
fq.startroom = obj->roomnum;
fq.p1 = &dest_vector;
fq.rad = vis->size;
fq.thisobjnum = -1;
fq.ignore_obj_list = NULL;
fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS;
fate = fvi_FindIntersection(&fq, &hit_data);
if (fate != HIT_NONE) {
vector subvec = hit_data.hit_pnt - laser_pos;
vis->lifetime = vm_GetMagnitudeFast(&subvec) / vm_GetMagnitudeFast(&vis->velocity);
life_scalar = vis->lifetime / vis->lifeleft;
vis->lifeleft = vis->lifetime;
}
float velmag = vm_GetMagnitudeFast(&vis->velocity);
vector velnorm = vis->velocity / velmag;
float len = Frametime * velmag;
float fextras = len * 2;
float fps = 1.0 / Frametime;
int extras = fextras;
extras = std::min(extras, 8);
for (int t = 0; t < extras; t++) {
vector newpos = vis->pos + (((velnorm * len) / extras) * (t + 1));
int extranum = VisEffectCreate(VIS_FIREBALL, SPRAY_INDEX, obj->roomnum, &newpos);
if (extranum >= 0) {
vis_effect *extravis = &VisEffects[extranum];
extravis->mass = vis->mass;
extravis->drag = vis->drag;
extravis->velocity = vis->velocity;
extravis->size = vis->size;
extravis->phys_flags = vis->phys_flags;
extravis->movement_type = MT_PHYSICS;
extravis->custom_handle = vis->custom_handle;
extravis->lifetime = vis->lifetime;
float life_adjust = ((Frametime / (float)extras) * (t + 1));
extravis->lifeleft = Frametime * (fps / 2);
extravis->lifeleft *= life_scalar;
life_adjust *= life_scalar;
if (extravis->lifeleft > 0)
extravis->creation_time = vis->creation_time - life_adjust;
else {
VisEffectDelete(extranum);
}
}
}
} else {
// Find hit point so we know when to stop
fvi_query fq;
fvi_info hit_data;
int fate;
vector dest_vector = laser_pos + (vis->velocity * vis->lifetime);
fq.p0 = &laser_pos;
fq.startroom = obj->roomnum;
fq.p1 = &dest_vector;
fq.rad = vis->size;
fq.thisobjnum = -1;
fq.ignore_obj_list = NULL;
fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS;
fate = fvi_FindIntersection(&fq, &hit_data);
if (fate != HIT_NONE) {
vector subvec = hit_data.hit_pnt - laser_pos;
vis->lifetime = vm_GetMagnitudeFast(&subvec) / vm_GetMagnitudeFast(&vis->velocity);
vis->lifeleft = vis->lifetime;
}
}
}
}
}
// Draws a streamer behind a weapon
void DrawWeaponStreamer(object *obj) {
float time_live = Gametime - obj->creation_time;
float len;
float norm_time = time_live / obj->lifetime;
if (norm_time >= 1)
norm_time = .99999f; // don't go over!
// Get length
object *parent_obj = ObjGet(obj->parent_handle);
if (!parent_obj)
return;
len = vm_VectorDistance(&parent_obj->pos, &obj->pos);
rend_SetAlphaType(AT_VERTEX);
rend_SetTextureType(TT_FLAT);
rend_SetLighting(LS_GOURAUD);
rend_SetColorModel(CM_RGB);
vector vecs[2];
g3Point pnts[2];
int i;
vecs[0] = obj->pos;
vecs[1] = obj->pos - (obj->orient.fvec * len);
for (i = 0; i < 2; i++) {
g3_RotatePoint(&pnts[i], &vecs[i]);
pnts[i].p3_flags |= PF_RGBA;
pnts[i].p3_r = Weapons[obj->id].lighting_info.red_light1;
pnts[i].p3_g = Weapons[obj->id].lighting_info.green_light1;
pnts[i].p3_b = Weapons[obj->id].lighting_info.blue_light1;
}
pnts[0].p3_a = (1.0 - norm_time) * .3f;
pnts[1].p3_a = 0;
g3_DrawSpecialLine(&pnts[0], &pnts[1]);
}
void DrawBlobbyWeaponRing(object *obj) {
vector vecs[30];
float lifenorm = (obj->lifetime - obj->lifeleft) / obj->lifetime;
int i;
int bm_handle;
int num_segments = 20;
int ring_increment = 65536 / num_segments;
int ring_angle = 0;
if (lifenorm >= 1.0)
lifenorm = .999f;
if (Weapons[obj->id].flags & WF_IMAGE_BITMAP)
bm_handle = GetWeaponFireImage(obj->id, 0);
else {
vclip *vc = &GameVClips[Weapons[obj->id].fire_image_handle];
int int_frame = vc->num_frames * lifenorm;
bm_handle = vc->frames[int_frame];
}
rend_SetLighting(LS_NONE);
rend_SetTextureType(TT_LINEAR);
rend_SetAlphaType(AT_CONSTANT_TEXTURE);
rend_SetAlphaValue((1.0 - lifenorm) * 255.0);
// Now draw a ring of blobs
ring_angle = 0;
float blob_size = (obj->size / 4);
for (i = 0; i < num_segments; i++, ring_angle += ring_increment) {
float ring_sin = FixSin(ring_angle);
float ring_cos = FixCos(ring_angle);
vecs[i] = obj->orient.rvec * (ring_cos * obj->size);
vecs[i] += obj->orient.uvec * (ring_sin * obj->size);
vecs[i] += obj->pos;
g3_DrawRotatedBitmap(&vecs[i], ring_angle, blob_size, (blob_size * bm_h(bm_handle, 0)) / bm_w(bm_handle, 0),
bm_handle);
}
}
void DrawPolygonalWeaponRing(object *obj) {
vector inner_vecs[30], outer_vecs[30];
g3Point inner_points[30], outer_points[30];
float lifenorm = (obj->lifetime - obj->lifeleft) / obj->lifetime;
int i;
g3Point *pntlist[4];
float inner_size = (obj->size * .8);
int objnum = obj - Objects;
int odd = objnum % 2;
int num_segments = 20;
int ring_increment = 65536 / num_segments;
int ring_angle = 0;
if (lifenorm > 1.0)
lifenorm = 1.0;
rend_SetTextureType(TT_FLAT);
rend_SetColorModel(CM_RGB);
rend_SetLighting(LS_GOURAUD);
rend_SetAlphaType(AT_SATURATE_VERTEX);
for (i = 0; i < num_segments; i++, ring_angle += ring_increment) {
float ring_sin = FixSin(ring_angle);
float ring_cos = FixCos(ring_angle);
inner_vecs[i] = obj->orient.rvec * (ring_cos * inner_size);
inner_vecs[i] += obj->orient.uvec * (ring_sin * inner_size);
inner_vecs[i] += obj->pos;
outer_vecs[i] = obj->orient.rvec * (ring_cos * obj->size);
outer_vecs[i] += obj->orient.uvec * (ring_sin * obj->size);
outer_vecs[i] += obj->pos;
g3_RotatePoint(&inner_points[i], &inner_vecs[i]);
g3_RotatePoint(&outer_points[i], &outer_vecs[i]);
outer_points[i].p3_flags |= PF_RGBA | PF_UV;
inner_points[i].p3_flags |= PF_RGBA | PF_UV;
outer_points[i].p3_a = (1.0 - lifenorm) * .3;
inner_points[i].p3_a = (1.0 - lifenorm) * .1;
outer_points[i].p3_r = Weapons[obj->id].lighting_info.red_light1;
outer_points[i].p3_g = Weapons[obj->id].lighting_info.green_light1;
outer_points[i].p3_b = Weapons[obj->id].lighting_info.blue_light1;
inner_points[i].p3_r = Weapons[obj->id].lighting_info.red_light1;
inner_points[i].p3_g = Weapons[obj->id].lighting_info.green_light1;
inner_points[i].p3_b = Weapons[obj->id].lighting_info.blue_light1;
}
for (i = 0; i < num_segments; i++) {
if (i % 2 && !odd)
continue;
if ((i % 2 == 0) && odd)
continue;
int next = (i + 1) % num_segments;
pntlist[0] = &outer_points[i];
pntlist[1] = &outer_points[next];
pntlist[2] = &inner_points[next];
pntlist[3] = &inner_points[i];
g3_DrawPoly(4, pntlist, 0);
}
}
// Draws a ring for the weapon
void DrawWeaponRing(object *obj) {
if ((Weapons[obj->id].flags & WF_IMAGE_BITMAP) || (Weapons[obj->id].flags & WF_IMAGE_VCLIP)) {
DrawBlobbyWeaponRing(obj);
return;
} else
DrawPolygonalWeaponRing(obj);
}
// Draws a weapon
void DrawWeaponObject(object *obj) {
ASSERT(obj->type == OBJ_WEAPON || obj->type == OBJ_POWERUP);
ASSERT(Weapons[obj->id].used > 0);
// Don't draw if spray
if (!(Weapons[obj->id].flags & WF_INVISIBLE)) {
if (Weapons[obj->id].flags & WF_ELECTRICAL) {
DrawElectricalWeapon(obj);
} else if ((Weapons[obj->id].flags & WF_IMAGE_BITMAP) || (Weapons[obj->id].flags & WF_IMAGE_VCLIP)) {
int bm_handle;
int objnum = obj - Objects;
float rot_temp = Weapons[obj->id].phys_info.rotvel.z() / (scalar)65536.0;
int int_game = Gametime / rot_temp;
float diff = Gametime - (int_game * rot_temp);
int rot_angle = diff * (scalar)65536;
if (Weapons[obj->id].flags & WF_NO_ROTATE)
rot_angle = (objnum * 1000) % 65536;
bm_handle = GetWeaponFireImage(obj->id, 0);
ASSERT(bm_handle >= 0);
if (Weapons[obj->id].flags & WF_SATURATE)
rend_SetAlphaType(AT_SATURATE_TEXTURE);
else
rend_SetAlphaType(AT_CONSTANT + AT_TEXTURE);
rend_SetAlphaValue(Weapons[obj->id].alpha * 255);
rend_SetOverlayType(OT_NONE);
rend_SetLighting(LS_NONE);
if (Weapons[obj->id].flags & WF_PLANAR) {
vector norm = obj->mtype.phys_info.velocity;
vm_NormalizeVectorFast(&norm);
g3_DrawPlanarRotatedBitmap(&obj->pos, &norm, rot_angle, obj->size,
(obj->size * bm_h(bm_handle, 0)) / bm_w(bm_handle, 0), bm_handle);
} else
g3_DrawRotatedBitmap(&obj->pos, rot_angle, obj->size, (obj->size * bm_h(bm_handle, 0)) / bm_w(bm_handle, 0),
bm_handle);
} else {
polymodel_effect pe = {0};
int use_effect = 0;
if (obj->type == OBJ_WEAPON && !Detail_settings.Weapon_coronas_enabled) {
pe.type |= PEF_NO_GLOWS;
use_effect = 1;
}
if (use_effect)
SetPolymodelEffect(&pe);
DrawPolygonModel(&obj->pos, &obj->orient, obj->rtype.pobj_info.model_num, NULL, 0, 1.0, 1.0, 1.0, 0xFFFFFFFF,
use_effect);
}
}
if (Weapons[obj->id].flags & WF_STREAMER) {
DrawWeaponStreamer(obj);
}
if (Weapons[obj->id].flags & WF_RING) {
DrawWeaponRing(obj);
}
}
// Stops any on/off weapons that are firing
void StopOnOffWeapon(object *obj) {
// mprintf(0,"Stopping on/off weapon!\n");
obj->weapon_fire_flags &= ~WFF_ON_OFF;
if (Demo_flags == DF_RECORDING) {
DemoWriteObjWeapFireFlagChanged(OBJNUM(obj));
}
}
// Starts an on/off weapon firing
void StartOnOffWeapon(object *obj, uint8_t wb_index) {
// mprintf(0,"Starting on/off weapon!\n");
obj->weapon_fire_flags |= WFF_ON_OFF;
dynamic_wb_info *p_dwb = &obj->dynamic_wb[wb_index];
p_dwb->cur_firing_mask = 0;
if (Demo_flags == DF_RECORDING) {
DemoWriteObjWeapFireFlagChanged(OBJNUM(obj));
}
}
float Zoom_fov_time = 0;
#define ZOOM_STAY_TIME .25
#define ZOOM_FOV_TARGET 18
void DoZoomStay() {
if (Zoom_fov_time > 0) {
Render_FOV = ZOOM_FOV_TARGET;
Zoom_fov_time -= Frametime;
if (Zoom_fov_time < 0) {
Zoom_fov_time = 0;
Players[Player_num].flags &= ~PLAYER_FLAGS_ZOOMED;
Render_FOV = Render_FOV_setting;
}
}
}
// Zooms in for this weapon
void DoZoomEffect(player_weapon *pw, uint8_t clear) {
if (pw->firing_time < .5) {
Players[Player_num].turn_scalar = 1.0;
Render_FOV = Render_FOV_setting;
if (!clear)
DoZoomStay();
return;
}
Players[Player_num].turn_scalar = .3f;
if (pw->firing_time > 1.0) {
// We are fully zoomed in
Players[Player_num].flags |= PLAYER_FLAGS_ZOOMED;
Render_FOV = ZOOM_FOV_TARGET;
Zoom_fov_time = ZOOM_STAY_TIME;
// See if we hit anything
// Cast a ray to see what we've hit
Players[Player_num].flags &= ~PLAYER_FLAGS_BULLSEYE;
object *obj = &Objects[Players[Player_num].objnum];
int fate; // Collision type for response code
fvi_info hit_info; // Hit information
fvi_query fq; // Movement query
vector dest = obj->pos + (obj->orient.fvec * (scalar)5000.0);
fq.p0 = &obj->pos;
fq.startroom = obj->roomnum;
fq.p1 = &dest;
fq.rad = (scalar).0001f;
fq.thisobjnum = Objects - obj;
fq.ignore_obj_list = NULL;
fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS;
fate = fvi_FindIntersection(&fq, &hit_info);
Players[Player_num].zoom_distance = vm_VectorDistance(&obj->pos, &hit_info.hit_pnt);
if (fate == HIT_SPHERE_2_POLY_OBJECT || fate == HIT_OBJECT) {
object *hit_obj = &Objects[hit_info.hit_object[0]];
if (hit_obj->type == OBJ_ROBOT || hit_obj->type == OBJ_PLAYER ||
(hit_obj->type == OBJ_BUILDING && hit_obj->ai_info))
Players[Player_num].flags |= PLAYER_FLAGS_BULLSEYE;
}
return;
}
// calculate zoom effect
scalar norm = (pw->firing_time - (scalar).5) * 2;
Render_FOV = (ZOOM_FOV_TARGET * norm) + (((scalar)1.0 - norm) * Render_FOV_setting);
return;
}
// Called when a player dies or switches weapons to clear out any active weapon stuff
void ClearPlayerFiring(object *objp, int weapon_type) {
// Check parms
ASSERT(objp->type == OBJ_PLAYER);
// ASSERT(objp->id == Player_num);
// Set up various vars
player *player = &Players[objp->id];
player_weapon *pw = &player->weapon[weapon_type];
// Clear firing flags
if (objp->weapon_fire_flags & WFF_ON_OFF)
StopOnOffWeapon(objp);
// Clear firing time
pw->firing_time = 0.0;
objp->weapon_fire_flags = 0;
if (Demo_flags == DF_RECORDING) {
DemoWriteObjWeapFireFlagChanged(OBJNUM(objp));
}
// Clear zoom if there is one
if (objp->id == Player_num) {
player_weapon *pw = &player->weapon[weapon_type];
int weapon_battery_index = pw->index;
ship *ship = &Ships[player->ship_index];
if (ship->fire_flags[weapon_battery_index] & SFF_ZOOM) {
DoZoomEffect(pw, 1);
Players[Player_num].flags &= ~PLAYER_FLAGS_ZOOMED;
Zoom_fov_time = 0;
}
}
// Stop firing sound if one playing
if (pw->sound_handle != -1) {
Sound_system.StopSoundImmediate(pw->sound_handle);
pw->sound_handle = -1;
}
}
// Stops weapon sound & visual effects
void StopWeapon(object *obj, player_weapon *pw, otype_wb_info *wb) {
// Stop any on/off weapons
if (obj->weapon_fire_flags & WFF_ON_OFF)
StopOnOffWeapon(obj);
// Stop spray weapons
if (wb->flags & WBF_SPRAY)
obj->weapon_fire_flags &= ~WFF_SPRAY;
// Stop firing sound if one playing
if (pw && pw->sound_handle != -1) {
Sound_system.StopSoundImmediate(pw->sound_handle);
pw->sound_handle = -1;
}
// Clear firing time
if (pw) {
pw->firing_time = 0.0;
}
if (Demo_flags == DF_RECORDING) {
DemoWriteObjWeapFireFlagChanged(OBJNUM(obj));
}
}
// Does weapon battery stuff for the permissable network architecture
void DoPermissableWeaponMask(int weapon_battery_index) {
player *player = &Players[Player_num];
object *objp = &Objects[player->objnum];
ship *ship = &Ships[player->ship_index];
otype_wb_info *wb = &ship->static_wb[weapon_battery_index];
dynamic_wb_info *p_dwb = &objp->dynamic_wb[weapon_battery_index];
player->last_fire_weapon_time = Gametime;
p_dwb->cur_firing_mask++;
p_dwb->last_fire_time = Gametime;
if (p_dwb->cur_firing_mask >= wb->num_masks)
p_dwb->cur_firing_mask = 0;
}
// Fires a weapon from our player. Won't fire if ammo/energy requirements aren't met.
// Parameters: weapon_type - either PW_PRIMARY or PW_SECONDARY
void FireWeaponFromPlayer(object *objp, int weapon_type, int down_count, bool down_state, float down_time) {
// Check parms
ASSERT(objp->type == OBJ_PLAYER);
// ASSERT(objp->id == Player_num);
ASSERT((weapon_type == PW_PRIMARY) || (weapon_type == PW_SECONDARY));
// Set up various vars
player *player = &Players[objp->id];
player_weapon *pw = &player->weapon[weapon_type];
int weapon_battery_index = pw->index;
ship *ship = &Ships[player->ship_index];
otype_wb_info *wb = &ship->static_wb[weapon_battery_index];
int fire_on_release = (ship->fire_flags[weapon_battery_index] & (SFF_FUSION | SFF_ZOOM));
bool can_fire_now = WBIsBatteryReady(objp, wb, weapon_battery_index);
dynamic_wb_info *p_dwb = &objp->dynamic_wb[weapon_battery_index];
float ammo_scalar = 1.0;
if (p_dwb->flags & DWBF_QUAD)
ammo_scalar = 2.0;
// Don't allow down_time to be zero if we have a down count
if (down_count && (down_time == 0.0))
down_time = 1.0f / 100.0f;
// Check to see if we can release a guided
if (down_count && down_time != 0.0 && Players[objp->id].guided_obj != NULL && weapon_type == PW_SECONDARY &&
can_fire_now) {
ReleaseGuidedMissile(objp->id);
p_dwb->last_fire_time = Gametime;
return;
}
// Check to see if we can release a user timeout weapon
if (down_count && down_time != 0.0 && Players[objp->id].user_timeout_obj != NULL && weapon_type == PW_SECONDARY &&
can_fire_now) {
if (Game_mode & GM_MULTI)
MultiSendReleaseTimeoutMissile();
ReleaseUserTimeoutMissile(objp->id);
p_dwb->last_fire_time = Gametime;
return;
}
// Check for weapon stopped firing
if ((down_count == 0) && (down_time == 0)) {
if (ship->fire_flags[weapon_battery_index] & SFF_ZOOM)
DoZoomEffect(pw, 0);
// Abort if we weren't firing last frame either
if (pw->firing_time == 0.0)
return;
// Check for fire-on-release weapon
if (fire_on_release) {
if (can_fire_now) {
float scalar = 1.0;
// Ready?
if (ship->fire_flags[weapon_battery_index] & SFF_FUSION) {
scalar = 1.0 + ((pw->firing_time / FUSION_RAMP_TIME) * 3.0);
}
if ((Game_mode & GM_MULTI) && (Netgame.flags & NF_PERMISSABLE)) {
MultiSendRequestToFire(weapon_battery_index, p_dwb->cur_firing_mask, scalar);
DoPermissableWeaponMask(weapon_battery_index);
} else
WBFireBattery(objp, wb, 0, weapon_battery_index, scalar);
player->energy -= wb->energy_usage * ammo_scalar;
if (player->energy < 0)
player->energy = 0;
if (player->weapon_ammo[weapon_battery_index] < (wb->ammo_usage * ammo_scalar))
player->weapon_ammo[weapon_battery_index] = 0;
else
player->weapon_ammo[weapon_battery_index] -= (wb->ammo_usage * ammo_scalar);
player->num_discharges_level++;
if (ship->fire_flags[weapon_battery_index] & SFF_ZOOM) {
AddToShakeMagnitude(ship->phys_info.mass * 2);
}
}
}
// Play cut-off sound if one (but not if in permissable game)
if (!(Game_mode & GM_MULTI) || !(Netgame.flags & NF_PERMISSABLE)) {
int cutoff_sound = ship->firing_release_sound[weapon_battery_index];
if (cutoff_sound != -1)
Sound_system.Play2dSound(cutoff_sound, SND_PRIORITY_HIGHEST);
}
// Stop the weapon sound & visual effects
if (weapon_type == PW_PRIMARY)
StopWeapon(objp, pw, wb);
// We're done
return;
}
// Check for adequate energy
if ((wb->energy_usage * ammo_scalar) && (player->energy <= 0)) {
AddHUDMessage(TXT_WPNNONRG);
if (weapon_type == PW_PRIMARY)
StopWeapon(objp, pw, wb);
AutoSelectWeapon(weapon_type);
return;
}
// Check for adequate ammo
if ((wb->ammo_usage * ammo_scalar) && (player->weapon_ammo[weapon_battery_index] <= 0)) {
if (weapon_type == PW_PRIMARY)
AddHUDMessage(TXT_WPNNOAMMO);
else
AddHUDMessage(TXT_WPNNOPROJ);
if (weapon_type == PW_PRIMARY)
StopWeapon(objp, pw, wb);
AutoSelectWeapon(weapon_type);
return;
}
// Check for adequate ammo for secondaries...
if (weapon_battery_index >= 10 && (wb->ammo_usage * ammo_scalar > player->weapon_ammo[weapon_battery_index])) {
if (weapon_type == PW_PRIMARY)
AddHUDMessage(TXT_WPNNOAMMO);
else
AddHUDMessage(TXT_WPNNOPROJ);
if (weapon_type == PW_PRIMARY)
StopWeapon(objp, pw, wb);
AutoSelectWeapon(weapon_type);
return;
}
if (!can_fire_now)
return;
// If continous sound weapon, start the sound if wasn't firing last frame
int firing_sound = ship->firing_sound[weapon_battery_index];
if (can_fire_now && (firing_sound != -1) && (pw->firing_time == 0.0))
pw->sound_handle = Sound_system.Play2dSound(firing_sound, SND_PRIORITY_HIGHEST);
// Set spray flag if spray weapon
if (wb->flags & WBF_SPRAY)
objp->weapon_fire_flags |= WFF_SPRAY;
if (ship->fire_flags[weapon_battery_index] & SFF_ZOOM)
DoZoomEffect(pw, 0);
// Do fusion effect if fusion weapon
if (can_fire_now && ship->fire_flags[weapon_battery_index] & SFF_FUSION)
DoFusionEffect(objp, weapon_type);
// Start any on/off weapons
if ((wb->flags & WBF_ON_OFF) && !(objp->weapon_fire_flags & WFF_ON_OFF))
StartOnOffWeapon(objp, weapon_battery_index);
// If not a fire-on-release weapon, then fire if ready
if (!fire_on_release && can_fire_now) {
if ((Game_mode & GM_MULTI) && (Netgame.flags & NF_PERMISSABLE)) {
MultiSendRequestToFire(weapon_battery_index, p_dwb->cur_firing_mask);
DoPermissableWeaponMask(weapon_battery_index);
} else
WBFireBattery(objp, wb, 0, weapon_battery_index);
// If on/off weapon, scale energy by framerate based on "reference" rate of 20 fps.
if (wb->flags & WBF_ON_OFF)
player->energy -= (wb->energy_usage * ammo_scalar * Frametime / (1.0 / 20.0));
else
player->energy -= (wb->energy_usage * ammo_scalar);
if (player->energy < 0)
player->energy = 0;
if (player->weapon_ammo[weapon_battery_index] < (wb->ammo_usage * ammo_scalar))
player->weapon_ammo[weapon_battery_index] = 0;
else
player->weapon_ammo[weapon_battery_index] -= (wb->ammo_usage * ammo_scalar);
player->num_discharges_level++;
}
// Update the firing time
pw->firing_time += down_time;
if (Demo_flags == DF_RECORDING) {
DemoWriteObjWeapFireFlagChanged(OBJNUM(objp));
}
}
// Fires a flare from our player.
// It might make sense to combine this with FireWeaponFromPlayer(), or maybe not
void FireFlareFromPlayer(object *objp) {
// Check parms
ASSERT(objp->type == OBJ_PLAYER);
// ASSERT(objp->id == Player_num || objp->control_type == CT_SOAR);
// Set up various vars
int weapon_battery_index = FLARE_INDEX;
ship *ship = &Ships[Players[objp->id].ship_index];
otype_wb_info *wb = &ship->static_wb[weapon_battery_index];
player *player = &Players[objp->id];
dynamic_wb_info *p_dwb = &objp->dynamic_wb[weapon_battery_index];
if (player->energy < wb->energy_usage) {
AddHUDMessage(TXT_WPNFLARENONRG);
return;
}
if (WBIsBatteryReady(Player_object, wb, weapon_battery_index)) {
if ((Game_mode & GM_MULTI) && (Netgame.flags & NF_PERMISSABLE)) {
MultiSendRequestToFire(weapon_battery_index, p_dwb->cur_firing_mask);
DoPermissableWeaponMask(weapon_battery_index);
} else
WBFireBattery(Player_object, wb, 0, weapon_battery_index);
player->energy -= wb->energy_usage;
if (player->energy < 0)
player->energy = 0;
// Samir & I think that firing a flare should not count as discharge, even though
// it would allow people push up their kill effiency by killing things with flares. -MT, 10/29/97
// Players[Player_num].num_discharges_level++;
}
}
// Plays the animation that accompanies a weapon death
void DoWeaponExploded(object *obj, vector *norm, vector *collision_point, object *hit_object) {
weapon *w = &Weapons[obj->id];
light_info *li = &w->lighting_info;
vector col_point, normal;
if (w->flags & WF_ELECTRICAL)
return;
MakeShockwave(obj, obj->parent_handle);
if (collision_point == NULL || !(Weapons[obj->id].flags & WF_PLANAR_BLAST))
col_point = obj->pos;
else
col_point = *collision_point;
if (norm == NULL)
normal = obj->orient.fvec;
else
normal = *norm;
if (Weapons[obj->id].flags & WF_GRAVITY_FIELD)
CreateGravityField(&obj->pos, obj->roomnum, Weapons[obj->id].gravity_size, Weapons[obj->id].gravity_time,
obj->parent_handle);
if (hit_object == Viewer_object && !(Weapons[obj->id].flags & WF_MATTER_WEAPON))
return; // Don't draw if it is the viewer who is getting hit
if (Weapons[obj->id].flags & WF_PLANAR_BLAST) {
if (Weapons[obj->id].flags & WF_BLAST_RING) {
// Create a planar ring
int visnum = VisEffectCreate(VIS_FIREBALL, BLAST_RING_INDEX, obj->roomnum, &col_point);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->size = Weapons[obj->id].explode_size;
vis->lifetime = Weapons[obj->id].explode_time;
vis->lifeleft = Weapons[obj->id].explode_time;
vis->custom_handle = GetTextureBitmap(Weapons[obj->id].explode_image_handle, 0);
vis->flags |= VF_PLANAR;
vis->end_pos = normal;
vis->lighting_color =
OPAQUE_FLAG | GR_RGB16(li->red_light2 * 255, li->green_light2 * 255, li->blue_light2 * 255);
}
} else {
// Create a planar explosion
int visnum;
if (Weapons[obj->id].explode_image_handle > 0) {
visnum = VisEffectCreate(VIS_FIREBALL, CUSTOM_EXPLOSION_INDEX, obj->roomnum, &col_point);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->size = Weapons[obj->id].explode_size;
vis->lifetime = Weapons[obj->id].explode_time;
vis->lifeleft = Weapons[obj->id].explode_time;
vis->custom_handle = Weapons[obj->id].explode_image_handle;
vis->lighting_color =
OPAQUE_FLAG | GR_RGB16(li->red_light2 * 255, li->green_light2 * 255, li->blue_light2 * 255);
}
} else
visnum = VisEffectCreate(VIS_FIREBALL, GetRandomSmallExplosion(), obj->roomnum, &col_point);
if (visnum >= 0) {
VisEffects[visnum].flags |= VF_PLANAR;
VisEffects[visnum].end_pos = normal;
if (Weapons[obj->id].flags & WF_EXPAND)
VisEffects[visnum].flags |= VF_EXPAND;
}
}
} else {
if (Weapons[obj->id].flags & WF_BLAST_RING) {
// Create a non-planar ring
int visnum = VisEffectCreate(VIS_FIREBALL, BLAST_RING_INDEX, obj->roomnum, &col_point);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->size = Weapons[obj->id].explode_size;
vis->lifetime = Weapons[obj->id].explode_time;
vis->lifeleft = Weapons[obj->id].explode_time;
vis->custom_handle = GetTextureBitmap(Weapons[obj->id].explode_image_handle, 0);
vis->lighting_color =
OPAQUE_FLAG | GR_RGB16(li->red_light2 * 255, li->green_light2 * 255, li->blue_light2 * 255);
}
} else {
// Create a normal "always facing you" explosion
if (Weapons[obj->id].explode_image_handle > 0) {
int visnum = VisEffectCreate(VIS_FIREBALL, CUSTOM_EXPLOSION_INDEX, obj->roomnum, &col_point);
if (visnum >= 0) {
vis_effect *vis = &VisEffects[visnum];
vis->size = Weapons[obj->id].explode_size;
vis->lifetime = Weapons[obj->id].explode_time;
vis->lifeleft = Weapons[obj->id].explode_time;
vis->custom_handle = Weapons[obj->id].explode_image_handle;
vis->lighting_color =
OPAQUE_FLAG | GR_RGB16(li->red_light2 * 255, li->green_light2 * 255, li->blue_light2 * 255);
if (Weapons[obj->id].flags & WF_EXPAND)
VisEffects[visnum].flags |= VF_EXPAND;
}
} else
VisEffectCreate(VIS_FIREBALL, GetRandomSmallExplosion(), obj->roomnum, &col_point);
}
}
}
// Creates chidren from a dying weapon
void CreateTimeoutSpawnFromWeapon(object *obj) {
int n = obj->id;
ASSERT(Weapons[n].spawn_count > 0 && Weapons[n].spawn_handle >= 0);
int num = Weapons[n].spawn_count;
int spawn_index = Weapons[n].spawn_handle;
vector pos;
matrix orient;
// See if we should spawn the alternate
if (Weapons[n].alternate_spawn_handle >= 0 && Weapons[n].alternate_chance > 0) {
if ((ps_rand() % 100) < Weapons[n].alternate_chance)
spawn_index = Weapons[n].alternate_spawn_handle;
}
for (int i = 0; i < num; i++) {
if (i == 0) // Make first one fire from the center of the ring
{
pos = obj->pos + (obj->orient.fvec * obj->size / 2);
CreateAndFireWeapon(&pos, &obj->orient.fvec, obj, spawn_index);
} else {
matrix temp_mat;
pos = obj->pos + (obj->orient.fvec * obj->size / 2);
float norm = ((float)(i - 1)) / ((float)num - 1);
float mysin = FixSin(norm * 65536);
float mycos = FixCos(norm * 65536);
int x_twist = mycos * 4096;
int y_twist = mysin * 4096;
vm_AnglesToMatrix(&temp_mat, x_twist, y_twist, 0);
vm_TransposeMatrix(&temp_mat);
orient = obj->orient * temp_mat;
CreateAndFireWeapon(&pos, &orient.fvec, obj, spawn_index);
}
}
}
// Creates a robot as a countermeasure
void CreateRobotSpawnFromWeapon(object *obj) {
int index = obj->id;
weapon *wp = &Weapons[index];
if ((Game_mode & GM_MULTI) && Netgame.local_role != LR_SERVER)
return; // clients do not create robots without the servers permission
if (wp->robot_spawn_handle == -1) {
LOG_WARNING << "Trying to create an invalid robot spawn!";
return;
}
object *parent_obj = ObjGetUltimateParent(obj);
int objnum = ObjCreate(OBJ_ROBOT, wp->robot_spawn_handle, obj->roomnum, &obj->pos, &obj->orient, parent_obj->handle);
if (objnum < 0)
return;
if ((Game_mode & GM_MULTI) && Netgame.local_role == LR_SERVER) {
MultiSendObject(&Objects[objnum], 0);
}
// Create the scripts for it
InitObjectScripts(&Objects[objnum]);
}
// Returns a object to home on for spawned weapons
object *FindSpawnHomingTarget(object *obj) {
int i;
float best_dist = MAX_HOMING_DIST / 2;
int best_index = -1;
object *weapon_parent = ObjGet(obj->parent_handle);
for (i = 0; i <= Highest_object_index; i++) {
if ((i != OBJNUM(weapon_parent)) && ((Objects[i].type == OBJ_ROBOT) || (Objects[i].type == OBJ_PLAYER) ||
(Objects[i].type == OBJ_BUILDING && Objects[i].ai_info))) {
if (BOA_IsVisible(obj->roomnum, Objects[i].roomnum)) {
vector to_target = Objects[i].pos - obj->pos;
if (Objects[i].effect_info && Objects[i].effect_info->type_flags & EF_CLOAKED)
continue;
if (ObjGet(obj->parent_handle) &&
!AIObjEnemy(ObjGetUltimateParent(ObjGet(obj->parent_handle)), ObjGetUltimateParent(&Objects[i])))
continue;
float dist_to_target = vm_NormalizeVector(&to_target) - obj->size - Objects[i].size;
if (dist_to_target < best_dist) {
fvi_query fq;
fvi_info hit_info;
fq.p0 = &obj->pos;
fq.startroom = obj->roomnum;
fq.p1 = &Objects[i].pos;
fq.rad = 0.0f;
fq.thisobjnum = OBJNUM(obj);
fq.ignore_obj_list = NULL;
fq.flags = FQ_TRANSPOINT | FQ_CHECK_OBJS | FQ_ONLY_DOOR_OBJ | FQ_NO_RELINK;
fvi_FindIntersection(&fq, &hit_info);
if (hit_info.hit_type[0] == HIT_NONE) {
best_index = i;
best_dist = dist_to_target;
}
}
}
}
}
if (best_index >= 0)
return &Objects[best_index];
return NULL;
}
// Creates chidren from a dying weapon
void CreateImpactSpawnFromWeapon(object *obj, vector *normal) {
int n = obj->id;
object *home_target = NULL;
ASSERT(Weapons[n].spawn_count > 0 && Weapons[n].spawn_handle >= 0);
int num = Weapons[n].spawn_count;
int spawn_index = Weapons[n].spawn_handle;
// See if we should spawn the alternate
if (Weapons[n].alternate_spawn_handle >= 0 && Weapons[n].alternate_chance > 0) {
if ((ps_rand() % 100) < Weapons[n].alternate_chance)
spawn_index = Weapons[n].alternate_spawn_handle;
}
if (Game_mode & GM_MULTI)
ps_srand(num);
if (Weapons[n].flags & WF_HOMING_SPLIT) {
home_target = FindSpawnHomingTarget(obj);
}
for (int i = 0; i < num; i++) {
if (home_target != NULL) {
vector subvec = home_target->pos - obj->pos;
float mag = vm_GetMagnitudeFast(&subvec);
subvec /= mag;
int objnum = CreateAndFireWeapon(&obj->pos, &subvec, obj, spawn_index);
// Stagger the weapons a bit
if (objnum > -1) {
object *created_obj = &Objects[objnum];
float scalar = .2 + ((float)(ps_rand() % 1000) / 1000.0);
created_obj->mtype.phys_info.velocity *= scalar;
}
} else {
matrix temp_mat;
matrix rot_mat;
vector new_norm;
float norm = ((float)(ps_rand() % 1000)) / 1000.0;
int x_twist = 16384 - (norm * 32768);
if (x_twist < 0)
x_twist = 65536 + x_twist;
norm = ((float)(ps_rand() % 1000)) / 1000.0;
int y_twist = 16384 - (norm * 32768);
if (y_twist < 0)
y_twist = 65536 + y_twist;
vm_VectorToMatrix(&rot_mat, normal, NULL, NULL);
vm_TransposeMatrix(&rot_mat);
vm_AnglesToMatrix(&temp_mat, x_twist, y_twist, 0);
new_norm = temp_mat.fvec * rot_mat;
CreateAndFireWeapon(&obj->pos, &new_norm, obj, spawn_index);
}
}
}
// Does whatever to be done when a weapon times out
void TimeoutWeapon(object *obj) {
int n = obj->id;
if ((Weapons[n].flags & WF_SPAWNS_TIMEOUT) && Weapons[n].spawn_count > 0 && Weapons[n].spawn_handle >= 0) {
DoWeaponExploded(obj, &obj->orient.fvec);
CreateTimeoutSpawnFromWeapon(obj);
}
if ((Weapons[n].flags & WF_MATTER_WEAPON) && !(Weapons[n].flags & WF_SPAWNS_TIMEOUT) &&
(Weapons[n].spawn_count <= 0)) {
vector temp{};
if (Weapons[obj->id].sounds[WSI_IMPACT_WALL] != SOUND_NONE_INDEX)
Sound_system.Play3dSound(Weapons[obj->id].sounds[WSI_IMPACT_WALL], SND_PRIORITY_HIGH, obj);
temp.y() = (scalar)1.0;
DoWeaponExploded(obj, &temp);
}
if (Weapons[n].flags & WF_TIMEOUT_WALL) {
if (Weapons[obj->id].sounds[WSI_IMPACT_WALL] != SOUND_NONE_INDEX)
Sound_system.Play3dSound(Weapons[obj->id].sounds[WSI_IMPACT_WALL], SND_PRIORITY_HIGH, obj);
DoWeaponExploded(obj);
}
if ((Weapons[obj->id].flags & WF_SPAWNS_ROBOT) && (Weapons[obj->id].flags & WF_COUNTERMEASURE) &&
Weapons[obj->id].robot_spawn_handle >= 0)
CreateRobotSpawnFromWeapon(obj);
}
// Given a parent object and a weapon id, creates that countermeasure
void CreateCountermeasureFromObject(object *parent, int weapon_id) {
ASSERT(parent != NULL);
ASSERT(Weapons[weapon_id].used);
if (Game_mode & GM_MULTI)
MultiSendRequestCountermeasure(parent - Objects, weapon_id);
else
FireWeaponFromObject(parent, weapon_id, 5);
}
// Releases the guided missile of a passed in player
void ReleaseGuidedMissile(int slot) {
ASSERT(Players[slot].guided_obj);
if (!Players[slot].guided_obj)
return;
if ((Game_mode & GM_MULTI) && (slot == Player_num)) {
MultiSendMissileRelease(slot, true);
}
Players[slot].guided_obj->mtype.phys_info.flags &= ~PF_GUIDED;
Players[slot].guided_obj = NULL;
}
// Releases the timeout missile of a passed in player
void ReleaseUserTimeoutMissile(int slot) {
ASSERT(Players[slot].user_timeout_obj);
if (!Players[slot].user_timeout_obj)
return;
if ((Game_mode & GM_MULTI) && (slot == Player_num)) {
MultiSendMissileRelease(slot, false);
}
TimeoutWeapon(Players[slot].user_timeout_obj);
SetObjectDeadFlag(Players[slot].user_timeout_obj);
Players[slot].user_timeout_obj = NULL;
}